Page 3 of 3

Re: Why most M1 indicators fail after transaction costs

Posted: Fri Sep 18, 2026 7:05 pm
by PTScalper
Key Engineering Details

Native .Pips Calculation: In cTrader, position.Pips dynamically evaluates against the current Bid/Ask. This means the 1:1 Risk/Reward trigger automatically clears the spread before executing the scale-out.

Broker Normalization: Symbol.NormalizeVolumeInUnits(..., RoundingMode.Down) prevents the cBot from crashing if dividing the initial volume by 2 results in a micro-lot fraction your broker doesn't support.

Crash Resilience: Because the bot uses the physical order volume rather than a List or HashSet to remember if a trade was scaled, you can shut down your workstation, reboot, and restart the cBot, and it will immediately resume managing the trade flawlessly.

Re: Why most M1 indicators fail after transaction costs

Posted: Fri Sep 18, 2026 7:08 pm
by PTScalper
To port this exact logic into Pine Script (v5), we have to tackle two specific architectural challenges unique to TradingView:

Multi-Timeframe (MTF) execution without Repainting: To execute on the 1-minute chart based on 15-minute structures, we must use request.security() meticulously to fetch the 15-minute data of the previously closed bar, ensuring absolutely zero lookahead bias.

Stateful Trade Management: Pine Script's strategy.exit() requires us to track whether the first Take Profit (TP1) was hit using a persistent var state, so we can dynamically update the Stop Loss parameter for the remaining 50% of the position.

Here is the complete Pine Script v5 adaptation of the Liquidity Sweep cBot, featuring the 1:1 scale-out and dynamic breakeven stop loss.

Re: Why most M1 indicators fail after transaction costs

Posted: Fri Sep 18, 2026 7:08 pm
by PTScalper
Pine Script v5: 15m Sweep & M1 Scale-Out

Code: Select all

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Quantitative_Insights

//@version=5
strategy("M1 Liquidity Sweep & Scale-Out", overlay=true, 
     margin_long=100, margin_short=100, 
     initial_capital=10000,
     commission_type=strategy.commission.cash_per_order, 
     commission_value=3.5, 
     slippage=2)

// ==========================================
// 1. SYSTEM PARAMETERS
// ==========================================
grp1 = "Structure & Filters"
swingLookback = input.int(20, title="Swing Lookback (15m Bars)", group=grp1)
useDailyBias  = input.bool(true, title="Align with Daily Trend", group=grp1)

grp2 = "Risk & Trade Management"
slPips = input.float(10.0, title="Stop Loss (Pips)", group=grp2)
tpPips = input.float(20.0, title="Final Take Profit (Pips)", group=grp2) 

// ==========================================
// 2. MTF DATA FETCHING (Zero Lookahead Bias)
// ==========================================
// 2a. Daily Bias (Yesterday's close vs Day Before's close)
[dClose1, dClose2] = request.security(syminfo.tickerid, "D", [close[1], close[2]], lookahead=barmerge.lookahead_ignore)
dailyBullish = dClose1 > dClose2
dailyBearish = dClose1 < dClose2

// 2b. 15-Minute Structural Data
// ta.highest()[2] isolates the 20 bars strictly prior to the 15m bar that just closed
[m15High, m15Low, m15Close, localHigh, localLow] = request.security(syminfo.tickerid, "15", 
     [high[1], low[1], close[1], ta.highest(high, swingLookback)[2], ta.lowest(low, swingLookback)[2]], 
     lookahead=barmerge.lookahead_ignore)

// ==========================================
// 3. SWEEP DETECTION LOGIC
// ==========================================
// This triggers 'true' only on the exact first M1 bar of a new 15m cycle
new15mCycle = ta.change(time("15"))

buySideSwept  = (m15High > localHigh) and (m15Close < localHigh)
sellSideSwept = (m15Low < localLow) and (m15Close > localLow)

validLongEntry  = new15mCycle and sellSideSwept and (not useDailyBias or dailyBullish)
validShortEntry = new15mCycle and buySideSwept  and (not useDailyBias or dailyBearish)

// ==========================================
// 4. TRADE STATE & MANAGEMENT VARIABLES
// ==========================================
// These 'var' declarations persist their values bar-to-bar
var float entryPrice = na
var float slPrice    = na
var float tp1Price   = na
var float tp2Price   = na
var bool  scaledOut  = false

pipSize = syminfo.mintick * (syminfo.type == "forex" ? 10 : 1)

// Reset state when flat
if strategy.position_size == 0
    scaledOut := false
    entryPrice := na
    slPrice    := na
    tp1Price   := na
    tp2Price   := na

// ==========================================
// 5. EXECUTION & ENTRY
// ==========================================
if validLongEntry and strategy.position_size == 0
    strategy.entry("Long_Sweep", strategy.long)
    entryPrice := close
    slPrice    := entryPrice - (slPips * pipSize)
    tp1Price   := entryPrice + (slPips * pipSize) // 1:1 Risk/Reward Target
    tp2Price   := entryPrice + (tpPips * pipSize) // Final Target
    scaledOut  := false

if validShortEntry and strategy.position_size == 0
    strategy.entry("Short_Sweep", strategy.short)
    entryPrice := close
    slPrice    := entryPrice + (slPips * pipSize)
    tp1Price   := entryPrice - (slPips * pipSize) // 1:1 Risk/Reward Target
    tp2Price   := entryPrice - (tpPips * pipSize) // Final Target
    scaledOut  := false

// ==========================================
// 6. SCALE OUT & BREAKEVEN MANAGEMENT
// ==========================================
if strategy.position_size > 0 // Managing a Long Position
    // Detect if TP1 is hit to flag the Breakeven Stop
    if high >= tp1Price
        scaledOut := true
    
    // Dynamic Stop: Shifts to Entry Price if scaledOut is true
    float currentStop = scaledOut ? entryPrice : slPrice
    
    // Exit 1: Manages the 50% scale-out at TP1 (1:1 RR)
    strategy.exit("TP1_Scale", "Long_Sweep", qty_percent = 50, limit = tp1Price, stop = currentStop)
    
    // Exit 2: Manages the remaining 50% runner to the final target
    strategy.exit("TP2_Runner", "Long_Sweep", limit = tp2Price, stop = currentStop)

if strategy.position_size < 0 // Managing a Short Position
    if low <= tp1Price
        scaledOut := true
        
    float currentStop = scaledOut ? entryPrice : slPrice
    
    strategy.exit("TP1_Scale", "Short_Sweep", qty_percent = 50, limit = tp1Price, stop = currentStop)
    strategy.exit("TP2_Runner", "Short_Sweep", limit = tp2Price, stop = currentStop)

// ==========================================
// 7. VISUALIZATION
// ==========================================
// Plots dynamic trade levels directly on your M1 chart
plot(strategy.position_size != 0 ? entryPrice : na, color=color.blue, style=plot.style_linebr, title="Entry Price")
plot(strategy.position_size != 0 ? tp1Price : na, color=color.new(color.green, 50), style=plot.style_linebr, title="TP1 (Scale Out)")
plot(strategy.position_size != 0 ? tp2Price : na, color=color.green, style=plot.style_linebr, title="TP2 (Final)")
plot(strategy.position_size != 0 ? (scaledOut ? entryPrice : slPrice) : na, color=color.red, style=plot.style_linebr, linewidth=2, title="Dynamic SL")

Re: Why most M1 indicators fail after transaction costs

Posted: Fri Sep 18, 2026 7:09 pm
by PTScalper
Pine Script Engineering Notes

The 15-Minute Sync (new15mCycle): Rather than evaluating on every M1 tick (which wastes compute and risks repainting), ta.change(time("15")) is only true on the absolute first 1-minute candle of a new 15-minute cycle (e.g., 10:00, 10:15). This guarantees the sweep conditions are locked in based on a closed bar.

The Stateful qty_percent = 50 Pattern: Pine v5 handles partial closes beautifully through strategy.exit. We issue two exits simultaneously. The first limits out at 50% size at TP1. The second order waits at TP2. Once TP1 is hit, the scaledOut boolean turns true, which forces currentStop to equal entryPrice, immediately updating the stop loss for the second exit order to breakeven.

Chart Visuals: When you apply this script to an M1 chart, it will automatically plot horizontal tracking lines while a trade is active. You will visually see the red Stop Loss line instantaneously snap from its initial position to the blue Entry line the moment the 1:1 Take Profit target is grazed.

Re: Why most M1 indicators fail after transaction costs

Posted: Thu Sep 24, 2026 2:04 am
by PropScalpDesk
PTScalper wrote:Quantitative Cost-Audit Pine Script (v5) Below is a Pine Script designed to demonstrate this exact variance. It features a standard M1 EMA Crossover, but realistic transaction costs are hard-coded into the strategy properties.
The reality of the 1-minute chart is brutally unforgiving. Most M1 edges look invincible in a frictionless vacuum, but they die instantly the moment you inject real-world transaction costs into the equation. If your backtest—whether coded in MQL, cAlgo, or Pine Script—only prints a positive equity curve by assuming fantasy spreads, zero slippage, and perfect fills without accounting for order book liquidity, you haven't built a viable strategy. You have simply generated marketing content, not a tradable book.

My threshold for structural failure is absolute. I immediately invalidate a trading method the second that the all-in transaction costs—accounting for the spread, broker commissions, and expected slippage—consume the median scalp target. You simply cannot scale a system where the broker makes more on the round trip than the strategy yields on a standard win.

This requires a hard, objective look at your own modeling parameters: what specific, worst-case cost assumption do you absolutely refuse to toggle off when stress-testing your systems? If you aren't forcing your algorithms to survive realistic market microstructure and sudden spread widening, your historical data is lying to you.

Surviving this environment requires active psychological management alongside technical precision. I meticulously log every single "refused ticket"—the setups that looked tempting but ultimately failed my strict filtering rules. By documenting these rejections, sitting flat on the sidelines officially counts as productive, active work. If you do not consciously frame patience as a core execution metric, the mind gets restless. Left unchecked, the desk inevitably invents phantom activity to scratch the itch, forcing you into suboptimal trades just to feel engaged with the tape.

Clarity of intent is paramount. A truly valid price action setup should be instantly recognizable. If the trade idea requires a complex narrative or a convoluted, multi-variable story longer than a single, punchy sentence to justify the entry, it is already compromised. The capital is preserved, and the idea waits for a much cleaner liquidity window.

Operating within the strict confines of a funded account is a powerful mechanism for enforcing this discipline. The funded trailing drawdown (DD) acts as a ruthless, external referee. It completely eliminates the illusion of flexibility that comes with your own capital, stepping in to keep the desk brutally honest when tilt sets in or self-control starts to slip.

To anchor this operational framework, I constantly refer back to a foundational topic note from my tracking sheet for t=12527: keep your risk parameters completely unchanged until the sample data explicitly dictates otherwise. You never tweak your lot sizing based on an emotional whim, fear of the trailing limit, or a recent hot streak. Risk is only adjusted when a statistically significant, closed sample size provides the undeniable mathematical proof to justify scaling up or down.

Re: Why most M1 indicators fail after transaction costs

Posted: Thu Sep 24, 2026 10:37 am
by LondonNewsTrader
PTScalper wrote:Quantitative Cost-Audit Pine Script (v5) Below is a Pine Script designed to demonstrate this exact variance. It features a standard M1 EMA Crossover, but realistic transaction costs are hard-coded into the strategy properties.
Useful demonstration, and the cost numbers in the header are realistic for a raw account. A few things before anyone runs it.

It won't compile as posted: finalShortSignal references validShort, but the variable defined above is validShortEnv.

cash_per_order at 3.5 charges $3.50 per order regardless of size, so the $7 round trip is only right if the position is exactly one standard lot. With default quantity you might be trading one unit and still paying seven dollars, which exaggerates the drag enormously. cash_per_contract with quantity in lots keeps it proportional.

The volatility floor deserves a closer look. A 3-pip M1 ATR on EURUSD is high; outside the open and data releases most minutes won't reach it. So the filter doesn't just remove dead markets, it concentrates the strategy into release minutes, which is exactly where a 2-tick slippage assumption is least true. Try the filtered version with slippage at 10 ticks and see whether it survives.

Finally, request.security on the 15-minute EMA updates intrabar in real time but shows only closed values on history, so live won't behave like the backtest. Using the previous 15m value with lookahead on is the usual fix.

Raw versus filtered is the right experiment; it just needs fair costs on both sides.