Broker scorecard quarterly update: spreads, rejects, and uptime
-
LondonScalper
- Posts: 407
- Joined: Sat Sep 05, 2026 7:54 am
Broker scorecard quarterly update: spreads, rejects, and uptime
I stopped arguing about brokers from memory. Once a quarter I update a simple scorecard from my logs.
Columns that matter for scalping
• Median spread by pair + session (London / overlap)
• p95 slippage on market orders
• Reject / requote count
• Time-to-fill feel (qualitative is OK if you don’t have timestamps)
• Outages / disconnects
• Cost per 100 round-turns (spread + commission + adverse slip)
Rule
A pretty marketing page loses to an ugly spreadsheet. If a broker wins on raw spread but loses on p95 slip + rejects, it gets demoted for my M1 work.
I’m refreshing my Q3→Q4 card now (majors + XAU). If you keep something similar, what metrics actually changed your broker choice last time — not what you wish mattered?
No account numbers. Comparison talk welcome. Not financial advice.
Columns that matter for scalping
• Median spread by pair + session (London / overlap)
• p95 slippage on market orders
• Reject / requote count
• Time-to-fill feel (qualitative is OK if you don’t have timestamps)
• Outages / disconnects
• Cost per 100 round-turns (spread + commission + adverse slip)
Rule
A pretty marketing page loses to an ugly spreadsheet. If a broker wins on raw spread but loses on p95 slip + rejects, it gets demoted for my M1 work.
I’m refreshing my Q3→Q4 card now (majors + XAU). If you keep something similar, what metrics actually changed your broker choice last time — not what you wish mattered?
No account numbers. Comparison talk welcome. Not financial advice.
Re: Broker scorecard quarterly update: spreads, rejects, and uptime
Hello traders, LondonScalper,LondonScalper wrote: Thu Sep 10, 2026 10:31 am I stopped arguing about brokers from memory. Once a quarter I update a simple scorecard from my logs.
Columns that matter for scalping
• Median spread by pair + session (London / overlap)
• p95 slippage on market orders
• Reject / requote count
• Time-to-fill feel (qualitative is OK if you don’t have timestamps)
• Outages / disconnects
• Cost per 100 round-turns (spread + commission + adverse slip)
Rule
A pretty marketing page loses to an ugly spreadsheet. If a broker wins on raw spread but loses on p95 slip + rejects, it gets demoted for my M1 work.
I’m refreshing my Q3→Q4 card now (majors + XAU). If you keep something similar, what metrics actually changed your broker choice last time — not what you wish mattered?
No account numbers. Comparison talk welcome. Not financial advice.
o answer your question on what actually changed my broker choice last time: Asymmetric slippage on market orders during the London/NY overlap.
Broker A had a 0.2 pip median spread, but my logs showed a p95 slippage of 0.8 pips. Worse, it was asymmetric—limit orders never got positive slippage, but market stops were slipped heavily. Broker B had a wider median spread (0.5 pips) but true DMA execution. Their p95 slippage was barely 0.1 pips, and positive slippage actually occurred.
The other breaking point was the Time-to-fill vs. Reject Rate tradeoff. Broker A rejected 8% of my orders during the 8:30 AM EST liquidity sweeps. Missing a high-probability trade costs way more expected value (EV) than paying an extra 0.3 pips in spread. While the broader price action and structure might be mapped clearly on D1 and M15 charts, the actual M1 execution is where those hidden friction costs destroy an edge.
Re: Broker scorecard quarterly update: spreads, rejects, and uptime
The Pine Script Tracker
Logging actual reject counts and exact fill slippage is straightforward when coding natively in MQL5 or C# cAlgo. However, TradingView doesn't natively store historical bid and ask arrays, so plotting historical spread distributions on TV is heavily restricted.
To get around this in Pine Script, you have to run a real-time monitor. You can slap this dashboard on your chart during active sessions; it continuously reads the live spread and calculates your theoretical "Cost per 100 Round Turns" on the fly based on your inputs.
It updates on every real-time tick, giving you a live look at exactly how much the current liquidity conditions are eating into your bottom line.
Logging actual reject counts and exact fill slippage is straightforward when coding natively in MQL5 or C# cAlgo. However, TradingView doesn't natively store historical bid and ask arrays, so plotting historical spread distributions on TV is heavily restricted.
To get around this in Pine Script, you have to run a real-time monitor. You can slap this dashboard on your chart during active sessions; it continuously reads the live spread and calculates your theoretical "Cost per 100 Round Turns" on the fly based on your inputs.
Code: Select all
//@version=5
indicator("Live Execution Cost Monitor", overlay=true)
// --- Inputs ---
var string GRP = "Broker Cost Parameters"
commRT = input.float(7.0, title="Commission per Round Turn ($)", group=GRP)
slipEst = input.float(0.2, title="Est. p95 Slippage (Pips)", group=GRP)
pipValue = input.float(10.0, title="Pip Value per Lot ($)", group=GRP, tooltip="Usually $10 for standard FX lot")
tradesVol = input.int(100, title="Volume (Round Turns)", group=GRP)
// --- Spread Calculation ---
// TV exposes live ask/bid, but historical bars return 'na'.
liveSpreadPips = syminfo.mintick > 0 ? (ask - bid) / (syminfo.mintick * 10) : na
// --- Cost Projections ---
spreadCost = liveSpreadPips * pipValue * tradesVol
commCost = commRT * tradesVol
slipCost = slipEst * pipValue * tradesVol
totalCost = spreadCost + commCost + slipCost
// --- Dashboard Rendering ---
var table dash = table.new(position.top_right, 2, 5, bgcolor=color.new(color.black, 80), border_color=color.gray, border_width=1)
if barstate.islast
table.cell(dash, 0, 0, "Metric (" + str.tostring(tradesVol) + " Trades)", text_color=color.gray, text_halign=text.align_left)
table.cell(dash, 1, 0, "Cost Impact", text_color=color.gray, text_halign=text.align_right)
table.cell(dash, 0, 1, "Live Spread (" + str.tostring(liveSpreadPips, "#.##") + " pips)", text_color=color.white, text_halign=text.align_left)
table.cell(dash, 1, 1, "$" + str.tostring(spreadCost, "#.##"), text_color=color.yellow, text_halign=text.align_right)
table.cell(dash, 0, 2, "Commission", text_color=color.white, text_halign=text.align_left)
table.cell(dash, 1, 2, "$" + str.tostring(commCost, "#.##"), text_color=color.white, text_halign=text.align_right)
table.cell(dash, 0, 3, "Est. Slippage", text_color=color.white, text_halign=text.align_left)
table.cell(dash, 1, 3, "$" + str.tostring(slipCost, "#.##"), text_color=color.red, text_halign=text.align_right)
table.cell(dash, 0, 4, "Total Cost", text_color=color.white, text_halign=text.align_left)
table.cell(dash, 1, 4, "$" + str.tostring(totalCost, "#.##"), text_color=color.red, text_halign=text.align_right)Re: Broker scorecard quarterly update: spreads, rejects, and uptime
Institutional Execution Friction Monitor (Pine Script)
Native environments like C# cAlgo or MQL5 are vastly superior for logging granular bid/ask arrays and precise millisecond fill times. TradingView’s Pine Script lacks robust historical tick data access, meaning you cannot backtest spread distributions accurately.
To bypass this architectural limitation in TradingView, you must run a real-time monitor. The following script computes theoretical execution drag dynamically by sampling live liquidity.
Note: The script is optimized to initialize the UI framework only on the first bar, executing computational updates strictly on real-time ticks to minimize resource consumption.
Native environments like C# cAlgo or MQL5 are vastly superior for logging granular bid/ask arrays and precise millisecond fill times. TradingView’s Pine Script lacks robust historical tick data access, meaning you cannot backtest spread distributions accurately.
To bypass this architectural limitation in TradingView, you must run a real-time monitor. The following script computes theoretical execution drag dynamically by sampling live liquidity.
Note: The script is optimized to initialize the UI framework only on the first bar, executing computational updates strictly on real-time ticks to minimize resource consumption.
Code: Select all
//@version=5
indicator("Execution Friction Monitor", overlay=true)
// --- Execution Parameters ---
var string GRP = "Cost Engine Engine"
float commRT = input.float(7.0, title="Commission (Round Turn)", group=GRP)
float slipEst = input.float(0.2, title="p95 Slippage Estimate (Pips)", group=GRP)
float pipValue = input.float(10.0, title="Pip Value ($)", group=GRP, tooltip="Standard lot = $10")
int tradesVol = input.int(100, title="Sample Size (Trades)", group=GRP)
// --- Spread Calculation ---
// Real-time ask/bid sampling; returns 'na' on historical bars
float liveSpreadPips = syminfo.mintick > 0 ? (ask - bid) / (syminfo.mintick * 10) : na
// --- Cost Projections ---
float spreadCost = liveSpreadPips * pipValue * tradesVol
float commCost = commRT * tradesVol
float slipCost = slipEst * pipValue * tradesVol
float totalCost = spreadCost + commCost + slipCost
// --- Dashboard UI Architecture ---
var table dash = table.new(position.top_right, 2, 5, bgcolor=color.new(color.black, 85), border_color=color.rgb(60, 60, 60), border_width=1)
// Initialize headers optimally on first bar
if barstate.isfirst
table.cell(dash, 0, 0, "METRIC (" + str.tostring(tradesVol) + " TRADES)", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
table.cell(dash, 1, 0, "IMPACT ($)", text_color=color.gray, text_halign=text.align_right, text_size=size.small)
table.cell(dash, 0, 1, "Real-Time Spread", text_color=color.white, text_halign=text.align_left, text_size=size.small)
table.cell(dash, 0, 2, "Commission Base", text_color=color.white, text_halign=text.align_left, text_size=size.small)
table.cell(dash, 0, 3, "Asymmetric Slippage", text_color=color.white, text_halign=text.align_left, text_size=size.small)
table.cell(dash, 0, 4, "TOTAL FRICTION", text_color=color.white, text_halign=text.align_left, text_size=size.small)
// Push dynamic updates on live ticks
if barstate.islast
table.cell(dash, 1, 1, str.tostring(spreadCost, "#.##"), text_color=color.yellow, text_halign=text.align_right, text_size=size.small)
table.cell(dash, 1, 2, str.tostring(commCost, "#.##"), text_color=color.white, text_halign=text.align_right, text_size=size.small)
table.cell(dash, 1, 3, str.tostring(slipCost, "#.##"), text_color=color.red, text_halign=text.align_right, text_size=size.small)
table.cell(dash, 1, 4, str.tostring(totalCost, "#.##"), text_color=color.red, text_halign=text.align_right, text_size=size.small)Re: Broker scorecard quarterly update: spreads, rejects, and uptime
Relying on empirical data over broker marketing is the only sustainable approach to scaling an edge. While mapping macro structure and liquidity sweeps on D1 and M15 charts provides a statistically sound directional bias, the actual execution on the M1 timeframe is strictly a function of order book microstructure. If that microstructure is compromised, a verified edge mathematically decays into a net-negative expected value (EV).
Regarding the metrics that forced my last infrastructure migration, the primary catalyst was toxic flow profiling resulting in asymmetric slippage.
The Slippage Asymmetry Profile: Broker A aggressively marketed a 0.1 to 0.2 pip median spread. However, my execution logs revealed a severe synthetic delay. During high-probability liquidity sweeps, p95 slippage on market orders consistently spiked to 0.8+ pips. More critically, the slippage was strictly asymmetric: limit orders never received price improvement (positive slippage), while market stops were subjected to maximum adverse excursion. A true DMA/STP environment yields a normal distribution of slippage; asymmetry confirms B-book latency injection.
Fill-Probability vs. Execution Drag: The secondary metric was the order rejection ratio during macroeconomic volatility (e.g., 08:30 EST data drops). Broker A exhibited an 8% rejection rate under the guise of "insufficient liquidity." Missing the fat-tail distribution of a high-conviction sweep destroys more portfolio EV than absorbing a marginally wider, but mathematically guaranteed, spread from a prime-of-prime liquidity provider.
Regarding the metrics that forced my last infrastructure migration, the primary catalyst was toxic flow profiling resulting in asymmetric slippage.
The Slippage Asymmetry Profile: Broker A aggressively marketed a 0.1 to 0.2 pip median spread. However, my execution logs revealed a severe synthetic delay. During high-probability liquidity sweeps, p95 slippage on market orders consistently spiked to 0.8+ pips. More critically, the slippage was strictly asymmetric: limit orders never received price improvement (positive slippage), while market stops were subjected to maximum adverse excursion. A true DMA/STP environment yields a normal distribution of slippage; asymmetry confirms B-book latency injection.
Fill-Probability vs. Execution Drag: The secondary metric was the order rejection ratio during macroeconomic volatility (e.g., 08:30 EST data drops). Broker A exhibited an 8% rejection rate under the guise of "insufficient liquidity." Missing the fat-tail distribution of a high-conviction sweep destroys more portfolio EV than absorbing a marginally wider, but mathematically guaranteed, spread from a prime-of-prime liquidity provider.
Re: Broker scorecard quarterly update: spreads, rejects, and uptime
Real-Time Microstructure Cost Engine (Pine Script)
Native environments like C# cAlgo and MQL5 are fundamentally superior for this analysis, as they allow direct access to historical bid/ask tick arrays and millisecond-level execution logging. TradingView’s architecture isolates Pine Script from historical spread data, rendering traditional backtesting of execution costs impossible.
To circumvent this limitation, the engine must be built as a real-time sampling monitor. The script below computes theoretical execution drag dynamically by evaluating live liquidity conditions on the current tick.
Native environments like C# cAlgo and MQL5 are fundamentally superior for this analysis, as they allow direct access to historical bid/ask tick arrays and millisecond-level execution logging. TradingView’s architecture isolates Pine Script from historical spread data, rendering traditional backtesting of execution costs impossible.
To circumvent this limitation, the engine must be built as a real-time sampling monitor. The script below computes theoretical execution drag dynamically by evaluating live liquidity conditions on the current tick.
Code: Select all
//@version=5
//@description Real-time execution friction and liquidity spread monitor for M1/M5 scalping environments.
indicator("Microstructure Cost Engine", overlay=true, display=display.all)
// =============================================================================
// PARAMETERS & CONSTANTS
// =============================================================================
var string GRP_COST = "Execution Drag Parameters"
float commRT = input.float(7.0, title="Commission per Round Turn ($)", group=GRP_COST)
float slipEst = input.float(0.2, title="p95 Adverse Slippage Est. (Pips)", group=GRP_COST)
float pipValue = input.float(10.0, title="Standard Pip Value ($)", group=GRP_COST, tooltip="Defaults to $10 for standard 1.0 FX lot")
int tradesVol = input.int(100, title="Sample Size (Round Turns)", group=GRP_COST)
// =============================================================================
// LIQUIDITY SAMPLING
// =============================================================================
// TV exposes live ask/bid strictly on the real-time bar. Historical references return 'na'.
float liveSpreadPips = syminfo.mintick > 0 ? (ask - bid) / (syminfo.mintick * 10) : na
// =============================================================================
// EV DEGRADATION CALCULATION
// =============================================================================
float spreadCost = liveSpreadPips * pipValue * tradesVol
float commCost = commRT * tradesVol
float slipCost = slipEst * pipValue * tradesVol
float totalCost = spreadCost + commCost + slipCost
// =============================================================================
// UI ARCHITECTURE
// =============================================================================
var color bgCol = color.new(#000000, 85)
var color borderCol = color.rgb(45, 45, 45)
var color textMain = color.rgb(200, 200, 200)
var color textWarn = color.rgb(255, 82, 82)
var color textAlert = color.rgb(255, 215, 0)
var table dash = table.new(position.top_right, 2, 5, bgcolor=bgCol, border_color=borderCol, border_width=1)
// Initialize matrix architecture strictly on the first bar to optimize rendering overhead
if barstate.isfirst
table.cell(dash, 0, 0, "METRIC (" + str.tostring(tradesVol) + " RTs)", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
table.cell(dash, 1, 0, "CAPITAL DRAG", text_color=color.gray, text_halign=text.align_right, text_size=size.small)
table.cell(dash, 0, 1, "Real-Time Spread", text_color=textMain, text_halign=text.align_left, text_size=size.small)
table.cell(dash, 0, 2, "Base Commission", text_color=textMain, text_halign=text.align_left, text_size=size.small)
table.cell(dash, 0, 3, "Synthetic Slippage", text_color=textMain, text_halign=text.align_left, text_size=size.small)
table.cell(dash, 0, 4, "TOTAL FRICTION", text_color=color.white, text_halign=text.align_left, text_size=size.small)
// Push state updates exclusively on real-time ticks
if barstate.islast
table.cell(dash, 1, 1, str.tostring(spreadCost, format.mintick), text_color=textAlert, text_halign=text.align_right, text_size=size.small)
table.cell(dash, 1, 2, str.tostring(commCost, format.mintick), text_color=textMain, text_halign=text.align_right, text_size=size.small)
table.cell(dash, 1, 3, str.tostring(slipCost, format.mintick), text_color=textWarn, text_halign=text.align_right, text_size=size.small)
table.cell(dash, 1, 4, str.tostring(totalCost, format.mintick), text_color=textWarn, text_halign=text.align_right, text_size=size.small)Re: Broker scorecard quarterly update: spreads, rejects, and uptime
In MT4 and MT5, you have direct access to millisecond/microsecond hardware timers, tick-level spread sampling, and post-trade execution event loops (OnTradeTransaction in MQL5). This allows you to output hard numbers directly into a CSV for your quarterly scorecard instead of estimating.
Here is how to set up the empirical execution tracker in both environments.
1. MetaTrader 5 (MQL5) – Execution & Microstructure Logger
MT5 provides native transaction event hooks via OnTradeTransaction(). This Expert Advisor samples live spread into dynamic memory arrays, measures round-trip order execution latency in microseconds (GetMicrosecondCount()), calculates actual realized slippage against your pre-submission quote, and appends the raw audit trail to MQL5/Files/execution_scorecard.csv.
Here is how to set up the empirical execution tracker in both environments.
1. MetaTrader 5 (MQL5) – Execution & Microstructure Logger
MT5 provides native transaction event hooks via OnTradeTransaction(). This Expert Advisor samples live spread into dynamic memory arrays, measures round-trip order execution latency in microseconds (GetMicrosecondCount()), calculates actual realized slippage against your pre-submission quote, and appends the raw audit trail to MQL5/Files/execution_scorecard.csv.
Code: Select all
//+------------------------------------------------------------------+
//| ExecutionQualityAuditor.mq5 |
//| Microstructure Scorecard Logger |
//+------------------------------------------------------------------+
#property copyright "Proprietary Institutional Scalping Tool"
#property link ""
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
input group "=== Logging Parameters ==="
input string InpFileName = "execution_scorecard.csv"; // Audit CSV File
input double InpCommissionPerLot = 7.0; // Round-Turn Commission ($)
// State Tracking for Latency & Slippage
struct PendingExecution
{
ulong ticket;
ulong sendTimeMicroseconds;
double intendedPrice;
ENUM_ORDER_TYPE orderType;
};
PendingExecution lastOrder;
CTrade trade;
// Tick Spread Metrics
double spreadBuffer[];
int spreadSampleCount = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
ArrayResize(spreadBuffer, 0, 10000);
// Initialize CSV Header if newly created
int fileHandle = FileOpen(InpFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON, ",");
if(fileHandle != INVALID_HANDLE)
{
if(FileSize(fileHandle) == 0)
{
FileWrite(fileHandle, "Timestamp", "Symbol", "Action", "ExecutionTime_ms",
"IntendedPrice", "ExecutedPrice", "Slippage_Pts",
"Spread_Pts", "Comment");
}
FileClose(fileHandle);
}
Print("[AUDITOR] Initialized. Logging to Common/Files/", InpFileName);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function (Spread Sampling) |
//+------------------------------------------------------------------+
void OnTick()
{
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) return;
double currentSpreadPts = (tick.ask - tick.bid) / _Point;
spreadSampleCount++;
ArrayResize(spreadBuffer, spreadSampleCount);
spreadBuffer[spreadSampleCount - 1] = currentSpreadPts;
// Lightweight On-Chart Telemetry
Comment(StringFormat("=== EXECUTION QUALITY AUDITOR ===\n" +
"Symbol: %s\n" +
"Live Spread: %.1f pts\n" +
"Tick Samples: %d\n" +
"Log File: Common/Files/%s",
_Symbol, currentSpreadPts, spreadSampleCount, InpFileName));
}
//+------------------------------------------------------------------+
//| Execution Wrapper: Call this to track send -> fill metrics |
//+------------------------------------------------------------------+
bool ExecuteAuditedMarketOrder(ENUM_ORDER_TYPE orderType, double volume)
{
MqlTick tick;
SymbolInfoTick(_Symbol, tick);
lastOrder.orderType = orderType;
lastOrder.intendedPrice = (orderType == ORDER_TYPE_BUY) ? tick.ask : tick.bid;
lastOrder.sendTimeMicroseconds = GetMicrosecondCount();
bool result = false;
if(orderType == ORDER_TYPE_BUY)
result = trade.Buy(volume, _Symbol);
else if(orderType == ORDER_TYPE_SELL)
result = trade.Sell(volume, _Symbol);
return result;
}
//+------------------------------------------------------------------+
//| Trade Transaction Hook (Catches Actual Deal Fills) |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
{
ulong dealTicket = trans.deal;
if(dealTicket > 0)
{
ulong latencyMicroseconds = GetMicrosecondCount() - lastOrder.sendTimeMicroseconds;
double executionTimeMs = (double)latencyMicroseconds / 1000.0;
double fillPrice = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
double slippagePts = 0.0;
if(trans.deal_type == DEAL_TYPE_BUY)
slippagePts = (fillPrice - lastOrder.intendedPrice) / _Point;
else if(trans.deal_type == DEAL_TYPE_SELL)
slippagePts = (lastOrder.intendedPrice - fillPrice) / _Point;
MqlTick tick;
SymbolInfoTick(_Symbol, tick);
double liveSpread = (tick.ask - tick.bid) / _Point;
// Append to Scorecard CSV
int fileHandle = FileOpen(InpFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON, ",");
if(fileHandle != INVALID_HANDLE)
{
FileSeek(fileHandle, 0, SEEK_END);
FileWrite(fileHandle,
TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
_Symbol,
EnumToString(trans.deal_type),
DoubleToString(executionTimeMs, 2),
DoubleToString(lastOrder.intendedPrice, _Digits),
DoubleToString(fillPrice, _Digits),
DoubleToString(slippagePts, 1),
DoubleToString(liveSpread, 1),
"Deal #" + IntegerToString(dealTicket));
FileClose(fileHandle);
}
PrintFormat("[EXECUTION LOG] Fill: %s | Time: %.2f ms | Slippage: %.1f pts | Spread: %.1f pts",
_Symbol, executionTimeMs, slippagePts, liveSpread);
}
}
}Re: Broker scorecard quarterly update: spreads, rejects, and uptime
2. MetaTrader 4 (MQL4) – Execution Wrapper & Latency Logger
Because MT4 lacks the asynchronous OnTradeTransaction architecture, execution timing must be measured synchronously around OrderSend(). This script measures millisecond round-trip response time, verifies whether requotes (ERR_REQUOTE / 138) occurred, logs the exact price deviation, and flushes to disk.
Because MT4 lacks the asynchronous OnTradeTransaction architecture, execution timing must be measured synchronously around OrderSend(). This script measures millisecond round-trip response time, verifies whether requotes (ERR_REQUOTE / 138) occurred, logs the exact price deviation, and flushes to disk.
Code: Select all
//+------------------------------------------------------------------+
//| ExecutionQualityAuditor.mq4 |
//| Microstructure Scorecard Logger |
//+------------------------------------------------------------------+
#property copyright "Proprietary Institutional Scalping Tool"
#property link ""
#property version "1.00"
#property strict
input string InpFileName = "execution_scorecard_mt4.csv";
input double InpCommissionPerLot = 7.0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
int fileHandle = FileOpen(InpFileName, FILE_READ|FILE_WRITE|FILE_CSV, ",");
if(fileHandle != INVALID_HANDLE)
{
if(FileSize(fileHandle) == 0)
{
FileWrite(fileHandle, "Timestamp", "Symbol", "Type", "ExecutionTime_ms",
"IntendedPrice", "ExecutedPrice", "Slippage_Pts",
"Spread_Pts", "ResultCode");
}
FileClose(fileHandle);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Synchronous Execution & Latency Hook |
//+------------------------------------------------------------------+
int SendAuditedMarketOrder(int cmd, double volume, int slippageAllowed)
{
RefreshRates();
double intendedPrice = (cmd == OP_BUY) ? Ask : Bid;
double currentSpread = (Ask - Bid) / Point;
uint startTick = GetTickCount();
int ticket = OrderSend(Symbol(), cmd, volume, intendedPrice, slippageAllowed, 0, 0, "ScorecardAudit", 0, 0, clrNONE);
uint durationMs = GetTickCount() - startTick;
int lastErr = GetLastError();
double executedPrice = 0.0;
double realizedSlippagePts = 0.0;
if(ticket > 0)
{
if(OrderSelect(ticket, SELECT_BY_TICKET))
{
executedPrice = OrderOpenPrice();
if(cmd == OP_BUY)
realizedSlippagePts = (executedPrice - intendedPrice) / Point;
else if(cmd == OP_SELL)
realizedSlippagePts = (intendedPrice - executedPrice) / Point;
}
}
// Write to Audit Log
int fileHandle = FileOpen(InpFileName, FILE_READ|FILE_WRITE|FILE_CSV, ",");
if(fileHandle != INVALID_HANDLE)
{
FileSeek(fileHandle, 0, SEEK_END);
FileWrite(fileHandle,
TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
Symbol(),
(cmd == OP_BUY ? "BUY" : "SELL"),
IntegerToString(durationMs),
DoubleToString(intendedPrice, Digits),
DoubleToString(executedPrice, Digits),
DoubleToString(realizedSlippagePts, 1),
DoubleToString(currentSpread, 1),
(ticket > 0 ? "SUCCESS" : "ERROR_" + IntegerToString(lastErr)));
FileClose(fileHandle);
}
return ticket;
}
//+------------------------------------------------------------------+
//| OnTick Display |
//+------------------------------------------------------------------+
void OnTick()
{
double spread = (Ask - Bid) / Point;
Comment(StringFormat("MT4 Microstructure Logger\nSpread: %.1f pts\nFile: MQL4/Files/%s",
spread, InpFileName));
}Re: Broker scorecard quarterly update: spreads, rejects, and uptime
How to Pipe This into Your Quarterly Scorecard
Calculate True p95 Slippage: In your CSV sheet, run =PERCENTILE.INC(FILTER(Slippage_Pts, Action="BUY"), 0.95). Compare this against the median spread column. If your median spread is 0.2 pips (2 pts) but p95 slippage is 0.9 pips (9 pts), your actual execution cost is 1.1 pips per entry before commission.
Rejection / Requote Frequency: Filter ResultCode for error codes (in MT4: 138 Requote, 136 Off quotes; in MT5: TRADE_RETCODE_REQUOTE, TRADE_RETCODE_PRICE_OFF).
Execution Latency Profiling: Check if ExecutionTime_ms correlates with trade direction or market volatility bursts. If fills are 20ms during calm sessions but jump to 450ms+ on news sweeps, the broker is routing your flow into a virtual dealer plugin to inject delay.
Calculate True p95 Slippage: In your CSV sheet, run =PERCENTILE.INC(FILTER(Slippage_Pts, Action="BUY"), 0.95). Compare this against the median spread column. If your median spread is 0.2 pips (2 pts) but p95 slippage is 0.9 pips (9 pts), your actual execution cost is 1.1 pips per entry before commission.
Rejection / Requote Frequency: Filter ResultCode for error codes (in MT4: 138 Requote, 136 Off quotes; in MT5: TRADE_RETCODE_REQUOTE, TRADE_RETCODE_PRICE_OFF).
Execution Latency Profiling: Check if ExecutionTime_ms correlates with trade direction or market volatility bursts. If fills are 20ms during calm sessions but jump to 450ms+ on news sweeps, the broker is routing your flow into a virtual dealer plugin to inject delay.
Re: Broker scorecard quarterly update: spreads, rejects, and uptime
Moving this execution tracker into cTrader provides a massive architectural advantage. Because cAlgo runs natively on the .NET framework, you can bypass proprietary platform timers entirely and utilize OS-level hardware counters for high-resolution latency tracking.
By leveraging System.Diagnostics.Stopwatch, we can measure the exact execution drag from the moment the method is called to the moment the FIX API returns the deal confirmation. Furthermore, native System.IO access makes flushing the telemetry to a local CSV completely seamless.
Here is the institutional execution wrapper configured as a cBot. You can run this alongside your manual trading routines to intercept, time, and log every market execution.
cTrader (C# cAlgo) – .NET Microstructure Auditor
Note: You must set the bot’s access rights to AccessRights.FileSystem to allow it to write the CSV to your Documents folder.
By leveraging System.Diagnostics.Stopwatch, we can measure the exact execution drag from the moment the method is called to the moment the FIX API returns the deal confirmation. Furthermore, native System.IO access makes flushing the telemetry to a local CSV completely seamless.
Here is the institutional execution wrapper configured as a cBot. You can run this alongside your manual trading routines to intercept, time, and log every market execution.
cTrader (C# cAlgo) – .NET Microstructure Auditor
Note: You must set the bot’s access rights to AccessRights.FileSystem to allow it to write the CSV to your Documents folder.
Code: Select all
using System;
using System.Diagnostics;
using System.IO;
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
public class ExecutionQualityAuditor : Robot
{
[Parameter("Audit Log Filename", DefaultValue = "cTrader_Execution_Scorecard.csv")]
public string LogFileName { get; set; }
private string _filePath;
protected override void OnStart()
{
// Route log to the standard Windows Documents folder
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
_filePath = Path.Combine(docPath, LogFileName);
// Initialize CSV header if the file is new
if (!File.Exists(_filePath))
{
string header = "Timestamp,Symbol,Direction,Latency_ms,IntendedPrice,ExecutedPrice,Slippage_Pips,Spread_Pips,Status\n";
File.WriteAllText(_filePath, header);
}
Print($"[AUDITOR] Tracking initialized. Writing to: {_filePath}");
}
// --- Execution Wrapper ---
// Call this method instead of native ExecuteMarketOrder when entering positions
public void SendAuditedMarketOrder(TradeType tradeType, double volumeInUnits)
{
// Sample exact liquidity state immediately prior to submission
double intendedPrice = tradeType == TradeType.Buy ? Symbol.Ask : Symbol.Bid;
double currentSpreadPips = (Symbol.Ask - Symbol.Bid) / Symbol.PipSize;
// Utilize .NET high-resolution timer for strict latency profiling
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
// Synchronous execution dispatch
TradeResult result = ExecuteMarketOrder(tradeType, SymbolName, volumeInUnits, "ScorecardAudit");
stopwatch.Stop();
double latencyMs = stopwatch.Elapsed.TotalMilliseconds;
if (result.IsSuccessful)
{
double executedPrice = result.Position.EntryPrice;
double slippagePips = 0;
if (tradeType == TradeType.Buy)
slippagePips = (executedPrice - intendedPrice) / Symbol.PipSize;
else
slippagePips = (intendedPrice - executedPrice) / Symbol.PipSize;
AppendToScorecard(tradeType.ToString(), latencyMs, intendedPrice, executedPrice, slippagePips, currentSpreadPips, "FILLED");
}
else
{
// Capture the specific FIX rejection reason
AppendToScorecard(tradeType.ToString(), latencyMs, intendedPrice, 0, 0, currentSpreadPips, $"REJECTED_{result.Error}");
}
}
private void AppendToScorecard(string direction, double latency, double intended, double executed, double slippage, double spread, string status)
{
// Format telemetry for easy quarter-end aggregation in Excel/Sheets
string record = $"{Server.Time:yyyy-MM-dd HH:mm:ss.fff},{SymbolName},{direction},{latency:F2},{intended},{executed},{slippage:F2},{spread:F2},{status}\n";
File.AppendAllText(_filePath, record);
// Push immediate terminal feedback
Print($"[EXECUTION] {direction} | Latency: {latency:F2} ms | Slippage: {slippage:F2} pips | Spread: {spread:F2} pips");
}
protected override void OnTick()
{
// Maintain a live telemetry overlay on the active chart
double spread = (Symbol.Ask - Symbol.Bid) / Symbol.PipSize;
string dashText = $"cTrader Execution Auditor\nLive Spread: {spread:F1} pips\nTarget: {LogFileName}";
Chart.DrawStaticText("audit_dash", dashText, VerticalAlignment.Top, HorizontalAlignment.Right, Color.DimGray);
}
}
}