Page 2 of 2

Re: Two-timeframe gold scalp: M5 bias, M1 entry, hard time stop

Posted: Sun Sep 27, 2026 2:59 pm
by PTScalper
Architectural Nuances for Pine Script

Handling Time via Bar Index:

In MQL4/MQL5, we use TimeCurrent() - OrderOpenTime() to get real elapsed seconds. In Pine Script, time is inherently tied to bars. By running the script on a 1-minute chart, bar_index - entry_bar_index perfectly aligns with your elapsed minutes rule. If the trade is open for 15 bars, it has been 15 minutes.

Preventing Repainting on MTF (Multi-Timeframe):

The request.security call specifically references [1] (the previous candle) for open, high, low, and close. This is mandatory for algorithmic backtesting in Pine Script. If you reference the live M5 candle on an M1 chart, the M5 candle properties will morph over the course of 5 minutes, causing false signals that "repaint" historically.

Asymmetric Journaling in the List of Trades:

The most powerful feature of this Pine port for your specific philosophy is the comment="Time Stop: Momentum Failed" inside the strategy.close() function. When you view the "List of Trades" tab in the TradingView Strategy Tester, hard stop-outs will show up clearly as exits triggered by loss (SL), while your momentum failures will be distinctly labeled as Time Stops. You can export this CSV to analyze exactly how much capital your time stop saved you versus just holding to the hard stop.

Re: Two-timeframe gold scalp: M5 bias, M1 entry, hard time stop

Posted: Sun Sep 27, 2026 3:00 pm
by PTScalper
To elevate this template to a professional, institutional-grade Pine Script (v5) framework, we must move beyond naive candle logic and implement three critical upgrades:

Dynamic Risk Management: Institutional scripts do not use fixed lots. They size positions dynamically based on account equity and a strict Risk % per trade, mathematically tied to your point-based Stop Loss.

Microstructure Detection: We replace the placeholder logic with actual Liquidity Sweep (Trap) and Displacement (Imbalance/FVG) detection using MTF arrays.

State Management & Telemetry: We introduce state trackers to prevent double-firing on the same M5 signal and add an on-chart dashboard (HUD) to monitor the Time Stop decay in real-time.

Here is the professional-grade architecture. Apply this strictly to the 1-minute (M1) chart.

Re: Two-timeframe gold scalp: M5 bias, M1 entry, hard time stop

Posted: Sun Sep 27, 2026 3:01 pm
by PTScalper
Professional Pine Script Architecture

Code: Select all

//@version=5
strategy("Institutional Gold Scalper [Asymmetric Journaling]", shorttitle="XAU Pro Scalp", overlay=true, calc_on_every_tick=true, initial_capital=100000, currency=currency.USD, commission_value=3.0)

// =========================================================================
// 1. INPUTS & CONFIGURATION
// =========================================================================
grpRisk = "Risk & Position Sizing"
inpRiskPct    = input.float(1.0, title="Risk Per Trade (%)", step=0.1, group=grpRisk, tooltip="Position size is dynamically calculated based on this risk % and the SL distance.")
inpStopLoss   = input.int(200, title="Hard Stop Loss (Ticks)", group=grpRisk)
inpTakeProfit = input.int(400, title="Take Profit (Ticks)", group=grpRisk)

grpTime = "Time Stop Mechanics"
inpTimeStopMins = input.int(15, title="Time Stop Window (Minutes)", group=grpTime)

grpPA = "Price Action Sensitivity"
inpSweepSize  = input.float(0.6, title="Sweep Wick Min %", step=0.1, group=grpPA, tooltip="For a trap, the wick must be at least this % of the total candle size.")
inpDispSize   = input.float(0.8, title="Displacement Body Min %", step=0.1, group=grpPA, tooltip="For momentum, the body must be at least this % of the total candle size.")

// =========================================================================
// 2. MTF DATA REQUESTS (Strict Anti-Repaint)
// =========================================================================
// Fetching the last CLOSED M5 candle to prevent lookahead bias.
// We use a custom function to calculate M5 internal metrics efficiently.
f_get_m5_data() =>
    body = math.abs(close - open)
    range_ = high - low
    topWick = high - math.max(close, open)
    btmWick = math.min(close, open) - low
    
    // Sweep Detectors
    isBullSweep = range_ > 0 and (btmWick / range_ >= inpSweepSize) and close > open
    isBearSweep = range_ > 0 and (topWick / range_ >= inpSweepSize) and close < open
    
    // Displacement Detectors (FVG/Imbalance precursors)
    isBullDisp = range_ > 0 and (body / range_ >= inpDispSize) and close > open
    isBearDisp = range_ > 0 and (body / range_ >= inpDispSize) and close < open
    
    [isBullSweep, isBearSweep, isBullDisp, isBearDisp, high, low]

[m5BullSweep, m5BearSweep, m5BullDisp, m5BearDisp, m5H, m5L] = request.security(syminfo.tickerid, "5", f_get_m5_data(), lookahead=barmerge.lookahead_off)

// Establish M5 Context (Holds until a new context overrides it)
var int m5Bias = 0 // 1 = Long, -1 = Short, 0 = Neutral
if m5BullSweep or m5BullDisp
    m5Bias := 1
else if m5BearSweep or m5BearDisp
    m5Bias := -1
else 
    // Decay bias if M5 prints neutral chop (dojis)
    m5Bias := 0 

// =========================================================================
// 3. M1 MICROSTRUCTURE ENTRY LOGIC
// =========================================================================
// We only want to execute on the *first* valid M1 pullback to prevent overtrading.
var bool signalExecuted = false
if m5Bias == 0
    signalExecuted := false // Reset when M5 loses clarity

// M1 Confirmation: Engulfing or strong momentum in the direction of M5 bias
m1BullConfirm = close > open and close > high[1] // Reclaim / Engulf
m1BearConfirm = close < open and close < low[1]  // Rejection / Engulf

bool validLong  = m5Bias == 1 and m1BullConfirm and not signalExecuted
bool validShort = m5Bias == -1 and m1BearConfirm and not signalExecuted

// =========================================================================
// 4. POSITION SIZING & EXECUTION
// =========================================================================
// Dynamic Position Sizing based on Account Equity and Tick Value
float riskAmount = (strategy.equity * inpRiskPct) / 100
float tickValue  = syminfo.mintick * syminfo.pointvalue
float slCashRisk = inpStopLoss * tickValue
float dynamicQty = slCashRisk > 0 ? (riskAmount / slCashRisk) : 0

var string tradeId = "Scalp"

if strategy.position_size == 0
    if validLong
        strategy.entry(tradeId, strategy.long, qty=dynamicQty)
        strategy.exit("SL/TP", from_entry=tradeId, loss=inpStopLoss, profit=inpTakeProfit)
        signalExecuted := true
        
    if validShort
        strategy.entry(tradeId, strategy.short, qty=dynamicQty)
        strategy.exit("SL/TP", from_entry=tradeId, loss=inpStopLoss, profit=inpTakeProfit)
        signalExecuted := true

// =========================================================================
// 5. STRICT TIME STOP MANAGEMENT
// =========================================================================
int barsInTrade = 0
if strategy.position_size != 0
    entryBarIndex = strategy.opentrades.entry_bar_index(strategy.opentrades - 1)
    barsInTrade  := bar_index - entryBarIndex
    
    // Time Stop Execution
    if barsInTrade >= inpTimeStopMins
        strategy.close(tradeId, comment="Time Stop") // Clean CSV output

// =========================================================================
// 6. UI & TELEMETRY DASHBOARD
// =========================================================================
// Draw M5 Sweep/Displacement levels on the M1 chart for visual backtesting
bgcolor(m5BullSweep or m5BullDisp ? color.new(color.teal, 90) : na, title="M5 Bull Zone")
bgcolor(m5BearSweep or m5BearDisp ? color.new(color.maroon, 90) : na, title="M5 Bear Zone")

var table hud = table.new(position.top_right, 2, 4, border_width=1, border_color=color.gray, frame_color=color.black, frame_width=1)

if barstate.islast
    // Header
    table.cell(hud, 0, 0, "SYSTEM", text_color=color.white, bgcolor=color.black)
    table.cell(hud, 1, 0, "STATE", text_color=color.white, bgcolor=color.black)
    
    // M5 Bias
    color biasColor = m5Bias == 1 ? color.teal : m5Bias == -1 ? color.maroon : color.gray
    string biasText = m5Bias == 1 ? "BULLISH" : m5Bias == -1 ? "BEARISH" : "NEUTRAL"
    table.cell(hud, 0, 1, "M5 Bias", text_color=color.white, bgcolor=color.new(color.gray, 80))
    table.cell(hud, 1, 1, biasText, text_color=color.white, bgcolor=biasColor)
    
    // Position Status
    string posText = strategy.position_size > 0 ? "LONG" : strategy.position_size < 0 ? "SHORT" : "FLAT"
    color posColor = strategy.position_size > 0 ? color.teal : strategy.position_size < 0 ? color.maroon : color.gray
    table.cell(hud, 0, 2, "Active Trade", text_color=color.white, bgcolor=color.new(color.gray, 80))
    table.cell(hud, 1, 2, posText, text_color=color.white, bgcolor=posColor)
    
    // Time Stop Countdown
    string timeText = strategy.position_size != 0 ? str.tostring(barsInTrade) + " / " + str.tostring(inpTimeStopMins) + "m" : "--"
    color timeColor = strategy.position_size != 0 ? (barsInTrade >= inpTimeStopMins - 2 ? color.red : color.green) : color.gray
    table.cell(hud, 0, 3, "Time Elapsed", text_color=color.white, bgcolor=color.new(color.gray, 80))
    table.cell(hud, 1, 3, timeText, text_color=color.white, bgcolor=timeColor)

Re: Two-timeframe gold scalp: M5 bias, M1 entry, hard time stop

Posted: Sun Sep 27, 2026 3:01 pm
by PTScalper
Pro Upgrades Explained

Fractional Market Microstructure Math (f_get_m5_data)

Instead of looking for a generically "large" candle, the script mathematically isolates wicks and bodies to define sweeps and displacement exactly. You can now tune the inpSweepSize (e.g., wick must be 60%+ of the candle to qualify as a liquidity sweep trap) and inpDispSize (body must be 80%+ of the candle to qualify as true momentum displacement) directly from the settings UI.

Dynamic Percentage-Based Risk Sizing

A fixed lot size distorts algorithmic backtesting as your account grows or draws down. The script dynamically calculates qty=dynamicQty by dividing your exact cash risk (e.g., 1% of $100,000) by the cash value of your hard stop-loss. This ensures every trade risks exactly your defined percentage, regardless of account size.

Overtrade Prevention (signalExecuted State)

When the M5 prints a bullish sweep, the M1 chart will often print multiple bullish engulfing candles over the next 5 minutes. The naive script would take a trade on every single one. The signalExecuted boolean locks out the system after the first precise entry, only resetting when the M5 context resets.

Telemetry HUD (Heads Up Display)

A live table is rendered in the top right corner of your M1 chart. It gives you instant, visual verification of the algorithmic state: what the system reads the M5 bias as, your current position, and a real-time countdown of your Time Stop (which turns red when you are 2 minutes away from an automatic kill).

Re: Two-timeframe gold scalp: M5 bias, M1 entry, hard time stop

Posted: Sun Sep 27, 2026 3:02 pm
by PTScalper
Because cTrader’s native environment (cAlgo) is built on C#, we can abandon the procedural limitations of Pine Script and MQL. We can leverage true event-driven architecture, clean object-oriented state management, and native multi-timeframe data series without risking lookahead bias.

Here is the professional C# implementation for cTrader. It is designed to be attached to the M1 chart, while cleanly pulling the M5 series asynchronously.

Re: Two-timeframe gold scalp: M5 bias, M1 entry, hard time stop

Posted: Sun Sep 27, 2026 3:02 pm
by PTScalper
The cAlgo (C#) Architecture

Code: Select all

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

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class GoldProScalp : Robot
    {
        // =========================================================================
        // INPUTS & CONFIGURATION
        // =========================================================================
        [Parameter("Risk Per Trade (%)", Group = "Risk Management", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1)]
        public double RiskPercent { get; set; }

        [Parameter("Hard Stop Loss (Pips)", Group = "Risk Management", DefaultValue = 20)]
        public double StopLossPips { get; set; }

        [Parameter("Take Profit (Pips)", Group = "Risk Management", DefaultValue = 40)]
        public double TakeProfitPips { get; set; }

        [Parameter("Time Stop Window (Minutes)", Group = "Time Stop Mechanics", DefaultValue = 15)]
        public int TimeStopMinutes { get; set; }

        [Parameter("Sweep Wick Min %", Group = "Price Action Sensitivity", DefaultValue = 0.6)]
        public double SweepWickMinRatio { get; set; }

        [Parameter("Displacement Body Min %", Group = "Price Action Sensitivity", DefaultValue = 0.8)]
        public double DisplacementBodyMinRatio { get; set; }

        // =========================================================================
        // STATE & GLOBALS
        // =========================================================================
        private Bars _m5Bars;
        private MarketBias _m5Bias = MarketBias.Neutral;
        private bool _signalExecuted = false;
        private const string TradeLabel = "XAU_Pro_Scalp";

        private enum MarketBias { Neutral, Bullish, Bearish }

        // =========================================================================
        // INITIALIZATION
        // =========================================================================
        protected override void OnStart()
        {
            // Initialize MTF Data Series (No lookahead bias, naturally aligned)
            _m5Bars = MarketData.GetBars(TimeFrame.Minute5);
        }

        // =========================================================================
        // TICK EVENT: STRICT TIME STOP MANAGEMENT
        // =========================================================================
        protected override void OnTick()
        {
            var activePositions = Positions.FindAll(TradeLabel, SymbolName);

            foreach (var position in activePositions)
            {
                TimeSpan elapsed = Server.Time - position.EntryTime;

                if (elapsed.TotalMinutes >= TimeStopMinutes)
                {
                    // Asymmetric Journaling: Log the specific failure mode before the kill command
                    Print($"TIME STOP TRIGGERED: Pos #{position.Id} | Elapsed: {elapsed.TotalMinutes:F1}m | PnL: {position.NetProfit:C}. Reason: Lack of momentum.");
                    ClosePosition(position);
                }
            }
        }

        // =========================================================================
        // BAR EVENT: MICROSTRUCTURE ENTRY LOGIC (Runs on M1 Bar Close)
        // =========================================================================
        protected override void OnBar()
        {
            // 1. Manage State: Reset execution lock if M5 loses clarity
            UpdateM5Bias();
            if (_m5Bias == MarketBias.Neutral)
            {
                _signalExecuted = false;
                return;
            }

            // If a position is already open, do not pyramid
            if (Positions.FindAll(TradeLabel, SymbolName).Length > 0)
                return;

            // 2. M1 Confirmation (Engulfing / Reclaim)
            bool isM1BullConfirm = Bars.Last(1).Close > Bars.Last(1).Open && Bars.Last(1).Close > Bars.Last(2).High;
            bool isM1BearConfirm = Bars.Last(1).Close < Bars.Last(1).Open && Bars.Last(1).Close < Bars.Last(2).Low;

            // 3. Execution
            if (_m5Bias == MarketBias.Bullish && isM1BullConfirm && !_signalExecuted)
            {
                ExecuteScalp(TradeType.Buy);
            }
            else if (_m5Bias == MarketBias.Bearish && isM1BearConfirm && !_signalExecuted)
            {
                ExecuteScalp(TradeType.Sell);
            }
        }

        // =========================================================================
        // METHODS: MTF ANALYSIS & POSITION SIZING
        // =========================================================================
        private void UpdateM5Bias()
        {
            // Always reference the last *closed* M5 bar to lock the structure
            var lastM5 = _m5Bars.Last(1);

            double body = Math.Abs(lastM5.Close - lastM5.Open);
            double range = lastM5.High - lastM5.Low;
            
            if (range == 0) return;

            double topWick = lastM5.High - Math.Max(lastM5.Close, lastM5.Open);
            double bottomWick = Math.Min(lastM5.Close, lastM5.Open) - lastM5.Low;

            // Sweep Detectors (Liquidity Traps)
            bool isBullSweep = (bottomWick / range >= SweepWickMinRatio) && (lastM5.Close > lastM5.Open);
            bool isBearSweep = (topWick / range >= SweepWickMinRatio) && (lastM5.Close < lastM5.Open);

            // Displacement Detectors (Momentum / Imbalance)
            bool isBullDisp = (body / range >= DisplacementBodyMinRatio) && (lastM5.Close > lastM5.Open);
            bool isBearDisp = (body / range >= DisplacementBodyMinRatio) && (lastM5.Close < lastM5.Open);

            if (isBullSweep || isBullDisp)
                _m5Bias = MarketBias.Bullish;
            else if (isBearSweep || isBearDisp)
                _m5Bias = MarketBias.Bearish;
            else
                _m5Bias = MarketBias.Neutral;
        }

        private void ExecuteScalp(TradeType tradeType)
        {
            double riskInMoney = Account.Equity * (RiskPercent / 100);
            
            // cTrader specific: Calculate volume based on risk amount and pip value
            double volumeCalc = riskInMoney / (StopLossPips * Symbol.PipValue);
            double normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeCalc, RoundingMode.Down);

            if (normalizedVolume < Symbol.VolumeInUnitsMin)
            {
                Print("Calculated volume is below symbol minimum. Aborting execution.");
                return;
            }

            var result = ExecuteMarketOrder(tradeType, SymbolName, normalizedVolume, TradeLabel, StopLossPips, TakeProfitPips);

            if (result.IsSuccessful)
            {
                _signalExecuted = true;
                Print($"ENTRY VALIDATED: {tradeType} executed. Time Stop initialized for {TimeStopMinutes} mins.");
            }
        }
    }
}

Re: Two-timeframe gold scalp: M5 bias, M1 entry, hard time stop

Posted: Sun Sep 27, 2026 3:03 pm
by PTScalper
C# / cAlgo Nuances

Event Splitting (OnTick vs. OnBar)

The time-stop evaluates inside OnTick(). This guarantees the trade is killed exactly down to the millisecond your momentum window expires, regardless of whether the current M1 bar has finished printing. Meanwhile, entry evaluations are isolated to OnBar() to strictly execute on confirmed closed M1 data, eliminating intra-bar spread chop.

Native MTF Handling (_m5Bars)

cTrader's MarketData.GetBars(TimeFrame.Minute5) creates a parallel data series that syncs natively. Referencing _m5Bars.Last(1) always guarantees you are reading the last mathematically finalized M5 candle relative to current server time, eliminating the repainting hazards inherent to Pine Script.

Volume Normalization (Symbol.NormalizeVolumeInUnits)

Forex/Gold volume scaling can be treacherous across different brokers (e.g., standard lots vs. units). By utilizing Symbol.PipValue in conjunction with Symbol.NormalizeVolumeInUnits(), the algorithm calculates precise fractional sizing based on live equity and safely clamps it to the broker's step requirements via RoundingMode.Down.

Re: Two-timeframe gold scalp: M5 bias, M1 entry, hard time stop

Posted: Mon Sep 28, 2026 4:46 pm
by LondonScalper
Of your three M5 cues, the sweep with the body closing back inside is the one I'd make mandatory for gold, and the other two optional. Displacement on M5 gold happens several times a morning and on its own hasn't told me much; plenty of full-bodied candles get retraced within the next three bars. A sweep and close back inside gives a place for the stop and a reason for the move, which is why it earns the drop to M1.

On the time stop, I don't think the probability falls off a cliff for every setup. My log is less dramatic: gold retests that work usually show 0.5R inside eight to ten M1 bars. The ones that haven't got there by fifteen bars are roughly break-even as a group, not disasters. That's why my window is fifteen minutes and not five. Tight enough to stop the drift, loose enough not to cut the slow ones that were going to work.

Keeping the two exit types in separate columns is the piece I'd recommend to anyone, whatever their numbers turn out to be.