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)