Page 2 of 2

Re: Desk rule: one metal or one yen theme, not both oversized

Posted: Tue Sep 29, 2026 8:25 am
by PTScalper
Ctrader version 1.0

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);
        }
    }
}

Re: Desk rule: one metal or one yen theme, not both oversized

Posted: Tue Sep 29, 2026 8:25 am
by PTScalper
Key cTrader Advantages in this Build:

True Cross-Symbol Analysis: Using MarketData.GetBars(TimeFrame, TkrXAU) allows cTrader to natively pull exact multi-timeframe ATRs for other assets without the buggy "waiting for tick data" issues commonly seen in MT4/MT5.

NormalizeVolumeInUnits: This cTrader-specific API method completely removes the need to manually calculate broker-specific Min Lot, Max Lot, and Lot Steps. You feed it a raw dollar-risk derived volume, and it automatically snaps it to a valid, broker-compliant lot size using RoundingMode.Down to ensure you never violate your risk cap.

UI StackPanel: The UI is anchored cleanly to the chart without drawing on top of your candles, and resizes dynamically.

Re: Desk rule: one metal or one yen theme, not both oversized

Posted: Tue Sep 29, 2026 8:27 am
by PTScalper
To elevate this from a static calculator to a Tier-1 Institutional Desk Tool, we must cross the gap from theoretical limits to live exposure tracking.

On a professional desk, a risk matrix doesn't just tell you what you can trade; it monitors what you are actually holding in real-time, compares it to your limits, and flags breaches.

Here is the cTrader "Pro" Upgrade.

The Upgrades:

Live Book Sync: It actively reads your open positions (Positions.FindAll) and compares your Current Lots vs. your Allowed Lots.

Risk Utilization Metrics: Calculates exactly what percentage of your thematic budget is currently deployed.

Interactive UI Controls: You no longer need to open the indicator settings to change your theme. You click interactive buttons directly on the chart to declare your 07:00 intent.

Breach Alerts: If you exceed the fractional cap on your secondary theme, the HUD flashes red, signaling a risk mandate violation.

Re: Desk rule: one metal or one yen theme, not both oversized

Posted: Tue Sep 29, 2026 8:27 am
by PTScalper
The Institutional cTrader Code (C#)

Save this as a new Indicator named InstitutionalRiskMatrix.

Code: Select all

using System;
using System.Linq;
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 InstitutionalRiskMatrix : Indicator
    {
        // =========================================================================
        // RISK MANDATE 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 Mandate")]
        public double MaxSessionRiskPct { get; set; }

        [Parameter("Secondary Scalp Cap (%)", DefaultValue = 15.0, MinValue = 0.0, MaxValue = 50.0, Group = "2. Risk Mandate")]
        public double SecondaryCapPct { get; set; }

        [Parameter("ATR Period", DefaultValue = 14, Group = "3. Volatility Engine")]
        public int AtrPeriod { get; set; }

        [Parameter("Stop Multiplier (ATR)", DefaultValue = 1.5, MinValue = 0.5, Step = 0.1, Group = "3. Volatility Engine")]
        public double AtrMultiplier { get; set; }

        // =========================================================================
        // INTERNAL STATE & UI COMPONENTS
        // =========================================================================
        private Symbol _symXau, _symJpy;
        private Bars _barsXau, _barsJpy;
        private AverageTrueRange _atrXau, _atrJpy;
        private PrimaryTheme _currentTheme = PrimaryTheme.FLAT;

        // UI Elements
        private TextBlock _tbXauCurrent, _tbXauLimit, _tbXauUtil;
        private TextBlock _tbJpyCurrent, _tbJpyLimit, _tbJpyUtil;
        private TextBlock _tbTotalRisk;
        private Button _btnThemeXau, _btnThemeJpy, _btnThemeFlat;
        private Border _mainBorder;

        protected override void Initialize()
        {
            _symXau = Symbols.GetSymbol(TkrXAU);
            _symJpy = Symbols.GetSymbol(TkrJPY);
            _barsXau = MarketData.GetBars(TimeFrame, TkrXAU);
            _barsJpy = MarketData.GetBars(TimeFrame, TkrJPY);
            
            _atrXau = Indicators.AverageTrueRange(_barsXau, AtrPeriod, MovingAverageType.Simple);
            _atrJpy = Indicators.AverageTrueRange(_barsJpy, AtrPeriod, MovingAverageType.Simple);

            // Hook into live position changes to update instantly
            Positions.Opened += OnPositionChanged;
            Positions.Closed += OnPositionChanged;
            Positions.Modified += OnPositionChanged;

            DrawInteractiveDashboard();
            UpdateDashboard();
        }

        public override void Calculate(int index)
        {
            if (IsLastBar) UpdateDashboard();
        }

        private void OnPositionChanged(PositionEventArgs args)
        {
            UpdateDashboard();
        }

        // =========================================================================
        // CORE RISK MATH & LIVE BOOK SYNC
        // =========================================================================
        private double CalculateMaxLots(Symbol sym, double riskCapital, double atrValue)
        {
            if (riskCapital <= 0 || atrValue <= 0 || sym.TickValue == 0 || sym.TickSize == 0) return 0.0;
            double stopDistancePrice = atrValue * AtrMultiplier;
            double lossPerUnit = stopDistancePrice * (sym.TickValue / sym.TickSize);
            if (lossPerUnit == 0) return 0;
            double rawUnits = riskCapital / lossPerUnit;
            double normalizedUnits = sym.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);
            return Math.Round(normalizedUnits / sym.LotSize, 2);
        }

        private double GetOpenLots(string ticker)
        {
            var volumeInUnits = Positions.Where(p => p.SymbolName == ticker).Sum(p => p.VolumeInUnits);
            var sym = Symbols.GetSymbol(ticker);
            return Math.Round(volumeInUnits / 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;

            if (_currentTheme == PrimaryTheme.XAU_Dominant)
            {
                xauRisk = primaryCap; jpyRisk = secondaryCap;
                _mainBorder.BorderColor = Color.Gold;
            }
            else if (_currentTheme == PrimaryTheme.JPY_Dominant)
            {
                jpyRisk = primaryCap; xauRisk = secondaryCap;
                _mainBorder.BorderColor = Color.DeepSkyBlue;
            }
            else
            {
                _mainBorder.BorderColor = Color.Gray;
            }

            double xauAtr = _atrXau.Result.Last(1);
            double jpyAtr = _atrJpy.Result.Last(1);

            double xauMaxLots = CalculateMaxLots(_symXau, xauRisk, xauAtr);
            double jpyMaxLots = CalculateMaxLots(_symJpy, jpyRisk, jpyAtr);

            double xauOpenLots = GetOpenLots(TkrXAU);
            double jpyOpenLots = GetOpenLots(TkrJPY);

            double xauUtilPct = xauMaxLots > 0 ? (xauOpenLots / xauMaxLots) * 100 : (xauOpenLots > 0 ? 999 : 0);
            double jpyUtilPct = jpyMaxLots > 0 ? (jpyOpenLots / jpyMaxLots) * 100 : (jpyOpenLots > 0 ? 999 : 0);

            // Update UI Fields
            UpdateRow(_tbXauCurrent, _tbXauLimit, _tbXauUtil, xauOpenLots, xauMaxLots, xauUtilPct);
            UpdateRow(_tbJpyCurrent, _tbJpyLimit, _tbJpyUtil, jpyOpenLots, jpyMaxLots, jpyUtilPct);

            _tbTotalRisk.Text = $"${Math.Round(xauRisk + jpyRisk):N0} ({MaxSessionRiskPct}% Eq)";
            
            // Button States
            _btnThemeXau.BackgroundColor = _currentTheme == PrimaryTheme.XAU_Dominant ? Color.Gold : Color.FromArgb(50, 50, 50);
            _btnThemeXau.ForegroundColor = _currentTheme == PrimaryTheme.XAU_Dominant ? Color.Black : Color.Gray;
            _btnThemeJpy.BackgroundColor = _currentTheme == PrimaryTheme.JPY_Dominant ? Color.DeepSkyBlue : Color.FromArgb(50, 50, 50);
            _btnThemeJpy.ForegroundColor = _currentTheme == PrimaryTheme.JPY_Dominant ? Color.Black : Color.Gray;
        }

        private void UpdateRow(TextBlock tbCurrent, TextBlock tbLimit, TextBlock tbUtil, double open, double limit, double utilPct)
        {
            tbCurrent.Text = $"{open:F2} Lots";
            tbLimit.Text = $"{limit:F2} Lots";
            tbUtil.Text = utilPct > 100 ? "BREACH!" : $"{utilPct:F0}%";

            Color statusColor = utilPct > 100 ? Color.Red : (utilPct > 80 ? Color.DarkOrange : Color.LimeGreen);
            if (utilPct == 0) statusColor = Color.Gray;

            tbCurrent.ForegroundColor = statusColor;
            tbUtil.ForegroundColor = statusColor;
        }

        // =========================================================================
        // INTERACTIVE UI CONSTRUCTION
        // =========================================================================
        private void DrawInteractiveDashboard()
        {
            var grid = new Grid(4, 4) { BackgroundColor = Color.FromArgb(220, 10, 10, 10), Margin = new Thickness(10) };
            
            var styleHeader = new Style();
            styleHeader.Set(ControlProperty.ForegroundColor, Color.DimGray);
            styleHeader.Set(ControlProperty.FontWeight, FontWeight.Bold);
            styleHeader.Set(ControlProperty.Margin, new Thickness(5, 5, 15, 5));

            var styleCell = new Style();
            styleCell.Set(ControlProperty.Margin, new Thickness(5, 5, 15, 5));
            styleCell.Set(ControlProperty.FontWeight, FontWeight.Bold);

            // Headers
            grid.AddChild(new TextBlock { Text = "ASSET", Style = styleHeader }, 0, 0);
            grid.AddChild(new TextBlock { Text = "OPEN (LIVE)", Style = styleHeader }, 0, 1);
            grid.AddChild(new TextBlock { Text = "LIMIT (MAX)", Style = styleHeader }, 0, 2);
            grid.AddChild(new TextBlock { Text = "UTILIZATION", Style = styleHeader }, 0, 3);

            // XAU Row
            grid.AddChild(new TextBlock { Text = TkrXAU, Style = styleCell, ForegroundColor = Color.Gold }, 1, 0);
            _tbXauCurrent = new TextBlock { Style = styleCell };
            _tbXauLimit = new TextBlock { Style = styleCell, ForegroundColor = Color.White };
            _tbXauUtil = new TextBlock { Style = styleCell };
            grid.AddChild(_tbXauCurrent, 1, 1); grid.AddChild(_tbXauLimit, 1, 2); grid.AddChild(_tbXauUtil, 1, 3);

            // JPY Row
            grid.AddChild(new TextBlock { Text = TkrJPY, Style = styleCell, ForegroundColor = Color.DeepSkyBlue }, 2, 0);
            _tbJpyCurrent = new TextBlock { Style = styleCell };
            _tbJpyLimit = new TextBlock { Style = styleCell, ForegroundColor = Color.White };
            _tbJpyUtil = new TextBlock { Style = styleCell };
            grid.AddChild(_tbJpyCurrent, 2, 1); grid.AddChild(_tbJpyLimit, 2, 2); grid.AddChild(_tbJpyUtil, 2, 3);

            // Bottom Footer
            grid.AddChild(new TextBlock { Text = "TOTAL CAP:", Style = styleCell, ForegroundColor = Color.DimGray }, 3, 0);
            _tbTotalRisk = new TextBlock { Style = styleCell, ForegroundColor = Color.White };
            grid.AddChild(_tbTotalRisk, 3, 1);

            // Interactive Theme Selectors
            var controlPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 10), HorizontalAlignment = HorizontalAlignment.Center };
            
            _btnThemeXau = new Button { Text = "SET XAU THEME", Margin = new Thickness(0, 0, 5, 0), Width = 110, FontWeight = FontWeight.Bold };
            _btnThemeXau.Click += args => { _currentTheme = PrimaryTheme.XAU_Dominant; UpdateDashboard(); };
            
            _btnThemeJpy = new Button { Text = "SET JPY THEME", Margin = new Thickness(5, 0, 5, 0), Width = 110, FontWeight = FontWeight.Bold };
            _btnThemeJpy.Click += args => { _currentTheme = PrimaryTheme.JPY_Dominant; UpdateDashboard(); };

            _btnThemeFlat = new Button { Text = "FLAT MODE", Margin = new Thickness(5, 0, 0, 0), Width = 90, BackgroundColor = Color.Crimson, ForegroundColor = Color.White, FontWeight = FontWeight.Bold };
            _btnThemeFlat.Click += args => { _currentTheme = PrimaryTheme.FLAT; UpdateDashboard(); };

            controlPanel.AddChild(_btnThemeXau);
            controlPanel.AddChild(_btnThemeJpy);
            controlPanel.AddChild(_btnThemeFlat);

            var mainPanel = new StackPanel { Orientation = Orientation.Vertical, BackgroundColor = Color.FromArgb(230, 15, 15, 15) };
            
            var header = new TextBlock { Text = "DESK RULE: THEMATIC RISK OVERLAP", Margin = new Thickness(10, 10, 10, 0), FontWeight = FontWeight.ExtraBold, ForegroundColor = Color.White, HorizontalAlignment = HorizontalAlignment.Center };
            
            mainPanel.AddChild(header);
            mainPanel.AddChild(grid);
            mainPanel.AddChild(controlPanel);

            _mainBorder = new Border { BorderColor = Color.Gray, BorderThickness = new Thickness(2), Child = mainPanel, HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Top, Margin = new Thickness(20) };

            Chart.AddControl(_mainBorder);
        }
    }
}

Re: Desk rule: one metal or one yen theme, not both oversized

Posted: Tue Sep 29, 2026 8:28 am
by PTScalper
Why this is a True Professional Framework:

Interactive Control Panel: Instead of digging into indicator settings, you have a physical switchboard on your chart. Clicking "SET XAU THEME" instantly updates the border color to Gold and recalculates your lot allowances dynamically.

The "Live Sync" Engine: The code hooks into Positions.Opened and Positions.Closed. If you open a 0.50 lot position on Gold, the dashboard updates instantly—without waiting for the next candle to form.

Breach Enforcement: The dashboard calculates Utilization (%).

0% - 80%: Your live position text is Green (Safe).

80% - 100%: The text turns Orange (Warning: Approaching Mandate Limit).

> 100%: The text turns Red, the Utilization box screams "BREACH!".

The "FLAT MODE" Kill Switch: If you realize you are emotionally double-dipping, clicking the Crimson "FLAT MODE" button instantly zeroes out your allowed thematic risk for the session, forcing you to step back and evaluate your book.