Page 3 of 5
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:45 pm
by PTScalper
3. Execution & Sizing Implementation
Notice the shift from raw functions to object methods (m_account.Balance(), m_symbol.TickValue()).
Code: Select all
bool CSilverManager::IsExecutionSafe() {
m_symbol.RefreshRates();
int currentSpread = m_symbol.Spread();
if(currentSpread > InpMaxSpreadPoints) {
PrintFormat("Blocked: Spread %d > %d", currentSpread, InpMaxSpreadPoints);
return false;
}
return true;
}
double CSilverManager::CalculateLotSize(double entryPrice, double stopLossPrice) {
double riskAmount = m_account.Balance() * (InpRiskPercent / 100.0);
double slDistancePoints = MathAbs(entryPrice - stopLossPrice) / m_symbol.Point();
if (slDistancePoints == 0 || m_symbol.TickSize() == 0) return 0.0;
// Normalize tick value for metals
double pointValue = m_symbol.TickValue() * (m_symbol.Point() / m_symbol.TickSize());
double riskPerLot = slDistancePoints * pointValue;
if (riskPerLot == 0) return 0.0;
double rawLotSize = riskAmount / riskPerLot;
// Floor to nearest broker step
double step = m_symbol.LotsStep();
double finalLot = MathFloor(rawLotSize / step) * step;
finalLot = MathMax(finalLot, m_symbol.LotsMin());
finalLot = MathMin(finalLot, m_symbol.LotsMax());
return finalLot;
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:46 pm
by PTScalper
4. OOP Trade Management
Instead of manual variable tracking, m_position.SelectByIndex(i) automatically loads all attributes of that position into the CPositionInfo object. The script checks m_trade.ResultRetcode() to professionally handle server execution errors.
Code: Select all
void CSilverManager::ManageOpenPositions() {
m_symbol.RefreshRates();
for (int i = PositionsTotal() - 1; i >= 0; i--) {
if(m_position.SelectByIndex(i)) {
if(m_position.Symbol() == m_symbol.Name() && m_position.Magic() == InpMagicNumber) {
ENUM_POSITION_TYPE type = m_position.PositionType();
double openPrice = m_position.PriceOpen();
double currentSL = m_position.StopLoss();
double volume = m_position.Volume();
double bePrice = (type == POSITION_TYPE_BUY) ?
openPrice + (m_beOffsetPts * m_symbol.Point()) :
openPrice - (m_beOffsetPts * m_symbol.Point());
// State check
if ((type == POSITION_TYPE_BUY && currentSL >= bePrice) ||
(type == POSITION_TYPE_SELL && currentSL <= bePrice && currentSL != 0)) {
continue;
}
double riskDist = MathAbs(openPrice - currentSL);
if(riskDist == 0) continue;
bool targetHit = false;
if(type == POSITION_TYPE_BUY && m_symbol.Bid() >= openPrice + (riskDist * InpRR_Target)) targetHit = true;
if(type == POSITION_TYPE_SELL && m_symbol.Ask() <= openPrice - (riskDist * InpRR_Target)) targetHit = true;
if (targetHit) {
// 1. Move SL to Breakeven
if (m_trade.PositionModify(m_position.Ticket(), bePrice, m_position.TakeProfit())) {
// 2. Partial Volume Close
double step = m_symbol.LotsStep();
double lotsToClose = MathFloor((volume * (InpPartialClosePct / 100.0)) / step) * step;
if (lotsToClose >= m_symbol.LotsMin() && lotsToClose < volume) {
if(!m_trade.PositionClosePartial(m_position.Ticket(), lotsToClose)) {
PrintFormat("Partial close failed. Retcode: %d", m_trade.ResultRetcode());
}
}
} else {
PrintFormat("BE modification failed. Retcode: %d", m_trade.ResultRetcode());
}
}
}
}
}
}
void CSilverManager::ApplyTrailingStop() {
m_symbol.RefreshRates();
double trailPoints = InpTrailingDistPips * (m_pip / m_symbol.Point());
double stepPoints = InpTrailingStepPips * (m_pip / m_symbol.Point());
for (int i = PositionsTotal() - 1; i >= 0; i--) {
if(m_position.SelectByIndex(i)) {
if(m_position.Symbol() == m_symbol.Name() && m_position.Magic() == InpMagicNumber) {
ENUM_POSITION_TYPE type = m_position.PositionType();
double openPrice = m_position.PriceOpen();
double currentSL = m_position.StopLoss();
double bePrice = (type == POSITION_TYPE_BUY) ?
openPrice + (m_beOffsetPts * m_symbol.Point()) :
openPrice - (m_beOffsetPts * m_symbol.Point());
// Ensure position is past BE before trailing
bool isPastBE = false;
if(type == POSITION_TYPE_BUY && currentSL >= bePrice) isPastBE = true;
if(type == POSITION_TYPE_SELL && currentSL <= bePrice && currentSL != 0) isPastBE = true;
if(!isPastBE) continue;
if (type == POSITION_TYPE_BUY) {
double newSL = m_symbol.Bid() - (trailPoints * m_symbol.Point());
if (newSL > currentSL + (stepPoints * m_symbol.Point())) {
m_trade.PositionModify(m_position.Ticket(), newSL, m_position.TakeProfit());
}
}
else if (type == POSITION_TYPE_SELL) {
double newSL = m_symbol.Ask() + (trailPoints * m_symbol.Point());
if (newSL < currentSL - (stepPoints * m_symbol.Point()) || currentSL == 0) {
m_trade.PositionModify(m_position.Ticket(), newSL, m_position.TakeProfit());
}
}
}
}
}
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:46 pm
by PTScalper
5. The Main Event Loop
Because the complexity is now hidden inside the class, the main OnInit() and OnTick() functions remain incredibly clean and readable.
Code: Select all
//--- Instantiate the Manager Object globally
CSilverManager Manager;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
if(!Manager.Init(_Symbol, InpMagicNumber)) {
Print("Failed to initialize CSilverManager");
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick() {
// 1. Defend open capital
Manager.ManageOpenPositions();
Manager.ApplyTrailingStop();
// 2. Scan for entries
/*
if (IsSilverBulletWindow() && Manager.IsExecutionSafe()) {
double lots = Manager.CalculateLotSize(entry, sl);
// ... execute logic via m_trade ...
}
*/
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:47 pm
by PTScalper
For Ctrader and ICtrader traders i have prepared this version
MetaTrader forces you to build complex workarounds for basic market mechanics—like "ticket splitting" on partial closes and manual point-to-pip normalizations for 3-digit silver brokers. The cAlgo API handles all of this natively. In cTrader:
Partial Closes don't destroy the Position ID. position.Close(volume) simply reduces the size of the open position. Your tracking logic remains perfectly intact.
Symbol.PipSize is absolute. cTrader abstracts away the fractional tick sizing in the backend, meaning your pip calculations work exactly the same on a 2-digit, 3-digit, or fractional broker.
Here is the complete, professional-grade C# cBot architecture for the Silver Bullet manager.
The cTrader C# Implementation
Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class SilverBulletManager : Robot
{
[Parameter("Risk Percent", DefaultValue = 1.0, Group = "Risk & Sizing", MinValue = 0.1)]
public double RiskPercent { get; set; }
[Parameter("Max Spread (Pips)", DefaultValue = 4.0, Group = "Risk & Sizing")]
public double MaxSpreadPips { get; set; }
[Parameter("RR Target", DefaultValue = 1.0, Group = "Trade Management")]
public double RR_Target { get; set; }
[Parameter("Partial Close %", DefaultValue = 50.0, Group = "Trade Management", MinValue = 10, MaxValue = 90)]
public double PartialClosePct { get; set; }
[Parameter("Breakeven Offset (Pips)", DefaultValue = 1.0, Group = "Trade Management")]
public double BreakevenOffsetPips { get; set; }
[Parameter("Trailing Distance (Pips)", DefaultValue = 15.0, Group = "Trade Management")]
public double TrailingDistPips { get; set; }
[Parameter("Trailing Step (Pips)", DefaultValue = 2.0, Group = "Trade Management")]
public double TrailingStepPips { get; set; }
// Unique identifier for the strategy's trades
private const string Label = "SilverBullet";
protected override void OnTick()
{
// 1. Defend open capital first
ManageOpenPositions();
ApplyTrailingStop();
// 2. Scan & execute new entries here
// if (IsSilverBulletWindow() && IsExecutionSafe()) {
// PlaceLimitOrder(TradeType.Buy, SymbolName, volume, entry, Label, slPips, tpPips);
// }
}
//+------------------------------------------------------------------+
//| Dynamic Sizing & Safety |
//+------------------------------------------------------------------+
public bool IsExecutionSafe()
{
// cTrader directly provides spread divided by PipSize
double currentSpread = Symbol.Spread / Symbol.PipSize;
if (currentSpread > MaxSpreadPips)
{
Print("Execution Blocked: Spread {0} pips > {1} pips", Math.Round(currentSpread, 1), MaxSpreadPips);
return false;
}
return true;
}
public double CalculateVolume(double entryPrice, double stopLossPrice)
{
double riskAmount = Account.Balance * (RiskPercent / 100.0);
double slDistancePips = Math.Abs(entryPrice - stopLossPrice) / Symbol.PipSize;
if (slDistancePips == 0) return 0;
// Symbol.PipValue in cTrader is the value of 1 pip for Symbol.VolumeInUnitsMin
double riskPerMinVolume = slDistancePips * Symbol.PipValue;
double rawVolume = (riskAmount / riskPerMinVolume) * Symbol.VolumeInUnitsMin;
// Natively normalizes to the broker's minimum, maximum, and lot step limits
return Symbol.NormalizeVolumeInUnits(rawVolume, RoundingMode.Down);
}
//+------------------------------------------------------------------+
//| Trade Management: Native Partial Close |
//+------------------------------------------------------------------+
private void ManageOpenPositions()
{
// Easily isolate positions managed by this specific cBot
var positions = Positions.FindAll(Label, SymbolName);
foreach (var position in positions)
{
if (!position.StopLoss.HasValue) continue;
double bePrice = position.TradeType == TradeType.Buy
? position.EntryPrice + (BreakevenOffsetPips * Symbol.PipSize)
: position.EntryPrice - (BreakevenOffsetPips * Symbol.PipSize);
// State check: Skip if SL is already moved to/past BE
if ((position.TradeType == TradeType.Buy && position.StopLoss.Value >= bePrice) ||
(position.TradeType == TradeType.Sell && position.StopLoss.Value <= bePrice))
{
continue;
}
double riskDist = Math.Abs(position.EntryPrice - position.StopLoss.Value);
if (riskDist == 0) continue;
bool targetHit = false;
if (position.TradeType == TradeType.Buy && Symbol.Bid >= position.EntryPrice + (riskDist * RR_Target)) targetHit = true;
if (position.TradeType == TradeType.Sell && Symbol.Ask <= position.EntryPrice - (riskDist * RR_Target)) targetHit = true;
if (targetHit)
{
// 1. Move SL to Breakeven
var modifyResult = position.ModifyStopLossPrice(bePrice);
// 2. Execute Partial Volume Close
if (modifyResult.IsSuccessful)
{
double volumeToClose = Symbol.NormalizeVolumeInUnits(position.VolumeInUnits * (PartialClosePct / 100.0), RoundingMode.Down);
if (volumeToClose >= Symbol.VolumeInUnitsMin && volumeToClose < position.VolumeInUnits)
{
// cTrader simply reduces the position volume without creating a new ticket
position.Close(volumeToClose);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Trailing Stop Engine |
//+------------------------------------------------------------------+
private void ApplyTrailingStop()
{
var positions = Positions.FindAll(Label, SymbolName);
foreach (var position in positions)
{
if (!position.StopLoss.HasValue) continue;
double bePrice = position.TradeType == TradeType.Buy
? position.EntryPrice + (BreakevenOffsetPips * Symbol.PipSize)
: position.EntryPrice - (BreakevenOffsetPips * Symbol.PipSize);
// Ensure position is past BE before trailing
bool isPastBE = false;
if (position.TradeType == TradeType.Buy && position.StopLoss.Value >= bePrice) isPastBE = true;
if (position.TradeType == TradeType.Sell && position.StopLoss.Value <= bePrice) isPastBE = true;
if (!isPastBE) continue;
double trailPoints = TrailingDistPips * Symbol.PipSize;
double stepPoints = TrailingStepPips * Symbol.PipSize;
if (position.TradeType == TradeType.Buy)
{
double newSL = Symbol.Bid - trailPoints;
// Step filter to prevent server spam
if (newSL > position.StopLoss.Value + stepPoints)
{
position.ModifyStopLossPrice(newSL);
}
}
else if (position.TradeType == TradeType.Sell)
{
double newSL = Symbol.Ask + trailPoints;
// Step filter to prevent server spam
if (newSL < position.StopLoss.Value - stepPoints)
{
position.ModifyStopLossPrice(newSL);
}
}
}
}
}
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:47 pm
by PTScalper
Key Architectural Advantages in cTrader
Symbol.NormalizeVolumeInUnits: Instead of writing manual flooring logic to fit the broker's minimum lot sizes, this single cAlgo method natively parses your raw mathematical risk, rounds it correctly (RoundingMode.Down to protect risk ceilings), and formats it into broker-safe units.
The Label Parameter: Filtering by Positions.FindAll(Label, SymbolName) is a massive upgrade over MT4's MagicNumber system. It allows you to run multiple different strategies on Silver concurrently, and this class will only manage trades labeled "SilverBullet".
Time Zone Agnostic: Because cBots run on a server that might be in an entirely different timezone than your broker's price feed, you can set [Robot(TimeZone = TimeZones.UTC)] at the top level. This makes programming the strict New York Silver Bullet time windows highly precise, regardless of local machine configuration.
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:48 pm
by PTScalper
In MetaTrader, coding session times requires writing complex offset functions to calculate the difference between the broker's server time (often GMT+2/GMT+3) and New York time, while manually adjusting for Daylight Saving Time (DST) twice a year.
cTrader's cAlgo API completely abstracts this away. By defining the timezone at the class level, the C# environment automatically handles both the UTC offset and US Daylight Saving Time (switching between EST and EDT seamlessly).
Here is how you accurately script the ICT Silver Bullet windows.
1. The TimeZone Attribute
You set the time zone natively in the [Robot] attribute before the class declaration. By assigning TimeZones.EasternStandardTime, you are instructing the cBot to run in New York time.
2. The Server.Time Abstraction
Once the attribute is set, any call to Server.Time will represent the current time in New York. You do not need to use DateTime.UtcNow or calculate offsets.
The C# Implementation
Code: Select all
using cAlgo.API;
using System;
namespace cAlgo.Robots
{
// 1. Force the cBot to run in New York Local Time (handles DST automatically)
[Robot(TimeZone = TimeZones.EasternStandardTime, AccessRights = AccessRights.None)]
public class SilverBulletManager : Robot
{
[Parameter("Trade London Open (03:00 - 04:00 NY)", DefaultValue = false, Group = "ICT Sessions")]
public bool TradeLondonOpen { get; set; }
[Parameter("Trade NY AM (10:00 - 11:00 NY)", DefaultValue = true, Group = "ICT Sessions")]
public bool TradeNyAm { get; set; }
[Parameter("Trade NY PM (14:00 - 15:00 NY)", DefaultValue = true, Group = "ICT Sessions")]
public bool TradeNyPm { get; set; }
[Parameter("Hard Close at Session End?", DefaultValue = true, Group = "ICT Sessions")]
public bool HardClosePositions { get; set; }
// Unique identifier for the strategy
private const string Label = "SilverBullet";
protected override void OnTick()
{
if (IsSilverBulletWindow())
{
// Your Market Structure Shift & FVG scanning logic goes here
}
else if (HardClosePositions)
{
// Prevent holding trades in dead zones or overnight
EnforceSessionClose();
}
}
//+------------------------------------------------------------------+
//| The Time Window Gatekeeper |
//+------------------------------------------------------------------+
private bool IsSilverBulletWindow()
{
// Server.Time is now natively Eastern Time (EST/EDT)
int currentHour = Server.Time.Hour;
// ICT London Open: 3:00 AM - 3:59 AM NY Time
if (TradeLondonOpen && currentHour == 3) return true;
// ICT NY AM Session: 10:00 AM - 10:59 AM NY Time
if (TradeNyAm && currentHour == 10) return true;
// ICT NY PM Session: 2:00 PM - 2:59 PM (14:00 - 14:59) NY Time
if (TradeNyPm && currentHour == 14) return true;
return false;
}
//+------------------------------------------------------------------+
//| Session End Liquidation |
//+------------------------------------------------------------------+
private void EnforceSessionClose()
{
var positions = Positions.FindAll(Label, SymbolName);
// If we are outside the 3AM, 10AM, and 2PM hours, flatten the book
foreach (var position in positions)
{
position.Close();
}
// Cancel any pending limit orders at the FVG that never triggered
var pendingOrders = PendingOrders.Where(o => o.Label == Label && o.SymbolName == SymbolName);
foreach (var order in pendingOrders)
{
CancelPendingOrder(order);
}
}
}
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:48 pm
by PTScalper
Architectural Notes for Backtesting
Because cTrader's backtester simulates the historical data through the lens of your [Robot] attribute, a backtest run in February will automatically apply a UTC-5 offset, and a backtest run in June will apply a UTC-4 offset.
This guarantees that your algorithmic 10:00 AM execution always perfectly aligns with the opening hour of the New York stock market and the resulting silver volatility, regardless of the season.
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:49 pm
by PTScalper
To mathematically model market microstructure—sweeps, structural shifts, and imbalances—you must transition from tick-level execution to the OnBar() event loop. Scanning for structural shifts on every tick invites "intra-bar repainting," where a candle temporarily breaches a level and triggers your logic, only to wick back before the close.
In cTrader, the Bars collection handles this elegantly. Unlike MT4’s reverse arrays, cTrader’s Bars.Last(int index) method makes it incredibly intuitive to map out the 3-candle sequences required for Fair Value Gaps (FVGs) and Market Structure Shifts (MSS).
Here is the C# blueprint for the structural state machine.
1. The State Machine & Global Variables
We need an enum to track the chronological sequence. The algorithm must remain locked in its current state until the next geometric rule is satisfied.
Code: Select all
using cAlgo.API;
using System.Linq;
namespace cAlgo.Robots
{
public partial class SilverBulletManager : Robot
{
[Parameter("Liquidity Lookback (Bars)", DefaultValue = 50, Group = "Market Structure")]
public int LiquidityLookback { get; set; }
private enum SetupState
{
Monitoring,
BuysideSwept,
SellsideSwept,
MssConfirmed
}
private SetupState _currentState = SetupState.Monitoring;
private double _bsl; // Buyside Liquidity
private double _ssl; // Sellside Liquidity
private double _mssLevel; // The structural level that must be broken
// ... (previous time window and sizing parameters)
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:49 pm
by PTScalper
2. Capturing Liquidity at the Window Open
When the time window (e.g., 10:00 AM NY) opens, you must lock in the liquidity pools based on previous session data. You do not want these values updating continuously during the execution window.
Code: Select all
protected override void OnBar()
{
// 1. Reset state if outside the trading window
if (!IsSilverBulletWindow())
{
_currentState = SetupState.Monitoring;
return;
}
// 2. Lock in BSL and SSL on the very first bar of the window
if (Server.Time.Minute == 0)
{
// cTrader's DataSeries allows LINQ-style maximums over a specific range
// We start reading from index 1 to exclude the currently forming bar
_bsl = Bars.HighPrices.SkipLast(1).TakeLast(LiquidityLookback).Max();
_ssl = Bars.LowPrices.SkipLast(1).TakeLast(LiquidityLookback).Min();
}
// 3. Progress the State Machine
switch (_currentState)
{
case SetupState.Monitoring:
ScanForLiquiditySweep();
break;
case SetupState.BuysideSwept:
case SetupState.SellsideSwept:
ScanForMarketStructureShift();
break;
case SetupState.MssConfirmed:
ScanForFairValueGap();
break;
}
}
Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView
Posted: Tue Sep 01, 2026 11:50 pm
by PTScalper
3. Scanning for the Sweep & MSS
A sweep is defined as price piercing the liquidity pool but failing to hold it, printing a rejection wick. A Market Structure Shift is confirmed when the price subsequently closes past the fractal low/high that initiated the sweep.
Code: Select all
private void ScanForLiquiditySweep()
{
var lastBar = Bars.Last(1); // The most recently closed candle
// Buyside Sweep: Pierced BSL, but closed below it
if (lastBar.High > _bsl && lastBar.Close < _bsl)
{
_currentState = SetupState.BuysideSwept;
// Identify the most recent fractal low prior to the sweep to act as our MSS line
_mssLevel = Bars.LowPrices.SkipLast(2).TakeLast(5).Min();
Print("Buyside Liquidity Swept. Awaiting MSS below: {0}", _mssLevel);
}
// Sellside Sweep: Pierced SSL, but closed above it
else if (lastBar.Low < _ssl && lastBar.Close > _ssl)
{
_currentState = SetupState.SellsideSwept;
// Identify the most recent fractal high prior to the sweep
_mssLevel = Bars.HighPrices.SkipLast(2).TakeLast(5).Max();
Print("Sellside Liquidity Swept. Awaiting MSS above: {0}", _mssLevel);
}
}
private void ScanForMarketStructureShift()
{
var lastBar = Bars.Last(1);
// If we swept buyside, we need a strong bearish close below the MSS low
if (_currentState == SetupState.BuysideSwept && lastBar.Close < _mssLevel)
{
_currentState = SetupState.MssConfirmed;
Print("Bearish MSS Confirmed. Awaiting FVG.");
}
// If we swept sellside, we need a strong bullish close above the MSS high
else if (_currentState == SetupState.SellsideSwept && lastBar.Close > _mssLevel)
{
_currentState = SetupState.MssConfirmed;
Print("Bullish MSS Confirmed. Awaiting FVG.");
}
}