Page 2 of 2

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

Posted: Sat Sep 19, 2026 10:25 pm
by PTScalper
To ensure your data remains perfectly aligned with London cash hours regardless of your broker's server time zone or seasonal Daylight Saving Time shifts, this adaptation logs execution timestamps in strict GMT (TimeGMT()).

I’ve structured the file I/O to use the FILE_COMMON flag. This saves the CSV to your shared Terminal Common/Files directory, making it immediately accessible for data analysis without having to dig through isolated instance folders.

Code: Select all

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

input string InpFileName = "Liquidity_Sweeps_Log.csv"; 

int fileHandle = INVALID_HANDLE;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // Open file in common folder so it's easily accessible and shared
    // FILE_COMMON saves to: %APPDATA%\MetaQuotes\Terminal\Common\Files
    fileHandle = FileOpen(InpFileName, FILE_READ | FILE_WRITE | FILE_CSV | FILE_ANSI | FILE_COMMON, ',');
    
    if(fileHandle != INVALID_HANDLE)
    {
        if(FileSize(fileHandle) == 0)
        {
            // Write headers if file is new
            FileWrite(fileHandle, "GMT_Time", "Local_Ms_Tick", "Symbol", "Action", "Requested_Price", "Fill_Price", "Slippage_Points", "Volume", "Deal_Ticket");
        }
        // Move pointer to the end of the file for rolling append
        FileSeek(fileHandle, 0, SEEK_END);
        Print("Background Execution Logger Initialized. Writing to Common/Files/", InpFileName);
    }
    else
    {
        Print("Error opening file: ", GetLastError());
        return(INIT_FAILED);
    }
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    if(fileHandle != INVALID_HANDLE)
    {
        FileClose(fileHandle);
        Print("Execution Logger file closed.");
    }
}

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
{
    if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
        return;

    ulong dealTicket = trans.deal;
    
    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;

            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;
            }
            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;
            }
            
            string orderTypeStr = EnumToString(orderType);
            if(orderReason == ORDER_REASON_SL) orderTypeStr = "STOP_LOSS";
            else if(orderReason == ORDER_REASON_TP) orderTypeStr = "TAKE_PROFIT";
            
            // Format timestamp for London hour alignment (GMT baseline)
            datetime timeGMT = TimeGMT();
            string timeStr = TimeToString(timeGMT, TIME_DATE | TIME_SECONDS);
            ulong localMs = GetMicrosecondCount() / 1000; // Track millisecond pacing
            
            // Append row to CSV
            if(fileHandle != INVALID_HANDLE)
            {
                FileWrite(fileHandle, timeStr, localMs, symbol, orderTypeStr, requestedPrice, fillPrice, slippagePoints, volume, dealTicket);
                
                // Force write to disk immediately. Prevents data loss if the terminal crashes 
                // or connection is lost during high-frequency liquidity events.
                FileFlush(fileHandle); 
            }
            
            PrintFormat("LOGGED -> %s | Vol: %.2f | Slippage: %.1f pts", symbol, volume, slippagePoints);
        }
    }
}

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

Posted: Sat Sep 19, 2026 10:25 pm
by PTScalper
Analytical Advantages for Your Testing:

GMT Normalization: By standardizing on TimeGMT(), your exported logs can be directly dropped into Excel alongside structural charting data without worrying about whether your MT5 broker is on EET or shifts for DST. You can reliably map those 1-minute and 5-minute price action sweeps directly to 08:00 - 16:30 London times.

FileFlush() Execution: During volatile structural sweeps, RAM gets congested. Calling FileFlush(fileHandle) immediately writes the buffer to the NVMe storage. If the terminal locks up or crashes during extreme spread widening, your data is already secured on disk.

Millisecond Tagging: The localMs column captures terminal ticks. If you observe sequential partial fills against a single Stop order, you will see exactly how many milliseconds elapsed as the liquidity provider walked the book.

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

Posted: Sat Sep 19, 2026 10:26 pm
by PTScalper
Because cTrader uses C# (.NET) for its cAlgo API, we shift from MQL5's transaction-level polling to a purely event-driven architecture.

To achieve the exact same background logging for liquidity sweeps, this cBot hooks into PendingOrders.Filled (for your structural entries) and Positions.Closed (for Stop Loss / Take Profit sweeps). It requires the AccessRights.FileSystem permission to write the CSV to your local Documents folder.

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

Posted: Sat Sep 19, 2026 10:27 pm
by PTScalper

Code: Select all

using System;
using System.IO;
using System.Linq;
using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
    public class BackgroundExecutionLogger : Robot
    {
        [Parameter("Log File Name", DefaultValue = "cTrader_Liquidity_Sweeps.csv")]
        public string FileName { get; set; }

        private string _filePath;
        private readonly object _fileLock = new object();

        protected override void OnStart()
        {
            // cTrader sandboxes file I/O unless explicitly directed to a safe zone.
            // This saves the log to: Documents\cAlgo\ExecutionLogs\
            string directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "cAlgo", "ExecutionLogs");
            Directory.CreateDirectory(directory);
            _filePath = Path.Combine(directory, FileName);

            lock (_fileLock)
            {
                if (!File.Exists(_filePath))
                {
                    File.WriteAllText(_filePath, "GMT_Time,Local_Ms,Symbol,Action,Requested_Price,Fill_Price,Slippage_Points,Volume,Position_ID\n");
                }
            }

            // Hook into execution events
            PendingOrders.Filled += OnPendingOrderFilled;
            Positions.Closed += OnPositionClosed;

            Print($"Execution Logger running. Saving to: {_filePath}");
        }

        private void OnPendingOrderFilled(PendingOrderFilledEventArgs args)
        {
            var pendingOrder = args.PendingOrder;
            var position = args.Position;
            var symbol = Symbols.GetSymbol(position.SymbolName);

            double requestedPrice = pendingOrder.TargetPrice;
            double fillPrice = position.EntryPrice;
            double volume = position.VolumeInUnits;
            
            double slippagePoints = 0;
            string action = pendingOrder.OrderType.ToString().ToUpper();

            // Calculate slippage using TickSize (cTrader's equivalent to MT5 Point)
            // Positive = Cost to trader / Negative = Price improvement
            if (pendingOrder.TradeType == TradeType.Buy)
            {
                slippagePoints = (fillPrice - requestedPrice) / symbol.TickSize;
            }
            else
            {
                slippagePoints = (requestedPrice - fillPrice) / symbol.TickSize;
            }

            LogExecution(symbol.Name, action, requestedPrice, fillPrice, slippagePoints, volume, position.Id);
        }

        private void OnPositionClosed(PositionClosedEventArgs args)
        {
            var position = args.Position;
            var symbol = Symbols.GetSymbol(position.SymbolName);
            
            // Fetch exact closing execution price from history
            var historyItem = History.FirstOrDefault(x => x.PositionId == position.Id);
            if (historyItem == null) return;

            double fillPrice = historyItem.ClosingPrice;
            double requestedPrice = 0;
            string action = "";

            if (args.Reason == PositionCloseReason.StopLoss && position.StopLoss.HasValue)
            {
                requestedPrice = position.StopLoss.Value;
                action = "STOP_LOSS";
            }
            else if (args.Reason == PositionCloseReason.TakeProfit && position.TakeProfit.HasValue)
            {
                requestedPrice = position.TakeProfit.Value;
                action = "TAKE_PROFIT";
            }
            else
            {
                return; // We only want to log structural sweeps against SL/TP
            }

            double slippagePoints = 0;
            
            // Reverse slippage logic for exits (Buy position closes via Sell execution)
            if (position.TradeType == TradeType.Buy)
            {
                slippagePoints = (requestedPrice - fillPrice) / symbol.TickSize;
            }
            else
            {
                slippagePoints = (fillPrice - requestedPrice) / symbol.TickSize;
            }

            LogExecution(symbol.Name, action, requestedPrice, fillPrice, slippagePoints, historyItem.VolumeInUnits, position.Id);
        }

        private void LogExecution(string symbol, string action, double requestedPrice, double fillPrice, double slippagePoints, double volume, int positionId)
        {
            // Enforce GMT alignment for 1:1 comparison against London cash hours
            DateTime timeGmt = Server.TimeInUtc;
            string timeStr = timeGmt.ToString("yyyy.MM.dd HH:mm:ss");
            long localMs = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;

            string logLine = $"{timeStr},{localMs},{symbol},{action},{requestedPrice},{fillPrice},{Math.Round(slippagePoints, 1)},{volume},{positionId}";

            // cTrader event handlers execute asynchronously; lock required to prevent I/O race conditions during rapid partial fills
            lock (_fileLock)
            {
                using (StreamWriter sw = File.AppendText(_filePath))
                {
                    sw.WriteLine(logLine);
                }
            }
            
            Print($"LOGGED -> {symbol} | {action} | Slippage: {Math.Round(slippagePoints, 1)} pts");
        }
    }
}

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

Posted: Sat Sep 19, 2026 10:27 pm
by PTScalper
cTrader Architectural Nuances

Thread Safety (_fileLock): Unlike MT5 where OnTradeTransaction is processed sequentially by a single thread queue, cTrader's OnPendingOrderFilled can fire asynchronously. If a structural sweep triggers multiple limit orders simultaneously, the lock statement prevents file write collisions.

TickSize vs. Point: The cAlgo API uses Symbol.TickSize as the baseline measurement unit, which directly maps to how we used _Point in MQL5.

History Extraction: When a position closes in cTrader, the args.Position object doesn't always retain the exact closing execution price on the object itself. We pull the historyItem directly from the History collection using the PositionId to get the true, latency-adjusted fill price for your Stop Losses.

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

Posted: Sun Sep 20, 2026 12:11 pm
by LondonScalper
PTScalper wrote:The visual depth of the ladder has almost zero correlation with effective fill cost during active London hours.
That matches the gold book. A thick-looking cTrader ladder into a sweep, then a fill worse than the quoted spread, is no mystery if those limits can vanish before a retail market order arrives. The thin moment that fills cleanly is often the honest book, which is awkward if you have spent the morning staring at depth.

I am not ready to crown MT5 on that alone. The comparison only holds if size, session, and order type match. My worse cTrader tails have mostly been market orders into the sweep itself. Limits sitting a little back have been less dramatic, so I will not indict the platform for an impatient click.

I log request-to-ack, not matching-engine time. When the book turns theatrical, the rejections I see on both platforms sit in the same handful of tens of milliseconds. The cost difference is in the fill, especially the second slice of a partial. That is the number I rank. Depth on the screen does not replace it.

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

Posted: Thu Sep 24, 2026 1:11 am
by PropScalpDesk
PTScalper wrote:This Expert Advisor leverages the OnTradeTransaction event handler to listen for asynchronous broker events across your entire account.
Liquidity differences between books show up as rejects and weird partials before they show up as “strategy failure.” I log venue behaviour the same way I log setups.

A funded account on a thin gold book will teach expensive lessons.

What metric do you use to rank a venue for gold — reject rate, average slip, or both?

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=12568: keep risk unchanged until the sample says otherwise.

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

Posted: Thu Sep 24, 2026 9:21 am
by LondonNewsTrader
PTScalper wrote:This Expert Advisor leverages the OnTradeTransaction event handler to listen for asynchronous broker events across your entire account.
Using TRADE_TRANSACTION_DEAL_ADD and then pulling the parent order from history is the right way round; you're measuring against what the server actually recorded, not what the terminal thought it sent.

A few details worth checking. Slippage needs a sign that depends on side: a buy filled above the requested price is a cost, a sell filled above it is a gain. If the delta is taken as an absolute value, positive slips on limit fills inflate the cost column. Partial fills also produce several DEAL_ADD events for one order, so aggregating by order ticket before computing slip stops one ticket counting three times.

ORDER_REASON is a good inclusion. Stop-loss fills are where the interesting numbers are, and splitting SL-triggered deals from manual market orders shows whether the damage is at entry or at exit.

The one mismatch is platform: LondonScalper's log is on cTrader, and an MT5 EA won't see those fills. The cAlgo equivalent is subscribing to Positions.Opened and Positions.Closed and writing out entry and closing prices against the quote captured just before sending. Same idea, and it would let the DOM-versus-fill comparison in the opening post run automatically.

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

Posted: Thu Sep 24, 2026 9:45 am
by PTScalper
LondonNewsTrader wrote: Thu Sep 24, 2026 9:21 am
PTScalper wrote:This Expert Advisor leverages the OnTradeTransaction event handler to listen for asynchronous broker events across your entire account.
On cTrader liquidity differences that showed up in my gold log the desk question is not more tools — it is whether the calendar already owns the risk.

Prop or personal, the news folder changes behaviour. Near a target I cut size for two sessions rather than “finish it.” Trailing rules punish narrative trading faster than any pep talk.

I also log refused tickets so flat time counts. A quiet hour into CPI is a good hour.

If the idea needs a story longer than one line, it waits for another window.

PTScalper’s practical framing fits a news desk when the calendar is treated as market structure, not a footnote.

Where is your line between alert-assist and auto-send around Tier-1?
Hi LondonNewsTrader,

i personally most of the time use only alerts. I like to manage and make trading decisions based on my own point of view.
I found out, that i performs the best, if i have several auto alerts, watch markets several times a day (time to time).
And understand what is going on. What news can come, where the market is and what range could be traded.