Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;
using cAlgo.Indicators;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ProExecutionModel : Indicator
{
public enum FeeTypeEnum { PerLot, Percentage }
// =========================================================================
// INPUT PARAMETERS
// =========================================================================
[Parameter("Volume (Units, e.g., 100000 for 1 Lot)", Group = "1. Position & Fees", DefaultValue = 100000)]
public double InpVolume { get; set; }
[Parameter("Fee Type", Group = "1. Position & Fees", DefaultValue = FeeTypeEnum.PerLot)]
public FeeTypeEnum InpFeeType { get; set; }
[Parameter("Fee Rate (Per Lot or %)", Group = "1. Position & Fees", DefaultValue = 3.5)]
public double InpFeeRate { get; set; }
[Parameter("Fixed Ticket Fee ($)", Group = "1. Position & Fees", DefaultValue = 0.0)]
public double InpFixedFee { get; set; }
[Parameter("Use Live Broker Spread?", Group = "2. Market Impact", DefaultValue = true)]
public bool InpUseLiveSpread { get; set; }
[Parameter("Base Spread (Pips, if Live is false)", Group = "2. Market Impact", DefaultValue = 1.0)]
public double InpBaseSpread { get; set; }
[Parameter("Base Slippage Per Side (Pips)", Group = "2. Market Impact", DefaultValue = 1.0)]
public double InpBaseSlippage { get; set; }
[Parameter("Use Vol-Adjusted Slippage (ATR)?", Group = "2. Market Impact", DefaultValue = true)]
public bool InpUseDynSlip { get; set; }
[Parameter("ATR Slippage Factor", Group = "2. Market Impact", DefaultValue = 0.05)]
public double InpSlipFactor { get; set; }
// =========================================================================
// ON-CHART PLOTS (BREAKEVEN ENVELOPE)
// =========================================================================
[Output("Long Breakeven", LineColor = "#008080", PlotType = PlotType.Line, LineStyle = LineStyle.Lines)]
public IndicatorDataSeries LongBE { get; set; }
[Output("Short Breakeven", LineColor = "#800000", PlotType = PlotType.Line, LineStyle = LineStyle.Lines)]
public IndicatorDataSeries ShortBE { get; set; }
// =========================================================================
// GLOBAL VARIABLES & UI CONTROLS
// =========================================================================
private AverageTrueRange _atr;
private TextBlock _valNotional, _valComm, _valSpread, _valSlip, _valTotal, _valDrag, _valBE;
protected override void Initialize()
{
_atr = Indicators.AverageTrueRange(14, MovingAverageType.Simple);
DrawInstitutionalDashboard();
}
public override void Calculate(int index)
{
if (index < 14) return;
double currentPrice = Bars.ClosePrices[index];
// 1. Core Base Metrics
// cTrader's Symbol.PipValue is the value of 1 pip for 1 unit of volume.
double pipValueTotal = Symbol.PipValue * InpVolume;
if (pipValueTotal == 0) pipValueTotal = 1; // Failsafe
double notionalValue = InpVolume * currentPrice;
// 2. Volatility Adjusted Slippage (ATR)
double atrInPips = _atr.Result[index] / Symbol.PipSize;
double dynSlip = InpBaseSlippage;
if (InpUseDynSlip) dynSlip += (atrInPips * InpSlipFactor);
// 3. Live Spread vs Manual
double currentSpread = InpBaseSpread;
if (InpUseLiveSpread) currentSpread = Symbol.Spread / Symbol.PipSize;
// 4. Cost Calculations (Round Trip)
// Note: PerLot assumes 1 standard lot = 100,000 units
double commCost = (InpFeeType == FeeTypeEnum.PerLot)
? (InpFeeRate * (InpVolume / 100000)) + InpFixedFee
: (notionalValue * (InpFeeRate / 100)) + InpFixedFee;
double spreadCost = currentSpread * pipValueTotal;
double slipCost = (dynSlip * 2) * pipValueTotal;
double totalCost = commCost + spreadCost + slipCost;
// 5. Breakeven Math
double pipsToBE = totalCost / pipValueTotal;
LongBE[index] = currentPrice + (pipsToBE * Symbol.PipSize);
ShortBE[index] = currentPrice - (pipsToBE * Symbol.PipSize);
// 6. Update UI (Only on the live ticking bar to save CPU)
if (IsLastBar)
{
double acctBal = Account.Balance;
double acctDrag = (acctBal > 0) ? (totalCost / acctBal) * 100 : 0;
string ccy = Account.Asset.Name;
_valNotional.Text = $"{Math.Round(notionalValue, 2):N2} {ccy}";
_valComm.Text = $"{Math.Round(commCost, 2):N2} {ccy}";
_valSpread.Text = $"{Math.Round(spreadCost, 2):N2} {ccy}";
_valSlip.Text = $"{Math.Round(slipCost, 2):N2} {ccy}";
_valTotal.Text = $"{Math.Round(totalCost, 2):N2} {ccy}";
_valDrag.Text = $"{Math.Round(acctDrag, 3):N3}%";
_valBE.Text = $"{Math.Round(pipsToBE, 1):N1}";
}
}
// =========================================================================
// NATIVE WPF-STYLE UI GENERATION
// =========================================================================
private void DrawInstitutionalDashboard()
{
var grid = new Grid(7, 2) { Margin = new Thickness(15) };
// Grid Column Settings
grid.Columns.Add(new Column { Width = GridLength.Auto });
grid.Columns.Add(new Column { Width = new GridLength(120, GridUnitType.Pixel) });
// Initialize dynamic TextBlocks
_valNotional = CreateValueText("#B0BEC5");
_valComm = CreateValueText("#FF5252");
_valSpread = CreateValueText("#FF5252");
_valSlip = CreateValueText(InpUseDynSlip ? "#FFA726" : "#FF5252");
_valTotal = CreateValueText("#29B6F6", true);
_valDrag = CreateValueText("#FF8A65", true);
_valBE = CreateValueText("#FFD54F", true);
// Populate Grid Rows
AddGridRow(grid, 0, "Notional Exposure:", _valNotional, "#B0BEC5");
AddGridRow(grid, 1, "Broker Commissions:", _valComm, "#B0BEC5");
AddGridRow(grid, 2, InpUseLiveSpread ? "Spread Drag (LIVE):" : "Spread Drag (STATIC):", _valSpread, "#B0BEC5");
AddGridRow(grid, 3, "Est. Slippage (ATR):", _valSlip, "#B0BEC5");
AddGridRow(grid, 4, "Total Overhead:", _valTotal, "#29B6F6", true);
AddGridRow(grid, 5, "Portfolio Drag:", _valDrag, "#B0BEC5");
AddGridRow(grid, 6, "Pips to Breakeven:", _valBE, "#FFD54F", true);
// Container Panel Styling
var border = new Border
{
BackgroundColor = Color.FromArgb(220, 20, 20, 20),
BorderColor = Color.FromArgb(255, 60, 60, 60),
BorderThickness = new Thickness(1),
CornerRadius = 5,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(20),
Child = new StackPanel
{
Orientation = Orientation.Vertical,
Children =
{
new TextBlock
{
Text = $"Execution Drag Model | {SymbolName}",
ForegroundColor = Color.White,
FontWeight = FontWeight.Bold,
Margin = new Thickness(15, 15, 15, 5)
},
grid
}
}
};
Chart.AddControl(border);
}
private TextBlock CreateValueText(string hexColor, bool isBold = false)
{
return new TextBlock
{
ForegroundColor = Color.FromHex(hexColor),
HorizontalAlignment = HorizontalAlignment.Right,
FontWeight = isBold ? FontWeight.Bold : FontWeight.Normal,
Margin = new Thickness(10, 5, 0, 5)
};
}
private void AddGridRow(Grid grid, int rowIndex, string label, TextBlock valueBlock, string labelColorHex, bool isBold = false)
{
var title = new TextBlock
{
Text = label,
ForegroundColor = Color.FromHex(labelColorHex),
FontWeight = isBold ? FontWeight.Bold : FontWeight.Normal,
Margin = new Thickness(0, 5, 10, 5)
};
grid.AddChild(title, rowIndex, 0);
grid.AddChild(valueBlock, rowIndex, 1);
}
}
}