Advertisement IC Markets

The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

2. Add the Time Calculation Function

Paste this helper function at the bottom of your script. It calculates the exact timestamp for today's reset. Crucially, it includes a rollover failsafe: if the current time is 07:00 but your reset is 08:00, it knows to look back at yesterday's 08:00 timestamp.

Code: Select all

//+------------------------------------------------------------------+
//| Calculates the exact timestamp of the most recent session reset  |
//+------------------------------------------------------------------+
datetime GetSessionStartTime()
{
    // Get server midnight for today
    datetime midnight = iTime(Symbol(), PERIOD_D1, 0); 
    
    // Parse the HH:MM string input
    string timeParts[];
    StringSplit(SessionResetTime, ':', timeParts);
    int hours = (int)StringToInteger(timeParts[0]);
    int minutes = (int)StringToInteger(timeParts[1]);
    
    // Build today's target timestamp
    datetime resetTimeToday = midnight + (hours * 3600) + (minutes * 60);
    
    // If we haven't reached today's reset time yet, use yesterday's
    if (TimeCurrent() < resetTimeToday)
    {
        return resetTimeToday - 86400; // Subtract 24 hours
    }
    
    return resetTimeToday;
}
Recommended broker for automated trading & scalping IC Markets
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

3. Update the Lockout Functions

Now, swap out the first few lines of your IsSessionLockedOut() function to use the new dynamic timestamp.

For MT4:

Code: Select all

bool IsSessionLockedOut()
{
    int lossCount = 0;
    datetime sessionStart = GetSessionStartTime(); // <--- Replaced midnight logic
    int total = OrdersHistoryTotal();

    for (int i = total - 1; i >= 0; i--)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
        {
            if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
            {
                // Stop checking if the trade closed BEFORE our session start
                if (OrderCloseTime() < sessionStart) 
                    break; 

                // ... (rest of the profit calculation remains the same)
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

For MT5:

Code: Select all

bool IsSessionLockedOut()
{
    datetime sessionStart = GetSessionStartTime(); // <--- Replaced midnight logic
    
    // Load history strictly from the custom session start time
    if (!HistorySelect(sessionStart, TimeCurrent())) return false;
    
    int lossCount = 0;
    int totalDeals = HistoryDealsTotal();

    for (int i = totalDeals - 1; i >= 0; i--)
    {
        ulong ticket = HistoryDealGetTicket(i);
        // ... (rest of the deal entry check remains the same)
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

Why this creates a true "Session" budget

By combining this with the Magic Number, the bot completely isolates its London performance. If you manually take a trade at 07:30 and lose, the bot ignores it. If the bot takes a loss during the Asian session, the GetSessionStartTime() wipes the slate clean the second the Frankfurt/London volume hits the tape at 08:00, giving the algorithm its full 2-loss budget exactly when your edge is highest.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

Unlike MQL or cTrader where you query the live broker server for actual account deals, Pine Script executes entirely within its own backtesting engine. To translate this circuit breaker into Pine, we must build it as a strategy() rather than an indicator().

This script natively tracks the profit of the most recently closed trade, increments a loss counter, and completely shuts off the strategy.entry() function for the remainder of the session once the limit is hit.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

Code: Select all

//@version=5
strategy("Session Circuit Breaker & Loss Lockout", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=1)

// =========================================================================
// INPUTS: CIRCUIT BREAKER & SESSION
// =========================================================================
max_losses = input.int(2, title="Max Consecutive Losses")
sess_time  = input.session("0800-1700", title="Trading Session (e.g. London)")
tz         = input.string("UTC", title="Timezone")

// =========================================================================
// LOGIC: SESSION DETECTION
// =========================================================================
in_sess  = not na(time(timeframe.period, sess_time, tz))
new_sess = in_sess and not nz(in_sess[1])

// =========================================================================
// LOGIC: TRACK CONSECUTIVE LOSSES & LOCKOUT
// =========================================================================
var int  consec_losses = 0
var bool is_locked     = false
var int  closed_trades = 0

// 1. Reset everything exactly at the session open
if new_sess
    consec_losses := 0
    is_locked     := false

// 2. Evaluate PnL the moment a trade closes
if strategy.closedtrades > closed_trades
    // Pull the net profit of the most recently closed trade (index is total - 1)
    last_profit = strategy.closedtrades.profit(strategy.closedtrades - 1)
    
    if last_profit < 0
        consec_losses += 1
    else if last_profit > 0
        consec_losses := 0 // Streak broken by a winner
        
    // 3. Trigger the circuit breaker
    if consec_losses >= max_losses
        is_locked := true
        
    // Update trade counter so we don't count the same trade twice
    closed_trades := strategy.closedtrades

// =========================================================================
// VISUALIZATION: BACKGROUND ALERTS
// =========================================================================
// Paints a subtle red background on the chart when the bot is locked out
bgcolor(is_locked ? color.new(color.red, 90) : na, title="Locked Out BG")
// Marks the exact session reset point
bgcolor(new_sess ? color.new(color.blue, 80) : na, title="Session Reset BG")

// =========================================================================
// EXECUTION: GATEKEEPER
// =========================================================================
// Placeholder: Replace with your actual M1/M15 trigger logic
entry_signal = ta.crossover(close, ta.sma(close, 10)) 

// The script must pass the session check AND the lockout check
if entry_signal and in_sess and not is_locked
    strategy.entry("Long", strategy.long)
    // Exits for demonstration (assuming 5-digit broker: 50 points = 5 pips)
    strategy.exit("Exit", "Long", profit=100, loss=50)
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

Key Differences in Pine Script Architecture

strategy.closedtrades array: Pine Script doesn't use "Magic Numbers" or query the broker. It reads its own isolated trade history using strategy.closedtrades. This makes backtesting blazingly fast, but remember that the profit() function calculates net profit inclusive of whatever commission structure you set in the Strategy Properties tab.

The Array Indexing (closed_trades - 1): Like MQL arrays, Pine Script's trade history is zero-indexed. If strategy.closedtrades equals 5, the first trade is index 0 and the most recent trade is index 4.

The State Variables (var): By declaring var int consec_losses = 0, Pine Script allocates memory that persists from bar to bar. If you forget the var keyword, Pine resets the loss counter to 0 on every single tick, permanently breaking the kill-switch.

Visual Debugging: The bgcolor function is critical here. When you load this strategy, you will literally see the background turn red the exact minute your second loss closes, remaining red until the blue vertical band signals the start of the next day's London open. It provides instant visual proof that your circuit breaker code is flawlessly rejecting entries.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

To make this professional-grade, we need to treat Pine Script less like a charting tool and more like an execution environment. For an algorithmic trader used to C# and MQL, a "pro" script requires modular architecture, strict data typing, multi-timeframe synchronization without repainting, and real-time state visibility.

Here is the institutional-grade upgrade. It integrates the M15 structural circuit breaker directly into the logic, uses a dynamic percentage-based risk model for position sizing, and includes a Heads-Up Display (HUD) table so you can visually verify the algorithm’s internal state on the chart without opening the logs.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

Code: Select all

//@version=5
strategy("Pro Session Circuit Breaker & MTF Logic", overlay=true, calc_on_every_tick=true, process_orders_on_close=true, max_labels_count=50)

// =========================================================================
// 1. INPUTS: MODULAR ARCHITECTURE
// Grouping inputs keeps the UI clean and parameter optimization efficient
// =========================================================================
grp_sess = "1. Session & Timing"
sess_time = input.session("0800-1700", title="Trading Session", group=grp_sess)
tz        = input.string("UTC", title="Timezone", group=grp_sess)

grp_risk = "2. Risk Management & Lockout"
max_losses = input.int(2, title="Max Consecutive Losses", group=grp_risk)
risk_pct   = input.float(1.0, title="Risk Per Trade (%)", step=0.1, group=grp_risk)

grp_mtf = "3. M15 Structural Filter"
min_body_ratio = input.float(0.25, title="Min M15 Body/Range Ratio", step=0.05, group=grp_mtf, tooltip="Rejects execution if M15 prints a doji/chop candle.")

// =========================================================================
// 2. MTF SYNCHRONIZATION: M15 STRUCTURAL CHECK
// lookahead = barmerge.lookahead_off ensures zero repainting in backtests
// =========================================================================
[m15_open, m15_close, m15_high, m15_low] = request.security(syminfo.tickerid, "15", [open, close, high, low], lookahead=barmerge.lookahead_off)

m15_range = m15_high - m15_low
m15_body  = math.abs(m15_open - m15_close)
// Protect against divide-by-zero on flat candles
m15_ratio = m15_range == 0 ? 0 : (m15_body / m15_range)

is_m15_valid = m15_ratio >= min_body_ratio

// =========================================================================
// 3. STATE MANAGEMENT: SESSION & CIRCUIT BREAKERS
// =========================================================================
in_sess  = not na(time(timeframe.period, sess_time, tz))
new_sess = in_sess and not nz(in_sess[1])

var int   consec_losses = 0
var bool  is_locked     = false
var int   closed_trades = 0
var float session_pnl   = 0.0

// Reset state strictly at the session open
if new_sess
    consec_losses := 0
    is_locked     := false
    session_pnl   := 0.0

// Evaluate PnL asynchronously upon trade closure
if strategy.closedtrades > closed_trades
    last_profit = strategy.closedtrades.profit(strategy.closedtrades - 1)
    session_pnl += last_profit
    
    if last_profit < 0
        consec_losses += 1
    else if last_profit > 0
        consec_losses := 0 // Winner resets the streak
        
    if consec_losses >= max_losses
        is_locked := true
        
    closed_trades := strategy.closedtrades

// =========================================================================
// 4. POSITION SIZING & EXECUTION
// =========================================================================
// Dynamic sizing based on ATR to represent a realistic stop loss distance
sl_pips = ta.atr(14) * 1.5 
risk_capital = (strategy.equity * risk_pct) / 100
// Convert distance to quantity based on asset point value
pos_qty = sl_pips > 0 ? (risk_capital / (sl_pips * syminfo.mintick * syminfo.pointvalue)) : 0

// Placeholder M1 Trigger
entry_signal = ta.crossover(ta.ema(close, 9), ta.ema(close, 21))

// The Ultimate Gatekeeper
if entry_signal and in_sess and not is_locked and is_m15_valid
    strategy.entry("Long", strategy.long, qty=pos_qty)
    strategy.exit("Exit", "Long", loss=sl_pips, profit=sl_pips * 2) // 1:2 R:R

// =========================================================================
// 5. VISUALIZATION: THE PRO HUD (Heads-Up Display)
// =========================================================================
bgcolor(is_locked ? color.new(color.red, 92) : na, title="Locked Out BG")
bgcolor(new_sess ? color.new(color.blue, 85) : na, title="Session Reset BG")

// Initialize Table Object once
var table hud = table.new(position.top_right, 2, 4, border_width=1, border_color=color.new(color.gray, 50))

// Update HUD only on the last bar to save processing power
if barstate.islast
    // Row 0: System Status
    table.cell(hud, 0, 0, "System Status", text_color=color.gray, bgcolor=color.new(color.black, 20))
    table.cell(hud, 1, 0, is_locked ? "LOCKED" : in_sess ? "ACTIVE" : "WAITING", text_color=color.white, bgcolor=is_locked ? color.new(color.red, 30) : in_sess ? color.new(color.green, 30) : color.new(color.gray, 30))
    
    // Row 1: Loss Streak
    table.cell(hud, 0, 1, "Loss Streak", text_color=color.gray, bgcolor=color.new(color.black, 20))
    table.cell(hud, 1, 1, str.tostring(consec_losses) + " / " + str.tostring(max_losses), text_color=color.white, bgcolor=consec_losses > 0 ? color.new(color.orange, 50) : color.new(color.black, 20))
    
    // Row 2: M15 Structure
    table.cell(hud, 0, 2, "M15 Structure", text_color=color.gray, bgcolor=color.new(color.black, 20))
    table.cell(hud, 1, 2, is_m15_valid ? "CLEAN" : "CHOP", text_color=color.white, bgcolor=is_m15_valid ? color.new(color.green, 50) : color.new(color.red, 50))
    
    // Row 3: Session PnL
    table.cell(hud, 0, 3, "Session Net PnL", text_color=color.gray, bgcolor=color.new(color.black, 20))
    table.cell(hud, 1, 3, "$" + str.tostring(math.round(session_pnl, 2)), text_color=session_pnl >= 0 ? color.green : color.red, bgcolor=color.new(color.black, 20))
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: The Scalper's Secret Sauce: Why I Ditched the 1-Minute Chart

Post by FTtrader »

What Makes This "Pro"

Anti-Repainting MTF: The request.security() call uses lookahead=barmerge.lookahead_off. Beginners often omit this, which causes Pine Script to peek into the future during backtests, creating impossible M15 structural triggers that look perfect in replay but fail live.

Institutional Position Sizing: Hardcoding qty=1 is useless for real modeling. The execution logic dynamically calculates the exact lot size required to strictly risk 1.0% of your rolling equity based on current ATR-derived volatility.

The HUD Table: By rendering a table.new() object in the top right, you no longer have to guess why a trade didn't trigger. The HUD reads out in real-time: Is the M15 currently printing chop? Are we locked out for the day? What is the exact consecutive loss count? It brings the transparency of a cAlgo terminal straight onto your TradingView canvas.
Post Reply