Page 1 of 5
ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:34 pm
by PTScalper
Hi traders, forex scalpers.
Translating the ICT Silver Bullet into an automated MetaTrader 4 Expert Advisor requires moving from subjective chart reading to a strict mathematical state machine. Because MQL4 processes sequentially, the logic must track a rigid chain of events before validating an order.
Here is the architectural blueprint for mapping the Silver Bullet sequence into an MQL4 EA.
1. Time Normalization (The Environment)
The strategy strictly executes during specific New York windows (e.g., 10:00 AM – 11:00 AM EST). Because MT4 server times vary by broker, you must calculate the offset dynamically or use strict input parameters to align server time with EST.
Code: Select all
// Run this check on the open of a new bar to save CPU cycles
bool IsSilverBulletWindow() {
int currentHour = TimeHour(TimeCurrent());
// Add broker offset logic here to align with EST windows
if (currentHour >= StartHour && currentHour < EndHour) {
return true;
}
return false;
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:35 pm
by PTScalper
2. Defining Liquidity Pools (BSL/SSL)
Before the execution window opens, the algorithm needs to establish Buyside Liquidity (BSL) and Sellside Liquidity (SSL). These are typically the highest high and lowest low of a preceding session (such as the Asian range or overnight action).
Code: Select all
// Find the highest and lowest points over a defined lookback period
double BSL = High[iHighest(Symbol(), Period(), MODE_HIGH, LookbackBars, 1)];
double SSL = Low[iLowest(Symbol(), Period(), MODE_LOW, LookbackBars, 1)];
3. The State Machine: Sweep, MSS, and FVG
You cannot simply scan for a Fair Value Gap; it must occur in a specific sequence. Use a state tracking variable (e.g., int TradeState) to manage the progression. State 0 (Idle): The time window is open. Monitor for a Liquidity Sweep. If High[0] > BSL, a buyside sweep has occurred. Move to State 1.State 1 (Sweep Confirmed): Wait for a Market Structure Shift (MSS). Price must reverse and close below a recent structural fractal low. If Close[1] < RecentSwingLow, move to State 2. State 2 (Displacement & FVG Check): Look for the 3-candle imbalance that caused the MSS. MQL4 indexes backward, meaning [1] is the most recently closed candle, [2] is the aggressive displacement candle, and [3] is the origin candle.
Code: Select all
// Bearish FVG Logic (Sellside Imbalance Buyside Inefficiency)
bool isBearishFVG = false;
double fvgTop = 0;
double fvgBottom = 0;
// If the high of the recent candle is lower than the low of the origin candle
if (High[1] < Low[3]) {
isBearishFVG = true;
fvgTop = Low[3];
fvgBottom = High[1];
}
// Bullish FVG Logic (Buyside Imbalance Sellside Inefficiency)
bool isBullishFVG = false;
// If the low of the recent candle is higher than the high of the origin candle
if (Low[1] > High[3]) {
isBullishFVG = true;
fvgTop = High[3];
fvgBottom = Low[1];
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:36 pm
by PTScalper
4. Execution and Invalidation
Once your state machine confirms that a Sweep, MSS, and FVG have all occurred chronologically, the EA switches from monitoring to order management.Order Placement: Send an OrderSend() command for a Buy Limit or Sell Limit at the FVG. The highest probability entry is often the 50% midpoint of the gap, mathematically calculated as Consequent Encroachment (C.E.): double EntryPrice = fvgBottom + ((fvgTop - fvgBottom) / 2);Stop Loss Placement: Must be placed safely beyond the wick extreme of candle [3] (the origin swing high/low that initiated the sweep). Invalidation (Crucial for MT4): If the FVG is "mitigated" (a candle body closes completely through the FVG zone) before your pending limit order is triggered, the setup is voided. You must immediately run an OrderDelete() function to remove the pending limit order from the market.
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:37 pm
by PTScalper
To build a resilient dynamic position sizing function in MT4, you must calculate the exact monetary risk for the specific trade, convert that to a lot size based on the Stop Loss distance, and—crucially for automated execution—normalize the output to satisfy the broker's minimum, maximum, and step lot constraints.
For assets like XAG/USD, this calculation must also account for broker-specific discrepancies between Point and TickSize, which frequently break naive lot sizing formulas.
Here is a production-grade function you can drop directly into your EA.
The MQL4 Implementation
Code: Select all
//+------------------------------------------------------------------+
//| Calculates dynamic lot size based on account risk percentage |
//+------------------------------------------------------------------+
double CalculateLotSize(double entryPrice, double stopLossPrice, double riskPercent) {
// 1. Calculate maximum risk in the account's base currency
double accountBalance = AccountBalance(); // Use AccountEquity() for stricter risk management
double riskAmount = accountBalance * (riskPercent / 100.0);
// 2. Calculate Stop Loss distance in points
double slDistancePoints = MathAbs(entryPrice - stopLossPrice) / Point;
// Safety check to prevent division by zero (e.g., if FVG logic failed)
if (slDistancePoints == 0) {
Print("Error: SL distance is zero.");
return 0;
}
// 3. Calculate the monetary value of a single point per 1 standard lot
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
// Critical fix: Standardizes value for metals where Point != TickSize
double pointValue = tickValue * (Point / tickSize);
// 4. Calculate raw lot size
double riskPerLot = slDistancePoints * pointValue;
double rawLotSize = riskAmount / riskPerLot;
// 5. Normalize against broker lot constraints
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
// Round mathematically to the broker's required lot step
int steps = (int)MathFloor(rawLotSize / lotStep);
double finalLotSize = steps * lotStep;
// Clamp the final size to min/max boundaries
if (finalLotSize < minLot) finalLotSize = minLot;
if (finalLotSize > maxLot) finalLotSize = maxLot;
return finalLotSize;
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:37 pm
by PTScalper
Architectural Breakdown
1. The Point vs. TickSize Trap
In forex pairs, Point and TickSize are almost always identical. However, for CFD metals like Silver, brokers frequently configure their MT4 servers differently (e.g., pricing XAG/USD to 2 or 3 decimal places). If TickSize is 0.01 but Point is 0.001, standard lot sizing formulas will calculate a position 10 times too large, blowing past your intended risk profile. The (Point / tickSize) multiplier neutralizes this discrepancy.
2. MathFloor vs. MathRound
When calculating the steps for the lot step, MathFloor is strictly used over MathRound. If you are risking exactly 1.0%, MathRound might round a raw size of 0.186 to 0.19. In highly leveraged scalps, that rounding up pushes your actual risk to 1.02%. MathFloor ensures the position size never exceeds your strict risk ceiling.
3. Execution Integration
You call this function just before your OrderSend() command within your State 2 (FVG Check) execution block.
Code: Select all
// Example implementation in the execution phase
double myRisk = 1.0; // 1% account risk
double calcLot = CalculateLotSize(EntryPrice, StopLoss, myRisk);
if (calcLot > 0) {
int ticket = OrderSend(Symbol(), OP_BUYLIMIT, calcLot, EntryPrice, 3, StopLoss, TakeProfit, "Silver Bullet Buy", MagicNumber, 0, Blue);
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:38 pm
by PTScalper
In high-volume algorithmic environments, the exact moments your script wants to execute an ICT Silver Bullet—during aggressive liquidity sweeps and market structure shifts—are the precise moments brokers widen spreads and liquidity vacuums cause severe slippage.
If you are running this on an ECN/STP broker, standard MT4 slippage parameters are often ignored during market execution. To protect the edge, you need a pre-execution spread gatekeeper and digit-normalized slippage controls.
Here is the implementation to lock down execution conditions.
1. The Dynamic Spread Gatekeeper
Relying on MarketInfo(Symbol(), MODE_SPREAD) can be unreliable on some broker price feeds. Calculating the raw difference between Ask and Bid in real-time ensures you are measuring the exact liquidity gap at the millisecond of execution.
Code: Select all
//+------------------------------------------------------------------+
//| Checks if current market conditions are safe for execution |
//+------------------------------------------------------------------+
bool IsExecutionSafe(int maxSpreadPoints) {
// 1. Calculate the real-time spread accurately
int currentSpread = (int)MathRound((Ask - Bid) / Point);
// 2. Block execution if spread exceeds the threshold
if (currentSpread > maxSpreadPoints) {
Print("Execution Blocked: Spread widened to ", currentSpread, " points. Max allowed: ", maxSpreadPoints);
return false;
}
// 3. Prevent execution if pricing feed is frozen/invalid
if (Ask == 0 || Bid == 0) {
Print("Execution Blocked: Invalid price feed.");
return false;
}
return true;
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:38 pm
by PTScalper
2. Normalizing Slippage for Broker Digits
MT4 requires the slippage parameter in OrderSend() to be passed as an integer representing points. However, a hardcoded slippage of 3 means 3 pips on a standard 4-digit broker, but only 0.3 pips on a 5-digit fractional broker.
To make your EA portable and strict, you must automatically adjust the slippage parameter based on the broker's digit structure.
Code: Select all
//+------------------------------------------------------------------+
//| Adjusts slippage points for 3/5 digit fractional brokers |
//+------------------------------------------------------------------+
int NormalizeSlippage(int rawSlippagePips) {
int adjustedSlippage = rawSlippagePips;
// If the broker uses 3 or 5 digits (fractional pricing)
if (Digits == 3 || Digits == 5) {
adjustedSlippage = rawSlippagePips * 10;
}
return adjustedSlippage;
}
3. Integrating the Controls into the Execution Block
When State 2 of your machine confirms the Fair Value Gap, you wrap your order execution in the safety checks.
Code: Select all
// External parameters for user optimization
extern int MaxSpreadPoints = 40; // e.g., 40 points (4.0 pips) max spread for Silver
extern int MaxSlippagePips = 2; // 2 pips max slippage
// ... Inside your main execution logic (State 2) ...
if (IsExecutionSafe(MaxSpreadPoints)) {
// Calculate dynamic lot size (from previous implementation)
double calcLot = CalculateLotSize(EntryPrice, StopLoss, AccountRiskPercent);
if (calcLot > 0) {
// Normalize the slippage for the OrderSend command
int safeSlippage = NormalizeSlippage(MaxSlippagePips);
// Attempt execution
int ticket = OrderSend(
Symbol(),
OP_BUYLIMIT, // Pending limit order at FVG
calcLot,
EntryPrice,
safeSlippage, // Digit-adjusted slippage
StopLoss,
TakeProfit,
"Silver Bullet Entry",
MagicNumber,
0,
Blue
);
if (ticket < 0) {
Print("Order placement failed. Error: ", GetLastError());
}
}
} else {
// Optional: Reset state machine or wait for next tick if spread is too high
Print("Waiting for spread to compress before placing limit order.");
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:39 pm
by PTScalper
A Note on Limit Orders and Slippage
Because the ICT setup uses OP_BUYLIMIT and OP_SELLLIMIT orders placed at the FVG, the slippage parameter in OrderSend() dictates the slip allowed when placing the pending order, not when it is triggered.
When a limit order is triggered during a rapid silver spike, ECN brokers will execute it at the requested price or better (positive slippage). However, if a price gap jumps entirely over your limit order, it may not trigger at all, or the broker may convert it to a market order, resulting in negative slippage. The spread gatekeeper function mitigates this by keeping you out of the market entirely if the spread indicates a severe liquidity gap right as your limit order is about to become active.
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:40 pm
by PTScalper
To build an automated trade manager in MT4, you have to account for a critical quirk in the MQL4 engine: Ticket Splitting.
When you partially close a position in MT4, the server moves the original order ticket into history and instantly creates a new ticket for the remaining lot size. If your code relies on tracking the original ticket number, it will instantly lose control of the trade after the partial close.
To solve this, we use the Breakeven (BE) modification as a "State Flag." By modifying the Stop Loss to the entry price before executing the partial close, the new split ticket will automatically inherit the updated breakeven Stop Loss. This prevents the EA from getting stuck in an infinite loop.
Here is the production-grade trade management block.
The MQL4 Implementation
Code: Select all
// External parameters for user optimization
extern double RR_Target = 1.0; // Risk:Reward ratio to trigger the event (e.g., 1.0 = 1R)
extern double PartialClosePercent = 50.0; // Percentage of the position to close (e.g., 50%)
extern int BreakevenOffsetPips = 1; // Pips to lock in to cover commissions/swap
//+------------------------------------------------------------------+
//| Moves SL to Breakeven and takes partial profit at an RR target |
//+------------------------------------------------------------------+
void ManageTrades() {
// Loop backwards to safely handle index shifts during OrderClose
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
// Filter for our EA's trades on the current chart
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
int type = OrderType();
double openPrice = OrderOpenPrice();
double currentSL = OrderStopLoss();
// Calculate offset in points (using previous fractional digit logic)
double offsetPoints = BreakevenOffsetPips * 10 * Point; // Assuming 3/5 digit broker
double bePrice = (type == OP_BUY) ? openPrice + offsetPoints : openPrice - offsetPoints;
// 1. STATE CHECK: If SL is already at or past BE, skip to prevent infinite loops
if ((type == OP_BUY && currentSL >= bePrice) ||
(type == OP_SELL && (currentSL <= bePrice && currentSL != 0))) {
continue;
}
// 2. Calculate the original risk distance to find the exact RR target
double riskDistance = MathAbs(openPrice - currentSL);
if (riskDistance == 0) continue; // Safety check
bool targetHit = false;
if (type == OP_BUY) {
double targetPrice = openPrice + (riskDistance * RR_Target);
if (Bid >= targetPrice) targetHit = true;
}
else if (type == OP_SELL) {
double targetPrice = openPrice - (riskDistance * RR_Target);
if (Ask <= targetPrice) targetHit = true;
}
// 3. EXECUTION: Target Reached
if (targetHit) {
// STEP A: Move Stop Loss to Breakeven FIRST
// The new child ticket will inherit this modification
bool slMoved = OrderModify(OrderTicket(), openPrice, bePrice, OrderTakeProfit(), 0, Blue);
// STEP B: Execute Partial Close
if (slMoved) {
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
// Calculate exact lots to close, rounded down to broker's lot step
double rawLots = OrderLots() * (PartialClosePercent / 100.0);
double lotsToClose = MathFloor(rawLots / lotStep) * lotStep;
// Ensure we don't try to close less than the minimum allowed, or the full position
if (lotsToClose >= minLot && lotsToClose < OrderLots()) {
double closePrice = (type == OP_BUY) ? Bid : Ask;
int safeSlippage = NormalizeSlippage(2); // From previous slippage control
bool closed = OrderClose(OrderTicket(), lotsToClose, closePrice, safeSlippage, clrGreen);
if (!closed) {
Print("Partial close failed. Error: ", GetLastError());
}
}
} else {
Print("Failed to move SL to Breakeven. Error: ", GetLastError());
}
}
}
}
}
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:40 pm
by PTScalper
Architectural Breakdown
Reverse Index Looping: The for loop counts down from OrdersTotal() - 1. If you count up (i++), executing an OrderClose() dynamically shifts the array index of all remaining trades, causing the loop to skip the next order in the queue.
The Breakeven Offset: Moving the SL to the exact entry price often results in a net loss due to commissions and swap fees. The BreakevenOffsetPips variable ensures the modified Stop Loss covers the mechanical costs of the trade.
MathFloor on Lot Steps: If you have an odd lot size (e.g., 0.15) and want to close 50%, the raw math yields 0.075. MT4 will reject an OrderClose() command for 0.075 lots. The MathFloor() step rounds this safely down to 0.07 to satisfy the broker's minimum MODE_LOTSTEP.
Call ManageTrades() directly inside your OnTick() function, placing it before your entry logic. This prioritizes the defense of open capital before the EA scans for new setups.