Advertisement IC Markets

Tagging slippage separately from spread in the trade journal

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging slippage separately from spread in the trade journal

Post by PTScalper »

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?
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging slippage separately from spread in the trade journal

Post by PTScalper »

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)

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.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging slippage separately from spread in the trade journal

Post by PTScalper »

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.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging slippage separately from spread in the trade journal

Post by PTScalper »

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.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging slippage separately from spread in the trade journal

Post by PTScalper »

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.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging slippage separately from spread in the trade journal

Post by PTScalper »

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.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging slippage separately from spread in the trade journal

Post by PTScalper »

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?
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

Post by LondonScalper »

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.
Dynamic thresholds beat fixed pip rules — context changes by session and pair.

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

Post by PropScalpDesk »

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.
Tagging slip separate from spread in the journal made my broker comparisons honest. Without it I blamed strategy for venue behaviour.

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

Post by LondonNewsTrader »

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 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.

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.
Post Reply