IC Markets

ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

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

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

4. Mathematical FVG Detection (The 3-Candle Array)

Once the MSS is confirmed, the script checks the last three closed candles for an imbalance. In cTrader, Bars.Last(1) is the post-displacement candle, Bars.Last(2) is the displacement candle, and Bars.Last(3) is the origin candle.

Code: Select all

private void ScanForFairValueGap()
        {
            // Extract the last 3 closed candles
            var candle1 = Bars.Last(1);
            var candle3 = Bars.Last(3);

            double fvgTop = 0;
            double fvgBottom = 0;
            TradeType executionDirection;

            // Bearish FVG Check (after Buyside Sweep)
            if (_currentState == SetupState.MssConfirmed && _bsl > _ssl) // Implicitly means we are bearish
            {
                // The high of the recent candle fails to reach the low of the origin candle
                if (candle1.High < candle3.Low)
                {
                    fvgTop = candle3.Low;
                    fvgBottom = candle1.High;
                    executionDirection = TradeType.Sell;
                    
                    ExecuteLimitOrder(executionDirection, fvgTop, fvgBottom, Bars.Last(2).High);
                }
            }
            // Bullish FVG Check (after Sellside Sweep)
            else if (_currentState == SetupState.MssConfirmed)
            {
                // The low of the recent candle fails to reach the high of the origin candle
                if (candle1.Low > candle3.High)
                {
                    fvgTop = candle1.Low;
                    fvgBottom = candle3.High;
                    executionDirection = TradeType.Buy;
                    
                    ExecuteLimitOrder(executionDirection, fvgTop, fvgBottom, Bars.Last(2).Low);
                }
            }
        }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

Architectural Considerations

The MSS Fractal Calculation: When the ScanForLiquiditySweep method confirms a sweep, it dynamically scans the previous 5 candles SkipLast(2).TakeLast(5) to locate the lowest low (or highest high) that preceded the push into liquidity. This prevents hardcoding absolute pip values and allows the algorithm to adapt to silver's real-time volatility structure.

Execution Handoff: In the ExecuteLimitOrder() method, you would calculate the Consequent Encroachment (the exact midpoint of the fvgTop and fvgBottom), pass it to the dynamic sizing module we built previously, and issue the PlaceLimitOrder() command. Once the order is placed, you must reset _currentState = SetupState.Monitoring to prevent the bot from placing duplicate orders on the next bar.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

This is the final piece of the architecture. The ExecuteLimitOrder method acts as the bridge between the OnBar() structural scanning and the OnTick() trade management engine.

Because cTrader's PlaceLimitOrder method requires the Stop Loss to be defined in relative pips rather than an absolute price, this method must handle the conversion natively. Furthermore, we intentionally leave the Take Profit parameter empty (null) so that the custom partial-close and trailing-stop loops we built earlier maintain absolute control over the exit.

Here is the C# execution module.

The Execution Method

Code: Select all

//+------------------------------------------------------------------+
//| Order Execution & Engine Handoff                                 |
//+------------------------------------------------------------------+
private void ExecuteLimitOrder(TradeType tradeType, double fvgTop, double fvgBottom, double stopLossPrice)
{
    // 1. Calculate Entry at Consequent Encroachment (50% midpoint)
    double entryPrice = fvgBottom + ((fvgTop - fvgBottom) / 2);
    
    // Ensure the calculated price aligns with the broker's decimal structure
    entryPrice = Math.Round(entryPrice, Symbol.Digits);

    // 2. Pre-Execution Safety Check (Spread Gatekeeper)
    if (!IsExecutionSafe())
    {
        Print("Execution blocked by spread gatekeeper. Resetting state.");
        _currentState = SetupState.Monitoring;
        return;
    }

    // 3. Dynamic Position Sizing
    double volume = CalculateVolume(entryPrice, stopLossPrice);
    
    if (volume < Symbol.VolumeInUnitsMin)
    {
        Print("Calculated volume is below broker minimum. Resetting state.");
        _currentState = SetupState.Monitoring;
        return;
    }

    // 4. Convert Absolute SL Price to Pips for the cTrader API
    double slPips = Math.Abs(entryPrice - stopLossPrice) / Symbol.PipSize;

    // 5. Order Expiration (Time-based Setup Invalidation)
    // If the price doesn't pull back into the FVG within a reasonable time, void the setup.
    DateTime expirationTime = Server.Time.AddHours(2);

    // 6. Execute the Limit Order
    // Take Profit is intentionally null. The OnTick() trade manager takes over upon execution.
    var result = PlaceLimitOrder(
        tradeType, 
        SymbolName, 
        volume, 
        entryPrice, 
        Label, 
        slPips, 
        null, 
        expirationTime, 
        "Silver Bullet FVG CE"
    );

    if (result.IsSuccessful)
    {
        Print("Limit {0} placed at {1}. SL at {2}.", tradeType, entryPrice, stopLossPrice);
    }
    else
    {
        Print("Order placement failed: {0}", result.Error);
    }

    // 7. Reset State Machine
    // This is critical. Without resetting, the OnBar loop will spam a new limit order 
    // on every subsequent bar that forms after the MSS.
    _currentState = SetupState.Monitoring;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

Architectural Breakdown

Consequent Encroachment (CE): Algorithmic liquidity models treat the 50% midpoint of a Fair Value Gap as the highest probability entry. The math fvgBottom + ((fvgTop - fvgBottom) / 2) perfectly calculates this midpoint, while Math.Round(..., Symbol.Digits) prevents the cTrader API from rejecting the order due to invalid fractional pricing.

The Handoff: By passing null to the takeProfitPips parameter, the limit order is placed without a hard target. Once the price triggers the limit order, it converts to an open position. Immediately, your OnTick() loop detects it via the Positions.FindAll(Label) check and begins monitoring it for the 1R partial close and the subsequent trailing stop.

Expiration Time: Silver is highly directional during the New York sessions. If an FVG is formed but price consolidates for hours instead of pulling back immediately, the setup becomes mathematically stale. The expirationTime parameter ensures your pending limit order is automatically wiped from the broker's server without requiring you to manually loop through and delete stale orders.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

And finally for Trading View traders, here is the version for version 5 of Pine script.

Transitioning this architecture to TradingView’s Pine Script v5 requires a complete paradigm shift. Because Pine operates on a Time Series basis—executing the entire script on every historical bar sequentially before moving to real-time ticks—you do not use arrays, loops, or object-oriented classes to track state.

Instead, we use the var keyword to create a persistent State Machine. Variables declared with var retain their values across bars unless explicitly updated, allowing us to perfectly replicate the Sweep → MSS → FVG chronological sequence.

Here is the professional, fully automated strategy() implementation in Pine Script v5.

The Pine Script v5 Implementation

Code: Select all

//@version=5
strategy("Silver Bullet ICT", overlay=true, calc_on_every_tick=true, currency=currency.USD, initial_capital=10000)

// =========================================================================
// 1. INPUTS
// =========================================================================
risk_pct     = input.float(1.0, "Risk %", group="Risk & Sizing")
rr_target    = input.float(1.0, "R:R Target", group="Trade Management")
partial_pct  = input.float(50.0, "Partial Close %", group="Trade Management")
trail_pips   = input.float(15.0, "Trailing Distance (Pips)", group="Trade Management")

// Time windows natively handle EST/EDT daylight saving shifts
session_time = input.session("1000-1100", "ICT NY AM Session", timezone="America/New_York")
lookback     = input.int(50, "Liquidity Lookback", group="Market Structure")

// =========================================================================
// 2. TIME & SESSION PARSING
// =========================================================================
in_session  = not na(time(timeframe.period, session_time, "America/New_York"))
new_session = in_session and not in_session[1]

// =========================================================================
// 3. PERSISTENT STATE MACHINE
// =========================================================================
var int STATE_IDLE       = 0
var int STATE_SWEPT_BSL  = 1
var int STATE_SWEPT_SSL  = 2
var int STATE_MSS_BEAR   = 3
var int STATE_MSS_BULL   = 4

var int   state = STATE_IDLE
var float bsl = na
var float ssl = na
var float mss_level = na
var float entry_price = na
var float stop_loss = na
var float take_profit = na

// Reset State at the exact minute the session opens
if new_session
    state := STATE_IDLE
    bsl := ta.highest(high, lookback)[1]
    ssl := ta.lowest(low, lookback)[1]
    strategy.cancel_all() // Clear stale FVG limit orders from yesterday

// =========================================================================
// 4. MICROSTRUCTURE SCANNING
// =========================================================================
if in_session and state == STATE_IDLE
    // Buyside Sweep (Pierced BSL, closed below)
    if high > bsl and close < bsl
        state := STATE_SWEPT_BSL
        mss_level := ta.lowest(low, 5)[1] // Identify structural fractal low
        
    // Sellside Sweep (Pierced SSL, closed above)
    else if low < ssl and close > ssl
        state := STATE_SWEPT_SSL
        mss_level := ta.highest(high, 5)[1] // Identify structural fractal high

// Market Structure Shift (MSS)
if state == STATE_SWEPT_BSL and close < mss_level
    state := STATE_MSS_BEAR
else if state == STATE_SWEPT_SSL and close > mss_level
    state := STATE_MSS_BULL

// =========================================================================
// 5. FVG DETECTION & EXECUTION
// =========================================================================
// Convert User Pips to Broker Points
pips_to_points = trail_pips * (syminfo.mintick * 10)

if state == STATE_MSS_BEAR
    // Bearish FVG Check: High of recent candle fails to reach Low of origin candle
    if high < low[2] 
        entry_price := high + ((low[2] - high) / 2) // CE (50% midpoint)
        stop_loss := high[1] // Safe SL above displacement wick
        take_profit := entry_price - (math.abs(entry_price - stop_loss) * rr_target)
        
        // Sizing Math
        risk_amt = strategy.equity * (risk_pct / 100)
        qty = risk_amt / (math.abs(entry_price - stop_loss) * syminfo.pointvalue)
        
        strategy.entry("Short", strategy.short, qty=qty, limit=entry_price)
        state := STATE_IDLE // Lock state to prevent duplicate orders

else if state == STATE_MSS_BULL
    // Bullish FVG Check: Low of recent candle fails to reach High of origin candle
    if low > high[2] 
        entry_price := low + ((high[2] - low) / 2) // CE (50% midpoint)
        stop_loss := low[1] // Safe SL below displacement wick
        take_profit := entry_price + (math.abs(entry_price - stop_loss) * rr_target)
        
        // Sizing Math
        risk_amt = strategy.equity * (risk_pct / 100)
        qty = risk_amt / (math.abs(entry_price - stop_loss) * syminfo.pointvalue)
        
        strategy.entry("Long", strategy.long, qty=qty, limit=entry_price)
        state := STATE_IDLE // Lock state to prevent duplicate orders

// =========================================================================
// 6. TRADE MANAGEMENT (PARTIALS & TRAILING)
// =========================================================================
if strategy.position_size > 0
    // Exit 1: The 1R Partial Close
    strategy.exit("1R TP", from_entry="Long", qty_percent=partial_pct, limit=take_profit, stop=stop_loss)
    strategy.exit("1R TP", from_entry="Short", qty_percent=partial_pct, limit=take_profit, stop=stop_loss)
    
    // Exit 2: The Runner (Activates Trailing at 1R)
    strategy.exit("Runner", from_entry="Long", stop=stop_loss, trail_price=take_profit, trail_points=pips_to_points)
    strategy.exit("Runner", from_entry="Short", stop=stop_loss, trail_price=take_profit, trail_points=pips_to_points)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

Architectural Advantages in Pine Script

Native Timezone Parsing: The timezone="America/New_York" parameter completely eliminates the need for UTC/DST offset logic. The script guarantees the 10:00 AM window always corresponds strictly to New York local time, directly aligning with stock market open volatility.

The var Lock: Notice how state := STATE_IDLE is executed immediately after strategy.entry is called. Because var holds its state across bars, this explicitly locks the execution engine. It forces the algorithm to wait until tomorrow's session to scan for a new sweep, preventing it from rapid-firing orders into chop.

Split Trade Management: TradingView eliminates the need for loop-based ticket splitting. By calling strategy.exit() twice for the same entry, the engine handles the split routing. Exit 1 automatically cashes out partial_pct when the 1R limit is hit. Exit 2 only activates its trailing logic after price reaches trail_price=take_profit, acting as a flawless breakeven-and-trail trigger.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

Routing executions from TradingView to a bridge requires constructing a JSON payload that combines TradingView’s native backend placeholders (which populate automatically upon execution) with your dynamic script variables (like your calculated Stop Loss and Take Profit).

When the strategy.entry() or strategy.exit() function fires, it passes this string to the TradingView alert engine, which sends the HTTP POST request to your webhook URL.

Here is how to architect the JSON payload in Pine Script v5.

1. Constructing the JSON Payload

Because TradingView uses double curly braces for its native placeholders (e.g., {{strategy.order.action}}), you must concatenate the string if you want to include custom variables like your dynamically calculated stop_loss.

Add this JSON construction logic right before your execution commands:

Code: Select all

// =========================================================================
// 5. JSON WEBHOOK PAYLOADS
// =========================================================================

// Construct the Entry JSON
// We use TV's native placeholders for action and contracts, but inject our custom SL/TP variables.
string json_entry = '{' +
  '"passphrase": "YOUR_SECURE_KEY",' +
  '"action": "{{strategy.order.action}}",' +
  '"symbol": "XAGUSD",' +
  '"type": "limit",' +
  '"price": {{strategy.order.price}},' +
  '"volume": {{strategy.order.contracts}},' +
  '"sl": ' + str.tostring(stop_loss) + ',' +
  '"tp": ' + str.tostring(take_profit) + 
'}'

// Construct the Exit/Partial Close JSON
string json_exit = '{' +
  '"passphrase": "YOUR_SECURE_KEY",' +
  '"action": "close",' +
  '"symbol": "XAGUSD",' +
  '"volume": {{strategy.order.contracts}}' +
'}'
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

2. Attaching the Payload to the Strategy

You must bind these strings to the alert_message parameter inside your strategy execution calls. This tells TradingView exactly what to transmit when the specific order fills.

Code: Select all

// Modify your entry execution
strategy.entry("Long", strategy.long, qty=qty, limit=entry_price, alert_message=json_entry)
strategy.entry("Short", strategy.short, qty=qty, limit=entry_price, alert_message=json_entry)

// Modify your exit executions
// The 1R Partial Close
strategy.exit("1R TP", from_entry="Long", qty_percent=partial_pct, limit=take_profit, stop=stop_loss, alert_message=json_exit)

// The Trailing Runner
strategy.exit("Runner", from_entry="Long", stop=stop_loss, trail_price=take_profit, trail_points=pips_to_points, alert_message=json_exit)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

3. The UI Bridge Configuration

Code alone will not fire the webhook. You must link the script's alert_message output to TradingView's alert engine.

Click the Create Alert icon on your TradingView chart.

Set the Condition to your specific script ("Silver Bullet ICT").

In the Message box, delete everything and paste exactly this native placeholder:
{{strategy.order.alert_message}}

Check the Webhook URL box and input the endpoint of your bridge (e.g., your Node.js server, PineConnector, or ProfitView endpoint).

Architectural Note on Bridge Compatibility

Not all commercial bridges process JSON natively—some (like PineConnector) require comma-separated text strings (e.g., license,buy,XAGUSD,risk=1,sl=23.50). However, if you are building a custom REST API receiver or using an advanced bridge, standardizing to application/json ensures your endpoint can seamlessly parse the request using standard JSON.parse() methods.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 771
Joined: Mon Jul 20, 2026 1:28 pm

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Post by PTScalper »

To bridge TradingView and cTrader Open API, you need a Node.js application that handles two distinct protocols: an HTTP/REST server to receive the TradingView JSON webhook, and a TCP socket using Google Protocol Buffers (Protobuf) to transmit the order to the cTrader backend.

The most robust way to handle the Protobuf serialization and connection state in Node is the community-standard @reiryoku/ctrader-layer package.

Here is the professional architecture for the webhook receiver.

The Node.js Implementation

First, install the required dependencies:
npm install express dotenv @reiryoku/ctrader-layer

Code: Select all

require('dotenv').config();
const express = require('express');
const { CTraderConnection } = require('@reiryoku/ctrader-layer');

const app = express();
app.use(express.json());

// 1. Initialize the Protobuf Connection
const connection = new CTraderConnection({
    host: 'live.ctraderapi.com', // Use 'demo.ctraderapi.com' for testing
    port: 5035,
});

const CTID = parseInt(process.env.CTID_ACCOUNT_ID);
let isConnected = false;

async function startBridge() {
    await connection.open();
    
    // Authenticate the Application
    await connection.sendCommand('ProtoOAApplicationAuthReq', {
        clientId: process.env.CLIENT_ID,
        clientSecret: process.env.CLIENT_SECRET,
    });

    // Authenticate the Trading Account
    await connection.sendCommand('ProtoOAAccountAuthReq', {
        ctidTraderAccountId: CTID,
        accessToken: process.env.ACCESS_TOKEN,
    });

    isConnected = true;
    console.log("Bridge Online: cTrader API Authenticated.");
    
    // Open API strictly requires a heartbeat every 25s to prevent socket termination
    setInterval(() => connection.sendHeartbeat(), 25000);
}

// 2. The HTTP Webhook Receiver
app.post('/tv-webhook', async (req, res) => {
    const payload = req.body;

    // Security Gatekeeper
    if (payload.passphrase !== process.env.WEBHOOK_PASSPHRASE) {
        return res.status(401).json({ error: 'Unauthorized' });
    }

    if (!isConnected) {
        return res.status(503).json({ error: 'Bridge disconnected' });
    }

    try {
        if (payload.action === 'buy' || payload.action === 'sell') {
            await executeEntry(payload);
        } else if (payload.action === 'close') {
            await executeClose(payload);
        }
        res.status(200).json({ message: 'Command accepted and routed' });
    } catch (error) {
        console.error("Execution Failed:", error);
        res.status(500).json({ error: 'Internal execution error' });
    }
});

// 3. cTrader Open API Execution Mapping
async function executeEntry(payload) {
    const tradeSide = payload.action.toUpperCase(); 
    const orderType = payload.type.toUpperCase();   
    
    // cTrader requires integer Symbol IDs (e.g., 42 for XAGUSD on most brokers)
    const symbolId = 42; 
    
    // Convert TV decimal lots into absolute broker units (e.g., 5000 units for 1 lot of silver)
    const volumeInUnits = Math.floor(payload.volume * 5000); 

    const orderReq = {
        ctidTraderAccountId: CTID,
        symbolId: symbolId,
        orderType: orderType,
        tradeSide: tradeSide,
        volume: volumeInUnits,
        label: "SilverBullet-TV" // Crucial for isolating strategy trades
    };

    if (orderType === 'LIMIT') {
        orderReq.limitPrice = payload.price;
    }

    if (payload.sl) orderReq.stopLoss = payload.sl;
    if (payload.tp) orderReq.takeProfit = payload.tp;
    
    await connection.sendCommand('ProtoOANewOrderReq', orderReq);
    console.log(`Executed ${tradeSide} ${orderType} on Symbol ${symbolId}`);
}

async function executeClose(payload) {
    // 1. Fetch all open positions for the account
    const positionsRes = await connection.sendCommand('ProtoOAReconcileReq', {
        ctidTraderAccountId: CTID
    });

    // 2. Filter for the exact trade managed by this TV strategy
    const position = positionsRes.position.find(p => p.tradeData.label === "SilverBullet-TV");
    if (!position) return;

    // 3. Send the Close Request using the specific Position ID
    await connection.sendCommand('ProtoOAClosePositionReq', {
        ctidTraderAccountId: CTID,
        positionId: position.positionId,
        volume: position.tradeData.volume // Closes full remaining volume
    });
}

// Boot the server
app.listen(3000, async () => {
    console.log("Express Webhook Receiver listening on port 3000");
    await startBridge();
});
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply