Advertisement IC Markets

Forex scalping trading strategy for Gold (XAU/USD)

Share, develop, and backtest custom MQL4/MQL5 Expert Advisors, Python data-scraping scripts, trading bots, and automated market alert systems.
PTScalper
Site Admin
Posts: 7643
Joined: Mon Jul 20, 2026 1:28 pm

Re: Forex scalping trading strategy for Gold (XAU/USD)

Post by PTScalper »

Deployment & Strategy Tester Checklist

Attach to an XAUUSD M5 Chart: While the EA pulls D1, H4, and M15 data multi-timeframe via native handles, attach it to the M5 chart (InpTriggerTF = PERIOD_M5) so candle-close triggers evaluate every 5 minutes.

Align Server Time Offset: Check your broker's Market Watch server time offset (most Raw ECN brokers run GMT+2 / GMT+3 DST). If your server is GMT+3, shift InpLondonStartHour to 10 (10:00–13:00 server time) and InpNYStartHour to 15 (15:00–19:00 server time).

Backtest Modeling Quality: In the MT5 Strategy Tester, select "Every tick based on real ticks" to accurately model XAU/USD variable spread widening and partial-fill execution during the London–NY overlap.
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: 7643
Joined: Mon Jul 20, 2026 1:28 pm

Re: Forex scalping trading strategy for Gold (XAU/USD)

Post by PTScalper »

This complete cTrader cBot (XAUUSD_LiquidityScalper.cs) ports the entire MQL5 multi-timeframe liquidity sweep and pullback engine into native cAlgo C# (.NET 6) for Raw ECN execution.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7643
Joined: Mon Jul 20, 2026 1:28 pm

Re: Forex scalping trading strategy for Gold (XAU/USD)

Post by PTScalper »

Key cTrader / cAlgo Architectural Differences from MQL5

Volume in Units (VolumeInUnits) vs. Lots: Unlike MetaTrader 5 where order volume is passed in lots (0.50), cTrader executes orders in raw asset units (1.00 Standard Lot of Gold = 100 Troy Ounces, minimum volume 1 Ounce = 0.01 Lots). The position sizing function automatically computes exact risk using Symbol.TickValue, Symbol.TickSize, and Symbol.LotSize, then normalizes to step units via Symbol.NormalizeVolumeInUnits(..., RoundingMode.Down).

True UTC Clock: Server.TimeInUtc is natively UTC across all cTrader brokers, removing any GMT+2/GMT+3 server-time offset ambiguity for the Asian, London, and NY session windows.

Event-Driven Bar vs. Tick Splitting: Active open positions (1.5R partial close, Breakeven + Commission stop move, M15 structural trail, and time stop) are evaluated on every tick inside OnTick(), while new entry setups evaluate strictly on closed candles inside OnBar().
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7643
Joined: Mon Jul 20, 2026 1:28 pm

Re: Forex scalping trading strategy for Gold (XAU/USD)

Post by PTScalper »

Complete cTrader cBot Source Code (XAUUSD_LiquidityScalper.cs)

In cTrader Automate, create a new cBot named XAUUSD_LiquidityScalper, replace the template code with the source below, and press Ctrl + B to build:

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.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class XAUUSD_LiquidityScalper : Robot
    {
        #region 1. Strategy & Setup Filters

        [Parameter("Enable Liquidity Sweep Setup", Group = "1. Strategy & Setup Filters", DefaultValue = true)]
        public bool InpEnableSweepSetup { get; set; }

        [Parameter("Enable M15 EMA Pullback Setup", Group = "1. Strategy & Setup Filters", DefaultValue = true)]
        public bool InpEnablePullbackSetup { get; set; }

        [Parameter("M15 Fast EMA Period", Group = "1. Strategy & Setup Filters", DefaultValue = 20, MinValue = 5)]
        public int InpEmaFast { get; set; }

        [Parameter("M15 Slow EMA Period", Group = "1. Strategy & Setup Filters", DefaultValue = 50, MinValue = 10)]
        public int InpEmaSlow { get; set; }

        [Parameter("M5 ATR Period", Group = "1. Strategy & Setup Filters", DefaultValue = 14, MinValue = 5)]
        public int InpAtrPeriod { get; set; }

        [Parameter("H4 Swing Lookback (Bars)", Group = "1. Strategy & Setup Filters", DefaultValue = 10, MinValue = 3)]
        public int InpH4SwingLookback { get; set; }

        [Parameter("Max Sweep Overshoot ($ USD)", Group = "1. Strategy & Setup Filters", DefaultValue = 2.50, MinValue = 0.20, Step = 0.10)]
        public double InpMaxSweepOvershootUSD { get; set; }

        [Parameter("EMA Touch Tolerance ($ USD)", Group = "1. Strategy & Setup Filters", DefaultValue = 0.35, MinValue = 0.05, Step = 0.05)]
        public double InpTouchToleranceUSD { get; set; }

        #endregion

        #region 2. XAU/USD Stop-Loss & Target Calibration

        [Parameter("ATR Multiplier Beyond Wick", Group = "2. Stop-Loss & Targets", DefaultValue = 1.0, MinValue = 0.2, Step = 0.1)]
        public double InpAtrStopMultiplier { get; set; }

        [Parameter("Min Structural Stop ($ USD)", Group = "2. Stop-Loss & Targets", DefaultValue = 1.20, MinValue = 0.50, Step = 0.10)]
        public double InpMinStopUSD { get; set; }

        [Parameter("Max Structural Stop ($ USD)", Group = "2. Stop-Loss & Targets", DefaultValue = 3.50, MinValue = 1.50, Step = 0.10)]
        public double InpMaxStopUSD { get; set; }

        [Parameter("Partial Close & BE Trigger (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 1.5, MinValue = 0.5, Step = 0.1)]
        public double InpPartialCloseRR { get; set; }

        [Parameter("Partial Close Ratio (0.5 = 50%)", Group = "2. Stop-Loss & Targets", DefaultValue = 0.50, MinValue = 0.10, MaxValue = 0.90, Step = 0.05)]
        public double InpPartialCloseRatio { get; set; }

        [Parameter("Start M15 Structural Trail (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 2.0, MinValue = 1.0, Step = 0.1)]
        public double InpTrailStartRR { get; set; }

        [Parameter("Final Hard Target (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 3.5, MinValue = 1.5, Step = 0.1)]
        public double InpFinalTargetRR { get; set; }

        [Parameter("Time Stop if < 0.5R (Mins, 0=Off)", Group = "2. Stop-Loss & Targets", DefaultValue = 45, MinValue = 0)]
        public int InpTimeStopMinutes { get; set; }

        #endregion

        #region 3. Risk, ECN Commission & Profit-Lock Scaling

        [Parameter("Baseline Risk Per Trade (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 0.50, MinValue = 0.10, MaxValue = 5.0, Step = 0.05)]
        public double InpBaseRiskPct { get; set; }

        [Parameter("Round-Turn Commission / Lot ($)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 7.00, MinValue = 0.0, Step = 0.50)]
        public double InpCommissionPerLotRT { get; set; }

        [Parameter("Enable Profit-Lock Scaling", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = true)]
        public bool InpUseProfitLockScaling { get; set; }

        [Parameter("Gain % to Trigger 50% Lock", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 2.00, MinValue = 0.50, Step = 0.25)]
        public double InpProfitLockTriggerPct { get; set; }

        [Parameter("% of Unlocked Buffer Added to Risk", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 25.0, MinValue = 5.0, MaxValue = 100.0, Step = 5.0)]
        public double InpUnlockedBufferRiskPct { get; set; }

        [Parameter("Hard Cap on Scaled Risk (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 1.25, MinValue = 0.25, MaxValue = 5.0, Step = 0.25)]
        public double InpMaxScaledRiskPct { get; set; }

        [Parameter("Daily Max Drawdown Breaker (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 1.50, MinValue = 0.50, Step = 0.25)]
        public double InpMaxDailyDrawdownPct { get; set; }

        [Parameter("Max Consecutive Losses / Day", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 3, MinValue = 1)]
        public int InpMaxDailyConsecLosses { get; set; }

        #endregion

        #region 4. Raw ECN Spread & Session Windows (UTC)

        [Parameter("Max Allowed Live Spread ($ USD)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 0.25, MinValue = 0.05, Step = 0.05)]
        public double InpMaxSpreadUSD { get; set; }

        [Parameter("Asian Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 0, MinValue = 0, MaxValue = 23)]
        public int InpAsianStartHour { get; set; }

        [Parameter("Asian End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 7, MinValue = 1, MaxValue = 23)]
        public int InpAsianEndHour { get; set; }

        [Parameter("Trade London Open Window", Group = "4. Spread & Session Windows (UTC)", DefaultValue = true)]
        public bool InpTradeLondonSession { get; set; }

        [Parameter("London Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 7, MinValue = 0, MaxValue = 23)]
        public int InpLondonStartHour { get; set; }

        [Parameter("London End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 10, MinValue = 1, MaxValue = 23)]
        public int InpLondonEndHour { get; set; }

        [Parameter("Trade London-NY Overlap", Group = "4. Spread & Session Windows (UTC)", DefaultValue = true)]
        public bool InpTradeNYOverlap { get; set; }

        [Parameter("NY Overlap Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 12, MinValue = 0, MaxValue = 23)]
        public int InpNYStartHour { get; set; }

        [Parameter("NY Overlap End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 16, MinValue = 1, MaxValue = 23)]
        public int InpNYEndHour { get; set; }

        [Parameter("Bot Label", Group = "4. Spread & Session Windows (UTC)", DefaultValue = "XAU_Scalper_2026")]
        public string InpBotLabel { get; set; }

        #endregion

        #region Private Fields & Multi-Timeframe Series

        private Bars _m5Bars;
        private Bars _m15Bars;
        private Bars _h4Bars;
        private Bars _d1Bars;

        private ExponentialMovingAverage _emaFastM15;
        private ExponentialMovingAverage _emaSlowM15;
        private AverageTrueRange _atrM5;

        // Risk & Profit-Lock State
        private int _currentDayOfYear = -1;
        private double _dayStartEquity;
        private double _baseReferenceEq;
        private double _lockedEquityFloor;
        private int _dailyConsecLosses;
        private bool _dailyHalted;

        // Liquidity Pool Cache
        private double _pdh;
        private double _pdl;
        private double _asianHigh;
        private double _asianLow;
        private double _h4SwingHigh;
        private double _h4SwingLow;

        // Track 1R price distance and Partial-Close execution per Position ID
        private readonly HashSet<int> _partialClosedIds = new HashSet<int>();
        private readonly Dictionary<int, double> _initialRiskDistByPosId = new Dictionary<int, double>();

        #endregion

        protected override void OnStart()
        {
            // Load Multi-Timeframe Bars
            _m5Bars  = MarketData.GetBars(TimeFrame.Minute5);
            _m15Bars = MarketData.GetBars(TimeFrame.Minute15);
            _h4Bars  = MarketData.GetBars(TimeFrame.Hour4);
            _d1Bars  = MarketData.GetBars(TimeFrame.Daily);

            // Initialize Indicators on their respective timeframes
            _emaFastM15 = Indicators.ExponentialMovingAverage(_m15Bars.ClosePrices, InpEmaFast);
            _emaSlowM15 = Indicators.ExponentialMovingAverage(_m15Bars.ClosePrices, InpEmaSlow);
            _atrM5      = Indicators.AverageTrueRange(_m5Bars, InpAtrPeriod, MovingAverageType.Exponential);

            // Initialize Equity & Profit-Lock State
            double eq = Account.Equity;
            _dayStartEquity    = eq;
            _baseReferenceEq   = eq;
            _lockedEquityFloor = eq * (1.0 - InpMaxDailyDrawdownPct / 100.0);
            _currentDayOfYear  = Server.TimeInUtc.DayOfYear;

            Positions.Closed += OnPositionClosed;

            Print("XAUUSD_LiquidityScalper initialized on {0}. PipSize: {1}, TickSize: {2}, LotSize: {3}",
                SymbolName, Symbol.PipSize, Symbol.TickSize, Symbol.LotSize);
        }

        protected override void OnStop()
        {
            Positions.Closed -= OnPositionClosed;
        }

        protected override void OnTick()
        {
            // 1. Daily Reset & Circuit Breaker Audit
            CheckDailyResetAndDrawdown();

            // 2. Manage Open Positions on Every Tick (Partial Close, BE+Comm, Structural Trail, Time Stop)
            var activePositions = Positions.FindAll(InpBotLabel, SymbolName);
            if (activePositions.Length > 0)
            {
                ManageOpenPositions(activePositions);
            }
        }

        protected override void OnBar()
        {
            if (_dailyHalted)
                return;

            // One active position at a time on XAU/USD
            if (Positions.FindAll(InpBotLabel, SymbolName).Length > 0)
                return;

            // 1. Verify Active Session Window (UTC)
            if (!IsWithinActiveSession())
                return;

            // 2. Verify Live Spread Filter ($ USD)
            double liveSpreadUSD = Symbol.Ask - Symbol.Bid;
            if (liveSpreadUSD > InpMaxSpreadUSD)
                return;

            // 3. Refresh H4/D1 & Asian Session Liquidity Pools
            UpdateLiquidityPools();

            double currentAtr = _atrM5.Result.Last(1);
            if (double.IsNaN(currentAtr) || currentAtr <= 0.0)
                return;

            // 4. Evaluate Setup 1: Liquidity Sweep (False Breakout of Key Level)
            if (InpEnableSweepSetup)
            {
                if (CheckBullishSweep(currentAtr, out double buySweepSl))
                {
                    ExecuteScalpTrade(TradeType.Buy, buySweepSl, "XAU_Sweep_Buy");
                    return;
                }

                if (CheckBearishSweep(currentAtr, out double sellSweepSl))
                {
                    ExecuteScalpTrade(TradeType.Sell, sellSweepSl, "XAU_Sweep_Sell");
                    return;
                }
            }

            // 5. Evaluate Setup 2: M15 EMA 20/50 Trend Pullback + Micro BoS
            if (InpEnablePullbackSetup)
            {
                double m15Fast = _emaFastM15.Result.Last(1);
                double m15Slow = _emaSlowM15.Result.Last(1);

                if (CheckBullishPullback(m15Fast, m15Slow, currentAtr, out double buyPbSl))
                {
                    ExecuteScalpTrade(TradeType.Buy, buyPbSl, "XAU_Pullback_Buy");
                    return;
                }

                if (CheckBearishPullback(m15Fast, m15Slow, currentAtr, out double sellPbSl))
                {
                    ExecuteScalpTrade(TradeType.Sell, sellPbSl, "XAU_Pullback_Sell");
                    return;
                }
            }
        }

        #region Position Closed Event & Profit-Lock Scaling State

        private void OnPositionClosed(PositionClosedEventArgs args)
        {
            Position pos = args.Position;
            if (pos.Label != InpBotLabel || pos.SymbolName != SymbolName)
                return;

            // Only count full position closures (when no remaining slice of the position exists)
            if (Positions.FindAll(InpBotLabel, SymbolName).Length == 0)
            {
                _partialClosedIds.Remove(pos.Id);
                _initialRiskDistByPosId.Remove(pos.Id);

                if (pos.NetProfit < -1.0)
                    _dailyConsecLosses++;
                else if (pos.NetProfit > 1.0)
                    _dailyConsecLosses = 0;
            }

            // Evaluate Exponential Profit-Lock Trigger
            double eq = Account.Equity;
            double gainPct = ((eq - _baseReferenceEq) / _baseReferenceEq) * 100.0;

            if (InpUseProfitLockScaling && gainPct >= InpProfitLockTriggerPct)
            {
                double gainedUSD   = eq - _baseReferenceEq;
                _lockedEquityFloor = _baseReferenceEq + (gainedUSD * 0.50);
                _baseReferenceEq   = eq;

                Print("Profit-Lock Triggered! New Locked Equity Floor: ${0:F2} \vert{} New Reference:${1:F2}",
                    _lockedEquityFloor, _baseReferenceEq);
            }
        }

        private void CheckDailyResetAndDrawdown()
        {
            DateTime utcNow = Server.TimeInUtc;
            double eq = Account.Equity;

            if (utcNow.DayOfYear != _currentDayOfYear)
            {
                _currentDayOfYear  = utcNow.DayOfYear;
                _dayStartEquity    = eq;
                _dailyConsecLosses = 0;
                _dailyHalted       = false;
            }

            if (_dailyHalted)
                return;

            double dailyDDPct = ((_dayStartEquity - eq) / _dayStartEquity) * 100.0;
            if (dailyDDPct >= InpMaxDailyDrawdownPct)
            {
                _dailyHalted = true;
                Print("Circuit Breaker: Max Daily Drawdown ({0:F2}%) reached. Trading halted for today.", dailyDDPct);
            }
            else if (_dailyConsecLosses >= InpMaxDailyConsecLosses)
            {
                _dailyHalted = true;
                Print("Circuit Breaker: {0} Consecutive Losses reached. Trading halted for today.", _dailyConsecLosses);
            }
        }

        #endregion

        #region Liquidity Pool Mapping (PDH/PDL, Asian Session, H4 Swings)

        private void UpdateLiquidityPools()
        {
            // 1. Previous Day High & Low (Last closed D1 bar = Last(1))
            if (_d1Bars.Count >= 2)
            {
                _pdh = _d1Bars.HighPrices.Last(1);
                _pdl = _d1Bars.LowPrices.Last(1);
            }

            // 2. H4 Structural Swing High & Low (Last N closed H4 bars)
            _h4SwingHigh = 0.0;
            _h4SwingLow  = double.MaxValue;

            int h4Count = _h4Bars.Count;
            for (int i = 1; i <= InpH4SwingLookback && (h4Count - 1 - i) >= 0; i++)
            {
                double h = _h4Bars.HighPrices.Last(i);
                double l = _h4Bars.LowPrices.Last(i);
                if (h > _h4SwingHigh) _h4SwingHigh = h;
                if (l < _h4SwingLow)  _h4SwingLow  = l;
            }
            if (_h4SwingLow == double.MaxValue) _h4SwingLow = 0.0;

            // 3. Asian Session High & Low (Calculated from closed M15 bars today in UTC)
            DateTime utcNow     = Server.TimeInUtc;
            DateTime asianStart = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, InpAsianStartHour, 0, 0, DateTimeKind.Utc);
            DateTime asianEnd   = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, InpAsianEndHour,   0, 0, DateTimeKind.Utc);

            _asianHigh = 0.0;
            _asianLow  = double.MaxValue;

            int m15Count = _m15Bars.Count;
            for (int i = 1; i <= 100 && (m15Count - 1 - i) >= 0; i++)
            {
                int idx = m15Count - 1 - i;
                DateTime barTime = _m15Bars.OpenTimes[idx];
                if (barTime < asianStart)
                    break;

                if (barTime >= asianStart && barTime < asianEnd)
                {
                    double h = _m15Bars.HighPrices[idx];
                    double l = _m15Bars.LowPrices[idx];
                    if (h > _asianHigh) _asianHigh = h;
                    if (l < _asianLow)  _asianLow  = l;
                }
            }
            if (_asianLow == double.MaxValue) _asianLow = 0.0;
        }

        #endregion

        #region Entry Setup Detection (Liquidity Sweep & M15 EMA Pullback)

        private bool CheckBullishSweep(double atr, out double outSlPrice)
        {
            outSlPrice = 0.0;

            double open1  = Bars.OpenPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double high1  = Bars.HighPrices.Last(1);
            double low1   = Bars.LowPrices.Last(1);
            double low2   = Bars.LowPrices.Last(2);
            double high2  = Bars.HighPrices.Last(2);

            double range1 = high1 - low1;
            double body1  = close1 - open1;
            if (range1 <= 0.0 || body1 <= 0.45 * range1)
                return false;

            double sweepLow = Math.Min(low1, low2);
            double[] levels = { _pdl, _asianLow, _h4SwingLow };

            foreach (double lvl in levels)
            {
                if (lvl <= 0.0) continue;

                double overshoot = lvl - sweepLow;
                if (overshoot > 0.0 && overshoot <= InpMaxSweepOvershootUSD &&
                    close1 > lvl && (close1 > high2 || body1 > 0.60 * range1))
                {
                    outSlPrice = sweepLow - (atr * InpAtrStopMultiplier);
                    return true;
                }
            }
            return false;
        }

        private bool CheckBearishSweep(double atr, out double outSlPrice)
        {
            outSlPrice = 0.0;

            double open1  = Bars.OpenPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double high1  = Bars.HighPrices.Last(1);
            double low1   = Bars.LowPrices.Last(1);
            double high2  = Bars.HighPrices.Last(2);
            double low2   = Bars.LowPrices.Last(2);

            double range1 = high1 - low1;
            double body1  = open1 - close1;
            if (range1 <= 0.0 || body1 <= 0.45 * range1)
                return false;

            double sweepHigh = Math.Max(high1, high2);
            double[] levels  = { _pdh, _asianHigh, _h4SwingHigh };

            foreach (double lvl in levels)
            {
                if (lvl <= 0.0) continue;

                double overshoot = sweepHigh - lvl;
                if (overshoot > 0.0 && overshoot <= InpMaxSweepOvershootUSD &&
                    close1 < lvl && (close1 < low2 || body1 > 0.60 * range1))
                {
                    outSlPrice = sweepHigh + (atr * InpAtrStopMultiplier);
                    return true;
                }
            }
            return false;
        }

        private bool CheckBullishPullback(double emaFast, double emaSlow, double atr, out double outSlPrice)
        {
            outSlPrice = 0.0;
            if (emaFast <= emaSlow)
                return false;

            double open1  = Bars.OpenPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double low1   = Bars.LowPrices.Last(1);
            double high2  = Bars.HighPrices.Last(2);
            double low2   = Bars.LowPrices.Last(2);

            double pullbackLow = Math.Min(low1, low2);

            bool touchedValueZone = (pullbackLow <= emaFast + InpTouchToleranceUSD) &&
                                    (pullbackLow >= emaSlow - InpTouchToleranceUSD);
            bool bullishBoS       = (close1 > open1) && (close1 > emaFast) && (close1 > high2);

            if (touchedValueZone && bullishBoS)
            {
                outSlPrice = pullbackLow - (atr * InpAtrStopMultiplier);
                return true;
            }
            return false;
        }

        private bool CheckBearishPullback(double emaFast, double emaSlow, double atr, out double outSlPrice)
        {
            outSlPrice = 0.0;
            if (emaFast >= emaSlow)
                return false;

            double open1  = Bars.OpenPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double high1  = Bars.HighPrices.Last(1);
            double high2  = Bars.HighPrices.Last(2);
            double low2   = Bars.LowPrices.Last(2);

            double pullbackHigh = Math.Max(high1, high2);

            bool touchedValueZone = (pullbackHigh >= emaFast - InpTouchToleranceUSD) &&
                                    (pullbackHigh <= emaSlow + InpTouchToleranceUSD);
            bool bearishBoS       = (close1 < open1) && (close1 < emaFast) && (close1 < low2);

            if (touchedValueZone && bearishBoS)
            {
                outSlPrice = pullbackHigh + (atr * InpAtrStopMultiplier);
                return true;
            }
            return false;
        }

        #endregion

        #region Execution & ECN Commission-Adjusted Volume Sizing

        private void ExecuteScalpTrade(TradeType tradeType, double rawSlPrice, string comment)
        {
            double entryPrice = (tradeType == TradeType.Buy) ? Symbol.Ask : Symbol.Bid;
            double slDistUSD  = Math.Abs(entryPrice - rawSlPrice);

            // Clamp Stop-Loss distance to XAU/USD structural bounds ($1.20 to $3.50)
            if (slDistUSD < InpMinStopUSD)
                slDistUSD = InpMinStopUSD;
            else if (slDistUSD > InpMaxStopUSD)
                return; // Skip trade if structural stop is too wide for a scalp

            double tpDistUSD = slDistUSD * InpFinalTargetRR;

            // Convert USD Price Delta to cTrader Pips (Symbol.PipSize)
            double slPips = Math.Round(slDistUSD / Symbol.PipSize, 1);
            double tpPips = Math.Round(tpDistUSD / Symbol.PipSize, 1);

            double volumeInUnits = CalculateVolumeInUnits(slDistUSD);
            if (volumeInUnits < Symbol.VolumeInUnitsMin)
                return;

            TradeResult result = ExecuteMarketOrder(tradeType, SymbolName, volumeInUnits, InpBotLabel, slPips, tpPips, comment);
            if (result.IsSuccessful && result.Position != null)
            {
                _initialRiskDistByPosId[result.Position.Id] = slDistUSD;
            }
        }

        private double CalculateVolumeInUnits(double slDistUSD)
        {
            double eq      = Account.Equity;
            double riskUSD = eq * (InpBaseRiskPct / 100.0);

            // Apply Exponential Profit-Lock Scaling if active and above locked floor
            if (InpUseProfitLockScaling && eq > _lockedEquityFloor)
            {
                double unlockedProfit = Math.Max(0.0, eq - _baseReferenceEq);
                if (unlockedProfit > 0.0)
                {
                    double bonusRiskUSD = unlockedProfit * (InpUnlockedBufferRiskPct / 100.0);
                    double maxRiskUSD   = eq * (InpMaxScaledRiskPct / 100.0);
                    riskUSD = Math.Min(riskUSD + bonusRiskUSD, maxRiskUSD);
                }
            }

            if (Symbol.TickSize <= 0.0 || Symbol.TickValue <= 0.0 || Symbol.LotSize <= 0.0)
                return 0.0;

            // Symbol.TickValue in cTrader is the value of 1 tick for 1 unit (1 oz on XAUUSD)
            // Multiply by Symbol.LotSize (100 oz) to get loss per 1.00 Standard Lot
            double priceLossPerUnit = (slDistUSD / Symbol.TickSize) * Symbol.TickValue;
            double priceLossPerLot  = priceLossPerUnit * Symbol.LotSize;
            double totalLossPerLot  = priceLossPerLot + InpCommissionPerLotRT;

            if (totalLossPerLot <= 0.0)
                return 0.0;

            double rawLots      = riskUSD / totalLossPerLot;
            double rawUnits     = rawLots * Symbol.LotSize;
            double normUnits    = Symbol.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);

            if (normUnits < Symbol.VolumeInUnitsMin)
                return 0.0;
            if (normUnits > Symbol.VolumeInUnitsMax)
                normUnits = Symbol.VolumeInUnitsMax;

            return normUnits;
        }

        #endregion

        #region Position Management (Partial Close, BE+Comm, M15 Trail, Time Stop)

        private void ManageOpenPositions(Position[] positions)
        {
            foreach (Position pos in positions)
            {
                double openPrice    = pos.EntryPrice;
                double currentPrice = (pos.TradeType == TradeType.Buy) ? Symbol.Bid : Symbol.Ask;
                double profitDist   = (pos.TradeType == TradeType.Buy)
                                    ? (currentPrice - openPrice)
                                    : (openPrice - currentPrice);

                // Retrieve initial 1R price distance
                if (!_initialRiskDistByPosId.TryGetValue(pos.Id, out double initialRiskDist) || initialRiskDist <= 0.0)
                {
                    initialRiskDist = pos.TakeProfit.HasValue
                        ? Math.Abs(pos.TakeProfit.Value - openPrice) / InpFinalTargetRR
                        : InpMinStopUSD;
                    _initialRiskDistByPosId[pos.Id] = initialRiskDist;
                }

                double currentR = profitDist / initialRiskDist;

                // 1. Time Stop Check: Close if open > InpTimeStopMinutes and profit < 0.5R
                if (InpTimeStopMinutes > 0)
                {
                    double elapsedMins = (Server.TimeInUtc - pos.EntryTime).TotalMinutes;
                    if (elapsedMins >= InpTimeStopMinutes && currentR < 0.5)
                    {
                        ClosePosition(pos);
                        Print("Time Stop executed on Position #{0} after {1:F0} mins (R = {2:F2})",
                            pos.Id, elapsedMins, currentR);
                        continue;
                    }
                }

                // 2. Partial Close at +1.5R & Move Stop-Loss to Breakeven + $0.15 Commission Buffer
                if (currentR >= InpPartialCloseRR && !_partialClosedIds.Contains(pos.Id))
                {
                    double rawCloseUnits  = pos.VolumeInUnits * InpPartialCloseRatio;
                    double normCloseUnits = Symbol.NormalizeVolumeInUnits(rawCloseUnits, RoundingMode.Down);

                    if (normCloseUnits >= Symbol.VolumeInUnitsMin &&
                        (pos.VolumeInUnits - normCloseUnits) >= Symbol.VolumeInUnitsMin)
                    {
                        ClosePosition(pos, normCloseUnits);
                    }

                    // Move Stop-Loss to Breakeven + $0.15 Commission/Spread Offset
                    double commOffsetUSD = 0.15;
                    double newSlPrice = (pos.TradeType == TradeType.Buy)
                        ? Math.Round(openPrice + commOffsetUSD, Symbol.Digits)
                        : Math.Round(openPrice - commOffsetUSD, Symbol.Digits);

                    bool shouldModifySL = (pos.TradeType == TradeType.Buy)
                        ? (!pos.StopLoss.HasValue || newSlPrice > pos.StopLoss.Value)
                        : (!pos.StopLoss.HasValue || newSlPrice < pos.StopLoss.Value);

                    if (shouldModifySL)
                    {
                        ModifyPosition(pos, newSlPrice, pos.TakeProfit);
                    }

                    _partialClosedIds.Add(pos.Id);
                }

                // 3. M15 Structural Trailing Stop once trade reaches +2.0R
                if (currentR
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7643
Joined: Mon Jul 20, 2026 1:28 pm

Re: Forex scalping trading strategy for Gold (XAU/USD)

Post by PTScalper »

cTrader Backtesting & Live Deployment Notes

Attach to an XAUUSD m5 Chart: The cBot evaluates OnBar() on your chart's primary timeframe (m5 recommended) while automatically pulling m15, h4, and D1 series in the background via MarketData.GetBars(...).

Accurate Commission Simulation in Backtesting: In the cTrader Automate Backtesting Settings tab (gear icon), set Data to "Tick data from Server (accurate)" and set Commission to 35 per million USD (or equivalent $3.50 per side / $7.00 round-turn per standard lot) so the Strategy Tester's net equity curve matches the internal sizing formula.

Mobile Notification / cTrader ID Sync: If you monitor or manage runners via cTrader Mobile while the cBot runs on a VPS or workstation, keep InpBotLabel unique (XAU_Scalper_2026) so the cBot only manages its own positions and leaves any manual mobile scalp entries untouched.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7643
Joined: Mon Jul 20, 2026 1:28 pm

Re: Forex scalping trading strategy for Gold (XAU/USD)

Post by PTScalper »

This extended version of the cTrader cBot (XAUUSD_LiquidityScalper.cs) adds:

Live Chart Drawings: Automatically plots and updates horizontal lines for PDH, PDL, Asian Session High/Low, and H4 Structural Swings directly on the cTrader chart with distinct color codings.

Top-Right HUD (Chart.DrawStaticText): Renders a real-time HUD displaying live spread, current equity, locked equity floor, session status, and active position metrics.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7643
Joined: Mon Jul 20, 2026 1:28 pm

Re: Forex scalping trading strategy for Gold (XAU/USD)

Post by PTScalper »

Extended cTrader cBot Code (XAUUSD_LiquidityScalper.cs)

Replace your cBot code in cTrader Automate with the script below and press Ctrl + B to build:

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.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class XAUUSD_LiquidityScalper : Robot
    {
        #region 1. Strategy & Setup Filters

        [Parameter("Enable Liquidity Sweep Setup", Group = "1. Strategy & Setup Filters", DefaultValue = true)]
        public bool InpEnableSweepSetup { get; set; }

        [Parameter("Enable M15 EMA Pullback Setup", Group = "1. Strategy & Setup Filters", DefaultValue = true)]
        public bool InpEnablePullbackSetup { get; set; }

        [Parameter("M15 Fast EMA Period", Group = "1. Strategy & Setup Filters", DefaultValue = 20, MinValue = 5)]
        public int InpEmaFast { get; set; }

        [Parameter("M15 Slow EMA Period", Group = "1. Strategy & Setup Filters", DefaultValue = 50, MinValue = 10)]
        public int InpEmaSlow { get; set; }

        [Parameter("M5 ATR Period", Group = "1. Strategy & Setup Filters", DefaultValue = 14, MinValue = 5)]
        public int InpAtrPeriod { get; set; }

        [Parameter("H4 Swing Lookback (Bars)", Group = "1. Strategy & Setup Filters", DefaultValue = 10, MinValue = 3)]
        public int InpH4SwingLookback { get; set; }

        [Parameter("Max Sweep Overshoot ($ USD)", Group = "1. Strategy & Setup Filters", DefaultValue = 2.50, MinValue = 0.20, Step = 0.10)]
        public double InpMaxSweepOvershootUSD { get; set; }

        [Parameter("EMA Touch Tolerance ($ USD)", Group = "1. Strategy & Setup Filters", DefaultValue = 0.35, MinValue = 0.05, Step = 0.05)]
        public double InpTouchToleranceUSD { get; set; }

        #endregion

        #region 2. XAU/USD Stop-Loss & Target Calibration

        [Parameter("ATR Multiplier Beyond Wick", Group = "2. Stop-Loss & Targets", DefaultValue = 1.0, MinValue = 0.2, Step = 0.1)]
        public double InpAtrStopMultiplier { get; set; }

        [Parameter("Min Structural Stop ($ USD)", Group = "2. Stop-Loss & Targets", DefaultValue = 1.20, MinValue = 0.50, Step = 0.10)]
        public double InpMinStopUSD { get; set; }

        [Parameter("Max Structural Stop ($ USD)", Group = "2. Stop-Loss & Targets", DefaultValue = 3.50, MinValue = 1.50, Step = 0.10)]
        public double InpMaxStopUSD { get; set; }

        [Parameter("Partial Close & BE Trigger (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 1.5, MinValue = 0.5, Step = 0.1)]
        public double InpPartialCloseRR { get; set; }

        [Parameter("Partial Close Ratio (0.5 = 50%)", Group = "2. Stop-Loss & Targets", DefaultValue = 0.50, MinValue = 0.10, MaxValue = 0.90, Step = 0.05)]
        public double InpPartialCloseRatio { get; set; }

        [Parameter("Start M15 Structural Trail (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 2.0, MinValue = 1.0, Step = 0.1)]
        public double InpTrailStartRR { get; set; }

        [Parameter("Final Hard Target (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 3.5, MinValue = 1.5, Step = 0.1)]
        public double InpFinalTargetRR { get; set; }

        [Parameter("Time Stop if < 0.5R (Mins, 0=Off)", Group = "2. Stop-Loss & Targets", DefaultValue = 45, MinValue = 0)]
        public int InpTimeStopMinutes { get; set; }

        #endregion

        #region 3. Risk, ECN Commission & Profit-Lock Scaling

        [Parameter("Baseline Risk Per Trade (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 0.50, MinValue = 0.10, MaxValue = 5.0, Step = 0.05)]
        public double InpBaseRiskPct { get; set; }

        [Parameter("Round-Turn Commission / Lot ($)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 7.00, MinValue = 0.0, Step = 0.50)]
        public double InpCommissionPerLotRT { get; set; }

        [Parameter("Enable Profit-Lock Scaling", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = true)]
        public bool InpUseProfitLockScaling { get; set; }

        [Parameter("Gain % to Trigger 50% Lock", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 2.00, MinValue = 0.50, Step = 0.25)]
        public double InpProfitLockTriggerPct { get; set; }

        [Parameter("% of Unlocked Buffer Added to Risk", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 25.0, MinValue = 5.0, MaxValue = 100.0, Step = 5.0)]
        public double InpUnlockedBufferRiskPct { get; set; }

        [Parameter("Hard Cap on Scaled Risk (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 1.25, MinValue = 0.25, MaxValue = 5.0, Step = 0.25)]
        public double InpMaxScaledRiskPct { get; set; }

        [Parameter("Daily Max Drawdown Breaker (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 1.50, MinValue = 0.50, Step = 0.25)]
        public double InpMaxDailyDrawdownPct { get; set; }

        [Parameter("Max Consecutive Losses / Day", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 3, MinValue = 1)]
        public int InpMaxDailyConsecLosses { get; set; }

        #endregion

        #region 4. Raw ECN Spread & Session Windows (UTC)

        [Parameter("Max Allowed Live Spread ($ USD)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 0.25, MinValue = 0.05, Step = 0.05)]
        public double InpMaxSpreadUSD { get; set; }

        [Parameter("Asian Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 0, MinValue = 0, MaxValue = 23)]
        public int InpAsianStartHour { get; set; }

        [Parameter("Asian End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 7, MinValue = 1, MaxValue = 23)]
        public int InpAsianEndHour { get; set; }

        [Parameter("Trade London Open Window", Group = "4. Spread & Session Windows (UTC)", DefaultValue = true)]
        public bool InpTradeLondonSession { get; set; }

        [Parameter("London Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 7, MinValue = 0, MaxValue = 23)]
        public int InpLondonStartHour { get; set; }

        [Parameter("London End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 10, MinValue = 1, MaxValue = 23)]
        public int InpLondonEndHour { get; set; }

        [Parameter("Trade London-NY Overlap", Group = "4. Spread & Session Windows (UTC)", DefaultValue = true)]
        public bool InpTradeNYOverlap { get; set; }

        [Parameter("NY Overlap Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 12, MinValue = 0, MaxValue = 23)]
        public int InpNYStartHour { get; set; }

        [Parameter("NY Overlap End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 16, MinValue = 1, MaxValue = 23)]
        public int InpNYEndHour { get; set; }

        [Parameter("Bot Label", Group = "4. Spread & Session Windows (UTC)", DefaultValue = "XAU_Scalper_2026")]
        public string InpBotLabel { get; set; }

        #endregion

        #region Private Fields & Multi-Timeframe Series

        private Bars _m5Bars;
        private Bars _m15Bars;
        private Bars _h4Bars;
        private Bars _d1Bars;

        private ExponentialMovingAverage _emaFastM15;
        private ExponentialMovingAverage _emaSlowM15;
        private AverageTrueRange _atrM5;

        // Risk & Profit-Lock State
        private int _currentDayOfYear = -1;
        private double _dayStartEquity;
        private double _baseReferenceEq;
        private double _lockedEquityFloor;
        private int _dailyConsecLosses;
        private bool _dailyHalted;

        // Liquidity Pool Cache
        private double _pdh;
        private double _pdl;
        private double _asianHigh;
        private double _asianLow;
        private double _h4SwingHigh;
        private double _h4SwingLow;

        // Track 1R price distance and Partial-Close execution per Position ID
        private readonly HashSet<int> _partialClosedIds = new HashSet<int>();
        private readonly Dictionary<int, double> _initialRiskDistByPosId = new Dictionary<int, double>();

        #endregion

        protected override void OnStart()
        {
            // Load Multi-Timeframe Bars
            _m5Bars  = MarketData.GetBars(TimeFrame.Minute5);
            _m15Bars = MarketData.GetBars(TimeFrame.Minute15);
            _h4Bars  = MarketData.GetBars(TimeFrame.Hour4);
            _d1Bars  = MarketData.GetBars(TimeFrame.Daily);

            // Initialize Indicators
            _emaFastM15 = Indicators.ExponentialMovingAverage(_m15Bars.ClosePrices, InpEmaFast);
            _emaSlowM15 = Indicators.ExponentialMovingAverage(_m15Bars.ClosePrices, InpEmaSlow);
            _atrM5      = Indicators.AverageTrueRange(_m5Bars, InpAtrPeriod, MovingAverageType.Exponential);

            // Initialize Equity & Profit-Lock State
            double eq = Account.Equity;
            _dayStartEquity    = eq;
            _baseReferenceEq   = eq;
            _lockedEquityFloor = eq * (1.0 - InpMaxDailyDrawdownPct / 100.0);
            _currentDayOfYear  = Server.TimeInUtc.DayOfYear;

            Positions.Closed += OnPositionClosed;

            Print("XAUUSD_LiquidityScalper initialized on {0}. PipSize: {1}, TickSize: {2}, LotSize: {3}",
                SymbolName, Symbol.PipSize, Symbol.TickSize, Symbol.LotSize);
        }

        protected override void OnStop()
        {
            Positions.Closed -= OnPositionClosed;
            ClearChartDrawings();
        }

        protected override void OnTick()
        {
            // 1. Daily Reset & Circuit Breaker Audit
            CheckDailyResetAndDrawdown();

            // 2. Manage Open Positions on Every Tick
            var activePositions = Positions.FindAll(InpBotLabel, SymbolName);
            if (activePositions.Length > 0)
            {
                ManageOpenPositions(activePositions);
            }

            // 3. Update HUD Display on Every Tick for Live Spread & Equity Feedback
            UpdateChartHUD();
        }

        protected override void OnBar()
        {
            if (_dailyHalted)
                return;

            // One active position at a time on XAU/USD
            if (Positions.FindAll(InpBotLabel, SymbolName).Length > 0)
                return;

            // 1. Verify Active Session Window (UTC)
            if (!IsWithinActiveSession())
                return;

            // 2. Verify Live Spread Filter ($ USD)
            double liveSpreadUSD = Symbol.Ask - Symbol.Bid;
            if (liveSpreadUSD > InpMaxSpreadUSD)
                return;

            // 3. Refresh H4/D1 & Asian Session Liquidity Pools & Draw Chart Lines
            UpdateLiquidityPools();
            DrawLiquidityLines();

            double currentAtr = _atrM5.Result.Last(1);
            if (double.IsNaN(currentAtr) || currentAtr <= 0.0)
                return;

            // 4. Evaluate Setup 1: Liquidity Sweep
            if (InpEnableSweepSetup)
            {
                if (CheckBullishSweep(currentAtr, out double buySweepSl))
                {
                    ExecuteScalpTrade(TradeType.Buy, buySweepSl, "XAU_Sweep_Buy");
                    return;
                }

                if (CheckBearishSweep(currentAtr, out double sellSweepSl))
                {
                    ExecuteScalpTrade(TradeType.Sell, sellSweepSl, "XAU_Sweep_Sell");
                    return;
                }
            }

            // 5. Evaluate Setup 2: M15 EMA 20/50 Trend Pullback
            if (InpEnablePullbackSetup)
            {
                double m15Fast = _emaFastM15.Result.Last(1);
                double m15Slow = _emaSlowM15.Result.Last(1);

                if (CheckBullishPullback(m15Fast, m15Slow, currentAtr, out double buyPbSl))
                {
                    ExecuteScalpTrade(TradeType.Buy, buyPbSl, "XAU_Pullback_Buy");
                    return;
                }

                if (CheckBearishPullback(m15Fast, m15Slow, currentAtr, out double sellPbSl))
                {
                    ExecuteScalpTrade(TradeType.Sell, sellPbSl, "XAU_Pullback_Sell");
                    return;
                }
            }
        }

        #region Chart Drawings (Liquidity Lines & Top-Right HUD)

        private void DrawLiquidityLines()
        {
            if (_pdh > 0) Chart.DrawHorizontalLine("Line_PDH", _pdh, Color.Gold, 1, LineStyle.Lines);
            if (_pdl > 0) Chart.DrawHorizontalLine("Line_PDL", _pdl, Color.OrangeRed, 1, LineStyle.Lines);
            if (_asianHigh > 0) Chart.DrawHorizontalLine("Line_AsianHigh", _asianHigh, Color.DodgerBlue, 1, LineStyle.Dots);
            if (_asianLow > 0) Chart.DrawHorizontalLine("Line_AsianLow", _asianLow, Color.DodgerBlue, 1, LineStyle.Dots);
            if (_h4SwingHigh > 0) Chart.DrawHorizontalLine("Line_H4High", _h4SwingHigh, Color.MediumPurple, 2, LineStyle.Solid);
            if (_h4SwingLow > 0) Chart.DrawHorizontalLine("Line_H4Low", _h4SwingLow, Color.MediumPurple, 2, LineStyle.Solid);
        }

        private void ClearChartDrawings()
        {
            Chart.RemoveObject("Line_PDH");
            Chart.RemoveObject("Line_PDL");
            Chart.RemoveObject("Line_AsianHigh");
            Chart.RemoveObject("Line_AsianLow");
            Chart.RemoveObject("Line_H4High");
            Chart.RemoveObject("Line_H4Low");
            Chart.RemoveObject("XAU_Scalper_HUD");
        }

        private void UpdateChartHUD()
        {
            double liveSpreadUSD = Symbol.Ask - Symbol.Bid;
            double eq = Account.Equity;
            double gainPct = ((eq - _baseReferenceEq) / _baseReferenceEq) * 100.0;
            
            // Calculate indicative lot size for current default risk at current ATR
            double sampleAtr = _atrM5.Result.Last(1);
            double sampleLots = sampleAtr > 0 ? CalculateVolumeInUnits(sampleAtr * InpAtrStopMultiplier) / Symbol.LotSize : 0.0;

            string sessionStatus = "OUT OF SESSION";
            int hour = Server.TimeInUtc.Hour;
            bool inLondon = InpTradeLondonSession && (hour >= InpLondonStartHour && hour < InpLondonEndHour);
            bool inNY = InpTradeNYOverlap && (hour >= InpNYStartHour && hour < InpNYEndHour);
            if (inLondon) sessionStatus = "LONDON OPEN";
            if (inNY) sessionStatus = "NY OVERLAP";

            string hudText = string.Format(
                "=== XAU/USD SCALPER HUD ===\n" +
                "Session: {0} (UTC {1:D2}:00)\n" +
                "Live Spread: ${1:F2} (Max: ${2:F2})\n" +
                "Equity: ${3:F2} | Gain: {4:+0.00;-0.00}%\n" +
                "Locked Floor: ${5:F2}\n" +
                "Consec. Losses: {6}/{7} | Halted: {8}\n" +
                "Indicative Lot Size: {9:F2} Lots\n" +
                "Active Positions: {10}",
                sessionStatus,
                hour,
                liveSpreadUSD,
                InpMaxSpreadUSD,
                eq,
                gainPct,
                _lockedEquityFloor,
                _dailyConsecLosses,
                InpMaxDailyConsecLosses,
                _dailyHalted,
                sampleLots,
                Positions.FindAll(InpBotLabel, SymbolName).Length
            );

            Chart.DrawStaticText("XAU_Scalper_HUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, Color.White);
        }

        #endregion

        #region Position Closed Event & Profit-Lock Scaling State

        private void OnPositionClosed(PositionClosedEventArgs args)
        {
            Position pos = args.Position;
            if (pos.Label != InpBotLabel || pos.SymbolName != SymbolName)
                return;

            if (Positions.FindAll(InpBotLabel, SymbolName).Length == 0)
            {
                _partialClosedIds.Remove(pos.Id);
                _initialRiskDistByPosId.Remove(pos.Id);

                if (pos.NetProfit < -1.0)
                    _dailyConsecLosses++;
                else if (pos.NetProfit > 1.0)
                    _dailyConsecLosses = 0;
            }

            double eq = Account.Equity;
            double gainPct = ((eq - _baseReferenceEq) / _baseReferenceEq) * 100.0;

            if (InpUseProfitLockScaling && gainPct >= InpProfitLockTriggerPct)
            {
                double gainedUSD   = eq - _baseReferenceEq;
                _lockedEquityFloor = _baseReferenceEq + (gainedUSD * 0.50);
                _baseReferenceEq   = eq;

                Print("Profit-Lock Triggered! New Locked Equity Floor: ${0:F2} | New Reference: ${1:F2}",
                    _lockedEquityFloor, _baseReferenceEq);
            }
        }

        private void CheckDailyResetAndDrawdown()
        {
            DateTime utcNow = Server.TimeInUtc;
            double eq = Account.Equity;

            if (utcNow.DayOfYear != _currentDayOfYear)
            {
                _currentDayOfYear  = utcNow.DayOfYear;
                _dayStartEquity    = eq;
                _dailyConsecLosses = 0;
                _dailyHalted       = false;
            }

            if (_dailyHalted)
                return;

            double dailyDDPct = ((_dayStartEquity - eq) / _dayStartEquity) * 100.0;
            if (dailyDDPct >= InpMaxDailyDrawdownPct)
            {
                _dailyHalted = true;
                Print("Circuit Breaker: Max Daily Drawdown ({0:F2}%) reached. Trading halted for today.", dailyDDPct);
            }
            else if (_dailyConsecLosses >= InpMaxDailyConsecLosses)
            {
                _dailyHalted = true;
                Print("Circuit Breaker: {0} Consecutive Losses reached. Trading halted for today.", _dailyConsecLosses);
            }
        }

        #endregion

        #region Liquidity Pool Mapping

        private void UpdateLiquidityPools()
        {
            if (_d1Bars.Count >= 2)
            {
                _pdh = _d1Bars.HighPrices.Last(1);
                _pdl = _d1Bars.LowPrices.Last(1);
            }

            _h4SwingHigh = 0.0;
            _h4SwingLow  = double.MaxValue;

            int h4Count = _h4Bars.Count;
            for (int i = 1; i <= InpH4SwingLookback && (h4Count - 1 - i) >= 0; i++)
            {
                double h = _h4Bars.HighPrices.Last(i);
                double l = _h4Bars.LowPrices.Last(i);
                if (h > _h4SwingHigh) _h4SwingHigh = h;
                if (l < _h4SwingLow)  _h4SwingLow  = l;
            }
            if (_h4SwingLow == double.MaxValue) _h4SwingLow = 0.0;

            DateTime utcNow     = Server.TimeInUtc;
            DateTime asianStart = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, InpAsianStartHour, 0, 0, DateTimeKind.Utc);
            DateTime asianEnd   = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, InpAsianEndHour,   0, 0, DateTimeKind.Utc);

            _asianHigh = 0.0;
            _asianLow  = double.MaxValue;

            int m15Count = _m15Bars.Count;
            for (int i = 1; i <= 100 && (m15Count - 1 - i) >= 0; i++)
            {
                int idx = m15Count - 1 - i;
                DateTime barTime = _m15Bars.OpenTimes[idx];
                if (barTime < asianStart)
                    break;

                if (barTime >= asianStart && barTime < asianEnd)
                {
                    double h = _m15Bars.HighPrices[idx];
                    double l = _m15Bars.LowPrices[idx];
                    if (h > _asianHigh) _asianHigh = h;
                    if (l < _asianLow)  _asianLow  = l;
                }
            }
            if (_asianLow == double.MaxValue) _asianLow = 0.0;
        }

        #endregion

        #region Entry Setup Detection

        private bool CheckBullishSweep(double atr, out double outSlPrice)
        {
            outSlPrice = 0.0;

            double open1  = Bars.OpenPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double high1  = Bars.HighPrices.Last(1);
            double low1   = Bars.LowPrices.Last(1);
            double low2   = Bars.LowPrices.Last(2);
            double high2  = Bars.HighPrices.Last(2);

            double range1 = high1 - low1;
            double body1  = close1 - open1;
            if (range1 <= 0.0 || body1 <= 0.45 * range1)
                return false;

            double sweepLow = Math.Min(low1, low2);
            double[] levels = { _pdl, _asianLow, _h4SwingLow };

            foreach (double lvl in levels)
            {
                if (lvl <= 0.0) continue;

                double overshoot = lvl - sweepLow;
                if (overshoot > 0.0 && overshoot <= InpMaxSweepOvershootUSD &&
                    close1 > lvl && (close1 > high2 || body1 > 0.60 * range1))
                {
                    outSlPrice = sweepLow - (atr * InpAtrStopMultiplier);
                    return true;
                }
            }
            return false;
        }

        private bool CheckBearishSweep(double atr, out double outSlPrice)
        {
            outSlPrice = 0.0;

            double open1  = Bars.OpenPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double high1  = Bars.HighPrices.Last(1);
            double low1   = Bars.LowPrices.Last(1);
            double high2  = Bars.HighPrices.Last(2);
            double low2   = Bars.LowPrices.Last(2);

            double range1 = high1 - low1;
            double body1  = open1 - close1;
            if (range1 <= 0.0 || body1 <= 0.45 * range1)
                return false;

            double sweepHigh = Math.Max(high1, high2);
            double[] levels  = { _pdh, _asianHigh, _h4SwingHigh };

            foreach (double lvl in levels)
            {
                if (lvl <= 0.0) continue;

                double overshoot = sweepHigh - lvl;
                if (overshoot > 0.0 && overshoot <= InpMaxSweepOvershootUSD &&
                    close1 < lvl && (close1 < low2 || body1 > 0.60 * range1))
                {
                    outSlPrice = sweepHigh + (atr * InpAtrStopMultiplier);
                    return true;
                }
            }
            return false;
        }

        private bool CheckBullishPullback(double emaFast, double emaSlow, double atr, out double outSlPrice)
        {
            outSlPrice = 0.0;
            if (emaFast <= emaSlow)
                return false;

            double open1  = Bars.OpenPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double low1   = Bars.LowPrices.Last(1);
            double high2  = Bars.HighPrices.Last(2);
            double low2   = Bars.LowPrices.Last(2);

            double pullbackLow = Math.Min(low1, low2);

            bool touchedValueZone = (pullbackLow <= emaFast + InpTouchToleranceUSD) &&
                                    (pullbackLow >= emaSlow - InpTouchToleranceUSD);
            bool bullishBoS       = (close1 > open1) && (close1 > emaFast) && (close1 > high2);

            if (touchedValueZone && bullishBoS)
            {
                outSlPrice = pullbackLow - (atr * InpAtrStopMultiplier);
                return true;
            }
            return false;
        }

        private bool CheckBearishPullback(double emaFast, double emaSlow, double atr, out double outSlPrice)
        {
            outSlPrice = 0.0;
            if (emaFast >= emaSlow)
                return false;

            double open1  = Bars.OpenPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double high1  = Bars.HighPrices.Last(1);
            double high2  = Bars.HighPrices.Last(2);
            double low2   = Bars.LowPrices.Last(2);

            double pullbackHigh = Math.Max(high1, high2);

            bool touchedValueZone = (pullbackHigh >= emaFast - InpTouchToleranceUSD) &&
                                    (pullbackHigh <= emaSlow + InpTouchToleranceUSD);
            bool bearishBoS       = (close1 < open1) && (close1 < emaFast) && (close1 < low2);

            if (touchedValueZone && bearishBoS)
            {
                outSlPrice = pullbackHigh + (atr * InpAtrStopMultiplier);
                return true;
            }
            return false;
        }

        #endregion

        #region Execution & Volume Sizing

        private void ExecuteScalpTrade(TradeType tradeType, double rawSlPrice, string comment)
        {
            double entryPrice = (tradeType == TradeType.Buy) ? Symbol.Ask : Symbol.Bid;
            double slDistUSD  = Math.Abs(entryPrice - rawSlPrice);

            if (slDistUSD < InpMinStopUSD)
                slDistUSD = InpMinStopUSD;
            else if (slDistUSD > InpMaxStopUSD)
                return;

            double tpDistUSD = slDistUSD * InpFinalTargetRR;

            double slPips = Math.Round(slDistUSD / Symbol.PipSize, 1);
            double tpPips = Math.Round(tpDistUSD / Symbol.PipSize, 1);

            double volumeInUnits = CalculateVolumeInUnits(slDistUSD);
            if (volumeInUnits < Symbol.VolumeInUnitsMin)
                return;

            TradeResult result = ExecuteMarketOrder(tradeType, SymbolName, volumeInUnits, InpBotLabel, slPips, tpPips, comment);
            if (result.IsSuccessful && result.Position != null)
            {
                _initialRiskDistByPosId[result.Position.Id] = slDistUSD;
            }
        }

        private double CalculateVolumeInUnits(double slDistUSD)
        {
            double eq      = Account.Equity;
            double riskUSD = eq * (InpBaseRiskPct / 100.0);

            if (InpUseProfitLockScaling && eq > _lockedEquityFloor)
            {
                double unlockedProfit = Math.Max(0.0, eq - _baseReferenceEq);
                if (unlockedProfit > 0.0)
                {
                    double bonusRiskUSD = unlockedProfit * (InpUnlockedBufferRiskPct / 100.0);
                    double maxRiskUSD   = eq * (InpMaxScaledRiskPct / 100.0);
                    riskUSD = Math.Min(riskUSD + bonusRiskUSD, maxRiskUSD);
                }
            }

            if (Symbol.TickSize <= 0.0 || Symbol.TickValue <= 0.0 || Symbol.LotSize <= 0.0)
                return 0.0;

            double priceLossPerUnit = (slDistUSD / Symbol.TickSize) * Symbol.TickValue;
            double priceLossPerLot  = priceLossPerUnit * Symbol.LotSize;
            double totalLossPerLot  = priceLossPerLot + InpCommissionPerLotRT;

            if (totalLossPerLot <= 0.0)
                return 0.0;

            double rawLots      = riskUSD / totalLossPerLot;
            double rawUnits     = rawLots * Symbol.LotSize;
            double normUnits    = Symbol.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);

            if (normUnits < Symbol.VolumeInUnitsMin)
                return 0.0;
            if (normUnits > Symbol.VolumeInUnitsMax)
                normUnits = Symbol.VolumeInUnitsMax;

            return normUnits;
        }

        #endregion

        #region Position Management

        private void ManageOpenPositions(Position[] positions)
        {
            foreach (Position pos in positions)
            {
                double openPrice    = pos.EntryPrice;
                double currentPrice = (pos.TradeType == TradeType.Buy) ? Symbol.Bid : Symbol.Ask;
                double profitDist   = (pos.TradeType == TradeType.Buy)
                                    ? (currentPrice - openPrice)
                                    : (openPrice - currentPrice);

                if (!_initialRiskDistByPosId.TryGetValue(pos.Id, out double initialRiskDist) || initialRiskDist <= 0.0)
                {
                    initialRiskDist = pos.TakeProfit.HasValue
                        ? Math.Abs(pos.TakeProfit.Value - openPrice) / InpFinalTargetRR
                        : InpMinStopUSD;
                    _initialRiskDistByPosId[pos.Id] = initialRiskDist;
                }

                double currentR = profitDist / initialRiskDist;

                if (InpTimeStopMinutes > 0)
                {
                    double elapsedMins = (Server.TimeInUtc - pos.EntryTime).TotalMinutes;
                    if (elapsedMins >= InpTimeStopMinutes && currentR < 0.5)
                    {
                        ClosePosition(pos);
                        Print("Time Stop executed on Position #{0} after {1:F0} mins (R = {2:F2})",
                            pos.Id, elapsedMins, currentR);
                        continue;
                    }
                }

                if (currentR >= InpPartialCloseRR && !_partialClosedIds.Contains(pos.Id))
                {
                    double rawCloseUnits  = pos.VolumeInUnits * InpPartialCloseRatio;
                    double normCloseUnits = Symbol.NormalizeVolumeInUnits(rawCloseUnits, RoundingMode.Down);

                    if (normCloseUnits >= Symbol.VolumeInUnitsMin &&
                        (pos.VolumeInUnits - normCloseUnits) >= Symbol.VolumeInUnitsMin)
                    {
                        ClosePosition(pos, normCloseUnits);
                    }

                    double commOffsetUSD = 0.15;
                    double newSlPrice = (pos.TradeType == TradeType.Buy)
                        ? Math.Round(openPrice + commOffsetUSD, Symbol.Digits)
                        : Math.Round(openPrice - commOffsetUSD, Symbol.Digits);

                    bool shouldModifySL = (pos.TradeType == TradeType.Buy)
                        ? (!pos.StopLoss.HasValue || newSlPrice > pos.StopLoss.Value)
                        : (!pos.StopLoss.HasValue || newSlPrice < pos.StopLoss.Value);

                    if (shouldModifySL)
                    {
                        ModifyPosition(pos, newSlPrice, pos.TakeProfit);
                    }

                    _partialClosedIds.Add(pos.Id);
                }

                if (currentR >= InpTrailStartRR && _m15Bars.Count >= 2)
                {
                    double trailBuffer = 0.20;
                    if (pos.TradeType == TradeType.Buy)
                    {
                        double m15Low = Math.Round(_m15Bars.LowPrices.Last(1) - trailBuffer, Symbol.Digits);
                        if ((!pos.StopLoss.HasValue || m15Low > pos.StopLoss.Value) && m15Low < currentPrice)
                        {
                            ModifyPosition(pos, m15Low, pos.TakeProfit);
                        }
                    }
                    else
                    {
                        double m15High = Math.Round(_m15Bars.HighPrices.Last(1) + trailBuffer, Symbol.Digits);
                        if ((!pos.StopLoss.HasValue || m15High < pos.StopLoss.Value) && m15High > currentPrice)
                        {
                            ModifyPosition(pos, m15High, pos.StopLoss); // Bugfix: preserve TP
                        }
                    }
                }
            }
        }

        private bool IsWithinActiveSession()
        {
            int hour = Server.TimeInUtc.Hour;

            bool inLondon = InpTradeLondonSession && (hour >= InpLondonStartHour && hour < InpLondonEndHour);
            bool inNY     = InpTradeNYOverlap     && (hour >= InpNYStartHour     && hour < InpNYEndHour);

            return inLondon || inNY;
        }

        #endregion
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply