Advertisement IC Markets

Pro level forex strategy: scalping breakout levels

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

Re: Pro level forex strategy: scalping breakout levels

Post by PTScalper »

Complete Pine Script v5 Code

Copy and paste the script below directly into the Pine Editor in TradingView and click Add to chart on an M1 timeframe.

Code: Select all

// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Institutional Breakout Scalper [M15/M5/M1]

//@version=5
indicator("Institutional Breakout Scalper [M15/M5/M1]", shorttitle="IBS [M1/M5]", overlay=true, max_lines_count=500, max_boxes_count=200, max_labels_count=200)

// ============================================================================
// 1. USER INPUTS & STRATEGY PARAMETERS
// ============================================================================
grp_sess = "1. Session Windows & Liquidity Ranges (UTC)"
asianSess       = input.session("0000-0630", "Asian Range (Liquidity Pool)", group=grp_sess)
preNySess       = input.session("1030-1215", "Pre-NY Consolidation Range", group=grp_sess)
lonExecSess     = input.session("0700-1000", "London Execution Window", group=grp_sess)
nyExecSess      = input.session("1230-1530", "NY Open Execution Window", group=grp_sess)
useSessionGate  = input.bool(true, "Restrict Signals to Execution Windows", group=grp_sess)
usePreNyRange   = input.bool(true, "Trade Pre-NY Range Breakouts", group=grp_sess)
usePdhPdl       = input.bool(true, "Trade Previous Day High/Low (PDH/PDL)", group=grp_sess)

grp_mtf = "2. M5 Compression & Trend Filters (Non-Repainting)"
m5EmaLen        = input.int(20, "M5 EMA Length", minval=5, group=grp_mtf)
m5AtrLen        = input.int(14, "M5 ATR Length", minval=5, group=grp_mtf)
minM5AtrPips    = input.float(3.5, "Minimum M5 ATR Threshold (Pips)", minval=0.5, step=0.5, group=grp_mtf, tooltip="Use 3.5 for EURUSD, 4.5 for GBPUSD/USDJPY.")
requireM5Filter = input.bool(true, "Enforce M5 EMA & ATR Alignment", group=grp_mtf)

grp_disp = "3. M1 Displacement Candle Rules"
minBodyPct      = input.float(65.0, "Min Body-to-Range Ratio (%)", minval=30.0, maxval=95.0, step=5.0, group=grp_disp)
minClearancePct = input.float(50.0, "Min Body Clearance Beyond Level (%)", minval=10.0, maxval=90.0, step=5.0, group=grp_disp)
volSmaLen       = input.int(20, "Tick Volume SMA Length", minval=5, group=grp_disp)
minVolMult      = input.float(1.5, "Min Volume Expansion Multiplier", minval=1.0, step=0.1, group=grp_disp)
m1AtrLen        = input.int(14, "M1 ATR Length", minval=5, group=grp_disp)
minM1AtrMult    = input.float(1.2, "Min Candle Length (x M1 ATR)", minval=0.5, step=0.1, group=grp_disp)
maxM1AtrMult    = input.float(2.5, "Anti-Exhaustion Cap (x M1 ATR)", minval=1.5, step=0.1, group=grp_disp)

grp_risk = "4. Execution, Risk & Position Sizing"
acctBalance     = input.float(50000.0, "Account Balance ($)", minval=100.0, step=1000.0, group=grp_risk)
riskPct         = input.float(0.5, "Risk Per Trade (%)", minval=0.1, maxval=5.0, step=0.1, group=grp_risk)
pipValuePerLot  = input.float(10.0, "Pip Value per Standard Lot ($)", minval=1.0, step=0.5, group=grp_risk, tooltip="$10 for USD-quoted majors (EURUSD, GBPUSD).")
entryBufferPips = input.float(0.4, "Model A Limit Entry Buffer (Pips)", minval=0.0, step=0.1, group=grp_risk)
slBufferPips    = input.float(1.2, "Stop-Loss Buffer Beyond Candle (Pips)", minval=0.5, step=0.1, group=grp_risk)
maxDecayBars    = input.int(4, "Model A Time Decay Limit (M1 Bars)", minval=1, maxval=15, group=grp_risk)
showHud         = input.bool(true, "Show Institutional Telemetry HUD", group=grp_risk)

// ============================================================================
// 2. PIP CONVERSION & NON-REPAINTING MTF DATA
// ============================================================================
pipSize = syminfo.mintick * (syminfo.mintick == 0.00001 or syminfo.mintick == 0.001 ? 10 : 1)

// Local M1 Indicators
m1Ema = ta.ema(close, 20)
m1Atr = ta.atr(m1AtrLen)
volSma = ta.sma(volume, volSmaLen)

// Non-Repainting M5 Data (Confirmed closed M5 bar values)
m5EmaExpr = ta.ema(close, m5EmaLen)
m5AtrExpr = ta.atr(m5AtrLen)
[m5Close, m5Ema, m5EmaPrev, m5Atr] = request.security(syminfo.tickerid, "5", [close[1], m5EmaExpr[1], m5EmaExpr[2], m5AtrExpr[1]], barmerge.gaps_off, barmerge.lookahead_on)

// Non-Repainting Previous Day High & Low (PDH / PDL)
[pdh, pdl] = request.security(syminfo.tickerid, "D", [high[1], low[1]], barmerge.gaps_off, barmerge.lookahead_on)

// Evaluate M5 Institutional Filters
m5AtrPips  = m5Atr / pipSize
isM5AtrOk  = m5AtrPips >= minM5AtrPips
isM5Bull   = m5Close > m5Ema and m5Ema > m5EmaPrev
isM5Bear   = m5Close < m5Ema and m5Ema < m5EmaPrev

// ============================================================================
// 3. SESSION & LIQUIDITY POOL TRACKING (ASIAN, PRE-NY, PDH/PDL)
// ============================================================================
inAsian  = not na(time(timeframe.period, asianSess + ":1234567", "UTC"))
inPreNy  = not na(time(timeframe.period, preNySess + ":1234567", "UTC"))
inLonExe = not na(time(timeframe.period, lonExecSess + ":1234567", "UTC"))
inNyExe  = not na(time(timeframe.period, nyExecSess + ":1234567", "UTC"))
inExecWindow = not useSessionGate or inLonExe or inNyExe

// Asian Range Variables
var float asianHigh = na
var float asianLow  = na
var bool  asianHighBroken = false
var bool  asianLowBroken  = false

if inAsian and not inAsian[1]
    asianHigh       := high
    asianLow        := low
    asianHighBroken := false
    asianLowBroken  := false
else if inAsian
    asianHigh := math.max(nz(asianHigh, high), high)
    asianLow  := math.min(nz(asianLow, low), low)

// Pre-NY Range Variables
var float preNyHigh = na
var float preNyLow  = na
var bool  preNyHighBroken = false
var bool  preNyLowBroken  = false

if inPreNy and not inPreNy[1]
    preNyHigh       := high
    preNyLow        := low
    preNyHighBroken := false
    preNyLowBroken  := false
else if inPreNy
    preNyHigh := math.max(nz(preNyHigh, high), high)
    preNyLow  := math.min(nz(preNyLow, low), low)

// Reset PDH/PDL break flags on new trading day
var bool pdhBroken = false
var bool pdlBroken = false
if ta.change(time("D"))
    pdhBroken := false
    pdlBroken := false
    preNyHigh := na
    preNyLow  := na

// ============================================================================
// 4. M1 DISPLACEMENT CANDLE ENGINE
// ============================================================================
candleRange = high - low
candleBody  = math.abs(close - open)
bodyRatio   = candleRange > 0 ? (candleBody / candleRange) * 100.0 : 0.0

isBodyValid = bodyRatio >= minBodyPct
isVolValid  = volume >= (volSma * minVolMult)
isAtrValid  = candleRange >= (m1Atr * minM1AtrMult) and candleRange <= (m1Atr * maxM1AtrMult)
isBaseDisp  = isBodyValid and isVolValid and isAtrValid

// Helper function to validate clearance across a specific horizontal liquidity level
f_checkBullBreak(float lvl, bool alreadyBroken) =>
    if na(lvl) or alreadyBroken or close <= open
        false
    else
        clearance = close - lvl
        open < lvl and close > lvl and (clearance / candleBody) * 100.0 >= minClearancePct

f_checkBearBreak(float lvl, bool alreadyBroken) =>
    if na(lvl) or alreadyBroken or close >= open
        false
    else
        clearance = lvl - close
        open > lvl and close < lvl and (clearance / candleBody) * 100.0 >= minClearancePct

// Check individual liquidity pools once their formation window is complete
asianReady = not inAsian and not na(asianHigh)
preNyReady = usePreNyRange and not inPreNy and not na(preNyHigh)
pdhReady   = usePdhPdl and not na(pdh)

bullAsian = asianReady and f_checkBullBreak(asianHigh, asianHighBroken)
bearAsian = asianReady and f_checkBearBreak(asianLow, asianLowBroken)

bullPreNy = preNyReady and f_checkBullBreak(preNyHigh, preNyHighBroken)
bearPreNy = preNyReady and f_checkBearBreak(preNyLow, preNyLowBroken)

bullPdh   = pdhReady and f_checkBullBreak(pdh, pdhBroken)
bearPdl   = pdhReady and f_checkBearBreak(pdl, pdlBroken)

// Combine with M5 filter and Execution Window
m5BullGate = not requireM5Filter or (isM5AtrOk and isM5Bull)
m5BearGate = not requireM5Filter or (isM5AtrOk and isM5Bear)

validBullDisp = inExecWindow and isBaseDisp and m5BullGate and (bullAsian or bullPreNy or bullPdh)
validBearDisp = inExecWindow and isBaseDisp and m5BearGate and (bearAsian or bearPreNy or bearPdl)

// Identify the exact broken level and mark it as consumed for the session
float brokenLvl = na
string lvlName  = ""

if validBullDisp
    if bullAsian
        brokenLvl       := asianHigh
        lvlName         := "Asian High"
        asianHighBroken := true
    else if bullPreNy
        brokenLvl       := preNyHigh
        lvlName         := "Pre-NY High"
        preNyHighBroken := true
    else if bullPdh
        brokenLvl       := pdh
        lvlName         := "PDH"
        pdhBroken       := true

if validBearDisp
    if bearAsian
        brokenLvl      := asianLow
        lvlName        := "Asian Low"
        asianLowBroken := true
    else if bearPreNy
        brokenLvl      := preNyLow
        lvlName        := "Pre-NY Low"
        preNyLowBroken := true
    else if bearPdl
        brokenLvl      := pdl
        lvlName        := "PDL"
        pdlBroken      := true

// Cancel out untouched levels if price breaks through without valid displacement criteria
if asianReady and close > asianHigh and not validBullDisp
    asianHighBroken := true
if asianReady and close < asianLow and not validBearDisp
    asianLowBroken  := true
if preNyReady and close > preNyHigh and not validBullDisp
    preNyHighBroken := true
if preNyReady and close < preNyLow and not validBearDisp
    preNyLowBroken  := true
if pdhReady and close > pdh and not validBullDisp
    pdhBroken       := true
if pdhReady and close < pdl and not validBearDisp
    pdlBroken       := true

// ============================================================================
// 5. STATE MACHINE: MODEL A LIMIT RETEST, 4-MIN DECAY & 2-BAR TRAP FILTER
// ============================================================================
// States: 0 = Flat, 1 = Pending Limit Order, 2 = Active Position
var int   tradeState     = 0
var int   tradeDir       = 0      // 1 = Long, -1 = Short
var int   signalBarIndex = na
var float activeLevel    = na
var float entryPrice     = na
var float slPrice        = na
var float tp1Price       = na
var float tp2Price       = na
var float activeLots     = na
var int   insideRangeCnt = 0

bool evtLimitFilled  = false
bool evtOrderExpired = false
bool evtTrapScratch  = false
bool evtTp2Hit       = false
bool evtSlHit        = false

// Manage Pending Order (State 1)
if tradeState == 1
    barsElapsed = bar_index - signalBarIndex
    if tradeDir == 1
        // Check if limit filled
        if low <= entryPrice
            tradeState     := 2
            insideRangeCnt := 0
            evtLimitFilled := true
        // Check 4-minute time decay or runaway 1.5R move before fill
        else if barsElapsed > maxDecayBars or high >= tp1Price
            tradeState      := 0
            evtOrderExpired := true
    else if tradeDir == -1
        if high >= entryPrice
            tradeState     := 2
            insideRangeCnt := 0
            evtLimitFilled := true
        else if barsElapsed > maxDecayBars or low <= tp1Price
            tradeState      := 0
            evtOrderExpired := true

// Manage Active Trade (State 2)
else if tradeState == 2
    if tradeDir == 1
        // Check SL or TP2 first
        if low <= slPrice
            tradeState := 0
            evtSlHit   := true
        else if high >= tp2Price
            tradeState := 0
            evtTp2Hit  := true
        else
            // Two-Candle Acceptance Failure Rule (Bull Trap Filter)
            insideRangeCnt := close < activeLevel ? insideRangeCnt + 1 : 0
            if insideRangeCnt >= 2
                tradeState     := 0
                evtTrapScratch := true
    else if tradeDir == -1
        if high >= slPrice
            tradeState := 0
            evtSlHit   := true
        else if low <= tp2Price
            tradeState := 0
            evtTp2Hit  := true
        else
            // Two-Candle Acceptance Failure Rule (Bear Trap Filter)
            insideRangeCnt := close > activeLevel ? insideRangeCnt + 1 : 0
            if insideRangeCnt >= 2
                tradeState     := 0
                evtTrapScratch := true

// Trigger New Setup on Valid M1 Displacement Candle
if (validBullDisp or validBearDisp) and tradeState == 0
    tradeState     := 1
    tradeDir       := validBullDisp ? 1 : -1
    signalBarIndex := bar_index
    activeLevel    := brokenLvl
    insideRangeCnt := 0
    
    entryPrice     := validBullDisp ? activeLevel + (entryBufferPips * pipSize) : activeLevel - (entryBufferPips * pipSize)
    slPrice        := validBullDisp ? low - (slBufferPips * pipSize) : high + (slBufferPips * pipSize)
    
    // Enforce minimum 3.5-pip structural stop distance
    minSlDist      = 3.5 * pipSize
    if math.abs(entryPrice - slPrice) < minSlDist
        slPrice := validBullDisp ? entryPrice - minSlDist : entryPrice + minSlDist
        
    riskDist   = math.abs(entryPrice - slPrice)
    slPips     = riskDist / pipSize
    tp1Price   := validBullDisp ? entryPrice + (riskDist * 1.5) : entryPrice - (riskDist * 1.5)
    tp2Price   := validBullDisp ? entryPrice + (riskDist * 2.5) : entryPrice - (riskDist * 2.5)
    
    // Dynamic Position Size Calculation (Standard Lots)
    riskDollars = acctBalance * (riskPct / 100.0)
    activeLots  := slPips > 0 ? riskDollars / (slPips * pipValuePerLot) : 0.0

    // Draw Setup Visuals (Boxes & Labels)
    boxRight = bar_index + 12
    box.new(bar_index, tp2Price, boxRight, entryPrice, border_color=color.new(color.teal, 40), bgcolor=color.new(color.teal, 85), text="TP2 (+2.5R)\nTP1 (+1.5R): " + str.tostring(tp1Price, format.mintick), text_color=color.white, text_size=size.small)
    box.new(bar_index, entryPrice, boxRight, slPrice, border_color=color.new(color.maroon, 40), bgcolor=color.new(color.maroon, 85), text="SL (" + str.tostring(slPips, "#.#") + "p)", text_color=color.white, text_size=size.small)
    
    lblText = (validBullDisp ? "LONG " : "SHORT ") + lvlName + "\nLimit: " + str.tostring(entryPrice, format.mintick) + "\nSize: " + str.tostring(activeLots, "#.##") + " Lots"
    label.new(bar_index, validBullDisp ? low : high, lblText, style=validBullDisp ? label.style_label_up : label.style_label_down, color=validBullDisp ? color.teal : color.maroon, textcolor=color.white, size=size.small)

// ============================================================================
// 6. CHART PLOTS, BAR COLORING & HUD TABLE
// ============================================================================
plot(m1Ema, "M1 20 EMA", color=color.new(color.aqua, 20), linewidth=1)
plot(m5Ema, "M5 20 EMA (MTF)", color=color.new(color.orange, 10), linewidth=2)

plot(not inAsian ? asianHigh : na, "Asian High", color=color.new(color.yellow, 15), style=plot.style_linebr, linewidth=2)
plot(not inAsian ? asianLow  : na, "Asian Low",  color=color.new(color.yellow, 15), style=plot.style_linebr, linewidth=2)

plot(usePreNyRange and not inPreNy ? preNyHigh : na, "Pre-NY High", color=color.new(color.fuchsia, 25), style=plot.style_linebr, linewidth=1)
plot(usePreNyRange and not inPreNy ? preNyLow  : na, "Pre-NY Low",  color=color.new(color.fuchsia, 25), style=plot.style_linebr, linewidth=1)

plot(usePdhPdl ? pdh : na, "PDH", color=color.new(color.gray, 30), style=plot.style_circles, linewidth=1)
plot(usePdhPdl ? pdl : na, "PDL", color=color.new(color.gray, 30), style=plot.style_circles, linewidth=1)

// Highlight Active Execution Sessions & Displacement Candles
bgcolor(inLonExe or inNyExe ? color.new(color.blue, 94) : na, title="Execution Window Highlight")
barcolor(validBullDisp ? color.lime : validBearDisp ? color.red : na, title="Displacement Candle Highlight")

// Plot State Transition Markers
plotshape(evtLimitFilled,  title="Limit Order Filled",    style=shape.circle,   location=location.abovebar, color=color.aqua,   size=size.tiny, text="FILL")
plotshape(evtOrderExpired, title="4-Min Decay Cancelled", style=shape.xcross,   location=location.abovebar, color=color.gray,   size=size.tiny, text="4m EXP")
plotshape(evtTrapScratch,  title="2-Bar Trap Scratch",    style=shape.Warning,  location=location.abovebar, color=color.orange, size=size.small, text="TRAP SCRATCH")

// Institutional Telemetry HUD
var table hud = table.new(position.top_right, 2, 7, bgcolor=color.new(color.black, 20), border_color=color.new(color.gray, 60), border_width=1)
if barstate.islast and showHud
    sessStr = inLonExe ? "LONDON OPEN (ACTIVE)" : inNyExe ? "NY OPEN (ACTIVE)" : inAsian ? "ASIAN (BUILDING)" : "OFF-PEAK (STANDBY)"
    sessCol = (inLonExe or inNyExe) ? color.lime : color.gray
    
    biasStr = isM5Bull ? "BULLISH (Above 20 EMA)" : isM5Bear ? "BEARISH (Below 20 EMA)" : "NEUTRAL / CHOP"
    biasCol = isM5Bull ? color.lime : isM5Bear ? color.red : color.orange
    
    stateStr = tradeState == 1 ? "PENDING LIMIT (4m Timer)" : tradeState == 2 ? "IN POSITION (Monitoring)" : "SCANNING LEVELS"
    stateCol = tradeState == 1 ? color.yellow : tradeState == 2 ? color.aqua : color.white

    table.cell(hud, 0, 0, "INSTITUTIONAL SCALPER", text_color=color.white, bgcolor=color.new(color.blue, 40), text_size=size.small)
    table.cell(hud, 1, 0, syminfo.ticker, text_color=color.white, bgcolor=color.new(color.blue, 40), text_size=size.small)
    
    table.cell(hud, 0, 1, "Session Window", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 1, sessStr, text_color=sessCol, text_size=size.small)
    
    table.cell(hud, 0, 2, "M5 Volatility (ATR)", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 2, str.tostring(m5AtrPips, "#.##") + " Pips " + (isM5AtrOk ? "[OK]" : "[LOW]"), text_color=isM5AtrOk ? color.lime : color.red, text_size=size.small)
    
    table.cell(hud, 0, 3, "M5 20 EMA Pressure", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 3, biasStr, text_color=biasCol, text_size=size.small)
    
    table.cell(hud, 0, 4, "M1 Volume Spike", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 4, str.tostring(volume / volSma, "#.##") + "x Avg", text_color=isVolValid ? color.lime : color.silver, text_size=size.small)
    
    table.cell(hud, 0, 5, "Engine Status", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 5, stateStr, text_color=stateCol, text_size=size.small)
    
    table.cell(hud, 0, 6, "Last Calc Lot Size", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 6, na(activeLots) ? "N/A" : str.tostring(activeLots, "#.##") + " Lots (" + str.tostring(riskPct) + "%)", text_color=color.aqua, text_size=size.small)

// ============================================================================
// 7. REAL-TIME EXECUTION ALERTS
// ============================================================================
alertcondition(validBullDisp,   title="1. Bullish Displacement Setup", message="BULLISH M1 Displacement Breakout on {{ticker}}! Place Limit Retest Order.")
alertcondition(validBearDisp,   title="2. Bearish Displacement Setup", message="BEARISH M1 Displacement Breakout on {{ticker}}! Place Limit Retest Order.")
alertcondition(evtLimitFilled,  title="3. Model A Limit Order Filled", message="Model A Limit Order FILLED on {{ticker}}.")
alertcondition(evtOrderExpired, title="4. Limit Cancelled (4-Min Rule)", message="CANCEL PENDING LIMIT on {{ticker}}: 4-Minute Time Decay or 1.5R reached unfilled.")
alertcondition(evtTrapScratch,  title="5. EMERGENCY: 2-Bar Trap Scratch", message="TRAP DETECTED on {{ticker}}: 2 consecutive M1 closes back inside range. SCRATCH TRADE AT MARKET!")
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: 7645
Joined: Mon Jul 20, 2026 1:28 pm

Re: Pro level forex strategy: scalping breakout levels

Post by PTScalper »

Setup & Live Calibration Checklist

Chart Timeframe: Always load this indicator on the 1-Minute (M1) chart of EUR/USD, GBP/USD, or USD/JPY. The script pulls M5 and Daily data automatically in the background.

Pair-Specific ATR & Pip Value Adjustment:

For EUR/USD, keep Minimum M5 ATR Threshold at 3.5 pips and Pip Value per Standard Lot at $10.0.

For GBP/USD, raise Minimum M5 ATR Threshold to 4.5 pips in the indicator settings.

For USD/JPY, set Minimum M5 ATR Threshold to 4.5 pips and adjust Pip Value per Standard Lot to approximately $6.50 – $7.00 (depending on the current USD/JPY exchange rate).

Webhook / Alert Binding: Open TradingView's Create Alert dialog (Alt + A), select Institutional Breakout Scalper [M15/M5/M1] as the Condition, and create alerts for Bullish/Bearish Displacement Setup, Limit Cancelled (4-Min Rule), and EMERGENCY: 2-Bar Trap Scratch so you receive instant audio/push notifications at each stage of execution.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7645
Joined: Mon Jul 20, 2026 1:28 pm

Re: Pro level forex strategy: scalping breakout levels

Post by PTScalper »

The complete Pine Script v5 Strategy below converts the M15/M5/M1 institutional breakout indicator into an automated backtesting engine for the TradingView Strategy Tester. It uses a three-tier bracket architecture (50% at +1.5R, 30% at +2.5R, and a 20% runner), ratchets the resting stop-loss to Breakeven + 0.3 pips as soon as TP1 is touched, trails the final 20% runner behind the M1 20 EMA, cancels unfilled limit orders after 4 minutes, and scratches active trades on a 2-candle bull/bear trap.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7645
Joined: Mon Jul 20, 2026 1:28 pm

Re: Pro level forex strategy: scalping breakout levels

Post by PTScalper »

Complete Pine Script v5 strategy() Code

Copy the script below into the TradingView Pine Editor, click Save, and click Add to chart on a 1-Minute (M1) chart.

Code: Select all

// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Institutional Breakout Scalper Strategy [M15/M5/M1]

//@version=5
strategy(
     title               = "Institutional Breakout Scalper Strategy [M15/M5/M1]",
     shorttitle          = "IBS Strategy [M1/M5]",
     overlay             = true,
     initial_capital     = 50000,
     default_qty_type    = strategy.fixed,
     currency            = currency.USD,
     commission_type     = strategy.commission.cash_per_contract,
     commission_value    = 0.000035, // $3.50 per side per 100,000 units ($7.00 round-turn per Standard Lot)
     slippage            = 1,
     process_orders_on_close = true,
     calc_on_every_tick  = false,
     max_lines_count     = 500,
     max_boxes_count     = 200,
     max_labels_count    = 200
)

// ============================================================================
// 1. USER INPUTS & STRATEGY PARAMETERS
// ============================================================================
grp_sess = "1. Session Windows & Liquidity Ranges (UTC)"
asianSess        = input.session("0000-0630", "Asian Range (Liquidity Pool)", group=grp_sess)
preNySess        = input.session("1030-1215", "Pre-NY Consolidation Range", group=grp_sess)
lonExecSess      = input.session("0700-1000", "London Execution Window", group=grp_sess)
nyExecSess       = input.session("1230-1530", "NY Open Execution Window", group=grp_sess)
useSessionGate   = input.bool(true, "Restrict Entries to Execution Windows", group=grp_sess)
usePreNyRange    = input.bool(true, "Trade Pre-NY Range Breakouts", group=grp_sess)
usePdhPdl        = input.bool(true, "Trade Previous Day High/Low (PDH/PDL)", group=grp_sess)

grp_mtf = "2. M5 Compression & Trend Filters (Non-Repainting)"
m5EmaLen         = input.int(20, "M5 EMA Length", minval=5, group=grp_mtf)
m5AtrLen         = input.int(14, "M5 ATR Length", minval=5, group=grp_mtf)
minM5AtrPips     = input.float(3.5, "Minimum M5 ATR Threshold (Pips)", minval=0.5, step=0.5, group=grp_mtf, tooltip="3.5 for EURUSD, 4.5 for GBPUSD/USDJPY.")
requireM5Filter  = input.bool(true, "Enforce M5 EMA & ATR Alignment", group=grp_mtf)

grp_disp = "3. M1 Displacement Candle Rules"
minBodyPct       = input.float(65.0, "Min Body-to-Range Ratio (%)", minval=30.0, maxval=95.0, step=5.0, group=grp_disp)
minClearancePct  = input.float(50.0, "Min Body Clearance Beyond Level (%)", minval=10.0, maxval=90.0, step=5.0, group=grp_disp)
volSmaLen        = input.int(20, "Tick Volume SMA Length", minval=5, group=grp_disp)
minVolMult       = input.float(1.5, "Min Volume Expansion Multiplier", minval=1.0, step=0.1, group=grp_disp)
m1AtrLen         = input.int(14, "M1 ATR Length", minval=5, group=grp_disp)
minM1AtrMult     = input.float(1.2, "Min Candle Length (x M1 ATR)", minval=0.5, step=0.1, group=grp_disp)
maxM1AtrMult     = input.float(2.5, "Anti-Exhaustion Cap (x M1 ATR)", minval=1.5, step=0.1, group=grp_disp)

grp_exec = "4. Order Execution, Scaling & Trail Rules"
entryBufferPips  = input.float(0.4, "Model A Limit Entry Buffer (Pips)", minval=0.0, step=0.1, group=grp_exec)
slBufferPips     = input.float(1.2, "Stop-Loss Buffer Beyond Candle (Pips)", minval=0.5, step=0.1, group=grp_exec)
beOffsetPips     = input.float(0.3, "Breakeven Offset After TP1 (Pips)", minval=0.0, step=0.1, group=grp_exec)
maxDecayBars     = input.int(4, "Model A Time Decay Limit (M1 Bars)", minval=1, maxval=15, group=grp_exec)
tp1R             = input.float(1.5, "TP1 Target (R-Multiple, 50% Qty)", minval=0.5, step=0.25, group=grp_exec)
tp2R             = input.float(2.5, "TP2 Target (R-Multiple, 30% Qty)", minval=1.0, step=0.25, group=grp_exec)
emaTrailMode     = input.string("Full Candle Close Opposite EMA", "Runner (20%) M1 20 EMA Trail Rule", options=["Full Candle Close Opposite EMA", "Single Close Opposite EMA", "Ratchet Hard Stop Along EMA"], group=grp_exec)
useStallFilter   = input.bool(true, "Scratch Trade if Stalled 6 Bars Under +0.5R", group=grp_exec)

grp_risk = "5. Risk Sizing & Session Circuit Breakers"
riskPct          = input.float(0.5, "Risk Per Trade (% of Equity)", minval=0.1, maxval=5.0, step=0.1, group=grp_risk)
pipValuePerLot   = input.float(10.0, "Pip Value per Standard Lot ($)", minval=1.0, step=0.5, group=grp_risk, tooltip="$10 for EURUSD/GBPUSD. Adjust to ~$6.70 for USDJPY.")
contractMult     = input.int(100000, "Units per Standard Lot", options=[100000, 1], group=grp_risk, tooltip="Use 100000 for standard Forex feeds (OANDA/FXCM) or 1 if broker quotes directly in lots.")
useBreakers      = input.bool(true, "Enforce Daily -2.0R Stop & 3-Trade Session Cap", group=grp_risk)
maxDailyLossR    = input.float(-2.0, "Daily Max Drawdown Breaker (R)", maxval=-0.5, step=0.5, group=grp_risk)
maxSessProfitR   = input.float(2.5, "Session Profit Lock Target (R)", minval=1.0, step=0.5, group=grp_risk)
maxTradesPerSess = input.int(3, "Max Executed Trades Per Session", minval=1, maxval=10, group=grp_risk)
showHud          = input.bool(true, "Show Strategy Telemetry HUD", group=grp_risk)

// ============================================================================
// 2. PIP CONVERSION & NON-REPAINTING MTF DATA
// ============================================================================
pipSize = syminfo.mintick * (syminfo.mintick == 0.00001 or syminfo.mintick == 0.001 ? 10 : 1)

// Local M1 Indicators
m1Ema  = ta.ema(close, 20)
m1Atr  = ta.atr(m1AtrLen)
volSma = ta.sma(volume, volSmaLen)

// Non-Repainting M5 Data (Confirmed closed M5 bar values)
m5EmaExpr = ta.ema(close, m5EmaLen)
m5AtrExpr = ta.atr(m5AtrLen)
[m5Close, m5Ema, m5EmaPrev, m5Atr] = request.security(syminfo.tickerid, "5", [close[1], m5EmaExpr[1], m5EmaExpr[2], m5AtrExpr[1]], barmerge.gaps_off, barmerge.lookahead_on)

// Non-Repainting Previous Day High & Low (PDH / PDL)
[pdh, pdl] = request.security(syminfo.tickerid, "D", [high[1], low[1]], barmerge.gaps_off, barmerge.lookahead_on)

// Evaluate M5 Institutional Filters
m5AtrPips = m5Atr / pipSize
isM5AtrOk = m5AtrPips >= minM5AtrPips
isM5Bull  = m5Close > m5Ema and m5Ema > m5EmaPrev
isM5Bear  = m5Close < m5Ema and m5Ema < m5EmaPrev

// ============================================================================
// 3. SESSION & CIRCUIT BREAKER TRACKING
// ============================================================================
inAsian  = not na(time(timeframe.period, asianSess + ":1234567", "UTC"))
inPreNy  = not na(time(timeframe.period, preNySess + ":1234567", "UTC"))
inLonExe = not na(time(timeframe.period, lonExecSess + ":1234567", "UTC"))
inNyExe  = not na(time(timeframe.period, nyExecSess + ":1234567", "UTC"))
inExecWindow = not useSessionGate or inLonExe or inNyExe

// Track Daily & Session Realized R-Multiples and Trade Counts
var float dayStartEquity  = strategy.equity
var float sessStartEquity = strategy.equity
var int   sessTradeCount  = 0
var int   lastClosedCount = 0

if ta.change(time("D"))
    dayStartEquity := strategy.equity

if (inLonExe and not inLonExe[1]) or (inNyExe and not inNyExe[1])
    sessStartEquity := strategy.equity
    sessTradeCount  := 0

oneRiskDollars = strategy.equity * (riskPct / 100.0)
dailyNetR      = oneRiskDollars > 0 ? (strategy.equity - dayStartEquity) / oneRiskDollars : 0.0
sessNetR       = oneRiskDollars > 0 ? (strategy.equity - sessStartEquity) / oneRiskDollars : 0.0

breakerTripped = useBreakers and (dailyNetR <= maxDailyLossR or sessNetR >= maxSessProfitR or sessTradeCount >= maxTradesPerSess)

// ============================================================================
// 4. LIQUIDITY POOL TRACKING (ASIAN, PRE-NY, PDH/PDL)
// ============================================================================
var float asianHigh       = na
var float asianLow        = na
var bool  asianHighBroken = false
var bool  asianLowBroken  = false

if inAsian and not inAsian[1]
    asianHigh       := high
    asianLow        := low
    asianHighBroken := false
    asianLowBroken  := false
else if inAsian
    asianHigh := math.max(nz(asianHigh, high), high)
    asianLow  := math.min(nz(asianLow, low), low)

var float preNyHigh       = na
var float preNyLow        = na
var bool  preNyHighBroken = false
var bool  preNyLowBroken  = false

if inPreNy and not inPreNy[1]
    preNyHigh       := high
    preNyLow        := low
    preNyHighBroken := false
    preNyLowBroken  := false
else if inPreNy
    preNyHigh := math.max(nz(preNyHigh, high), high)
    preNyLow  := math.min(nz(preNyLow, low), low)

var bool pdhBroken = false
var bool pdlBroken = false
if ta.change(time("D"))
    pdhBroken := false
    pdlBroken := false
    preNyHigh := na
    preNyLow  := na

// ============================================================================
// 5. M1 DISPLACEMENT CANDLE ENGINE
// ============================================================================
candleRange = high - low
candleBody  = math.abs(close - open)
bodyRatio   = candleRange > 0 ? (candleBody / candleRange) * 100.0 : 0.0

isBodyValid = bodyRatio >= minBodyPct
isVolValid  = volume >= (volSma * minVolMult)
isAtrValid  = candleRange >= (m1Atr * minM1AtrMult) and candleRange <= (m1Atr * maxM1AtrMult)
isBaseDisp  = isBodyValid and isVolValid and isAtrValid

f_checkBullBreak(float lvl, bool alreadyBroken) =>
    if na(lvl) or alreadyBroken or close <= open
        false
    else
        clearance = close - lvl
        open < lvl and close > lvl and (clearance / candleBody) * 100.0 >= minClearancePct

f_checkBearBreak(float lvl, bool alreadyBroken) =>
    if na(lvl) or alreadyBroken or close >= open
        false
    else
        clearance = lvl - close
        open > lvl and close < lvl and (clearance / candleBody) * 100.0 >= minClearancePct

asianReady = not inAsian and not na(asianHigh)
preNyReady = usePreNyRange and not inPreNy and not na(preNyHigh)
pdhReady   = usePdhPdl and not na(pdh)

bullAsian = asianReady and f_checkBullBreak(asianHigh, asianHighBroken)
bearAsian = asianReady and f_checkBearBreak(asianLow, asianLowBroken)
bullPreNy = preNyReady and f_checkBullBreak(preNyHigh, preNyHighBroken)
bearPreNy = preNyReady and f_checkBearBreak(preNyLow, preNyLowBroken)
bullPdh   = pdhReady and f_checkBullBreak(pdh, pdhBroken)
bearPdl   = pdhReady and f_checkBearBreak(pdl, pdlBroken)

m5BullGate = not requireM5Filter or (isM5AtrOk and isM5Bull)
m5BearGate = not requireM5Filter or (isM5AtrOk and isM5Bear)

validBullDisp = inExecWindow and not breakerTripped and isBaseDisp and m5BullGate and (bullAsian or bullPreNy or bullPdh)
validBearDisp = inExecWindow and not breakerTripped and isBaseDisp and m5BearGate and (bearAsian or bearPreNy or bearPdl)

float brokenLvl = na
string lvlName  = ""

if validBullDisp
    if bullAsian
        brokenLvl       := asianHigh
        lvlName         := "Asian High"
        asianHighBroken := true
    else if bullPreNy
        brokenLvl       := preNyHigh
        lvlName         := "Pre-NY High"
        preNyHighBroken := true
    else if bullPdh
        brokenLvl       := pdh
        lvlName         := "PDH"
        pdhBroken       := true

if validBearDisp
    if bearAsian
        brokenLvl      := asianLow
        lvlName        := "Asian Low"
        asianLowBroken := true
    else if bearPreNy
        brokenLvl      := preNyLow
        lvlName        := "Pre-NY Low"
        preNyLowBroken := true
    else if bearPdl
        brokenLvl      := pdl
        lvlName        := "PDL"
        pdlBroken      := true

// Consume levels broken without valid displacement
if asianReady and close > asianHigh and not validBullDisp
    asianHighBroken := true
if asianReady and close < asianLow and not validBearDisp
    asianLowBroken  := true
if preNyReady and close > preNyHigh and not validBullDisp
    preNyHighBroken := true
if preNyReady and close < preNyLow and not validBearDisp
    preNyLowBroken  := true
if pdhReady and close > pdh and not validBullDisp
    pdhBroken       := true
if pdhReady and close < pdl and not validBearDisp
    pdlBroken       := true

// ============================================================================
// 6. STRATEGY EXECUTION ENGINE (LIMIT ORDERS, SCALING, BE, 20 EMA TRAIL)
// ============================================================================
var bool  orderPending   = false
var int   pendingDir     = 0
var int   signalBarIndex = na
var int   entryBarIndex  = na
var float activeLevel    = na
var float entryPrice     = na
var float initSlPrice    = na
var float currentSlPrice = na
var float runnerSlPrice  = na
var float tp1Price       = na
var float tp2Price       = na
var float halfRPrice     = na
var float activeLots     = na
var bool  tp1Reached     = false
var bool  tp2Reached     = false
var bool  halfRReached   = false
var int   insideRangeCnt = 0

bool evtOrderExpired = false
bool evtTrapScratch  = false
bool evtStallScratch = false
bool evtEmaTrailExit = false

isFlat  = strategy.position_size == 0
isLong  = strategy.position_size > 0
isShort = strategy.position_size < 0

// Detect New Fill
justEntered = (isLong and strategy.position_size[1] <= 0) or (isShort and strategy.position_size[1] >= 0)
if justEntered
    orderPending   := false
    entryBarIndex  := bar_index
    sessTradeCount += 1
    insideRangeCnt := 0

// Reset flags when flat and no order is pending
if isFlat and not orderPending
    tp1Reached     := false
    tp2Reached     := false
    halfRReached   := false
    insideRangeCnt := 0

// ----------------------------------------------------------------------------
// A. MANAGE PENDING LIMIT ORDER (4-MINUTE TIME DECAY RULE)
// ----------------------------------------------------------------------------
if isFlat and orderPending
    barsWaiting = bar_index - signalBarIndex
    unfilledRunaway = (pendingDir == 1 and high >= tp1Price) or (pendingDir == -1 and low <= tp1Price)
    if barsWaiting >= maxDecayBars or unfilledRunaway
        strategy.cancel_all()
        orderPending    := false
        pendingDir      := 0
        evtOrderExpired := true

// ----------------------------------------------------------------------------
// B. MANAGE ACTIVE POSITION (BE MOVE, 20 EMA TRAIL, 2-BAR TRAP & 6-BAR STALL)
// ----------------------------------------------------------------------------
if isLong or isShort
    // 1. Track +0.5R, TP1 (+1.5R), and TP2 (+2.5R) Milestones
    if isLong
        if high >= halfRPrice
            halfRReached := true
        if high >= tp1Price and not tp1Reached
            tp1Reached     := true
            currentSlPrice := strategy.position_avg_price + (beOffsetPips * pipSize)
            runnerSlPrice  := currentSlPrice
        if high >= tp2Price
            tp2Reached := true
    else if isShort
        if low <= halfRPrice
            halfRReached := true
        if low <= tp1Price and not tp1Reached
            tp1Reached     := true
            currentSlPrice := strategy.position_avg_price - (beOffsetPips * pipSize)
            runnerSlPrice  := currentSlPrice
        if low <= tp2Price
            tp2Reached := true

    // 2. Update Runner Hard Stop if "Ratchet Hard Stop Along EMA" is chosen
    if tp1Reached and emaTrailMode == "Ratchet Hard Stop Along EMA"
        if isLong
            emaStop = m1Ema - (0.5 * pipSize)
            runnerSlPrice := math.max(runnerSlPrice, emaStop)
        else if isShort
            emaStop = m1Ema + (0.5 * pipSize)
            runnerSlPrice := math.min(runnerSlPrice, emaStop)

    // 3. Update Three-Stage Bracket Exits dynamically
    entryId = isLong ? "Long" : "Short"
    strategy.exit("TP1 (50%)", from_entry=entryId, qty_percent=50, limit=tp1Price, stop=currentSlPrice, comment_profit="TP1 (+1.5R)", comment_loss=tp1Reached ? "BE Stop" : "Initial SL")
    strategy.exit("TP2 (30%)", from_entry=entryId, qty_percent=30, limit=tp2Price, stop=currentSlPrice, comment_profit="TP2 (+2.5R)", comment_loss=tp1Reached ? "BE Stop" : "Initial SL")
    strategy.exit("Runner SL (20%)", from_entry=entryId, stop=runnerSlPrice, comment_loss=tp1Reached ? "Runner Trail SL" : "Initial SL")

    // 4. Two-Candle Acceptance Failure Rule (Bull / Bear Trap Scratch)
    if isLong
        insideRangeCnt := close < activeLevel ? insideRangeCnt + 1 : 0
    else if isShort
        insideRangeCnt := close > activeLevel ? insideRangeCnt + 1 : 0

    if insideRangeCnt >= 2
        strategy.close_all(comment="2-Bar Trap Scratch")
        evtTrapScratch := true

    // 5. 6-Candle Order-Flow Stall Rule (No +0.5R progress within 6 bars)
    barsInTrade = bar_index - entryBarIndex
    if useStallFilter and barsInTrade >= 6 and not halfRReached and not evtTrapScratch
        strategy.close_all(comment="6-Bar Stall Scratch")
        evtStallScratch := true

    // 6. Runner M1 20 EMA Bar-Close Trailing Exit (Active once TP1 or TP2 reached)
    if tp2Reached and not evtTrapScratch and not evtStallScratch
        emaCrossExit = false
        if emaTrailMode == "Full Candle Close Opposite EMA"
            emaCrossExit := (isLong and close < m1Ema and high < m1Ema) or (isShort and close > m1Ema and low > m1Ema)
        else if emaTrailMode == "Single Close Opposite EMA"
            emaCrossExit := (isLong and close < m1Ema) or (isShort and close > m1Ema)

        if emaCrossExit
            strategy.close(entryId, comment="Runner M1 20 EMA Exit")
            evtEmaTrailExit := true

// ----------------------------------------------------------------------------
// C. SUBMIT NEW MODEL A LIMIT ENTRY & INITIAL BRACKET ORDERS
// ----------------------------------------------------------------------------
if (validBullDisp or validBearDisp) and isFlat and not orderPending
    orderPending   := true
    pendingDir     := validBullDisp ? 1 : -1
    signalBarIndex := bar_index
    activeLevel    := brokenLvl
    tp1Reached     := false
    tp2Reached     := false
    halfRReached   := false
    insideRangeCnt := 0

    entryPrice     := validBullDisp ? activeLevel + (entryBufferPips * pipSize) : activeLevel - (entryBufferPips * pipSize)
    initSlPrice    := validBullDisp ? low - (slBufferPips * pipSize) : high + (slBufferPips * pipSize)

    // Enforce minimum 3.5-pip structural stop distance
    minSlDist = 3.5 * pipSize
    if math.abs(entryPrice - initSlPrice) < minSlDist
        initSlPrice := validBullDisp ? entryPrice - minSlDist : entryPrice + minSlDist

    currentSlPrice := initSlPrice
    runnerSlPrice  := initSlPrice

    riskDist   = math.abs(entryPrice - initSlPrice)
    slPips     = riskDist / pipSize
    halfRPrice := validBullDisp ? entryPrice + (riskDist * 0.5)  : entryPrice - (riskDist * 0.5)
    tp1Price   := validBullDisp ? entryPrice + (riskDist * tp1R) : entryPrice - (riskDist * tp1R)
    tp2Price   := validBullDisp ? entryPrice + (riskDist * tp2R) : entryPrice - (riskDist * tp2R)

    // Exact Position Sizing in Lots & Broker Units
    riskDollars = strategy.equity * (riskPct / 100.0)
    activeLots  := slPips > 0 ? riskDollars / (slPips * pipValuePerLot) : 0.0
    orderQty    = math.max(0.01, math.round(activeLots * 100.0) / 100.0) * contractMult

    entryId = validBullDisp ? "Long" : "Short"
    strategy.entry(entryId, validBullDisp ? strategy.long : strategy.short, qty=orderQty, limit=entryPrice, comment=lvlName + " Limit")

    // Pre-attach bracket exits so intrabar fills are immediately protected
    strategy.exit("TP1 (50%)", from_entry=entryId, qty_percent=50, limit=tp1Price, stop=currentSlPrice, comment_profit="TP1 (+1.5R)", comment_loss="Initial SL")
    strategy.exit("TP2 (30%)", from_entry=entryId, qty_percent=30, limit=tp2Price, stop=currentSlPrice, comment_profit="TP2 (+2.5R)", comment_loss="Initial SL")
    strategy.exit("Runner SL (20%)", from_entry=entryId, stop=runnerSlPrice, comment_loss="Initial SL")

    // Draw Setup Visuals
    boxRight = bar_index + 12
    box.new(bar_index, tp2Price, boxRight, entryPrice, border_color=color.new(color.teal, 40), bgcolor=color.new(color.teal, 85), text="TP2 (+2.5R)\nTP1 (+1.5R): " + str.tostring(tp1Price, format.mintick), text_color=color.white, text_size=size.small)
    box.new(bar_index, entryPrice, boxRight, initSlPrice, border_color=color.new(color.maroon, 40), bgcolor=color.new(color.maroon, 85), text="SL (" + str.tostring(slPips, "#.#") + "p)", text_color=color.white, text_size=size.small)

    lblText = (validBullDisp ? "LONG " : "SHORT ") + lvlName + "\nLimit: " + str.tostring(entryPrice, format.mintick) + "\nLots: " + str.tostring(activeLots, "#.##")
    label.new(bar_index, validBullDisp ? low : high, lblText, style=validBullDisp ? label.style_label_up : label.style_label_down, color=validBullDisp ? color.teal : color.maroon, textcolor=color.white, size=size.small)

// ============================================================================
// 7. CHART PLOTS, BAR COLORING & TELEMETRY HUD
// ============================================================================
plot(m1Ema, "M1 20 EMA (Runner Trail)", color=color.new(color.aqua, 15), linewidth=1)
plot(m5Ema, "M5 20 EMA (MTF Filter)", color=color.new(color.orange, 10), linewidth=2)
plot(isLong or isShort ? currentSlPrice : na, "Active Stop-Loss / BE Line", color=color.new(color.red, 0), style=plot.style_linebr, linewidth=2)

plot(not inAsian ? asianHigh : na, "Asian High", color=color.new(color.yellow, 15), style=plot.style_linebr, linewidth=2)
plot(not inAsian ? asianLow  : na, "Asian Low",  color=color.new(color.yellow, 15), style=plot.style_linebr, linewidth=2)

plot(usePreNyRange and not inPreNy ? preNyHigh : na, "Pre-NY High", color=color.new(color.fuchsia, 25), style=plot.style_linebr, linewidth=1)
plot(usePreNyRange and not inPreNy ? preNyLow  : na, "Pre-NY Low",  color=color.new(color.fuchsia, 25), style=plot.style_linebr, linewidth=1)

plot(usePdhPdl ? pdh : na, "PDH", color=color.new(color.gray, 30), style=plot.style_circles, linewidth=1)
plot(usePdhPdl ? pdl : na, "PDL", color=color.new(color.gray, 30), style=plot.style_circles, linewidth=1)

bgcolor(inLonExe or inNyExe ? color.new(color.blue, 94) : na, title="Execution Window Highlight")
barcolor(validBullDisp ? color.lime : validBearDisp ? color.red : na, title="Displacement Candle Highlight")

plotshape(evtOrderExpired, title="4-Min Decay Cancelled", style=shape.xcross,  location=location.abovebar, color=color.gray,   size=size.tiny, text="4m EXP")
plotshape(evtTrapScratch,  title="2-Bar Trap Scratch",    style=shape.diamond, location=location.abovebar, color=color.orange, size=size.tiny, text="TRAP SCRATCH")
plotshape(evtStallScratch, title="6-Bar Stall Scratch",   style=shape.diamond, location=location.abovebar, color=color.yellow, size=size.tiny, text="STALL EXIT")
plotshape(evtEmaTrailExit, title="M1 20 EMA Runner Exit", style=shape.circle,  location=location.abovebar, color=color.aqua,   size=size.tiny, text="EMA TRAIL")

// Telemetry HUD Table
var table hud = table.new(position.top_right, 2, 8, bgcolor=color.new(color.black, 20), border_color=color.new(color.gray, 60), border_width=1)
if barstate.islast and showHud
    sessStr  = inLonExe ? "LONDON OPEN (ACTIVE)" : inNyExe ? "NY OPEN (ACTIVE)" : inAsian ? "ASIAN (BUILDING)" : "OFF-PEAK (STANDBY)"
    sessCol  = (inLonExe or inNyExe) ? color.lime : color.gray
    biasStr  = isM5Bull ? "BULLISH (Above 20 EMA)" : isM5Bear ? "BEARISH (Below 20 EMA)" : "NEUTRAL / CHOP"
    biasCol  = isM5Bull ? color.lime : isM5Bear ? color.red : color.orange
    stateStr = breakerTripped ? "CIRCUIT BREAKER LOCKED" : isLong or isShort ? (tp1Reached ? "IN TRADE (BE LOCKED)" : "IN TRADE (INITIAL SL)") : orderPending ? "PENDING LIMIT (4m Timer)" : "SCANNING LEVELS"
    stateCol = breakerTripped ? color.red : (isLong or isShort) ? color.aqua : orderPending ? color.yellow : color.white

    table.cell(hud, 0, 0, "IBS STRATEGY TESTER", text_color=color.white, bgcolor=color.new(color.blue, 40), text_size=size.small)
    table.cell(hud, 1, 0, syminfo.ticker, text_color=color.white, bgcolor=color.new(color.blue, 40), text_size=size.small)

    table.cell(hud, 0, 1, "Session Window", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 1, sessStr, text_color=sessCol, text_size=size.small)

    table.cell(hud, 0, 2, "M5 Volatility (ATR)", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 2, str.tostring(m5AtrPips, "#.##") + " Pips " + (isM5AtrOk ? "[OK]" : "[LOW]"), text_color=isM5AtrOk ? color.lime : color.red, text_size=size.small)

    table.cell(hud, 0, 3, "M5 20 EMA Pressure", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 3, biasStr, text_color=biasCol, text_size=size.small)

    table.cell(hud, 0, 4, "Engine Status", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 4, stateStr, text_color=stateCol, text_size=size.small)

    table.cell(hud, 0, 5, "Session Trades / Net R", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 5, str.tostring(sessTradeCount) + "/" + str.tostring(maxTradesPerSess) + " | " + str.tostring(sessNetR, "#.##") + "R", text_color=sessNetR >= 0 ? color.lime : color.orange, text_size=size.small)

    table.cell(hud, 0, 6, "Daily Net R", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 6, str.tostring(dailyNetR, "#.##") + "R (Limit " + str.tostring(maxDailyLossR) + "R)", text_color=dailyNetR > maxDailyLossR ? color.aqua : color.red, text_size=size.small)

    table.cell(hud, 0, 7, "Last Calc Lot Size", text_color=color.silver, text_size=size.small)
    table.cell(hud, 1, 7, na(activeLots) ? "N/A" : str.tostring(activeLots, "#.##") + " Lots (" + str.tostring(riskPct) + "%)", text_color=color.aqua, text_size=size.small)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7645
Joined: Mon Jul 20, 2026 1:28 pm

Re: Pro level forex strategy: scalping breakout levels

Post by PTScalper »

Strategy Tester Calibration Guide

Units vs. Lots (Units per Standard Lot Input):

Standard TradingView Forex feeds size positions in base currency units where 100,000 units equals 1.00 Standard Lot. Leave Units per Standard Lot at 100000.

If you backtest on a broker feed whose contract size is already 1 lot per unit, switch Units per Standard Lot to 1 in the Inputs tab.

Realistic ECN Commission & Slippage:

The script header pre-configures commission_value = 0.000035 (cash_per_contract), which equals $3.50 per side per 100,000 units ($7.00 round-turn per Standard Lot), alongside slippage = 1 tick.

Selecting the M1 20 EMA Runner Trail Mode:

Inside Settings - Inputs - Section 4, you can test three variations of the runner exit under Runner (20%) M1 20 EMA Trail Rule:

Full Candle Close Opposite EMA: Waits for an entire M1 candle (both body and wick) to close on the opposite side of the M1 20 EMA.

Single Close Opposite EMA: Exits the runner on the first M1 candle whose closing price crosses the M1 20 EMA.

Ratchet Hard Stop Along EMA: Continuously drags the hard stop-loss order 0.5 pips behind the M1 20 EMA on every bar after TP1.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
FXS HongKong Scalper
Posts: 407
Joined: Sat Oct 24, 2026 10:15 pm

Re: Pro level forex strategy: scalping breakout levels

Post by FXS HongKong Scalper »

Useful that the header sets realistic commission and slippage by default. Most published scripts leave both at zero, which makes any breakout system look better than it is.

One check worth adding for anyone testing this on TradingView data: the default forex feeds are often bid-only or from a different liquidity source than your broker, so the breakout candle on the feed might not match what your broker printed. On the Asian session especially, we've seen highs differ by 2 to 3 pips between feeds. For a strategy based on exact level breaks, that's enough to change the trade list. Running the same test on your broker's own data, if available, is a good sanity check.
FXS Official Team | Hong Kong Session Desk
Shadow Trader
Posts: 5438
Joined: Wed Sep 09, 2026 5:19 pm

Re: Pro level forex strategy: scalping breakout levels

Post by Shadow Trader »

Classic pivot levels are calculated from the previous day's high, low and close. Millions of traders have them on their charts, which gives them a self-fulfilling quality in some conditions.

On a scalping timescale, the picture is less tidy. A pivot level in the middle of a quiet range does nothing. One that coincides with a round number, a session high or a fixing time attracts more activity.

The difference comes from confluence. A level that three different things point to is more likely to produce a reaction than a level that only one calculation produces.

Another issue is the definition of "previous day." As covered earlier, server time determines where the day ends, so pivots differ between brokers.

Used as a map of places where something might happen, pivots are fine. Used as a rule that says price will reverse at S1, they lose money.

Which levels have shown the most consistent reactions, and what did they have in common?
Post Reply