Advertisement IC Markets

Calibrating my invalidation list for gold impulses under prop constraints

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Calibrating my invalidation list for gold impulses under prop constraints

Post by PTScalper »

cTrader’s C# API (cAlgo) provides a massive execution advantage over both TradingView and MetaTrader: it allows object-oriented state management and native event-driven tick analysis.

Instead of looping backwards over historical bars (like MQL) or relying on restrictive arrays (like Pine), we can instantiate a true "Setup" object in memory the moment an impulse fires, track it dynamically as new bars form, and kill it the exact tick it invalidates.

Here is the institutional-grade cTrader adaptation.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Calibrating my invalidation list for gold impulses under prop constraints

Post by PTScalper »

The cTrader C# Architecture

To install, open cTrader Automate, create a new Indicator, name it XauPropInvalidation, paste the code below, and build (Ctrl + B).

Code: Select all

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class XauPropInvalidation : Indicator
    {
        // ================= 1. Volatility Baseline =================
        [Parameter("ATR Period", Group = "1. Volatility Baseline", DefaultValue = 14)]
        public int AtrPeriod { get; set; }

        [Parameter("Displacement Threshold (x ATR)", Group = "1. Volatility Baseline", DefaultValue = 2.2)]
        public double ImpulseMult { get; set; }

        [Parameter("Require Volume Expansion", Group = "1. Volatility Baseline", DefaultValue = true)]
        public bool VolumeFilter { get; set; }

        // ================= 2. Invalidation Matrix =================
        [Parameter("Velocity Decay Limit (Bars)", Group = "2. Invalidation Matrix", DefaultValue = 4)]
        public int TimeStopBars { get; set; }

        [Parameter("Expected R-Multiple", Group = "2. Invalidation Matrix", DefaultValue = 2.0)]
        public double RrTarget { get; set; }

        [Parameter("Max Allowable Spread (Pips)", Group = "2. Invalidation Matrix", DefaultValue = 3.5)]
        public double MaxSpreadPips { get; set; }

        // ================= Internal State =================
        private AverageTrueRange _atr;
        private SimpleMovingAverage _volSma;
        private List<ActiveSetup> _activeSetups;

        private class ActiveSetup
        {
            public int StartIndex { get; set; }
            public int Direction { get; set; } // 1 for Long, -1 for Short
            public double StopPrice { get; set; }
            public double TargetPrice { get; set; }
            public string Id { get; set; }
        }

        protected override void Initialize()
        {
            _atr = Indicators.AverageTrueRange(AtrPeriod, MovingAverageType.Simple);
            _volSma = Indicators.SimpleMovingAverage(Bars.TickVolumes, 20);
            _activeSetups = new List<ActiveSetup>();
        }

        public override void Calculate(int index)
        {
            if (index < 25) return;

            // 1. Manage Existing Open Setups
            // We iterate backward to safely remove invalid setups from the list during iteration
            for (int i = _activeSetups.Count - 1; i >= 0; i--)
            {
                var setup = _activeSetups[i];
                int barsElapsed = index - setup.StartIndex;

                bool hitStop = (setup.Direction == 1 && Bars.LowPrices[index] <= setup.StopPrice) || 
                               (setup.Direction == -1 && Bars.HighPrices[index] >= setup.StopPrice);
                
                bool hitTarget = (setup.Direction == 1 && Bars.HighPrices[index] >= setup.TargetPrice) || 
                                 (setup.Direction == -1 && Bars.LowPrices[index] <= setup.TargetPrice);
                
                bool hitTime = barsElapsed >= TimeStopBars;

                if (hitStop)
                {
                    Chart.DrawText(setup.Id + "_Out", "STRUCTURAL INVALIDATION", index, setup.StopPrice, Color.Crimson);
                    if (IsLastBar) Print($"XAU {setup.Id}: STOPPED OUT.");
                    _activeSetups.RemoveAt(i);
                }
                else if (hitTarget)
                {
                    Chart.DrawText(setup.Id + "_Out", "TARGET REALIZED", index, setup.TargetPrice, Color.Teal);
                    _activeSetups.RemoveAt(i);
                }
                else if (hitTime)
                {
                    Chart.DrawText(setup.Id + "_Out", "VELOCITY DECAY (TIME STOP)", index, Bars.ClosePrices[index], Color.Silver);
                    if (IsLastBar) Print($"XAU {setup.Id}: TIME STOP TRIGGERED.");
                    _activeSetups.RemoveAt(i);
                }
            }

            // 2. Scan for New Impulse Signatures
            double currentAtr = _atr.Result[index];
            double body = Math.Abs(Bars.ClosePrices[index] - Bars.OpenPrices[index]);
            bool volPass = !VolumeFilter || (Bars.TickVolumes[index] > _volSma.Result[index]);

            bool isBullImpulse = Bars.ClosePrices[index] > Bars.OpenPrices[index] && body > (currentAtr * ImpulseMult) && volPass;
            bool isBearImpulse = Bars.OpenPrices[index] > Bars.ClosePrices[index] && body > (currentAtr * ImpulseMult) && volPass;

            if (isBullImpulse || isBearImpulse)
            {
                int dir = isBullImpulse ? 1 : -1;
                double entry = Bars.ClosePrices[index];
                double stop = isBullImpulse ? Bars.LowPrices[index] : Bars.HighPrices[index];
                double riskDistance = Math.Abs(entry - stop);
                
                // Prevent division by zero if candle has no wick
                if (riskDistance == 0) riskDistance = Symbol.PipSize * 2; 

                double target = isBullImpulse ? (entry + riskDistance * RrTarget) : (entry - riskDistance * RrTarget);
                string setupId = "XAU_" + index;

                // Draw Structural Boundaries directly into the future
                Chart.DrawTrendLine(setupId + "_Stop", index, stop, index + TimeStopBars, stop, Color.Crimson, 2, LineStyle.Lines);
                Chart.DrawTrendLine(setupId + "_Target", index, target, index + TimeStopBars, target, Color.Teal, 2, LineStyle.Lines);
                
                Chart.DrawRectangle(setupId + "_Box", index - 1, entry, index, stop, Color.FromArgb(50, dir == 1 ? Color.LimeGreen : Color.Crimson));

                _activeSetups.Add(new ActiveSetup 
                { 
                    StartIndex = index, 
                    Direction = dir, 
                    StopPrice = stop, 
                    TargetPrice = target, 
                    Id = setupId 
                });
            }

            // 3. Real-Time Risk Execution Dashboard (HUD)
            if (IsLastBar)
            {
                UpdateTerminalHUD(currentAtr);
            }
        }

        private void UpdateTerminalHUD(double currentAtr)
        {
            // Calculate live spread in fractional pips for prop execution limits
            double liveSpreadPips = Symbol.Spread / Symbol.PipSize;
            string spreadAlert = (liveSpreadPips > MaxSpreadPips) ? "BLOWOUT (B-BOOK EXPOSURE)" : "NORMAL (CLEARED)";
            Color spreadColor = (liveSpreadPips > MaxSpreadPips) ? Color.Crimson : Color.Teal;

            string status = _activeSetups.Count > 0 ? "IN TRADE (VULNERABLE)" : "SCANNING LIQUIDITY";
            Color statusColor = _activeSetups.Count > 0 ? Color.Gold : Color.Gray;

            string hudText = 
                $"=== XAU INSTITUTIONAL ENGINE (cTrader) ===\n" +
                $"STATE:           {status}\n" +
                $"LIVE SPREAD:     {Math.Round(liveSpreadPips, 1)} Pips [{spreadAlert}]\n" +
                $"IMPULSE REQ:     > {Math.Round(currentAtr * ImpulseMult, Symbol.Digits)}\n" +
                $"DECAY LIMIT:     Strict {TimeStopBars} Bars\n" +
                $"==========================================";

            Chart.DrawStaticText("PropHUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, Color.White);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Calibrating my invalidation list for gold impulses under prop constraints

Post by PTScalper »

Execution Protocol inside cTrader

The State Machine Advantage: Because we track setups in the _activeSetups List, this indicator will gracefully handle overlapping, back-to-back impulses without drawing bugs or resetting early. It evaluates the exact lifecycle of each impulse independently.

Native Spread Translation: MT4/5 read spread in raw points. cTrader calculates it natively using Symbol.Spread / Symbol.PipSize. Set MaxSpreadPips = 3.5 (or whatever your prop firm dictates), and the HUD will automatically flag if the spread blows out past standard execution thresholds.

Event Cleansing: In cAlgo, any Chart.Draw... commands assigned a specific ID are automatically memory-managed. When the time stop expires, the setup object is deleted from memory, maintaining a completely zero-lag execution profile even on the 1-minute chart.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Calibrating my invalidation list for gold impulses under prop constraints

Post by PTScalper »

To elevate this from a script to a Pro-Grade Institutional Risk Engine, we must leverage cTrader’s greatest advantage: Native WPF UI and Tick-Level Execution.

In the proprietary trading space, latency and sizing are everything. A "pro" tool doesn't just draw lines; it calculates your exact risk exposure dynamically and monitors the order book on a tick-by-tick basis, not bar-by-bar.

Here is the architectural upgrade:

Tick-Level Invalidation (OnTick): We move the invalidation logic out of Calculate() (which is bound to bar formation) and into OnTick(). If Gold spikes and touches your origin wick by a micro-pip, the engine flags it instantly.

Dynamic Prop Lot Sizing: The engine now automatically calculates exactly how many lots you should trade based on the origin-stop distance and your exact daily drawdown risk allowance.

Institutional HUD (WPF UI): Replaces static chart text with a sleek, docked, low-latency WPF dashboard built directly onto the chart.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Calibrating my invalidation list for gold impulses under prop constraints

Post by PTScalper »

The cTrader C# Institutional Engine

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class XauInstitutionalEngine : Indicator
    {
        // ================= 1. Liquidity & Volatility =================
        [Parameter("ATR Period", Group = "1. Volatility Baseline", DefaultValue = 14)]
        public int AtrPeriod { get; set; }

        [Parameter("Displacement Multiplier (x ATR)", Group = "1. Volatility Baseline", DefaultValue = 2.2)]
        public double ImpulseMult { get; set; }

        // ================= 2. Invalidation Matrix =================
        [Parameter("Velocity Decay (Bars)", Group = "2. Invalidation Matrix", DefaultValue = 4)]
        public int TimeStopBars { get; set; }

        [Parameter("Expected R-Multiple", Group = "2. Invalidation Matrix", DefaultValue = 2.0)]
        public double RrTarget { get; set; }

        [Parameter("Max Spread (Pips)", Group = "2. Invalidation Matrix", DefaultValue = 3.5)]
        public double MaxSpreadPips { get; set; }

        // ================= 3. Prop Risk Sizing =================
        [Parameter("Risk Per Setup (%)", Group = "3. Prop Risk", DefaultValue = 0.5)]
        public double RiskPercent { get; set; }

        // ================= Internal Architecture =================
        private AverageTrueRange _atr;
        private List<ActiveSetup> _activeSetups = new List<ActiveSetup>();
        
        // UI Elements
        private TextBlock _uiState;
        private TextBlock _uiSpread;
        private TextBlock _uiLotSize;
        private TextBlock _uiTimeStop;
        private Border _hudBorder;

        private class ActiveSetup
        {
            public int StartIndex { get; set; }
            public int Direction { get; set; } 
            public double EntryPrice { get; set; }
            public double StopPrice { get; set; }
            public double TargetPrice { get; set; }
            public string Id { get; set; }
            public double RequiredLots { get; set; }
        }

        protected override void Initialize()
        {
            _atr = Indicators.AverageTrueRange(AtrPeriod, MovingAverageType.Simple);
            InitializeHUD();
        }

        // ================= BAR LEVEL: SETUP DETECTION =================
        public override void Calculate(int index)
        {
            if (index < 25 || !IsLastBar) return;

            // Purge expired setups via Time Stop
            for (int i = _activeSetups.Count - 1; i >= 0; i--)
            {
                var setup = _activeSetups[i];
                if (index - setup.StartIndex >= TimeStopBars)
                {
                    Chart.DrawText(setup.Id + "_Msg", " VELOCITY DECAY (TIME STOP)", index, Bars.ClosePrices[index], Color.DimGray);
                    _activeSetups.RemoveAt(i);
                }
            }

            double currentAtr = _atr.Result[index - 1]; // Use previous bar ATR for stability
            double body = Math.Abs(Bars.ClosePrices[index] - Bars.OpenPrices[index]);
            
            bool isBullImpulse = Bars.ClosePrices[index] > Bars.OpenPrices[index] && body > (currentAtr * ImpulseMult);
            bool isBearImpulse = Bars.OpenPrices[index] > Bars.ClosePrices[index] && body > (currentAtr * ImpulseMult);

            if (isBullImpulse || isBearImpulse)
            {
                int dir = isBullImpulse ? 1 : -1;
                double entry = Bars.ClosePrices[index];
                double stop = isBullImpulse ? Bars.LowPrices[index] : Bars.HighPrices[index];
                
                // Add 1 micro-pip padding to stop to ensure it sits just beyond the wick
                stop = dir == 1 ? stop - Symbol.TickSize : stop + Symbol.TickSize;

                double riskDistance = Math.Abs(entry - stop);
                double target = isBullImpulse ? (entry + riskDistance * RrTarget) : (entry - riskDistance * RrTarget);
                
                // --- Institutional Risk Calculation ---
                double riskAmount = Account.Equity * (RiskPercent / 100.0);
                double pipDistance = riskDistance / Symbol.PipSize;
                double exactVolume = (riskAmount / (pipDistance * Symbol.PipValue));
                double normalizedLots = Symbol.NormalizeVolumeInUnits(exactVolume, RoundingMode.Down) / 100000.0; // Convert to standard lots
                
                string setupId = "XAU_PI_" + index;

                // Draw Visuals
                Chart.DrawTrendLine(setupId + "_Stop", index, stop, index + TimeStopBars, stop, Color.Crimson, 2, LineStyle.Lines);
                Chart.DrawTrendLine(setupId + "_Target", index, target, index + TimeStopBars, target, Color.Teal, 2, LineStyle.Lines);
                Chart.DrawRectangle(setupId + "_RiskBox", index - 1, entry, index, stop, Color.FromArgb(40, dir == 1 ? Color.MediumSeaGreen : Color.Crimson));

                _activeSetups.Add(new ActiveSetup 
                { 
                    StartIndex = index, Direction = dir, EntryPrice = entry, StopPrice = stop, TargetPrice = target, Id = setupId, RequiredLots = normalizedLots
                });
                
                Notifications.PlaySound(SoundType.Doorbell);
            }
        }

        // ================= TICK LEVEL: MICROSTRUCTURE EXECUTION =================
        protected override void OnTick()
        {
            UpdateHUD();

            if (_activeSetups.Count == 0) return;

            // Tick-by-tick Invalidation Check (Zero Latency)
            double ask = Symbol.Ask;
            double bid = Symbol.Bid;

            for (int i = _activeSetups.Count - 1; i >= 0; i--)
            {
                var setup = _activeSetups[i];

                // Check Stop (Bid for Longs, Ask for Shorts)
                bool hitStop = (setup.Direction == 1 && bid <= setup.StopPrice) || 
                               (setup.Direction == -1 && ask >= setup.StopPrice);
                               
                // Check Target
                bool hitTarget = (setup.Direction == 1 && bid >= setup.TargetPrice) || 
                                 (setup.Direction == -1 && ask <= setup.TargetPrice);

                if (hitStop)
                {
                    Chart.DrawText(setup.Id + "_Msg", " STRUCTURAL INVALIDATION", Bars.Count - 1, setup.StopPrice, Color.Crimson);
                    _activeSetups.RemoveAt(i);
                }
                else if (hitTarget)
                {
                    Chart.DrawText(setup.Id + "_Msg", " TARGET REALIZED", Bars.Count - 1, setup.TargetPrice, Color.Teal);
                    _activeSetups.RemoveAt(i);
                }
            }
        }

        // ================= INSTITUTIONAL HUD (WPF) =================
        private void InitializeHUD()
        {
            var grid = new Grid { Columns = 2, Rows = 4 };
            
            grid.AddChild(new TextBlock { Text = "SYSTEM STATE:", Foreground = Color.Gray, Margin = 5 }, 0, 0);
            _uiState = new TextBlock { Text = "SCANNING", Foreground = Color.White, Margin = 5, FontWeight = FontWeight.Bold };
            grid.AddChild(_uiState, 0, 1);

            grid.AddChild(new TextBlock { Text = "LIVE SPREAD:", Foreground = Color.Gray, Margin = 5 }, 1, 0);
            _uiSpread = new TextBlock { Text = "0.0", Foreground = Color.White, Margin = 5, FontWeight = FontWeight.Bold };
            grid.AddChild(_uiSpread, 1, 1);

            grid.AddChild(new TextBlock { Text = "PROP LOT SIZING:", Foreground = Color.Gray, Margin = 5 }, 2, 0);
            _uiLotSize = new TextBlock { Text = "-", Foreground = Color.White, Margin = 5, FontWeight = FontWeight.Bold };
            grid.AddChild(_uiLotSize, 2, 1);
            
            grid.AddChild(new TextBlock { Text = "T-MINUS (TIME STOP):", Foreground = Color.Gray, Margin = 5 }, 3, 0);
            _uiTimeStop = new TextBlock { Text = "-", Foreground = Color.White, Margin = 5, FontWeight = FontWeight.Bold };
            grid.AddChild(_uiTimeStop, 3, 1);

            _hudBorder = new Border
            {
                BackgroundColor = Color.FromArgb(220, 15, 15, 15),
                BorderColor = Color.FromArgb(100, 100, 100, 100),
                BorderThickness = 1,
                CornerRadius = 3,
                Margin = 10,
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Top,
                Child = grid
            };

            Chart.AddControl(_hudBorder);
        }

        private void UpdateHUD()
        {
            double currentSpread = Math.Round(Symbol.Spread / Symbol.PipSize, 1);
            
            _uiSpread.Text = $"{currentSpread} Pips";
            _uiSpread.Foreground = currentSpread > MaxSpreadPips ? Color.Crimson : Color.Teal;

            if (_activeSetups.Any())
            {
                var active = _activeSetups.Last(); // Track most recent
                int barsElapsed = Bars.Count - 1 - active.StartIndex;
                
                _uiState.Text = "IN TRADE (VULNERABLE)";
                _uiState.Foreground = Color.Gold;
                
                _uiLotSize.Text = $"{Math.Round(active.RequiredLots, 2)} Lots (Risking {RiskPercent}%)";
                _uiLotSize.Foreground = Color.Cyan;

                _uiTimeStop.Text = $"{TimeStopBars - barsElapsed} Bars Remaining";
                _uiTimeStop.Foreground = Color.White;
            }
            else
            {
                _uiState.Text = "SCANNING LIQUIDITY";
                _uiState.Foreground = Color.DarkGray;
                _uiLotSize.Text = "-";
                _uiTimeStop.Text = "-";
            }
        }
        
        protected override void OnDeinitialize()
        {
            if (_hudBorder != null) Chart.RemoveControl(_hudBorder);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Calibrating my invalidation list for gold impulses under prop constraints

Post by PTScalper »

The Institutional Upgrades Explained

cTrader utilizes Windows Presentation Foundation (WPF). Instead of messy text printed on the chart, this generates a docked, semi-transparent black dashboard panel. It reads seamlessly, doesn't clash with candles, and updates at a framerate completely detached from the chart's refresh rate.

Dynamic Risk-to-Lot Converter:

You input your daily/trade risk limit (e.g., 0.5%). When a Gold impulse fires, the script calculates the exact distance from close to the origin wick in pips, divides it by Gold's specific tick value on your prop firm's feed, and outputs the exact lot size you need to execute in the HUD. You never have to guess or under-leverage an impulse again.

Tick-Level Stop Detection (OnTick Method):

Retail indicators check if a stop was hit when the bar closes (via Calculate()). This engine checks Bid/Ask prices on every single raw tick against your origin line. If a Gold wick pierces the origin by Symbol.TickSize (one micro-pip), the system invalidates it on that exact millisecond.

B-Book Spread Monitoring:

Prop firms often stretch the spread precisely when an impulse forms to trigger origin stops prematurely. The HUD actively changes the spread color to Crimson the moment the live tick book spread exceeds your MaxSpreadPips parameter, warning you not to execute the lots it suggests.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply