Page 2 of 2

Re: Risk discipline: pair-specific notes for EURUSD

Posted: Thu Sep 24, 2026 11:14 am
by FTtrader
Here is the complete C# conversion for cTrader (cAlgo).

Because cTrader uses a modern C# API, it handles volume normalization natively (automatically accounting for your broker's minimum lot sizes and volume steps) and allows for a much cleaner implementation of the screen-locking background colors.

cTrader Version (.cs)

Open cTrader, go to the Automate tab, click New Indicator, name it EURUSDRiskDesk, and paste this code:

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class EURUSDRiskDesk : Indicator
    {
        // =========================================================================
        // INPUTS & RULES
        // =========================================================================
        [Parameter("News Start Hour", DefaultValue = 13, Group = "Session & News (Server Time)")]
        public int NewsStartHour { get; set; }
        [Parameter("News Start Min", DefaultValue = 15, Group = "Session & News (Server Time)")]
        public int NewsStartMin { get; set; }
        [Parameter("News End Hour", DefaultValue = 13, Group = "Session & News (Server Time)")]
        public int NewsEndHour { get; set; }
        [Parameter("News End Min", DefaultValue = 45, Group = "Session & News (Server Time)")]
        public int NewsEndMin { get; set; }

        [Parameter("London Start Hour", DefaultValue = 7, Group = "Session & News (Server Time)")]
        public int LondonStartHour { get; set; }
        [Parameter("London Start Min", DefaultValue = 0, Group = "Session & News (Server Time)")]
        public int LondonStartMin { get; set; }
        [Parameter("London End Hour", DefaultValue = 8, Group = "Session & News (Server Time)")]
        public int LondonEndHour { get; set; }
        [Parameter("London End Min", DefaultValue = 30, Group = "Session & News (Server Time)")]
        public int LondonEndMin { get; set; }

        [Parameter("2 Process Breaks Hit? (LOCK CHART)", DefaultValue = false, Group = "Discipline Breaker")]
        public bool ProcessBreaksHit { get; set; }

        [Parameter("Total Risk Budget (%)", DefaultValue = 1.0, Group = "Dynamic Sizing")]
        public double TotalRiskPct { get; set; }
        [Parameter("Active Correlated EUR Pairs", DefaultValue = 1, Group = "Dynamic Sizing", MinValue = 1)]
        public int ActivePairs { get; set; }
        [Parameter("ATR Length", DefaultValue = 14, Group = "Dynamic Sizing")]
        public int AtrLength { get; set; }
        [Parameter("ATR Multiplier", DefaultValue = 1.5, Group = "Dynamic Sizing")]
        public double AtrMult { get; set; }

        private AverageTrueRange _atr;
        private Color _originalBgColor;

        protected override void Initialize()
        {
            // Initialize ATR indicator (Wilder smoothing is standard)
            _atr = Indicators.AverageTrueRange(AtrLength, MovingAverageType.Wilder);
            
            // Save the user's original chart color so we can restore it later
            _originalBgColor = Chart.ColorSettings.BackgroundColor;
        }

        public override void Calculate(int index)
        {
            // Only update the dashboard and background on the live, current bar
            if (!IsLastBar) return;
            
            UpdateRiskDesk();
        }

        private void UpdateRiskDesk()
        {
            // 1. Time Logic (Using cTrader Server Time)
            DateTime currentTime = Server.Time;
            int currentMins = currentTime.Hour * 60 + currentTime.Minute;
            
            int newsStart = NewsStartHour * 60 + NewsStartMin;
            int newsEnd = NewsEndHour * 60 + NewsEndMin;
            bool inNews = (currentMins >= newsStart && currentMins <= newsEnd);
            
            int lonStart = LondonStartHour * 60 + LondonStartMin;
            int lonEnd = LondonEndHour * 60 + LondonEndMin;
            bool inLondon = (currentMins >= lonStart && currentMins <= lonEnd);

            // 2. Dynamic Budgeting
            double balance = Account.Balance;
            double totalRiskUsd = balance * (TotalRiskPct / 100.0);
            double allocatedRiskUsd = totalRiskUsd / ActivePairs;

            // 3. ATR & Stop Distance
            double atrValue = _atr.Result.LastValue;
            double slDistance = atrValue * AtrMult;
            double slDistancePips = slDistance / Symbol.PipSize;

            // 4. Exact Lot Sizing Math (Native cTrader Volume Calculation)
            double lots = 0;
            if (slDistancePips > 0 && Symbol.PipValue > 0)
            {
                // Risk per 1 unit of volume
                double riskPerUnit = slDistancePips * Symbol.PipValue;
                
                // Calculate exact units needed, then safely round down to broker's valid step size
                double rawUnits = allocatedRiskUsd / riskPerUnit;
                double validUnits = Symbol.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);
                
                // Convert units to Lots for easy readability
                lots = validUnits / Symbol.LotSize;
            }

            // 5. Visual Background Enforcement
            if (ProcessBreaksHit) 
                Chart.ColorSettings.BackgroundColor = Color.Maroon;
            else if (inNews)      
                Chart.ColorSettings.BackgroundColor = Color.DarkRed;
            else if (inLondon)    
                Chart.ColorSettings.BackgroundColor = Color.DarkOrange;
            else                  
                Chart.ColorSettings.BackgroundColor = _originalBgColor;

            // 6. On-Chart Dashboard
            string dash = "=== EURUSD RISK DESK ===\n";
            dash += string.Format("Live Balance: {0:C2}\n", balance);
            dash += string.Format("Budget ({0} Pairs): {1:C2}\n", ActivePairs, allocatedRiskUsd);
            dash += string.Format("Stop Distance: {0:F1} pips\n", slDistancePips);
            dash += "---------------------------------\n";
            dash += string.Format("MAX POSITION SIZE: {0:F2} LOTS\n", lots);
            dash += "---------------------------------\n";
            dash += "Tier-1 News: " + (inNews ? "FLAT / ZERO RISK" : "CLEAR") + "\n";
            dash += "London Open: " + (inLondon ? "MAX R / SWEEP RISK" : "CLEAR") + "\n";
            dash += "Discipline: " + (ProcessBreaksHit ? "DONE FOR MORNING (LOCKED)" : "ACTIVE") + "\n";

            // Draw to the top left of the screen
            Chart.DrawStaticText("RiskDeskUI", dash, VerticalAlignment.Top, HorizontalAlignment.Left, Color.White);
        }

        protected override void OnStop()
        {
            // Crucial: Restores your normal chart background color when you remove the indicator
            Chart.ColorSettings.BackgroundColor = _originalBgColor;
        }
    }
}

Re: Risk discipline: pair-specific notes for EURUSD

Posted: Thu Sep 24, 2026 11:15 am
by FTtrader
cTrader-Specific Advantages In This Build:

Symbol.NormalizeVolumeInUnits: Instead of trying to hack together Math.Floor calculations with tick values like MT4, cTrader natively understands your broker's exact volume steps. It calculates the raw volume and then actively truncates it safely downwards to ensure you never exceed your exact dollar risk limit.

OnStop() Reversion: If you hit your "2 Process Breaks" kill switch, the chart turns Maroon. In Pine Script/MT4, fixing this requires interacting with the settings. In this cTrader script, if you delete or turn off the indicator, the OnStop() command automatically triggers and restores your chart's original template background color so it isn't stuck red.

Server.Time Native Alignment: cTrader handles its time variables entirely based on the broker's server time seamlessly, which prevents the time-zone desynchronization bugs that often plague TradingView scripts during daylight saving changes. Input your lockout hours based on the time printed on the bottom axis of your cTrader chart.

Re: Risk discipline: pair-specific notes for EURUSD

Posted: Thu Sep 24, 2026 11:16 am
by FTtrader
To make this a genuinely professional-grade tool, we need to abandon crude text strings printed on the chart and leverage cTrader’s native UI rendering engine (which is built on WPF-style custom controls).

A professional trading desk tool does not make you dig through settings menus to hit a kill switch, nor does it ignore real-time execution costs.

This Pro Version upgrades your cTrader environment with four major architectural changes:

Interactive HUD (Heads-Up Display): It renders a sleek, floating UI panel.

Interactive UI Kill-Switch: There is now a physical button on the chart. If you break your process twice, you click it. It instantly locks the screen.

Live Spread Monitor: It calculates the real-time spread in pips. If the spread blows out during a Tier-1 news sweep, the dashboard flags it so you don't execute into a liquidity vacuum.

Tick-Level Optimization: The UI shell is drawn only once at startup, and only the raw data values are injected on each tick. This consumes zero unnecessary CPU overhead.

Re: Risk discipline: pair-specific notes for EURUSD

Posted: Thu Sep 24, 2026 11:16 am
by FTtrader
The Professional cTrader Build (.cs)

Open cTrader, go to the Automate tab, click New Indicator, name it EURUSD_ProDesk, and paste this code:

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class EURUSD_ProDesk : Indicator
    {
        // =========================================================================
        // INPUTS
        // =========================================================================
        [Parameter("News Start (Broker Time)", DefaultValue = "13:15", Group = "Session Rules")]
        public string NewsStartInput { get; set; }
        [Parameter("News End (Broker Time)", DefaultValue = "13:45", Group = "Session Rules")]
        public string NewsEndInput { get; set; }

        [Parameter("London Start (Broker Time)", DefaultValue = "07:00", Group = "Session Rules")]
        public string LondonStartInput { get; set; }
        [Parameter("London End (Broker Time)", DefaultValue = "08:30", Group = "Session Rules")]
        public string LondonEndInput { get; set; }

        [Parameter("Total Risk Budget (%)", DefaultValue = 1.0, Group = "Dynamic Sizing")]
        public double TotalRiskPct { get; set; }
        [Parameter("Active Correlated EUR Pairs", DefaultValue = 1, Group = "Dynamic Sizing", MinValue = 1)]
        public int ActivePairs { get; set; }
        [Parameter("ATR Length", DefaultValue = 14, Group = "Dynamic Sizing")]
        public int AtrLength { get; set; }
        [Parameter("ATR Multiplier", DefaultValue = 1.5, Group = "Dynamic Sizing")]
        public double AtrMult { get; set; }
        [Parameter("Max Spread Warning (Pips)", DefaultValue = 1.5, Group = "Dynamic Sizing")]
        public double MaxSpreadPips { get; set; }

        // =========================================================================
        // STATE & UI ELEMENTS
        // =========================================================================
        private AverageTrueRange _atr;
        private Color _originalBgColor;
        private bool _isLocked = false;

        // UI Controls updated in real-time
        private TextBlock _txtLots;
        private TextBlock _txtSession;
        private TextBlock _txtSpread;
        private TextBlock _txtDistance;
        private Button _btnKillSwitch;
        private Border _pnlLots;

        protected override void Initialize()
        {
            _atr = Indicators.AverageTrueRange(AtrLength, MovingAverageType.Wilder);
            _originalBgColor = Chart.ColorSettings.BackgroundColor;

            BuildInstitutionalUI();
        }

        public override void Calculate(int index)
        {
            // Only update calculations on the live edge (per tick)
            if (!IsLastBar) return;
            UpdateTelemetry();
        }

        // =========================================================================
        // CORE MATH & ENFORCEMENT
        // =========================================================================
        private void UpdateTelemetry()
        {
            // 1. Live Spread Logic
            double currentSpread = (Symbol.Ask - Symbol.Bid) / Symbol.PipSize;
            bool isSpreadHigh = currentSpread > MaxSpreadPips;
            _txtSpread.Text = string.Format("Live Spread: {0:F1} pips", currentSpread);
            _txtSpread.ForegroundColor = isSpreadHigh ? Color.Red : Color.DarkGray;

            // 2. Dynamic Budget & Lots
            double atrValue = _atr.Result.LastValue;
            double slDistancePips = (atrValue * AtrMult) / Symbol.PipSize;
            double totalRiskUsd = Account.Balance * (TotalRiskPct / 100.0);
            double allocatedRiskUsd = totalRiskUsd / ActivePairs;

            double lots = 0;
            if (slDistancePips > 0 && Symbol.PipValue > 0)
            {
                double riskPerUnit = slDistancePips * Symbol.PipValue;
                double rawUnits = allocatedRiskUsd / riskPerUnit;
                double validUnits = Symbol.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);
                lots = validUnits / Symbol.LotSize;
            }

            _txtDistance.Text = string.Format("Stop Distance: {0:F1} pips (ATR x{1})", slDistancePips, AtrMult);
            _txtLots.Text = string.Format("{0:F2} LOTS", lots);

            // 3. Time Logic (Broker Server Time)
            DateTime currentTime = Server.Time;
            string timeStr = currentTime.ToString("HH:mm");

            bool inNews = IsTimeInWindow(timeStr, NewsStartInput, NewsEndInput);
            bool inLondon = IsTimeInWindow(timeStr, LondonStartInput, LondonEndInput);

            // 4. Visual Enforcement via Backgrounds & Badges
            if (_isLocked)
            {
                Chart.ColorSettings.BackgroundColor = Color.Maroon;
                _txtSession.Text = "STATUS: DONE FOR MORNING (LOCKED)";
                _txtSession.ForegroundColor = Color.White;
                _pnlLots.BackgroundColor = Color.DimGray;
            }
            else if (inNews)
            {
                Chart.ColorSettings.BackgroundColor = Color.DarkRed;
                _txtSession.Text = "STATUS: TIER-1 NEWS (ZERO RISK)";
                _txtSession.ForegroundColor = Color.Red;
                _pnlLots.BackgroundColor = Color.DarkRed;
            }
            else if (inLondon)
            {
                Chart.ColorSettings.BackgroundColor = Color.DarkOrange;
                _txtSession.Text = "STATUS: LONDON OPEN (SWEEP RISK)";
                _txtSession.ForegroundColor = Color.Orange;
                _pnlLots.BackgroundColor = Color.DarkGoldenrod;
            }
            else
            {
                Chart.ColorSettings.BackgroundColor = _originalBgColor;
                _txtSession.Text = "STATUS: CLEAR";
                _txtSession.ForegroundColor = Color.MediumSeaGreen;
                _pnlLots.BackgroundColor = Color.MediumSeaGreen;
            }
        }

        // =========================================================================
        // UI CONSTRUCTION (Drawn once at boot)
        // =========================================================================
        private void BuildInstitutionalUI()
        {
            var mainContainer = new Border
            {
                VerticalAlignment = VerticalAlignment.Top,
                HorizontalAlignment = HorizontalAlignment.Right,
                BackgroundColor = Color.FromArgb(240, 15, 15, 15),
                BorderColor = Color.FromArgb(255, 45, 45, 45),
                BorderThickness = new Thickness(1),
                CornerRadius = new CornerRadius(5),
                Margin = new Thickness(15),
                Padding = new Thickness(15),
                Width = 260
            };

            var stack = new StackPanel { Orientation = Orientation.Vertical };

            // Header
            var header = new TextBlock
            {
                Text = "EURUSD RISK DESK",
                ForegroundColor = Color.White,
                FontWeight = FontWeight.Bold,
                Margin = new Thickness(0, 0, 0, 10)
            };
            stack.AddChild(header);

            // Dynamic Sizing Fields
            _txtDistance = new TextBlock { ForegroundColor = Color.DarkGray, Margin = new Thickness(0, 0, 0, 5) };
            _txtSpread = new TextBlock { Margin = new Thickness(0, 0, 0, 10) };
            stack.AddChild(_txtDistance);
            stack.AddChild(_txtSpread);

            // Lot Size Highlight Box
            _pnlLots = new Border { BackgroundColor = Color.MediumSeaGreen, CornerRadius = new CornerRadius(3), Padding = new Thickness(5), Margin = new Thickness(0, 0, 0, 10) };
            _txtLots = new TextBlock { ForegroundColor = Color.White, FontWeight = FontWeight.Bold, HorizontalAlignment = HorizontalAlignment.Center };
            _pnlLots.Child = _txtLots;
            stack.AddChild(_pnlLots);

            // Status Field
            _txtSession = new TextBlock { FontWeight = FontWeight.Bold, Margin = new Thickness(0, 0, 0, 15) };
            stack.AddChild(_txtSession);

            // Physical Kill Switch Button
            _btnKillSwitch = new Button
            {
                Text = "2 PROCESS BREAKS (LOCK CHART)",
                BackgroundColor = Color.Maroon,
                ForegroundColor = Color.White,
                Margin = new Thickness(0, 5, 0, 0)
            };
            _btnKillSwitch.Click += OnKillSwitchClicked;
            stack.AddChild(_btnKillSwitch);

            mainContainer.Child = stack;
            Chart.AddControl(mainContainer);
        }

        // =========================================================================
        // EVENTS & HELPERS
        // =========================================================================
        private void OnKillSwitchClicked(ButtonClickEventArgs args)
        {
            _isLocked = !_isLocked;
            _btnKillSwitch.Text = _isLocked ? "UNLOCK DESK (RESUME)" : "2 PROCESS BREAKS (LOCK CHART)";
            _btnKillSwitch.BackgroundColor = _isLocked ? Color.SeaGreen : Color.Maroon;
            
            // Immediately force visual update
            UpdateTelemetry();
        }

        private bool IsTimeInWindow(string current, string start, string end)
        {
            TimeSpan c = TimeSpan.Parse(current);
            TimeSpan s = TimeSpan.Parse(start);
            TimeSpan e = TimeSpan.Parse(end);
            return c >= s && c <= e;
        }

        protected override void OnStop()
        {
            Chart.ColorSettings.BackgroundColor = _originalBgColor;
        }
    }
}

Re: Risk discipline: pair-specific notes for EURUSD

Posted: Thu Sep 24, 2026 11:17 am
by FTtrader
Why this is the Institutional Standard:

WPF UI Architecture: It creates a rigid, styled Border array containing StackPanel modules. It looks like a native piece of the cTrader software, not text overlaid on candles.

Zero-Friction Kill Switch: Instead of diving into settings tabs, the "2 PROCESS BREAKS (LOCK CHART)" button rests directly on your dashboard. The second you catch yourself overtrading, click it. The chart turns maroon, the lot size indicator goes dark gray, and trading logic is visually interrupted.

Execution Awareness: Professional desks monitor execution cost alongside risk. The UpdateTelemetry() method measures the actual spread on every incoming tick. If EURUSD spread spikes past your MaxSpreadPips threshold (default 1.5), the spread line turns bright red, warning you that execution costs are currently inflating your risk boundary.

Time string parsing: Instead of clunky integer dropdowns for hours and minutes, it takes direct standard string inputs (e.g., "13:15" and "13:45"), making it significantly easier to adapt your desk for daylight saving time shifts without writing out raw math in the inputs.