Page 2 of 2
Re: Backtesting Made Me Confident. Live Trading Made Me Humble.
Posted: Tue Sep 22, 2026 2:17 pm
by PTScalper
The Live Market Realities
When running these EAs on MetaTrader, the backtest vs. live gap manifests in three specific areas the code forces you to acknowledge:
The Ask/Bid Spread: Notice how the OrderSend logic strictly uses Ask for long entries and Bid for short entries. When you backtest this in MT5 using "Every tick based on real ticks," a strategy with tight stops will get decimated by spread variations during rollover or news—exactly as it would in real life.
Tick Value Normalization (CalculateLotSize): Amateurs trade fixed lots (0.1, 1.0). Professionals risk percentages. The CalculateLotSize function dynamically queries your broker for the base-currency conversion rate of the traded pair at that exact millisecond so your 1% risk is truly 1%, whether you are trading EURUSD or GBPJPY.
Execution Latency: The MaxSlippage parameter acts as a hard stop against toxic execution. If market volatility spikes and your broker tries to fill you 10 points away from your request, the EA rejects it. A missed trade is infinitely better than a trade entered at a structural disadvantage.
Re: Backtesting Made Me Confident. Live Trading Made Me Humble.
Posted: Tue Sep 22, 2026 2:18 pm
by PTScalper
cTrader and its underlying C# framework (cAlgo) represent a massive leap forward for retail execution. Unlike MetaTrader’s legacy architecture, cTrader processes orders asynchronously by default and offers institutional-grade backtesting that natively models orderbook depth and latency.
When you move this strategy into cTrader, you must handle one critical difference: Volume Normalization. cTrader calculates position sizes in precise units rather than arbitrary "lots," meaning your risk algorithm must perfectly translate account currency risk into exact base-currency units while respecting the symbol's step size.
Re: Backtesting Made Me Confident. Live Trading Made Me Humble.
Posted: Tue Sep 22, 2026 2:18 pm
by PTScalper
Here is the institutional template adapted for a cBot. We use the OnBar method to enforce discipline—it prevents the bot from reacting to intra-bar tick noise and repainting illusions, executing only when the mathematical state of the market is finalized.
Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class InstitutionalStressTest : Robot
{
// =========================================================================
// 1. INPUTS & PARAMETERS
// =========================================================================
[Parameter("Risk Per Trade (%)", Group = "Risk Management", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1)]
public double RiskPercent { get; set; }
[Parameter("ATR Stop Loss Multiplier", Group = "Risk Management", DefaultValue = 1.5)]
public double AtrSlMult { get; set; }
[Parameter("ATR Take Profit Multiplier", Group = "Risk Management", DefaultValue = 3.0)]
public double AtrTpMult { get; set; }
[Parameter("Max Slippage (Pips)", Group = "Risk Management", DefaultValue = 0.5)]
public double MaxSlippage { get; set; }
[Parameter("Baseline Trend EMA", Group = "Strategy", DefaultValue = 200)]
public int EmaPeriod { get; set; }
[Parameter("Mean Reversion RSI", Group = "Strategy", DefaultValue = 4)]
public int RsiPeriod { get; set; }
[Parameter("Volatility ATR", Group = "Strategy", DefaultValue = 14)]
public int AtrPeriod { get; set; }
[Parameter("Start Hour (UTC)", Group = "Session Filter", DefaultValue = 9)]
public int StartHour { get; set; }
[Parameter("Start Min", Group = "Session Filter", DefaultValue = 30)]
public int StartMin { get; set; }
[Parameter("End Hour (UTC)", Group = "Session Filter", DefaultValue = 15)]
public int EndHour { get; set; }
[Parameter("End Min", Group = "Session Filter", DefaultValue = 45)]
public int EndMin { get; set; }
// =========================================================================
// 2. INDICATOR REFERENCES
// =========================================================================
private ExponentialMovingAverage _ema;
private RelativeStrengthIndex _rsi;
private AverageTrueRange _atr;
private const string Label = "Inst_StressTest";
protected override void OnStart()
{
_ema = Indicators.ExponentialMovingAverage(Bars.ClosePrices, EmaPeriod);
_rsi = Indicators.RelativeStrengthIndex(Bars.ClosePrices, RsiPeriod);
_atr = Indicators.AverageTrueRange(Bars, AtrPeriod, MovingAverageType.Simple);
}
// =========================================================================
// 3. CORE LOGIC & EXECUTION (Calculated on Bar Close)
// =========================================================================
protected override void OnBar()
{
// 3a. Session Filtering
DateTime currentTime = Server.Time;
int currentMinutes = currentTime.Hour * 60 + currentTime.Minute;
int startMinutes = StartHour * 60 + StartMin;
int endMinutes = EndHour * 60 + EndMin;
bool inSession = (currentMinutes >= startMinutes && currentMinutes <= endMinutes);
// 3b. End of Day Flattening
if (!inSession)
{
var openPositions = Positions.FindAll(Label, SymbolName);
foreach (var position in openPositions)
{
ClosePosition(position);
}
return;
}
// Prevent pyramiding - only one trade at a time
if (Positions.FindAll(Label, SymbolName).Length > 0)
return;
// 3c. Indicator Values (Index 1 is the last closed bar, Index 2 is the bar before that)
double closePrice = Bars.ClosePrices.Last(1);
double currentEma = _ema.Result.Last(1);
double currentAtr = _atr.Result.Last(1);
bool bullRegime = closePrice > currentEma;
bool bearRegime = closePrice < currentEma;
bool longTrigger = _rsi.Result.Last(2) > 30 && _rsi.Result.Last(1) <= 30; // Crossed under 30
bool shortTrigger = _rsi.Result.Last(2) < 70 && _rsi.Result.Last(1) >= 70; // Crossed over 70
// 3d. Dynamic Position Sizing & Execution
if (longTrigger && bullRegime)
{
double slPips = (currentAtr * AtrSlMult) / Symbol.PipSize;
double tpPips = (currentAtr * AtrTpMult) / Symbol.PipSize;
double volume = CalculateVolume(slPips);
if (volume >= Symbol.VolumeInUnitsMin)
{
// ExecuteMarketRangeOrder limits acceptable slippage natively
ExecuteMarketRangeOrder(TradeType.Buy, SymbolName, volume, MaxSlippage, Symbol.Ask, Label, slPips, tpPips);
}
}
else if (shortTrigger && bearRegime)
{
double slPips = (currentAtr * AtrSlMult) / Symbol.PipSize;
double tpPips = (currentAtr * AtrTpMult) / Symbol.PipSize;
double volume = CalculateVolume(slPips);
if (volume >= Symbol.VolumeInUnitsMin)
{
ExecuteMarketRangeOrder(TradeType.Sell, SymbolName, volume, MaxSlippage, Symbol.Bid, Label, slPips, tpPips);
}
}
}
// =========================================================================
// 4. INSTITUTIONAL POSITION SIZING ALGORITHM
// =========================================================================
private double CalculateVolume(double stopLossPips)
{
if (stopLossPips <= 0) return 0;
// Calculate exactly how much money we are willing to risk
double riskMoney = Account.Equity * (RiskPercent / 100.0);
// Calculate how much 1 unit of volume costs per pip
double exactVolume = riskMoney / (stopLossPips * Symbol.PipValue);
// Normalize the volume to the broker's required step sizes (e.g., 1000 for micro lots in FX)
return Symbol.NormalizeVolumeInUnits(exactVolume, RoundingMode.Down);
}
}
}
Re: Backtesting Made Me Confident. Live Trading Made Me Humble.
Posted: Tue Sep 22, 2026 2:19 pm
by PTScalper
The cTrader Execution Edge
When you compile this cBot, you unlock a few execution mechanics that separate cTrader from retail standard platforms:
ExecuteMarketRangeOrder: This is a professional execution type. Instead of blasting a blind market order and accepting whatever liquidity is left (which destroys edge), a Market Range order tells the FIX engine: "Fill me now, but if slippage exceeds MaxSlippage, kill the order entirely." It prevents you from catching the top of a news spike.
Symbol.NormalizeVolumeInUnits: Brokers vary wildly on minimum tick sizes. Crypto might allow 0.0001 BTC, while FX requires 1000-unit steps. This C# method natively prevents order rejections by flooring your exact risk calculation down to the nearest permissible trade size for the specific asset.
OnBar Event Firing: In Pine Script or MetaTrader, you often have to build complex workarounds to ensure your code only evaluates closed candles. cTrader's OnBar natively isolates your logic, ensuring your bot only acts when the data is mathematically finalized, killing the "repainting" effect that ruins backtests.
Re: Backtesting Made Me Confident. Live Trading Made Me Humble.
Posted: Wed Sep 23, 2026 7:03 pm
by LondonScalper
PTScalper wrote:Run this on a 5-minute chart, and then change slippage=0 and commission_value=0.0. You will watch an amazing, smooth equity curve instantly turn into a jagged, losing mess the moment you turn the friction back on.
Turning friction back on is the honest backtest. Smooth curves with zero slippage taught a generation to trust numbers that never survive a London open on gold.
I still run historical work, but I treat commission and slippage as first-class inputs, not a toggle for the screenshot. If the edge dies when friction is realistic, it was never an edge for a live book.
Live trading adds rejects, widened spreads into data, and the urge to “make the curve look like the test.” Humility is useful; abandoning a plan after one rough week is not.
Do you keep a separate live log of average slippage by hour, or only the flat backtest assumption?
Re: Backtesting Made Me Confident. Live Trading Made Me Humble.
Posted: Wed Sep 23, 2026 11:42 pm
by PropScalpDesk
PTScalper wrote:The MQL4 Institutional Template While MQL4 is deprecated by MetaQuotes, it remains the backbone of retail FX. Here is the functionally identical architecture adapted for MQL4's OrderSend system. Code: Select all //+------------------------------------------------------------------+ //| Institutional_StressTest.
Live friction is the humility machine. Backtests with polite spreads made me cocky once; a week of real XAU rejects fixed that.
On prop I run historical work with commission and slippage on by default. If the edge dies there, it does not get a challenge seat.
Humility is useful; abandoning a measured plan after one rough live week is not.
Do you keep a live slippage log by hour, or only a flat backtest assumption?
I also log refused tickets so flat time counts as work — otherwise the desk invents activity.
Re: Backtesting Made Me Confident. Live Trading Made Me Humble.
Posted: Thu Sep 24, 2026 6:44 am
by LondonNewsTrader
The institutional stress-test skeleton is valuable for the boring reasons: a hard slip ceiling, an explicit session window, ATR-based stops and targets, and a magic number that keeps the journal readable. That is the gap between a heroic backtest and something that can survive London when the book widens around a Tier-1 print.
I treat OrderSend-era code as a liability checklist. If live execution cannot honour the same slip ceiling you optimised against, the equity curve was fiction. The indicator stack inside the template is a scaffold only — fine for wiring risk, fatal if you prove expectancy without commission and a realistic reject rate into data.
Practical desk habit: lock the session window first, then raise the slip field until the strategy dies. Whatever still shows expectancy after that stress is closer to open reality than a clean tick dump. I would rather kill a pretty curve in research than discover the truth three seconds after CPI.