Page 2 of 2
Re: When a cheaper commission broker still cost me more
Posted: Sat Sep 19, 2026 12:08 pm
by PTScalper
Wrapping this logic into an MQL5/C++ OOP class (.mqh file) allows you to cleanly inject an execution drag check before any OrderSend() call.
The architecture below handles the exact scenario where liquidity drops out around the open or during a news event. Instead of letting the EA blindly execute a setup into a blown-out order book, the class intercepts the signal, calculates the live microstructural cost, and logs the rejection parameters so you can audit the broker's behavior later.
Re: When a cheaper commission broker still cost me more
Posted: Sat Sep 19, 2026 12:08 pm
by PTScalper
The Drag Filter Class (ExecutionDragFilter.mqh)
Code: Select all
//+------------------------------------------------------------------+
//| Class: CExecutionDragFilter |
//| Purpose: Intercepts and rejects execution during liquidity voids |
//| by calculating live spread + commission in native ticks.|
//+------------------------------------------------------------------+
class CExecutionDragFilter
{
private:
string m_symbol;
double m_commission_rt; // Round-turn commission in account currency
double m_max_drag_ticks; // Maximum allowable total drag in ticks
double m_tick_size;
public:
CExecutionDragFilter(void);
~CExecutionDragFilter(void);
// Initialize the filter parameters
bool Init(const string symbol, const double commission, const double max_drag_ticks);
// Calculate current live execution drag
double GetLiveDrag(void);
// Main validation check to wrap around your EA's entry logic
bool IsExecutionSafe(void);
};
//+------------------------------------------------------------------+
//| Constructor & Destructor |
//+------------------------------------------------------------------+
CExecutionDragFilter::CExecutionDragFilter(void) : m_symbol(""), m_commission_rt(0.0), m_max_drag_ticks(0.0), m_tick_size(0.0) {}
CExecutionDragFilter::~CExecutionDragFilter(void) {}
//+------------------------------------------------------------------+
//| Initialization |
//+------------------------------------------------------------------+
bool CExecutionDragFilter::Init(const string symbol, const double commission, const double max_drag_ticks)
{
m_symbol = (symbol == "") ? _Symbol : symbol;
m_commission_rt = commission;
m_max_drag_ticks = max_drag_ticks;
m_tick_size = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_SIZE);
if(m_tick_size == 0)
{
Print("Error: Failed to retrieve tick size for ", m_symbol);
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Core Calculation: Live Spread + Dynamic Commission Tick Value |
//+------------------------------------------------------------------+
double CExecutionDragFilter::GetLiveDrag(void)
{
double ask = SymbolInfoDouble(m_symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(m_symbol, SYMBOL_BID);
// We pull tick value dynamically here rather than caching it in Init()
// because cross-pair tick values fluctuate with live exchange rates.
double current_tick_value = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE);
// Failsafe for missing quotes or zero-divide errors
if(ask == 0 || bid == 0 || current_tick_value == 0) return 99999.9;
double spread_ticks = (ask - bid) / m_tick_size;
double comm_ticks = m_commission_rt / current_tick_value;
return spread_ticks + comm_ticks;
}
//+------------------------------------------------------------------+
//| Execution Validator & Rejection Logger |
//+------------------------------------------------------------------+
bool CExecutionDragFilter::IsExecutionSafe(void)
{
double live_drag = GetLiveDrag();
if(live_drag > m_max_drag_ticks)
{
double ask = SymbolInfoDouble(m_symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(m_symbol, SYMBOL_BID);
double spread_ticks = (ask - bid) / m_tick_size;
double current_tick_value = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE);
double comm_ticks = m_commission_rt / current_tick_value;
// Log the exact microstructural breakdown for post-session auditing
PrintFormat("[REJECT] Order Blocked on %s. Drag: %.1f ticks > Max Allowable: %.1f | Spread: %.1f, Comm_Eq: %.1f",
m_symbol, live_drag, m_max_drag_ticks, spread_ticks, comm_ticks);
return false;
}
return true;
}
Re: When a cheaper commission broker still cost me more
Posted: Sat Sep 19, 2026 12:09 pm
by PTScalper
Implementing the Class in Your EA
To use this, include the header file at the top of your main EA file and initialize the class instance inside OnInit().
Code: Select all
#include "ExecutionDragFilter.mqh"
// Instantiate the filter
CExecutionDragFilter DragFilter;
int OnInit()
{
// Initialize: Current Symbol, $6.00 Round-Turn Commission, Max 15.0 Ticks Drag
if(!DragFilter.Init(_Symbol, 6.0, 15.0))
{
Print("Failed to initialize Execution Drag Filter.");
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
void OnTick()
{
// 1. Your algorithmic signal generation logic goes here
bool isBuySignal = CheckForBuySetup();
if(isBuySignal)
{
// 2. Intercept the signal with the Drag Filter before sending to the broker
if(!DragFilter.IsExecutionSafe())
{
// The class automatically logs the rejection details to the terminal.
// Exit OnTick() until the next incoming tick.
return;
}
// 3. Execution is safe. Proceed to OrderSend...
ExecuteBuyOrder();
}
}
Re: When a cheaper commission broker still cost me more
Posted: Sat Sep 19, 2026 12:09 pm
by PTScalper
Key Engineering Benefits of this Approach
Dynamic Tick Value Handling: Calculating SYMBOL_TRADE_TICK_VALUE dynamically inside GetLiveDrag() (rather than caching it) is critical if you trade spot forex cross pairs. The tick value of EURGBP, for example, fluctuates based on the live GBPUSD exchange rate.
Decoupled Architecture: The entry signal logic is entirely separated from the execution validation. Your core strategy doesn't need to know anything about the broker's spread environment.
Automated Audit Trails: The PrintFormat block gives you a chronological log of exactly when and why your broker’s liquidity dried up, allowing you to cross-reference rejection streaks with specific time-of-day volatility or macro news events.
Re: When a cheaper commission broker still cost me more
Posted: Sat Sep 19, 2026 12:10 pm
by PTScalper
Moving this logic into cTrader (cAlgo) is highly efficient because you can leverage standard C# object-oriented practices like Dependency Injection. Instead of relying on global symbol functions like MQL, we can pass the Symbol object and the Algo context (which gives us access to the Print method) directly into a standalone utility class.
Here is a clean, reusable C# implementation designed to drop straight into your cBot ecosystem.
The Drag Filter Class (ExecutionDragFilter.cs)
You can place this class at the bottom of your cBot file, or keep it in a separate .cs file if you are structuring your algorithms as a broader .NET solution in Visual Studio.
Code: Select all
using cAlgo.API;
using System;
namespace cAlgo.Robots
{
//+------------------------------------------------------------------+
//| Class: ExecutionDragFilter |
//| Purpose: Intercepts and rejects execution during liquidity voids |
//| by calculating live spread + commission in native ticks.|
//+------------------------------------------------------------------+
public class ExecutionDragFilter
{
private readonly Algo _algo;
private readonly Symbol _symbol;
private readonly double _commissionRoundTurn;
private readonly double _maxDragTicks;
// Inject the Algo context (for logging) and Symbol data
public ExecutionDragFilter(Algo algo, Symbol symbol, double commissionRoundTurn, double maxDragTicks)
{
_algo = algo;
_symbol = symbol;
_commissionRoundTurn = commissionRoundTurn;
_maxDragTicks = maxDragTicks;
}
//+------------------------------------------------------------------+
//| Core Calculation: Live Spread + Dynamic Commission Tick Value |
//+------------------------------------------------------------------+
public double GetLiveDrag()
{
// Failsafe for missing market data or zero-divide
if (_symbol.Ask == 0 || _symbol.Bid == 0 || _symbol.TickValue == 0 || _symbol.TickSize == 0)
return double.MaxValue;
// Calculate spread in ticks
double spreadTicks = (_symbol.Ask - _symbol.Bid) / _symbol.TickSize;
// Convert fiat commission to tick equivalent dynamically
double commTicks = _commissionRoundTurn / _symbol.TickValue;
return spreadTicks + commTicks;
}
//+------------------------------------------------------------------+
//| Execution Validator & Rejection Logger |
//+------------------------------------------------------------------+
public bool IsExecutionSafe()
{
double liveDrag = GetLiveDrag();
if (liveDrag > _maxDragTicks)
{
double spreadTicks = (_symbol.Ask - _symbol.Bid) / _symbol.TickSize;
double commTicks = _commissionRoundTurn / _symbol.TickValue;
// Log the exact microstructural breakdown to the cTrader log
_algo.Print($"[REJECT] Order Blocked on {_symbol.Name}. Drag: {liveDrag:F1} ticks > Max Allowable: {_maxDragTicks:F1} | Spread: {spreadTicks:F1}, Comm_Eq: {commTicks:F1}");
return false;
}
return true;
}
}
}
Re: When a cheaper commission broker still cost me more
Posted: Sat Sep 19, 2026 12:10 pm
by PTScalper
Implementing the Class in Your cBot
To wire this up in the main Robot class, you define your standard cTrader [Parameter] attributes and instantiate the filter inside OnStart(). By passing this to the filter, the utility class gains access to the bot's logging stream.
Code: Select all
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class PriceActionScalper : Robot
{
[Parameter("Round-Turn Commission ($)", Group = "Execution Constraints", DefaultValue = 6.0)]
public double Commission { get; set; }
[Parameter("Max Allowable Drag (Ticks)", Group = "Execution Constraints", DefaultValue = 15.0)]
public double MaxDrag { get; set; }
private ExecutionDragFilter _dragFilter;
protected override void OnStart()
{
// Initialize the filter, passing 'this' (the Algo instance) and the current Symbol
_dragFilter = new ExecutionDragFilter(this, Symbol, Commission, MaxDrag);
}
protected override void OnTick()
{
// 1. Your algorithmic signal generation logic goes here
bool isBuySignal = CheckForBuySetup();
if (isBuySignal)
{
// 2. Intercept the signal with the Drag Filter
if (!_dragFilter.IsExecutionSafe())
{
// The class automatically logs the rejection parameters.
// Return out of OnTick to prevent execution on this tick.
return;
}
// 3. Execution is safe. Proceed to MarketOrder...
Print("Safe liquidity confirmed. Sending Market Order...");
ExecuteMarketOrder(TradeType.Buy, SymbolName, 100000, "Scalp Entry");
}
}
private bool CheckForBuySetup()
{
// Placeholder for 15m price action / liquidity sweep logic
return false;
}
}
}
Re: When a cheaper commission broker still cost me more
Posted: Sat Sep 19, 2026 12:11 pm
by PTScalper
Key cTrader Advantages
Native C# String Interpolation: The logging implementation uses standard C# $"{var}" interpolation, which is significantly cleaner and less error-prone than MQL's PrintFormat.
Reliable TickValue Updates: Because the calculation pulls _symbol.TickValue live inside GetLiveDrag(), it flawlessly handles cross-pair conversions on the fly, tracking real-time exchange rate fluctuations without needing to manually cache or recalculate the base currency ratios.
Re: When a cheaper commission broker still cost me more
Posted: Sat Sep 19, 2026 6:14 pm
by LondonScalper
PTScalper wrote:A 0.5 pip slip on a tight stop-loss instantly destroys the risk-to-reward ratio of a scalp, making the commission savings mathematically irrelevant.
That matches the month I moved. The brochure saving was real and small. The open was where the median spread widened, the rejects pushed me into chase fills, and the all-in number went the wrong way. I now price a lot on the hours I actually trade, including a bad open, not on the average banner. Rebate maths that assume volume I do not do are the other quiet leak. The support path only matters the day something breaks mid-session, which is when a cheap shop goes quiet.
A live histogram of spread plus commission is a fair monitor if it runs through your window. It will not reconstruct last month. TradingView keeping no historical bid and ask is why I kept the terminal log rather than a chart study. I would not switch books on one spike. I want a few weeks of the same pairs and the same size, written down, before I call one book more predictable. Predictable still beats cheapest, and I am no longer tempted by a headline that ignores rejects.
Re: When a cheaper commission broker still cost me more
Posted: Thu Sep 24, 2026 1:36 am
by PropScalpDesk
PTScalper wrote:MT4 / MQL4 Implementation Because MQL4 does not support a native DRAW_COLOR_HISTOGRAM as cleanly as MT5, we utilize two separate buffers (one for acceptable drag, one for excessive drag) to achieve the exact same visual flagging. Code: Select all //+------------------------------------------------------------------+ //| LiveExecutionDragMonitor.
Cheaper commission with worse slip into news is a false economy. I cost trades in cash all-in: commission + spread + slip.
Prop trailing DD does not care which line item hurt you.
Where do you see the biggest hidden cost on your current gold route?
I also log refused tickets so flat time counts as work — otherwise the desk invents activity.
Funded trailing DD is the external referee that keeps the desk honest.
Boring survival beats a clever recovery that spends the week’s DD band.
Topic note from my sheet for t=12530: keep risk unchanged until the sample says otherwise.
Re: When a cheaper commission broker still cost me more
Posted: Thu Sep 24, 2026 9:48 am
by LondonNewsTrader
PTScalper wrote:MT4 / MQL4 Implementation Because MQL4 does not support a native DRAW_COLOR_HISTOGRAM as cleanly as MT5, we utilize two separate buffers (one for acceptable drag, one for excessive drag) to achieve the exact same visual flagging.
This measures the two costs you can see on the quote, spread and commission, and that's useful. But the month-end surprise in the opening post came mostly from things it can't see: slips around the open and reject streaks that forced chase fills. So I'd treat the monitor as the 'brochure plus spread' half of the picture and pair it with a fill log for the other half.
On MT4, check what the spread[] array actually holds on your server. MT4 doesn't reliably keep spread history, and on many builds the values for older bars are zero or a flat default, which makes every historical bar look cheaper than it was. The live value on bar 0 from MODE_ASK minus MODE_BID is real; the history may not be.
The commission conversion divides the round-turn cost by tick value, which is quoted per standard lot, so the drag in ticks is the same at any size. Correct, but worth a comment so nobody later assumes it scales.
A 15-point ceiling on EURUSD, one and a half pips all-in, looks sensible for normal hours. Into a US release the histogram will jump straight to the danger colour, which is a handy visual reminder not to be there.