Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo
{
public enum PrimaryTheme
{
XAU_Dominant,
JPY_Dominant,
FLAT
}
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ThematicRiskMatrix : Indicator
{
// =========================================================================
// INPUTS
// =========================================================================
[Parameter("Gold Ticker", DefaultValue = "XAUUSD", Group = "1. Instruments")]
public string TkrXAU { get; set; }
[Parameter("Yen Ticker", DefaultValue = "USDJPY", Group = "1. Instruments")]
public string TkrJPY { get; set; }
[Parameter("Max Session Risk (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "2. Risk Limits")]
public double MaxSessionRiskPct { get; set; }
[Parameter("Secondary Scalp Cap (%)", DefaultValue = 15.0, MinValue = 0.0, MaxValue = 50.0, Group = "2. Risk Limits")]
public double SecondaryCapPct { get; set; }
[Parameter("The 07:00 Fix", DefaultValue = PrimaryTheme.XAU_Dominant, Group = "3. Theme Selection")]
public PrimaryTheme SessionTheme { get; set; }
[Parameter("ATR Period", DefaultValue = 14, Group = "4. Volatility Engine")]
public int AtrPeriod { get; set; }
[Parameter("Stop Multiplier (ATR)", DefaultValue = 1.5, MinValue = 0.5, Step = 0.1, Group = "4. Volatility Engine")]
public double AtrMultiplier { get; set; }
// =========================================================================
// INTERNAL VARIABLES & UI
// =========================================================================
private Symbol _symXau;
private Symbol _symJpy;
private Bars _barsXau;
private Bars _barsJpy;
private AverageTrueRange _atrXau;
private AverageTrueRange _atrJpy;
private Grid _riskGrid;
private TextBlock _tbXauRole, _tbXauRisk, _tbXauLots;
private TextBlock _tbJpyRole, _tbJpyRisk, _tbJpyLots;
private TextBlock _tbTotalRisk;
protected override void Initialize()
{
// Load external symbols and bars
_symXau = Symbols.GetSymbol(TkrXAU);
_symJpy = Symbols.GetSymbol(TkrJPY);
_barsXau = MarketData.GetBars(TimeFrame, TkrXAU);
_barsJpy = MarketData.GetBars(TimeFrame, TkrJPY);
// Initialize cross-symbol ATRs
_atrXau = Indicators.AverageTrueRange(_barsXau, AtrPeriod, MovingAverageType.Simple);
_atrJpy = Indicators.AverageTrueRange(_barsJpy, AtrPeriod, MovingAverageType.Simple);
DrawDashboard();
}
public override void Calculate(int index)
{
// Only update UI on the latest live tick
if (!IsLastBar) return;
UpdateDashboard();
}
// =========================================================================
// VOLATILITY & CAPITAL MATH
// =========================================================================
private double CalculateMaxLots(Symbol sym, double riskCapital, double atrValue)
{
if (riskCapital <= 0 || atrValue <= 0 || sym.TickValue == 0 || sym.TickSize == 0)
return 0.0;
// Step 1: Stop distance in absolute price terms based on real-time ATR
double stopDistancePrice = atrValue * AtrMultiplier;
// Step 2: Dollar loss per 1 unit of volume (not lots)
double lossPerUnit = stopDistancePrice * (sym.TickValue / sym.TickSize);
if (lossPerUnit == 0) return 0;
// Step 3: Allowed Volume (Units)
double rawUnits = riskCapital / lossPerUnit;
// Step 4: Broker normalization & Lot conversion
double normalizedUnits = sym.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);
return Math.Round(normalizedUnits / sym.LotSize, 2);
}
private void UpdateDashboard()
{
double equity = Account.Equity;
double totalRiskCapital = equity * (MaxSessionRiskPct / 100.0);
double primaryCap = totalRiskCapital * (1.0 - (SecondaryCapPct / 100.0));
double secondaryCap = totalRiskCapital * (SecondaryCapPct / 100.0);
double xauRisk = 0, jpyRisk = 0;
string xauRole = "FLAT", jpyRole = "FLAT";
Color xauColor = Color.Gray, jpyColor = Color.Gray;
// Apply Sticky Note Rules
if (SessionTheme == PrimaryTheme.XAU_Dominant)
{
xauRisk = primaryCap; xauRole = "DOMINANT"; xauColor = Color.Gold;
jpyRisk = secondaryCap; jpyRole = "SCALP ONLY"; jpyColor = Color.DeepSkyBlue;
}
else if (SessionTheme == PrimaryTheme.JPY_Dominant)
{
jpyRisk = primaryCap; jpyRole = "DOMINANT"; jpyColor = Color.DeepSkyBlue;
xauRisk = secondaryCap; xauRole = "SCALP ONLY"; xauColor = Color.Gold;
}
// Get Current ATR Values (using the last completed bar)
double xauAtr = _atrXau.Result.Last(1);
double jpyAtr = _atrJpy.Result.Last(1);
// Output updates to UI
_tbXauRole.Text = xauRole;
_tbXauRole.ForegroundColor = xauColor;
_tbXauRisk.Text = $"${Math.Round(xauRisk)}";
_tbXauRisk.ForegroundColor = xauColor;
_tbXauLots.Text = $"{CalculateMaxLots(_symXau, xauRisk, xauAtr):F2}";
_tbXauLots.ForegroundColor = xauColor;
_tbJpyRole.Text = jpyRole;
_tbJpyRole.ForegroundColor = jpyColor;
_tbJpyRisk.Text = $"${Math.Round(jpyRisk)}";
_tbJpyRisk.ForegroundColor = jpyColor;
_tbJpyLots.Text = $"{CalculateMaxLots(_symJpy, jpyRisk, jpyAtr):F2}";
_tbJpyLots.ForegroundColor = jpyColor;
_tbTotalRisk.Text = $"${Math.Round(xauRisk + jpyRisk)} ({MaxSessionRiskPct}%)";
}
// =========================================================================
// UI CONSTRUCTION (WPF-Style Grid)
// =========================================================================
private void DrawDashboard()
{
var styleHeader = new Style();
styleHeader.Set(ControlProperty.ForegroundColor, Color.Gray);
styleHeader.Set(ControlProperty.FontWeight, FontWeight.Bold);
styleHeader.Set(ControlProperty.Margin, new Thickness(5));
var styleCell = new Style();
styleCell.Set(ControlProperty.Margin, new Thickness(5));
styleCell.Set(ControlProperty.FontWeight, FontWeight.Bold);
_riskGrid = new Grid(5, 4)
{
BackgroundColor = Color.FromArgb(200, 15, 15, 15),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(10)
};
var border = new Border
{
BorderColor = Color.FromArgb(100, 100, 100, 100),
BorderThickness = new Thickness(1),
Child = _riskGrid
};
// Headers
_riskGrid.AddChild(new TextBlock { Text = "ASSET", Style = styleHeader }, 0, 0);
_riskGrid.AddChild(new TextBlock { Text = "THEME ROLE", Style = styleHeader }, 0, 1);
_riskGrid.AddChild(new TextBlock { Text = "CAPITAL ($)", Style = styleHeader }, 0, 2);
_riskGrid.AddChild(new TextBlock { Text = "MAX LOTS", Style = styleHeader }, 0, 3);
// XAU Row
_riskGrid.AddChild(new TextBlock { Text = TkrXAU, Style = styleCell, ForegroundColor = Color.White }, 1, 0);
_tbXauRole = new TextBlock { Style = styleCell };
_tbXauRisk = new TextBlock { Style = styleCell };
_tbXauLots = new TextBlock { Style = styleCell };
_riskGrid.AddChild(_tbXauRole, 1, 1);
_riskGrid.AddChild(_tbXauRisk, 1, 2);
_riskGrid.AddChild(_tbXauLots, 1, 3);
// JPY Row
_riskGrid.AddChild(new TextBlock { Text = TkrJPY, Style = styleCell, ForegroundColor = Color.White }, 2, 0);
_tbJpyRole = new TextBlock { Style = styleCell };
_tbJpyRisk = new TextBlock { Style = styleCell };
_tbJpyLots = new TextBlock { Style = styleCell };
_riskGrid.AddChild(_tbJpyRole, 2, 1);
_riskGrid.AddChild(_tbJpyRisk, 2, 2);
_riskGrid.AddChild(_tbJpyLots, 2, 3);
// Total Risk Row
var tbTotalHeader = new TextBlock { Text = "SESSION TOTAL:", Style = styleCell, ForegroundColor = Color.LightGray };
_tbTotalRisk = new TextBlock { Style = styleCell, ForegroundColor = Color.White };
_riskGrid.AddChild(tbTotalHeader, 3, 0);
_riskGrid.AddChild(_tbTotalRisk, 3, 2);
// Operational Rule Warning
var warningLabel = new TextBlock
{
Text = "DESK RULE: FLAT ALL RISK BEFORE CHANGING THEME",
ForegroundColor = Color.Red,
FontWeight = FontWeight.Bold,
Margin = new Thickness(5),
HorizontalAlignment = HorizontalAlignment.Center
};
_riskGrid.AddChild(warningLabel, 4, 0);
// Span the warning text across all 4 columns (cTrader specific API implementation)
// cTrader API doesn't support Grid.SetColumnSpan directly on AddChild like WPF,
// so we wrap it inside a specialized row or let it overflow naturally.
// Using a StackPanel to handle the spanning smoothly.
var mainPanel = new StackPanel { Orientation = Orientation.Vertical, BackgroundColor = Color.FromArgb(200, 15, 15, 15) };
mainPanel.AddChild(_riskGrid);
mainPanel.AddChild(new Border
{
BorderColor = Color.Red,
BorderThickness = new Thickness(0, 1, 0, 0),
Child = warningLabel
});
border.Child = mainPanel;
Chart.AddControl(border);
}
}
}