Tagging slippage separately from spread in the trade journal
Re: Tagging slippage separately from spread in the trade journal
During your weekly reconciliation, map your execution timestamps to this script's data window. If an execution slip exceeds the TCA Shortfall Limit line—especially inside a flagged Z-score regime—you process it objectively based on the matrix above.
At what sample size of these localized execution failures do you currently step in and rewrite the routing logic for a specific asset?
At what sample size of these localized execution failures do you currently step in and rewrite the routing logic for a specific asset?
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Tagging slippage separately from spread in the trade journal
Porting this to the MetaQuotes ecosystem actually gives you a distinct advantage. Because MetaTrader operates directly with broker-supplied data feeds (unlike TradingView’s aggregated third-party feeds), your Transaction Cost Analysis (TCA) becomes an exact audit of your specific broker's server conditions.
To execute this properly in MT4 and MT5, we must shift the nomenclature from "Ticks" to "Points" (_Point in MQL). For a standard 5-digit broker, 1 Pip equals 10 Points.
I. MQL4 Implementation (Quant_TCA.mq4)
To execute this properly in MT4 and MT5, we must shift the nomenclature from "Ticks" to "Points" (_Point in MQL). For a standard 5-digit broker, 1 Pip equals 10 Points.
I. MQL4 Implementation (Quant_TCA.mq4)
Code: Select all
//+------------------------------------------------------------------+
//| Quant_TCA.mq4 |
//| Institutional Execution Shortfall Model |
//+------------------------------------------------------------------+
#property copyright "Execution Desk TCA"
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_color1 clrRed // Dynamic Limit
#property indicator_color2 clrGray // Base Spread
#property indicator_color3 clrCrimson // Illiquidity Regime
input double InpBaseSpread = 20.0; // Expected Base Cost (In POINTS)
input int InpVolPeriod = 20; // Variance Lookback
input double InpSlipTolerance = 0.15; // Shortfall Tolerance Coefficient (0.15 = 15%)
input double InpZScoreLimit = 2.0; // Illiquidity Z-Score Threshold
double ExtDynamicLimit[];
double ExtBaseSpread[];
double ExtRegime[];
int OnInit() {
SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2);
SetIndexBuffer(0, ExtDynamicLimit);
SetIndexLabel(0, "TCA Shortfall Limit (Points)");
SetIndexStyle(1, DRAW_LINE, STYLE_DOT, 1);
SetIndexBuffer(1, ExtBaseSpread);
SetIndexLabel(1, "Theoretical Base Cost (Points)");
SetIndexStyle(2, DRAW_HISTOGRAM, STYLE_SOLID, 3);
SetIndexBuffer(2, ExtRegime);
SetIndexLabel(2, "Illiquidity Regime Warning");
IndicatorShortName("Quant TCA (" + IntegerToString(InpVolPeriod) + ")");
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total, const int prev_calculated,
const datetime &time[], const double &open[],
const double &high[], const double &low[],
const double &close[], const long &tick_volume[],
const long &volume[], const int &spread[]) {
if(rates_total <= InpVolPeriod) return(0);
int limit = rates_total - prev_calculated;
if(prev_calculated > 0) limit++;
for(int i = limit - 1; i >= 0; i--) {
double atrPoints = iATR(NULL, 0, InpVolPeriod, i) / Point;
// Calculate Rolling Mean
double sum = 0.0;
for(int j = 0; j < InpVolPeriod; j++) {
sum += (iATR(NULL, 0, InpVolPeriod, i + j) / Point);
}
double mean = sum / InpVolPeriod;
// Calculate Variance and Standard Deviation
double devSum = 0.0;
for(int j = 0; j < InpVolPeriod; j++) {
double val = (iATR(NULL, 0, InpVolPeriod, i + j) / Point);
devSum += MathPow(val - mean, 2);
}
double stdDev = MathSqrt(devSum / InpVolPeriod);
// Z-Score Calculation
double zScore = (stdDev == 0) ? 0 : (atrPoints - mean) / stdDev;
// Plotting
ExtBaseSpread[i] = InpBaseSpread;
ExtDynamicLimit[i] = InpBaseSpread + (atrPoints * InpSlipTolerance);
// Regime Filter
if(zScore >= InpZScoreLimit) {
ExtRegime[i] = ExtDynamicLimit[i];
} else {
ExtRegime[i] = 0;
}
}
return(rates_total);
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Tagging slippage separately from spread in the trade journal
II. MQL5 Implementation (Quant_TCA.mq5)
Code: Select all
//+------------------------------------------------------------------+
//| Quant_TCA.mq5 |
//| Institutional Execution Shortfall Model |
//+------------------------------------------------------------------+
#property copyright "Execution Desk TCA"
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_plots 3
#property indicator_label1 "TCA Shortfall Limit"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrRed
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_label2 "Theoretical Base Cost"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrGray
#property indicator_style2 STYLE_DOT
#property indicator_label3 "Illiquidity Regime"
#property indicator_type3 DRAW_HISTOGRAM
#property indicator_color3 clrCrimson
#property indicator_width3 3
input double InpBaseSpread = 20.0; // Expected Base Cost (In POINTS)
input int InpVolPeriod = 20; // Variance Lookback
input double InpSlipTolerance = 0.15; // Shortfall Tolerance Coefficient
input double InpZScoreLimit = 2.0; // Illiquidity Z-Score Threshold
double ExtDynamicLimit[];
double ExtBaseSpread[];
double ExtRegime[];
double ExtATRBuffer[];
int atrHandle;
int OnInit() {
SetIndexBuffer(0, ExtDynamicLimit, INDICATOR_DATA);
SetIndexBuffer(1, ExtBaseSpread, INDICATOR_DATA);
SetIndexBuffer(2, ExtRegime, INDICATOR_DATA);
SetIndexBuffer(3, ExtATRBuffer, INDICATOR_CALCULATIONS);
atrHandle = iATR(_Symbol, _Period, InpVolPeriod);
if(atrHandle == INVALID_HANDLE) return(INIT_FAILED);
IndicatorSetString(INDICATOR_SHORTNAME, "Quant TCA");
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
const double &open[], const double &high[], const double &low[],
const double &close[], const long &tick_volume[], const long &volume[],
const int &spread[]) {
if(rates_total < InpVolPeriod) return(0);
if(CopyBuffer(atrHandle, 0, 0, rates_total, ExtATRBuffer) <= 0) return 0;
int limit = prev_calculated == 0 ? InpVolPeriod : prev_calculated - 1;
double _point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
for(int i = limit; i < rates_total; i++) {
double atrPoints = ExtATRBuffer[i] / _point;
// Rolling Mean
double sum = 0.0;
for(int j = 0; j < InpVolPeriod; j++) {
sum += (ExtATRBuffer[i - j] / _point);
}
double mean = sum / InpVolPeriod;
// Variance & Standard Deviation
double devSum = 0.0;
for(int j = 0; j < InpVolPeriod; j++) {
double val = (ExtATRBuffer[i - j] / _point);
devSum += MathPow(val - mean, 2);
}
double stdDev = MathSqrt(devSum / InpVolPeriod);
// Z-Score Calculation
double zScore = (stdDev == 0) ? 0 : (atrPoints - mean) / stdDev;
ExtBaseSpread[i] = InpBaseSpread;
ExtDynamicLimit[i] = InpBaseSpread + (atrPoints * InpSlipTolerance);
// Regime Filter Visualization
if(zScore >= InpZScoreLimit) {
ExtRegime[i] = ExtDynamicLimit[i];
} else {
ExtRegime[i] = 0.0;
}
}
return(rates_total);
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Tagging slippage separately from spread in the trade journal
Since MetaQuotes stores exact spread histories directly inside its tick databases, are you currently using MQL scripts to log your execution data to a local CSV, or are you still matching up trade tickets manually during your weekly review?
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Tagging slippage separately from spread in the trade journal
Porting this to cTrader is where this methodology truly aligns with institutional workflows. Because cAlgo is natively built on C# and .NET, you avoid the sandbox limitations of MQL and the abstraction of Pine Script. You have direct access to asynchronous order routing and millisecond-level execution timestamps, allowing you to eventually integrate this TCA logic directly into your execution classes.
In cTrader, we normalize the variance using Symbol.PipSize instead of calculating point differentials, which standardizes the output across forex pairs, metals, and indices regardless of the broker's price digit feed.
Here is the C# source code for the custom indicator. It uses cTrader’s IndicatorDataSeries to calculate the rolling standard deviation array efficiently on every tick.
In cTrader, we normalize the variance using Symbol.PipSize instead of calculating point differentials, which standardizes the output across forex pairs, metals, and indices regardless of the broker's price digit feed.
Here is the C# source code for the custom indicator. It uses cTrader’s IndicatorDataSeries to calculate the rolling standard deviation array efficiently on every tick.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Tagging slippage separately from spread in the trade journal
C# cAlgo Implementation (QuantTCA.cs)
Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo
{
[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class QuantTCA : Indicator
{
[Parameter("Expected Base Cost (Pips)", DefaultValue = 2.0)]
public double BaseSpreadPips { get; set; }
[Parameter("Variance Lookback", DefaultValue = 20)]
public int VolPeriod { get; set; }
[Parameter("Shortfall Tolerance (0.15 = 15%)", DefaultValue = 0.15)]
public double SlipTolerance { get; set; }
[Parameter("Illiquidity Z-Score Threshold", DefaultValue = 2.0)]
public double ZScoreLimit { get; set; }
[Output("TCA Shortfall Limit", LineColor = "Red", Thickness = 2, PlotType = PlotType.Line)]
public IndicatorDataSeries DynamicLimit { get; set; }
[Output("Theoretical Base Cost", LineColor = "Gray", LineStyle = LineStyle.Lines, PlotType = PlotType.Line)]
public IndicatorDataSeries BaseSpreadLine { get; set; }
[Output("Illiquidity Regime Warning", LineColor = "Crimson", Thickness = 3, PlotType = PlotType.Histogram)]
public IndicatorDataSeries RegimeWarning { get; set; }
private AverageTrueRange _atr;
private IndicatorDataSeries _atrPips;
protected override void Initialize()
{
_atr = Indicators.AverageTrueRange(VolPeriod, MovingAverageType.Simple);
_atrPips = CreateDataSeries();
}
public override void Calculate(int index)
{
// Ensure enough data for the lookback period
if (index < VolPeriod) return;
// 1. Convert current ATR to Pips for normalized scaling
double currentAtrPips = _atr.Result[index] / Symbol.PipSize;
_atrPips[index] = currentAtrPips;
// 2. Calculate Rolling Mean (SMA) of ATR
double sum = 0;
for (int i = 0; i < VolPeriod; i++)
{
sum += _atrPips[index - i];
}
double mean = sum / VolPeriod;
// 3. Calculate Variance and Standard Deviation
double sqDiffSum = 0;
for (int i = 0; i < VolPeriod; i++)
{
double diff = _atrPips[index - i] - mean;
sqDiffSum += diff * diff;
}
double stdDev = Math.Sqrt(sqDiffSum / VolPeriod);
// 4. Dynamic Z-Score Calculation
double zScore = (stdDev == 0) ? 0 : (currentAtrPips - mean) / stdDev;
// 5. Visualization Assignments
BaseSpreadLine[index] = BaseSpreadPips;
DynamicLimit[index] = BaseSpreadPips + (currentAtrPips * SlipTolerance);
// 6. Regime Filter Logic: Plot histogram only during execution danger zones
if (zScore >= ZScoreLimit)
{
RegimeWarning[index] = DynamicLimit[index];
}
else
{
RegimeWarning[index] = 0;
}
}
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Tagging slippage separately from spread in the trade journal
Because cTrader operates within a full .NET environment, you can take this a step further. Instead of just visualizing the threshold on the chart, you can inject this logic directly into an automated cBot script to programmatically intercept and reject any ExecuteMarketOrderAsync calls if zScore >= ZScoreLimit, forcing the bot to fall back to a PlaceLimitOrderAsync command instead.
Are you manually reviewing your cTrader execution data natively through the platform's history tab, or are you exporting the trade history objects into a custom database for your weekly reviews?
Are you manually reviewing your cTrader execution data natively through the platform's history tab, or are you exporting the trade history objects into a custom database for your weekly reviews?
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
-
LondonScalper
- Posts: 701
- Joined: Sat Sep 05, 2026 7:54 am
Re: Tagging slippage separately from spread in the trade journal
Dynamic thresholds beat fixed pip rules — context changes by session and pair.PTScalper wrote:Slippage at or below 10–15% of 1-minute ATR is normal noise; above 15% merits execution review. Flag spreads above 2.5× the session average; negative-slippage limits or rejects go into a broker-issue bucket.
I separate spread-at-click from side-aware fill slippage in the journal, and I tag rejects/requotes in their own bucket. Recurring bad windows get reviewed weekly; a one-off spike on NFP is noise, the same window every London open is a venue problem.
Concrete process: each fill logs intended price, fill price, spread at click, and ATR(1m) at entry. Slip ≤ ~15% of that ATR is tagged noise; above that, or any reject, goes to execution review. Spreads past 2.5× session median are flagged even when the fill looks fine — that cost still ate the edge.
Rule: tag slip and spread apart; ATR sets the review line. Do you adjust the 15% ATR cut by order type (market vs limit), or keep one threshold across both?
-
PropScalpDesk
- Posts: 273
- Joined: Sat Sep 19, 2026 7:50 pm
Re: Tagging slippage separately from spread in the trade journal
Tagging slip separate from spread in the journal made my broker comparisons honest. Without it I blamed strategy for venue behaviour.PTScalper wrote:III. Quantitative TCA Pine Script To systemize this review process, the following logic models an institutional slippage threshold using Z-scores and volatility benchmarking.
Prop reviews look better when the tags are clean.
Do you tag rejects as their own line item too?
I also log refused tickets so flat time counts as work — otherwise the desk invents activity.
I would rather log a refused ticket than invent activity for the journal.
I write the walk-away before London so it is not negotiated mid-tape.
If the idea needs a story longer than one line, it waits for another window.
Topic note from my sheet for t=12346: keep risk unchanged until the sample says otherwise.
-
LondonNewsTrader
- Posts: 79
- Joined: Mon Sep 21, 2026 9:30 am
Re: Tagging slippage separately from spread in the trade journal
The dynamic threshold is a sensible idea: allowing more slippage when the market moves faster is fairer than one fixed number. I'd be careful with the conclusion drawn from it, though.PTScalper wrote:III. Quantitative TCA Pine Script To systemize this review process, the following logic models an institutional slippage threshold using Z-scores and volatility benchmarking.
The script measures volatility, ATR in ticks and its z-score, not liquidity. High volatility and thin liquidity often coincide, but not always. A steady trending morning after a data release can show a high ATR z-score with a perfectly deep book, and a quiet pre-holiday afternoon can show low ATR with wide spreads and poor fills. So calling slippage inside the highlighted zones trader timing error rather than a broker issue is a stronger claim than the inputs support. It might be either, and the whole point of the opening post's two tags is being able to tell.
It becomes much more useful as a benchmark for the journal than as a verdict. Take the slippage actually recorded on each ticket, compare it with the dynamic shortfall limit on that bar, and count how often you exceeded it inside and outside the flagged regime. If the exceedances cluster in calm bars, that's a broker conversation.
Also, a base of 2.0 ticks is only 0.2 pips on a five-digit pair, which is optimistic for most retail accounts.