Page 1 of 2

cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 9:55 pm
by LondonScalper
cTrader liquidity differences that showed up in my gold log

I moved some gold size onto cTrader to see whether the DOM story matched the fill story. The honest answer after a proper log: sometimes the book looked deep and the fill still hurt; sometimes a thinner-looking moment filled cleanly. Marketing screenshots did not predict my tails.

What I log now on gold via cTrader:
  • Quoted spread at click versus effective cost including slippage.
  • Partial fills — and whether my accept-versus-cancel policy was followed under stress.
  • Time-of-day clusters when the book goes theatrical relative to London cash hours.
Platform choice is part of cost, not aesthetics. If the ladder entertains you but the histogram does not improve, you bought a toy.

If you scalp gold on cTrader, what liquidity quirk actually showed up in your log rather than in marketing screenshots?

I compare those gold logs against the same hours on MT5 with the same size so the platform difference is isolated. Otherwise you end up blaming liquidity for a session-character change.

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:19 pm
by PTScalper
LondonScalper wrote: Sat Sep 19, 2026 9:55 pm cTrader liquidity differences that showed up in my gold log

I moved some gold size onto cTrader to see whether the DOM story matched the fill story. The honest answer after a proper log: sometimes the book looked deep and the fill still hurt; sometimes a thinner-looking moment filled cleanly. Marketing screenshots did not predict my tails.

What I log now on gold via cTrader:
  • Quoted spread at click versus effective cost including slippage.
  • Partial fills — and whether my accept-versus-cancel policy was followed under stress.
  • Time-of-day clusters when the book goes theatrical relative to London cash hours.
Platform choice is part of cost, not aesthetics. If the ladder entertains you but the histogram does not improve, you bought a toy.

If you scalp gold on cTrader, what liquidity quirk actually showed up in your log rather than in marketing screenshots?

I compare those gold logs against the same hours on MT5 with the same size so the platform difference is isolated. Otherwise you end up blaming liquidity for a session-character change.
Hello LondonScalper,

This is a fantastic approach. Running simultaneous A/B testing on MT5 and cTrader with the exact same size and session hours is the only way to isolate the platform's routing mechanics from natural market conditions.

To answer your question directly about the specific liquidity quirk I’ve logged on cTrader: it’s the "phantom depth" during structural liquidity sweeps.

Because I frame my trades heavily around raw price action and structural sweeps on the 15-minute and daily charts, I look for very specific reaction zones. When you zoom in to execute those zones on cTrader, the DOM often puts on a magic show right at those key structural levels. The ladder displays a massive wall of resting limit orders, making the book look incredibly thick and safe to lean on. You click to enter, and you still catch severe slippage.

Why? Because the liquidity providers pull those quotes milliseconds before your market order hits the matching engine. The DOM is just rendering high-frequency spoofing that retail clicks can't actually interact with.

I ended up building custom automated execution and order rejection logging tools in both C# (for cAlgo) and MQL5 to get to the bottom of this exact discrepancy. My logs showed exactly what yours are hinting at:

The visual depth of the ladder has almost zero correlation with effective fill cost during active London hours.

cTrader’s VWAP filling logic across the book can actually result in worse effective costs compared to MT5 when your size gets slightly larger, entirely because that top-of-book liquidity was an illusion.

The "thin" book often fills cleaner simply because it represents real, un-spoofed baseline liquidity rather than algorithmic posturing.

Your conclusion that the platform is a cost factor, not an aesthetic choice, is spot on. Staring at the DOM can often just be a distraction from the raw price action. If your logs are proving that the cTrader ladder isn't actually translating to a tangible execution edge over MT5, then MT5 is likely the more efficient execution environment for your broker's specific LP routing.

I'd be curious to hear if your logs show a difference in the exact milliseconds of order rejection between the two platforms when the book gets highly theatrical.

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:21 pm
by PTScalper
These drop-and-play scripts execute a single market order and dump the exact millisecond latency, point slippage, and fill efficiency directly into the Experts log. By isolating the time it takes the OrderSend request to return, you can distinguish pure broker routing latency from your own network ping, while capturing exact slippage against the quoted spread at the moment of the click.

MT5 Execution Logger

MQL5 provides microsecond-level timestamps and natively exposes partial fills in the result structure. This script automatically detects your broker's allowed order filling mode (FOK, IOC, or Return) to avoid immediate rejections.

Code: Select all

//+------------------------------------------------------------------+
//|                                     MT5_Execution_Logger.mq5     |
//+------------------------------------------------------------------+
#property script_show_inputs

input double InpLotSize = 0.01;      // Trade Volume
input ulong  InpMagicNumber = 12345; // Magic Number
input ulong  InpMaxDeviation = 50;   // Max Slippage (Points)

void OnStart()
{
    string symbol = _Symbol;
    if(!SymbolInfoInteger(symbol, SYMBOL_SELECT))
        SymbolSelect(symbol, true);

    double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
    double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
    int spread = (int)SymbolInfoInteger(symbol, SYMBOL_SPREAD);

    MqlTradeRequest request = {};
    MqlTradeResult  result  = {};
    
    request.action       = TRADE_ACTION_DEAL;
    request.symbol       = symbol;
    request.volume       = InpLotSize;
    request.type         = ORDER_TYPE_BUY; // Change to ORDER_TYPE_SELL for short testing
    request.price        = ask;
    request.deviation    = InpMaxDeviation;
    request.magic        = InpMagicNumber;
    
    // Auto-detect allowed broker filling mode to prevent order rejection
    int fillMode = (int)SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
    if((fillMode & SYMBOL_FILLING_FOK) != 0) 
        request.type_filling = ORDER_FILLING_FOK;
    else if((fillMode & SYMBOL_FILLING_IOC) != 0) 
        request.type_filling = ORDER_FILLING_IOC;
    else 
        request.type_filling = ORDER_FILLING_RETURN;

    Print("--- [MT5 Execution Test Initiated] ---");
    PrintFormat("Quoted Ask: %f | Quoted Bid: %f | Spread: %d points", ask, bid, spread);
    
    ulong startTick = GetMicrosecondCount(); 
    
    bool sent = OrderSend(request, result);
    
    ulong endTick = GetMicrosecondCount();
    double latencyMs = (endTick - startTick) / 1000.0;
    
    if(sent && result.retcode == TRADE_RETCODE_DONE)
    {
        double fillPrice = result.price;
        double slippagePoints = (fillPrice - ask) / point;
        
        PrintFormat("SUCCESS | Latency: %.3f ms | RetCode: %d", latencyMs, result.retcode);
        PrintFormat("Fill Price: %f | Slippage: %.1f points | Fill Vol: %.2f / Req Vol: %.2f", 
                    fillPrice, slippagePoints, result.volume, request.volume);
                    
        if (result.volume < request.volume)
            Print("WARNING: Partial Fill Detected!");
    }
    else
    {
        PrintFormat("REJECTED | Latency: %.3f ms | RetCode: %d | Comment: %s", 
                    latencyMs, result.retcode, result.comment);
    }
    Print("--------------------------------------");
}

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:21 pm
by PTScalper
MT4 Execution Logger

MT4 uses millisecond-level timestamps. Because MT4 does not natively structure partial fills in the same asynchronous way as MT5, the script fetches the final fill directly from the trade pool using OrderSelect.

Code: Select all

//+------------------------------------------------------------------+
//|                                     MT4_Execution_Logger.mq4     |
//+------------------------------------------------------------------+
#property script_show_inputs

input double InpLotSize = 0.01;      // Trade Volume
input int    InpMagicNumber = 12345; // Magic Number
input int    InpMaxDeviation = 50;   // Max Slippage (Points)

void OnStart()
{
    string symbol = Symbol();
    double ask = MarketInfo(symbol, MODE_ASK);
    double bid = MarketInfo(symbol, MODE_BID);
    double point = Point;
    int spread = (int)MarketInfo(symbol, MODE_SPREAD);

    Print("--- [MT4 Execution Test Initiated] ---");
    PrintFormat("Quoted Ask: %f | Quoted Bid: %f | Spread: %d points", ask, bid, spread);
    
    uint startTick = GetTickCount(); 
    
    int ticket = OrderSend(symbol, OP_BUY, InpLotSize, ask, InpMaxDeviation, 0, 0, "ExecTest", InpMagicNumber, 0, clrNONE);
    
    uint endTick = GetTickCount();
    uint latencyMs = endTick - startTick;
    
    if(ticket > 0)
    {
        if(OrderSelect(ticket, SELECT_BY_TICKET))
        {
            double fillPrice = OrderOpenPrice();
            double slippagePoints = (fillPrice - ask) / point;
            
            PrintFormat("SUCCESS | Latency: %d ms | Ticket: %d", latencyMs, ticket);
            PrintFormat("Fill Price: %f | Slippage: %.1f points | Req Vol: %.2f", 
                        fillPrice, slippagePoints, InpLotSize);
        }
        else
        {
            PrintFormat("Order sent but failed to select ticket %d. Error: %d", ticket, GetLastError());
        }
    }
    else
    {
        PrintFormat("REJECTED | Latency: %d ms | Error Code: %d", latencyMs, GetLastError());
    }
    Print("--------------------------------------");
}

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:22 pm
by PTScalper
Both scripts test a standard Buy execution. To test liquidity on the bid side of the book, change ORDER_TYPE_BUY to ORDER_TYPE_SELL in MT5, or OP_BUY to OP_SELL (and swap the target execution price to bid) in MT4.

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:22 pm
by PTScalper
Because pending orders execute asynchronously when price hits the trigger level, the architecture of these scripts shifts from instant-return measurement to a polling loop. The script places the order at a defined distance from current price, then waits (up to a defined timeout) for the market to sweep into the order, capturing the delta between your requested price and the actual fill.

A critical structural difference: MT4 does not natively support Stop Limit orders. The MT4 script focuses purely on Stop orders, while the MT5 script handles both Stop and Stop Limit execution types.

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:23 pm
by PTScalper
MT5 Pending Order Execution Logger

This script places a pending order and monitors the trade history for the exact deal that consumed the pending ticket. If you select a Stop Limit order, it uses InpStopLimitOffset to define the limit price relative to the trigger price.

Code: Select all

//+------------------------------------------------------------------+
//|                               MT5_Pending_Execution_Logger.mq5   |
//+------------------------------------------------------------------+
#property script_show_inputs

enum ENUM_PENDING_TYPE
  {
   TEST_BUY_STOP = ORDER_TYPE_BUY_STOP,
   TEST_BUY_STOP_LIMIT = ORDER_TYPE_BUY_STOP_LIMIT
  };

input ENUM_PENDING_TYPE InpOrderType = TEST_BUY_STOP;
input double InpLotSize = 0.01;      
input int    InpDistancePoints = 30;     // Distance from price to place order
input int    InpStopLimitOffset = 10;    // Offset for Stop Limit (Points)
input int    InpTimeoutSeconds = 120;    // Max time to wait for trigger
input ulong  InpMagicNumber = 12345; 

void OnStart()
{
    string symbol = _Symbol;
    double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
    double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
    
    double requestedPrice = ask + (InpDistancePoints * point);
    double limitPrice = requestedPrice - (InpStopLimitOffset * point);

    MqlTradeRequest request = {};
    MqlTradeResult  result  = {};
    
    request.action       = TRADE_ACTION_PENDING;
    request.symbol       = symbol;
    request.volume       = InpLotSize;
    request.type         = (ENUM_ORDER_TYPE)InpOrderType;
    request.price        = requestedPrice;
    request.magic        = InpMagicNumber;
    
    if(InpOrderType == TEST_BUY_STOP_LIMIT)
        request.stoplimit = limitPrice;
        
    Print("--- [MT5 Pending Execution Test Initiated] ---");
    PrintFormat("Placing %s at: %f", EnumToString((ENUM_ORDER_TYPE)InpOrderType), requestedPrice);
    
    if(!OrderSend(request, result) || result.retcode != TRADE_RETCODE_DONE)
    {
        PrintFormat("Failed to place pending order. RetCode: %d", result.retcode);
        return;
    }
    
    ulong pendingTicket = result.order;
    PrintFormat("Pending order %I64u placed successfully. Waiting for execution...", pendingTicket);
    
    uint startWaitTime = GetTickCount();
    bool triggered = false;
    
    // Polling loop to wait for execution
    while(!IsStopped() && (GetTickCount() - startWaitTime) < (InpTimeoutSeconds * 1000))
    {
        HistorySelect(0, TimeCurrent() + 100);
        int deals = HistoryDealsTotal();
        
        for(int i = deals - 1; i >= 0; i--)
        {
            ulong dealTicket = HistoryDealGetTicket(i);
            ulong dealOrder = HistoryDealGetInteger(dealTicket, DEAL_ORDER);
            
            if(dealOrder == pendingTicket) // Order was filled
            {
                double fillPrice = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
                double slippagePoints = (fillPrice - requestedPrice) / point;
                
                PrintFormat("TRIGGERED | Deal: %I64u", dealTicket);
                PrintFormat("Requested: %f | Fill Price: %f | Slippage: %.1f points", 
                            requestedPrice, fillPrice, slippagePoints);
                triggered = true;
                break;
            }
        }
        
        if(triggered) break;
        Sleep(10); // Prevent CPU maxing
    }
    
    if(!triggered)
    {
        Print("Test timed out before order was triggered. Deleting pending order...");
        request.action = TRADE_ACTION_REMOVE;
        request.order = pendingTicket;
        OrderSend(request, result);
    }
    Print("--------------------------------------");
}

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:23 pm
by PTScalper
MT4 Buy Stop Execution Logger

In MT4, when a pending order executes, the ticket number remains identical, but the OrderType() shifts from a pending state (OP_BUYSTOP) to a market state (OP_BUY). The OrderOpenPrice() updates to reflect the actual fill price, meaning we must cache the original requested price to calculate the slippage accurately.

Code: Select all

//+------------------------------------------------------------------+
//|                               MT4_Pending_Execution_Logger.mq4   |
//+------------------------------------------------------------------+
#property script_show_inputs

input double InpLotSize = 0.01;      
input int    InpDistancePoints = 30;     // Distance from price to place order
input int    InpTimeoutSeconds = 120;    // Max time to wait for trigger
input int    InpMagicNumber = 12345; 

void OnStart()
{
    string symbol = Symbol();
    double ask = MarketInfo(symbol, MODE_ASK);
    double point = Point;
    
    double requestedPrice = ask + (InpDistancePoints * point);

    Print("--- [MT4 Buy Stop Execution Test Initiated] ---");
    PrintFormat("Placing OP_BUYSTOP at: %f", requestedPrice);
    
    int ticket = OrderSend(symbol, OP_BUYSTOP, InpLotSize, requestedPrice, 0, 0, 0, "StopTest", InpMagicNumber, 0, clrNONE);
    
    if(ticket <= 0)
    {
        PrintFormat("Failed to place pending order. Error: %d", GetLastError());
        return;
    }
    
    PrintFormat("Pending order %d placed successfully. Waiting for execution...", ticket);
    
    uint startWaitTime = GetTickCount();
    bool triggered = false;
    
    // Polling loop to wait for execution
    while(!IsStopped() && (GetTickCount() - startWaitTime) < (InpTimeoutSeconds * 1000))
    {
        if(OrderSelect(ticket, SELECT_BY_TICKET))
        {
            if(OrderType() == OP_BUY) // Order transformed from pending to market position
            {
                double fillPrice = OrderOpenPrice();
                double slippagePoints = (fillPrice - requestedPrice) / point;
                
                PrintFormat("TRIGGERED | Ticket: %d", ticket);
                PrintFormat("Requested: %f | Fill Price: %f | Slippage: %.1f points", 
                            requestedPrice, fillPrice, slippagePoints);
                triggered = true;
                break;
            }
        }
        Sleep(10); // Prevent CPU maxing
    }
    
    if(!triggered)
    {
        Print("Test timed out before order was triggered. Deleting pending order...");
        OrderDelete(ticket);
    }
    Print("--------------------------------------");
}

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:24 pm
by PTScalper
This Expert Advisor leverages the OnTradeTransaction event handler to listen for asynchronous broker events across your entire account. You only need to attach this EA to a single chart, and it will automatically capture and log the exact slippage for every execution—market orders, pending orders, stop losses, and partial fills—without any manual intervention.

By trapping TRADE_TRANSACTION_DEAL_ADD, the EA waits until the deal is fully committed to the server history, retrieves the parent order to check the initially requested price, and calculates the delta.

Code: Select all

//+------------------------------------------------------------------+
//|                               Background_Execution_Logger.mq5    |
//+------------------------------------------------------------------+
#property copyright "Execution Logger"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    Print("Background Execution Logger Initialized. Listening for account-wide transactions...");
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
{
    // We only process completed deals added to the account history
    if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
        return;

    ulong dealTicket = trans.deal;
    
    // Ensure the deal and its parent order are fully propagated to history
    if(HistoryDealSelect(dealTicket))
    {
        ulong orderTicket = HistoryDealGetInteger(dealTicket, DEAL_ORDER);
        
        if(HistoryOrderSelect(orderTicket))
        {
            string symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
            double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
            if(point == 0) return; 
            
            ENUM_ORDER_TYPE orderType = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE);
            long orderReason = HistoryOrderGetInteger(orderTicket, ORDER_REASON);
            
            double requestedPrice = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN);
            double fillPrice = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
            double volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
            
            double slippagePoints = 0;
            string direction = "";

            // Standardize slippage math: 
            // Positive (+) = Cost to trader (worse price)
            // Negative (-) = Price improvement (better price)
            if(orderType == ORDER_TYPE_BUY || 
               orderType == ORDER_TYPE_BUY_STOP || 
               orderType == ORDER_TYPE_BUY_LIMIT || 
               orderType == ORDER_TYPE_BUY_STOP_LIMIT)
            {
                slippagePoints = (fillPrice - requestedPrice) / point;
                direction = "BUY";
            }
            else if(orderType == ORDER_TYPE_SELL || 
                    orderType == ORDER_TYPE_SELL_STOP || 
                    orderType == ORDER_TYPE_SELL_LIMIT || 
                    orderType == ORDER_TYPE_SELL_STOP_LIMIT)
            {
                slippagePoints = (requestedPrice - fillPrice) / point;
                direction = "SELL";
            }
            
            // Format origin for cleaner logs
            string orderTypeStr = EnumToString(orderType);
            if(orderReason == ORDER_REASON_SL) orderTypeStr = "STOP LOSS";
            else if(orderReason == ORDER_REASON_TP) orderTypeStr = "TAKE PROFIT";
            
            PrintFormat("--- [EXECUTION] %s | Deal: %I64u | Vol: %.2f ---", symbol, dealTicket, volume);
            PrintFormat("Type: %s | Requested: %f | Filled: %f | Slippage: %.1f pts", 
                        orderTypeStr, requestedPrice, fillPrice, slippagePoints);
        }
    }
}

Re: cTrader liquidity differences that showed up in my gold log

Posted: Sat Sep 19, 2026 10:24 pm
by PTScalper
Key Architectural Benefits for Liquidity Sweeps

Partial Fill Granularity: Because OnTradeTransaction fires individually for every single deal, if a 5-lot Stop order is filled in three separate chunks (e.g., 2 lots, 2 lots, 1 lot) by the LP, this EA will print three distinct logs. You will see exactly how the slippage degraded as it swept through the order book.

Universal Math Logic: The slippage calculation is normalized. A positive number always means you took a hit (slippage against you). A negative number indicates price improvement (common on Limit orders or Take Profits).

Zero Overhead: Because it doesn't run in OnTick() and relies strictly on event-driven transactions, it consumes zero CPU cycles while waiting for your structural sweep levels to trigger.