Page 4 of 5
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:50 pm
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);
}
}
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:50 pm
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.
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:51 pm
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;
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:51 pm
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.
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:53 pm
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)
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:53 pm
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.
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:54 pm
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}}' +
'}'
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:54 pm
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)
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:54 pm
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.
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:55 pm
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();
});