Page 2 of 2

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:35 pm
by PTScalper
Because MT5 operates dynamically in the live order book, an MQL5 implementation allows you to leverage live account equity, dynamic tick values, and real-time tick/point metrics directly from the broker rather than relying on static inputs.

Since TradingView's interactive input.price() doesn't translate 1:1 to MetaTrader, this MQL5 utility runs as a custom Indicator. Upon initialization, it generates three interactive horizontal lines (Requested Entry, SL, TP) on your chart. As you drag them to map your structure, the OnChartEvent handler instantly calculates the execution drag, plots the broker's realized fill lines (as read-only dashed lines), and updates a live telemetry HUD via Comment().

It natively auto-detects direction (based on TP placement relative to Entry) and sizes the position using SYMBOL_TRADE_TICK_VALUE to ensure perfect lot sizing across spot forex, XAUUSD, and equities.

Code: Select all

//+------------------------------------------------------------------+
//|                                    Execution_Drag_Modeler.mq5    |
//|                                     Cost-Adjusted Expectancy     |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_plots 0

//--- Inputs
input string   Grp1 = "--- Risk Parameters ---";
input double   InpRiskPercent       = 1.0;      // Risk Per Trade (%)

input string   Grp2 = "--- Execution Costs (Points) ---";
input int      InpSpreadPoints      = 15;       // Average Spread at Click (Points)
input int      InpSlippageInPoints  = 5;        // Negative Slippage Entry (Points)
input int      InpSlippageOutPoints = 10;       // Negative Slippage Exit (SL/TP) (Points)

input string   Grp3 = "--- Visuals ---";
input color    ColorReqEntry        = clrGray;
input color    ColorReqSL           = clrMaroon;
input color    ColorReqTP           = clrDarkGreen;
input color    ColorRealEntry       = clrDodgerBlue;
input color    ColorRealSL          = clrRed;
input color    ColorRealTP          = clrTeal;

//--- Object Names
string L_REQ_ENTRY = "Req_Entry_Line";
string L_REQ_SL    = "Req_SL_Line";
string L_REQ_TP    = "Req_TP_Line";

string L_REAL_ENTRY = "Real_Entry_Line";
string L_REAL_SL    = "Real_SL_Line";
string L_REAL_TP    = "Real_TP_Line";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
    // Initialize Requested Lines (Interactive)
    double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double atr = iATR(_Symbol, PERIOD_CURRENT, 14); 
    
    // Create interactive lines if they don't exist
    CreateDraggableLine(L_REQ_ENTRY, currentPrice, ColorReqEntry, "Requested Entry");
    CreateDraggableLine(L_REQ_SL, currentPrice - (50 * _Point), ColorReqSL, "Requested SL");
    CreateDraggableLine(L_REQ_TP, currentPrice + (100 * _Point), ColorReqTP, "Requested TP");

    // Create Realized Lines (Read-Only)
    CreateReadOnlyLine(L_REAL_ENTRY, ColorRealEntry, STYLE_DASH);
    CreateReadOnlyLine(L_REAL_SL, ColorRealSL, STYLE_DASH);
    CreateReadOnlyLine(L_REAL_TP, ColorRealTP, STYLE_DASH);

    CalculateAndDisplay();
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    ObjectsDeleteAll(0, L_REQ_ENTRY);
    ObjectsDeleteAll(0, L_REQ_SL);
    ObjectsDeleteAll(0, L_REQ_TP);
    ObjectsDeleteAll(0, L_REAL_ENTRY);
    ObjectsDeleteAll(0, L_REAL_SL);
    ObjectsDeleteAll(0, L_REAL_TP);
    Comment("");
}

//+------------------------------------------------------------------+
//| ChartEvent function for interactive dragging                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
    if(id == CHARTEVENT_OBJECT_DRAG)
    {
        CalculateAndDisplay();
    }
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
    // Update live if needed, but ChartEvent handles interaction
    return(rates_total);
}

//+------------------------------------------------------------------+
//| Core Math & Display Logic                                        |
//+------------------------------------------------------------------+
void CalculateAndDisplay()
{
    double req_entry = ObjectGetDouble(0, L_REQ_ENTRY, OBJPROP_PRICE);
    double req_sl    = ObjectGetDouble(0, L_REQ_SL, OBJPROP_PRICE);
    double req_tp    = ObjectGetDouble(0, L_REQ_TP, OBJPROP_PRICE);

    // Auto-detect direction
    bool is_long = (req_tp > req_entry);

    // Convert costs to price values
    double spread_cost = InpSpreadPoints * _Point;
    double slip_in_cost = InpSlippageInPoints * _Point;
    double slip_out_cost = InpSlippageOutPoints * _Point;

    // Realized levels
    double real_entry = is_long ? req_entry + spread_cost + slip_in_cost : req_entry - spread_cost - slip_in_cost;
    double real_sl    = is_long ? req_sl - slip_out_cost : req_sl + slip_out_cost;
    double real_tp    = is_long ? req_tp - slip_out_cost : req_tp + slip_out_cost;

    // Update Realized Lines on chart
    ObjectSetDouble(0, L_REAL_ENTRY, OBJPROP_PRICE, real_entry);
    ObjectSetDouble(0, L_REAL_SL, OBJPROP_PRICE, real_sl);
    ObjectSetDouble(0, L_REAL_TP, OBJPROP_PRICE, real_tp);

    // Theoretical R
    double theo_risk = MathAbs(req_entry - req_sl);
    double theo_reward = MathAbs(req_tp - req_entry);
    double theo_r = (theo_risk > 0) ? (theo_reward / theo_risk) : 0;

    // Realized R
    double real_risk = MathAbs(real_entry - real_sl);
    double real_reward = MathAbs(real_tp - real_entry);
    double real_r = (real_risk > 0) ? (real_reward / real_risk) : 0;

    // Metrics
    double r_bleed_pct = (theo_r > 0) ? ((theo_r - real_r) / theo_r) * 100.0 : 0.0;
    double risk_inflation = (real_risk > 0 && theo_risk > 0) ? ((real_risk / theo_risk) - 1.0) * 100.0 : 0.0;
    double mvwr = (real_r > 0) ? (1.0 / (1.0 + real_r)) * 100.0 : 100.0;

    // Position Sizing (using true tick values)
    double balance = AccountInfoDouble(ACCOUNT_BALANCE);
    double risk_money = balance * (InpRiskPercent / 100.0);
    
    double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

    double risk_ticks = real_risk / tick_size;
    double raw_lots = (risk_ticks > 0 && tick_value > 0) ? (risk_money / (risk_ticks * tick_value)) : 0;
    
    // Normalize lot size
    double final_lots = MathFloor(raw_lots / lot_step) * lot_step;
    final_lots = MathMax(min_lot, MathMin(final_lots, max_lot));

    // Format HUD
    string direction = is_long ? "LONG" : "SHORT";
    string hud = StringFormat(
        "--- EXECUTION TELEMETRY (%s - %s) ---\n" +
        "Capital at Risk: $%.2f (%.1f%%)\n\n" +
        "Theoretical R: %.2fR\n" +
        "Realized Net R: %.2fR\n" +
        "Expectancy Bleed: -%.1f%%\n\n" +
        "Risk Inflation Tax: +%.1f%%\n" +
        "Min Viable Win Rate: %.1f%%\n\n" +
        ">>> ADJUSTED SIZE: %.2f Lots <<<",
        _Symbol, direction, risk_money, InpRiskPercent,
        theo_r, real_r, r_bleed_pct, risk_inflation, mvwr, final_lots
    );

    Comment(hud);
}

//+------------------------------------------------------------------+
//| Helpers                                                          |
//+------------------------------------------------------------------+
void CreateDraggableLine(string name, double price, color clr, string tooltip)
{
    if(ObjectFind(0, name) < 0)
    {
        ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
        ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
        ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
        ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
        ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(0, name, OBJPROP_SELECTED, true); // Ready to drag
        ObjectSetString(0, name, OBJPROP_TOOLTIP, tooltip);
    }
}

void CreateReadOnlyLine(string name, color clr, ENUM_LINE_STYLE style)
{
    if(ObjectFind(0, name) < 0)
    {
        ObjectCreate(0, name, OBJ_HLINE, 0, 0, 0);
        ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
        ObjectSetInteger(0, name, OBJPROP_STYLE, style);
        ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
        ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
        ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
        ObjectSetString(0, name, OBJPROP_TOOLTIP, "\n");
    }
}

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:36 pm
by PTScalper
Compile this into your MQL5/Indicators directory. Drag it onto any chart (whether it's XAUUSD or a major), set your broker's current microstructure metrics in points via the indicator inputs, and drag the thick colored lines. The dashed lines and on-chart telemetry will instantly react, locking your required position size mathematically to the post-spread reality of the order book.

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:37 pm
by PTScalper
To elevate this to institutional standards, we need to abandon static inputs and Comment() blocks. True quantitative modeling requires Object-Oriented architecture, live order-book spread streaming, and direct data-logging to feed your execution journal.

I have re-architected this in MQL5 using a strict class-based structure that will feel right at home if you are used to writing in C# or .NET.

This version introduces three massive upgrades for a professional scalping workflow:

Live Microstructure Sync: If you set the spread input to 0, it pulls the live floating spread directly from the broker's Level 1 feed tick-by-tick.

Proprietary Graphical HUD: It replaces the amateur Comment() with a flicker-free, vector-drawn OBJ_LABEL interface anchored to the chart.

One-Key Journal Export: It listens for keystrokes. Pressing SPACEBAR instantly appends the current modeled telemetry (including an auto-calculated trading session tag like "London" or "NY Overlap") to a CSV file. You can point your C# backend or database tools directly at this file to automate your execution journal entirely.

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:37 pm
by PTScalper
Pro Level MQL5:

Code: Select all

//+------------------------------------------------------------------+
//|                                Pro_Execution_Drag_Modeler.mq5    |
//|                         OOP Architecture with CSV Journal Export |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_plots 0

//--- Risk Inputs
input double   InpRiskPercent       = 1.0;      // Risk Per Trade (%)

//--- Execution Inputs
input int      InpSpreadPoints      = 0;        // Spread (0 = Use Live Broker Spread)
input int      InpSlippageInPoints  = 5;        // Negative Slippage Entry (Points)
input int      InpSlippageOutPoints = 10;       // Negative Slippage Exit (Points)

//--- Hotkeys
input int      InpLogKey            = 32;       // Keycode to Log to CSV (32 = Spacebar)

//+------------------------------------------------------------------+
//| CExecutionModeler Class Definition                               |
//+------------------------------------------------------------------+
class CExecutionModeler
{
private:
    string         m_req_entry_name, m_req_sl_name, m_req_tp_name;
    string         m_real_entry_name, m_real_sl_name, m_real_tp_name;
    
    // Telemetry Data
    double         m_req_entry, m_req_sl, m_req_tp;
    double         m_real_entry, m_real_sl, m_real_tp;
    double         m_theo_r, m_real_r, m_bleed_pct, m_risk_inflation;
    double         m_lots;
    int            m_current_spread;
    bool           m_is_long;

    // UI Helpers
    void           DrawLabel(string name, int x, int y, string text, color clr, int font_size = 10);
    void           CreateLine(string name, double price, color clr, int style, int width, bool draggable);
    string         GetSessionTag();

public:
                   CExecutionModeler();
                  ~CExecutionModeler();
    void           Initialize();
    void           UpdateMath();
    void           UpdateUI();
    void           ExportToCSV();
};

//--- Constructor
CExecutionModeler::CExecutionModeler()
{
    m_req_entry_name = "REQ_ENTRY";
    m_req_sl_name    = "REQ_SL";
    m_req_tp_name    = "REQ_TP";
    m_real_entry_name = "REAL_ENTRY";
    m_real_sl_name    = "REAL_SL";
    m_real_tp_name    = "REAL_TP";
}

//--- Destructor
CExecutionModeler::~CExecutionModeler()
{
    ObjectsDeleteAll(0, -1, OBJ_HLINE);
    ObjectsDeleteAll(0, -1, OBJ_LABEL);
    ChartRedraw();
}

//--- Initialization
void CExecutionModeler::Initialize()
{
    double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double offset = 100 * _Point;

    CreateLine(m_req_entry_name, price, clrGray, STYLE_SOLID, 2, true);
    CreateLine(m_req_sl_name, price - offset, clrMaroon, STYLE_SOLID, 2, true);
    CreateLine(m_req_tp_name, price + (offset * 2), clrDarkGreen, STYLE_SOLID, 2, true);

    CreateLine(m_real_entry_name, 0, clrDodgerBlue, STYLE_DASH, 1, false);
    CreateLine(m_real_sl_name, 0, clrRed, STYLE_DASH, 1, false);
    CreateLine(m_real_tp_name, 0, clrTeal, STYLE_DASH, 1, false);
}

//--- Core Math
void CExecutionModeler::UpdateMath()
{
    m_req_entry = ObjectGetDouble(0, m_req_entry_name, OBJPROP_PRICE);
    m_req_sl    = ObjectGetDouble(0, m_req_sl_name, OBJPROP_PRICE);
    m_req_tp    = ObjectGetDouble(0, m_req_tp_name, OBJPROP_PRICE);

    m_is_long = (m_req_tp > m_req_entry);

    // Dynamic Live Spread or Fixed Input
    m_current_spread = (InpSpreadPoints == 0) ? (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) : InpSpreadPoints;

    double spread_cost   = m_current_spread * _Point;
    double slip_in_cost  = InpSlippageInPoints * _Point;
    double slip_out_cost = InpSlippageOutPoints * _Point;

    // Realized Levels
    m_real_entry = m_is_long ? m_req_entry + spread_cost + slip_in_cost : m_req_entry - spread_cost - slip_in_cost;
    m_real_sl    = m_is_long ? m_req_sl - slip_out_cost : m_req_sl + slip_out_cost;
    m_real_tp    = m_is_long ? m_req_tp - slip_out_cost : m_req_tp + slip_out_cost;

    // Update lines physically
    ObjectSetDouble(0, m_real_entry_name, OBJPROP_PRICE, m_real_entry);
    ObjectSetDouble(0, m_real_sl_name, OBJPROP_PRICE, m_real_sl);
    ObjectSetDouble(0, m_real_tp_name, OBJPROP_PRICE, m_real_tp);

    // Expectancy
    double theo_risk   = MathAbs(m_req_entry - m_req_sl);
    double theo_reward = MathAbs(m_req_tp - m_req_entry);
    m_theo_r           = (theo_risk > 0) ? (theo_reward / theo_risk) : 0;

    double real_risk   = MathAbs(m_real_entry - m_real_sl);
    double real_reward = MathAbs(m_real_tp - m_real_entry);
    m_real_r           = (real_risk > 0) ? (real_reward / real_risk) : 0;

    m_bleed_pct      = (m_theo_r > 0) ? ((m_theo_r - m_real_r) / m_theo_r) * 100.0 : 0;
    m_risk_inflation = (real_risk > 0 && theo_risk > 0) ? ((real_risk / theo_risk) - 1.0) * 100.0 : 0;

    // Position Sizing
    double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
    double risk_money = balance * (InpRiskPercent / 100.0);
    double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tick_size  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    
    double risk_ticks = real_risk / tick_size;
    double raw_lots   = (risk_ticks > 0 && tick_value > 0) ? (risk_money / (risk_ticks * tick_value)) : 0;
    
    double lot_step   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    m_lots            = MathFloor(raw_lots / lot_step) * lot_step;
}

//--- Render UI
void CExecutionModeler::UpdateUI()
{
    int x = 20, y = 30, spacing = 20;
    
    DrawLabel("HUD_TITLE", x, y, "/// EXECUTION TELEMETRY & EXPECTANCY", clrWhite, 12);
    y += spacing + 5;
    
    DrawLabel("HUD_SYM", x, y, StringFormat("Symbol: %s | Session: %s", _Symbol, GetSessionTag()), clrLightGray);
    y += spacing;
    DrawLabel("HUD_SPREAD", x, y, StringFormat("Modeled Spread: %d points", m_current_spread), clrLightGray);
    y += spacing + 10;

    DrawLabel("HUD_THEO", x, y, StringFormat("Theoretical Screen R: %.2fR", m_theo_r), clrSilver);
    y += spacing;
    DrawLabel("HUD_REAL", x, y, StringFormat("Realized Net R: %.2fR", m_real_r), (m_real_r > 1) ? clrTeal : clrRed, 11);
    y += spacing;
    DrawLabel("HUD_BLEED", x, y, StringFormat("Expectancy Bleed: -%.1f%%", m_bleed_pct), clrOrange);
    y += spacing + 10;

    DrawLabel("HUD_INFL", x, y, StringFormat("Risk Inflation Tax: +%.1f%%", m_risk_inflation), clrRed);
    y += spacing;
    DrawLabel("HUD_SIZE", x, y, StringFormat("Adjusted Lot Size: %.2f", m_lots), clrYellow, 12);
    y += spacing * 2;

    DrawLabel("HUD_HINT", x, y, "[PRESS SPACEBAR TO EXPORT TO JOURNAL]", clrDimGray);
    ChartRedraw();
}

//--- CSV Exporter
void CExecutionModeler::ExportToCSV()
{
    string filename = "Execution_Journal.csv";
    int file_handle = FileOpen(filename, FILE_CSV|FILE_WRITE|FILE_READ|FILE_COMMON|FILE_ANSI, ',');
    
    if(file_handle != INVALID_HANDLE)
    {
        // If file is empty, write headers
        if(FileSize(file_handle) == 0)
        {
            FileWrite(file_handle, "Time", "Symbol", "Session", "Direction", "ReqEntry", "RealEntry", "SpreadPts", "SlippageInPts", "TheoR", "RealR", "BleedPct");
        }
        
        FileSeek(file_handle, 0, SEEK_END);
        
        FileWrite(file_handle, 
                  TimeToString(TimeCurrent()), 
                  _Symbol, 
                  GetSessionTag(), 
                  m_is_long ? "LONG" : "SHORT", 
                  DoubleToString(m_req_entry, _Digits), 
                  DoubleToString(m_real_entry, _Digits),
                  IntegerToString(m_current_spread),
                  IntegerToString(InpSlippageInPoints),
                  DoubleToString(m_theo_r, 2),
                  DoubleToString(m_real_r, 2),
                  DoubleToString(m_bleed_pct, 2)
        );
        FileClose(file_handle);
        Print(">>> Trade Logged to Execution_Journal.csv");
    }
}

//--- Session Logic
string CExecutionModeler::GetSessionTag()
{
    MqlDateTime dt;
    TimeCurrent(dt);
    if(dt.hour >= 8 && dt.hour < 13) return "London";
    if(dt.hour >= 13 && dt.hour < 17) return "London/NY Overlap";
    if(dt.hour >= 17 && dt.hour < 22) return "NY";
    if(dt.hour >= 0 && dt.hour < 8) return "Asian";
    return "Unknown";
}

//--- UI Helper
void CExecutionModeler::DrawLabel(string name, int x, int y, string text, color clr, int font_size=10)
{
    if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
    ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
    ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
    ObjectSetString(0, name, OBJPROP_TEXT, text);
    ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
    ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_size);
    ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
    ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
}

void CExecutionModeler::CreateLine(string name, double price, color clr, int style, int width, bool draggable)
{
    if(ObjectFind(0, name) < 0)
    {
        ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
        ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
        ObjectSetInteger(0, name, OBJPROP_STYLE, style);
        ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
        ObjectSetInteger(0, name, OBJPROP_SELECTABLE, draggable);
        ObjectSetInteger(0, name, OBJPROP_SELECTED, draggable);
        if(!draggable) ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
    }
}

//+------------------------------------------------------------------+
//| Global Instance & Event Handlers                                 |
//+------------------------------------------------------------------+
CExecutionModeler *Modeler;

int OnInit()
{
    Modeler = new CExecutionModeler();
    Modeler.Initialize();
    Modeler.UpdateMath();
    Modeler.UpdateUI();
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
    delete Modeler;
}

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    // Handle Line Dragging
    if(id == CHARTEVENT_OBJECT_DRAG)
    {
        Modeler.UpdateMath();
        Modeler.UpdateUI();
    }
    
    // Handle Hotkey (Spacebar) to Export Log
    if(id == CHARTEVENT_KEYDOWN)
    {
        if(lparam == InpLogKey) 
        {
            Modeler.ExportToCSV();
            // Flash color to confirm
            ObjectSetInteger(0, "HUD_HINT", OBJPROP_COLOR, clrLimeGreen);
            ChartRedraw();
            Sleep(200);
            ObjectSetInteger(0, "HUD_HINT", OBJPROP_COLOR, clrDimGray);
            ChartRedraw();
        }
    }
}

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[])
{
    // Update live spread ticks if tracking active spread
    if(InpSpreadPoints == 0)
    {
        Modeler.UpdateMath();
        Modeler.UpdateUI();
    }
    return(rates_total);
}

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:38 pm
by PTScalper
The Professional Workflow

Model the Setup: Drop the lines onto your structural sweeps on the 15M or 1M chart.

Lock the Risk: The HUD automatically outputs exactly how many lots you can trade based on your risk percentage, factoring in the spread and the exact point-value of the symbol (whether it's Forex, Equities, or XAUUSD).

Log the Entry: Once the trade clears your visual expectancy rules, simply tap the Spacebar.

Aggregate: The script intercepts the keystroke and writes a structured row to Execution_Journal.csv in your MetaTrader terminal's \Common\Files directory. It tags the session, the asset, the theoretical parameters, and the cost-adjusted R.

Because it writes to the common directory, any external application you build can instantly pick up that CSV file on a loop and ingest it into an SQL database or web frontend for broader statistical aggregation.

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:39 pm
by PTScalper
Transitioning this architecture to MQL4 is incredibly smooth because modern MQL4 compiles with #property strict, which enforces C#-style typing and full Object-Oriented Programming support.

Since you specialize in C# and .NET backend systems, I have kept the class-based structure intact. The critical adjustment here for MT4 is swapping the MT5 SymbolInfo* functions for rock-solid MarketInfo() calls, which handle MT4's specific lot stepping and tick-value calculations flawlessly.

I also retained the FILE_COMMON flag in the CSV exporter. Since MT4 usually aggressively sandboxes file I/O into terminal-specific folders, FILE_COMMON punches a hole through that sandbox. It writes the Execution_Journal.csv directly to the \AppData\Roaming\MetaQuotes\Terminal\Common\Files directory, making it trivial for a C# microservice or Python script to monitor that single folder and ingest your execution data across multiple MT4 instances simultaneously.

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:39 pm
by PTScalper
Here is the professional MQL4 implementation:

Code: Select all

//+------------------------------------------------------------------+
//|                                Pro_Execution_Drag_Modeler.mq4    |
//|                         OOP Architecture with CSV Journal Export |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
#property indicator_plots 0

//--- Risk Inputs
input double   InpRiskPercent       = 1.0;      // Risk Per Trade (%)

//--- Execution Inputs
input int      InpSpreadPoints      = 0;        // Spread (0 = Use Live Broker Spread)
input int      InpSlippageInPoints  = 5;        // Negative Slippage Entry (Points)
input int      InpSlippageOutPoints = 10;       // Negative Slippage Exit (Points)

//--- Hotkeys
input int      InpLogKey            = 32;       // Keycode to Log to CSV (32 = Spacebar)

//+------------------------------------------------------------------+
//| CExecutionModeler Class Definition                               |
//+------------------------------------------------------------------+
class CExecutionModeler
{
private:
    string         m_req_entry_name, m_req_sl_name, m_req_tp_name;
    string         m_real_entry_name, m_real_sl_name, m_real_tp_name;
    
    // Telemetry Data
    double         m_req_entry, m_req_sl, m_req_tp;
    double         m_real_entry, m_real_sl, m_real_tp;
    double         m_theo_r, m_real_r, m_bleed_pct, m_risk_inflation;
    double         m_lots;
    int            m_current_spread;
    bool           m_is_long;

    // UI Helpers
    void           DrawLabel(string name, int x, int y, string text, color clr, int font_size = 10);
    void           CreateLine(string name, double price, color clr, int style, int width, bool draggable);
    string         GetSessionTag();

public:
                   CExecutionModeler();
                  ~CExecutionModeler();
    void           Initialize();
    void           UpdateMath();
    void           UpdateUI();
    void           ExportToCSV();
};

//--- Constructor
CExecutionModeler::CExecutionModeler()
{
    m_req_entry_name = "REQ_ENTRY";
    m_req_sl_name    = "REQ_SL";
    m_req_tp_name    = "REQ_TP";
    m_real_entry_name = "REAL_ENTRY";
    m_real_sl_name    = "REAL_SL";
    m_real_tp_name    = "REAL_TP";
}

//--- Destructor
CExecutionModeler::~CExecutionModeler()
{
    ObjectsDeleteAll(0, -1, OBJ_HLINE);
    ObjectsDeleteAll(0, -1, OBJ_LABEL);
    WindowRedraw();
}

//--- Initialization
void CExecutionModeler::Initialize()
{
    double price = Ask;
    double offset = 100 * Point;

    CreateLine(m_req_entry_name, price, clrGray, STYLE_SOLID, 2, true);
    CreateLine(m_req_sl_name, price - offset, clrMaroon, STYLE_SOLID, 2, true);
    CreateLine(m_req_tp_name, price + (offset * 2), clrDarkGreen, STYLE_SOLID, 2, true);

    CreateLine(m_real_entry_name, 0, clrDodgerBlue, STYLE_DASH, 1, false);
    CreateLine(m_real_sl_name, 0, clrRed, STYLE_DASH, 1, false);
    CreateLine(m_real_tp_name, 0, clrTeal, STYLE_DASH, 1, false);
}

//--- Core Math
void CExecutionModeler::UpdateMath()
{
    m_req_entry = ObjectGetDouble(0, m_req_entry_name, OBJPROP_PRICE);
    m_req_sl    = ObjectGetDouble(0, m_req_sl_name, OBJPROP_PRICE);
    m_req_tp    = ObjectGetDouble(0, m_req_tp_name, OBJPROP_PRICE);

    m_is_long = (m_req_tp > m_req_entry);

    // Dynamic Live Spread or Fixed Input (MT4 Specific)
    m_current_spread = (InpSpreadPoints == 0) ? (int)MarketInfo(_Symbol, MODE_SPREAD) : InpSpreadPoints;

    double spread_cost   = m_current_spread * Point;
    double slip_in_cost  = InpSlippageInPoints * Point;
    double slip_out_cost = InpSlippageOutPoints * Point;

    // Realized Levels
    m_real_entry = m_is_long ? m_req_entry + spread_cost + slip_in_cost : m_req_entry - spread_cost - slip_in_cost;
    m_real_sl    = m_is_long ? m_req_sl - slip_out_cost : m_req_sl + slip_out_cost;
    m_real_tp    = m_is_long ? m_req_tp - slip_out_cost : m_req_tp + slip_out_cost;

    // Update lines physically
    ObjectSetDouble(0, m_real_entry_name, OBJPROP_PRICE, m_real_entry);
    ObjectSetDouble(0, m_real_sl_name, OBJPROP_PRICE, m_real_sl);
    ObjectSetDouble(0, m_real_tp_name, OBJPROP_PRICE, m_real_tp);

    // Expectancy
    double theo_risk   = MathAbs(m_req_entry - m_req_sl);
    double theo_reward = MathAbs(m_req_tp - m_req_entry);
    m_theo_r           = (theo_risk > 0) ? (theo_reward / theo_risk) : 0;

    double real_risk   = MathAbs(m_real_entry - m_real_sl);
    double real_reward = MathAbs(m_real_tp - m_real_entry);
    m_real_r           = (real_risk > 0) ? (real_reward / real_risk) : 0;

    m_bleed_pct      = (m_theo_r > 0) ? ((m_theo_r - m_real_r) / m_theo_r) * 100.0 : 0;
    m_risk_inflation = (real_risk > 0 && theo_risk > 0) ? ((real_risk / theo_risk) - 1.0) * 100.0 : 0;

    // Position Sizing (MT4 Specific)
    double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
    double risk_money = balance * (InpRiskPercent / 100.0);
    double tick_value = MarketInfo(_Symbol, MODE_TICKVALUE);
    double tick_size  = MarketInfo(_Symbol, MODE_TICKSIZE);
    
    double risk_ticks = real_risk / tick_size;
    double raw_lots   = (risk_ticks > 0 && tick_value > 0) ? (risk_money / (risk_ticks * tick_value)) : 0;
    
    double lot_step   = MarketInfo(_Symbol, MODE_LOTSTEP);
    double min_lot    = MarketInfo(_Symbol, MODE_MINLOT);
    double max_lot    = MarketInfo(_Symbol, MODE_MAXLOT);
    
    m_lots = MathFloor(raw_lots / lot_step) * lot_step;
    m_lots = MathMax(min_lot, MathMin(m_lots, max_lot));
}

//--- Render UI
void CExecutionModeler::UpdateUI()
{
    int x = 20, y = 30, spacing = 20;
    
    DrawLabel("HUD_TITLE", x, y, "/// EXECUTION TELEMETRY & EXPECTANCY", clrWhite, 12);
    y += spacing + 5;
    
    DrawLabel("HUD_SYM", x, y, StringFormat("Symbol: %s | Session: %s", _Symbol, GetSessionTag()), clrLightGray);
    y += spacing;
    DrawLabel("HUD_SPREAD", x, y, StringFormat("Modeled Spread: %d points", m_current_spread), clrLightGray);
    y += spacing + 10;

    DrawLabel("HUD_THEO", x, y, StringFormat("Theoretical Screen R: %.2fR", m_theo_r), clrSilver);
    y += spacing;
    DrawLabel("HUD_REAL", x, y, StringFormat("Realized Net R: %.2fR", m_real_r), (m_real_r > 1) ? clrTeal : clrRed, 11);
    y += spacing;
    DrawLabel("HUD_BLEED", x, y, StringFormat("Expectancy Bleed: -%.1f%%", m_bleed_pct), clrOrange);
    y += spacing + 10;

    DrawLabel("HUD_INFL", x, y, StringFormat("Risk Inflation Tax: +%.1f%%", m_risk_inflation), clrRed);
    y += spacing;
    DrawLabel("HUD_SIZE", x, y, StringFormat("Adjusted Lot Size: %.2f", m_lots), clrYellow, 12);
    y += spacing * 2;

    DrawLabel("HUD_HINT", x, y, "[PRESS SPACEBAR TO EXPORT TO JOURNAL]", clrDimGray);
    WindowRedraw();
}

//--- CSV Exporter
void CExecutionModeler::ExportToCSV()
{
    string filename = "Execution_Journal.csv";
    // FILE_COMMON punches out of the MT4 sandbox to the shared Terminal/Common/Files folder
    int file_handle = FileOpen(filename, FILE_CSV|FILE_WRITE|FILE_READ|FILE_COMMON|FILE_ANSI, ',');
    
    if(file_handle != INVALID_HANDLE)
    {
        // If file is empty, write headers
        if(FileSize(file_handle) == 0)
        {
            FileWrite(file_handle, "Time", "Symbol", "Session", "Direction", "ReqEntry", "RealEntry", "SpreadPts", "SlippageInPts", "TheoR", "RealR", "BleedPct");
        }
        
        FileSeek(file_handle, 0, SEEK_END);
        
        FileWrite(file_handle, 
                  TimeToString(TimeCurrent()), 
                  _Symbol, 
                  GetSessionTag(), 
                  m_is_long ? "LONG" : "SHORT", 
                  DoubleToString(m_req_entry, _Digits), 
                  DoubleToString(m_real_entry, _Digits),
                  IntegerToString(m_current_spread),
                  IntegerToString(InpSlippageInPoints),
                  DoubleToString(m_theo_r, 2),
                  DoubleToString(m_real_r, 2),
                  DoubleToString(m_bleed_pct, 2)
        );
        FileClose(file_handle);
        Print(">>> Trade Logged to Shared Execution_Journal.csv");
    }
}

//--- Session Logic (MT4 Simplified TimeHour)
string CExecutionModeler::GetSessionTag()
{
    int hour = TimeHour(TimeCurrent());
    if(hour >= 8 && hour < 13) return "London";
    if(hour >= 13 && hour < 17) return "London/NY Overlap";
    if(hour >= 17 && hour < 22) return "NY";
    if(hour >= 0 && hour < 8) return "Asian";
    return "Unknown";
}

//--- UI Helper
void CExecutionModeler::DrawLabel(string name, int x, int y, string text, color clr, int font_size=10)
{
    if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
    ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
    ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
    ObjectSetString(0, name, OBJPROP_TEXT, text);
    ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
    ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_size);
    ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
    ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
}

void CExecutionModeler::CreateLine(string name, double price, color clr, int style, int width, bool draggable)
{
    if(ObjectFind(0, name) < 0)
    {
        ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
        ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
        ObjectSetInteger(0, name, OBJPROP_STYLE, style);
        ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
        ObjectSetInteger(0, name, OBJPROP_SELECTABLE, draggable);
        ObjectSetInteger(0, name, OBJPROP_SELECTED, draggable);
        if(!draggable) ObjectSetInteger(0, name, OBJPROP_BACK, true); // Hide behind candles in MT4
    }
}

//+------------------------------------------------------------------+
//| Global Instance & Event Handlers                                 |
//+------------------------------------------------------------------+
CExecutionModeler *Modeler;

int OnInit()
{
    Modeler = new CExecutionModeler();
    Modeler.Initialize();
    Modeler.UpdateMath();
    Modeler.UpdateUI();
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
    delete Modeler;
}

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    // Handle Line Dragging
    if(id == CHARTEVENT_OBJECT_DRAG)
    {
        Modeler.UpdateMath();
        Modeler.UpdateUI();
    }
    
    // Handle Hotkey (Spacebar) to Export Log
    if(id == CHARTEVENT_KEYDOWN)
    {
        if(lparam == InpLogKey) 
        {
            Modeler.ExportToCSV();
            // Flash color to confirm
            ObjectSetInteger(0, "HUD_HINT", OBJPROP_COLOR, clrLimeGreen);
            WindowRedraw();
            Sleep(200);
            ObjectSetInteger(0, "HUD_HINT", OBJPROP_COLOR, clrDimGray);
            WindowRedraw();
        }
    }
}

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[])
{
    // Update live spread ticks if tracking active spread
    if(InpSpreadPoints == 0)
    {
        Modeler.UpdateMath();
        Modeler.UpdateUI();
    }
    return(rates_total);
}

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:41 pm
by PTScalper
In MQL4/5, we had to hack our way around a 20-year-old graphics engine using OBJ_LABEL. In cTrader, we have access to a modern WPF-style UI framework (StackPanel, Border, TextBlock) natively rendered on the chart, and event-driven delegates for object interaction (Chart.ObjectUpdated).

I have engineered this as a strictly typed cTrader Indicator. It leverages the AccessRights.FileSystem attribute so it can securely write the CSV journal directly to your My Documents folder, making it trivial for one of your external .NET microservices to ingest the data.

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:41 pm
by PTScalper
The Professional cAlgo Implementation

Code: Select all

using System;
using System.IO;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
    public class ExecutionDragModeler : Indicator
    {
        #region Parameters
        [Parameter("Risk Per Trade (%)", Group = "Risk Management", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1)]
        public double RiskPercent { get; set; }

        [Parameter("Spread in Pips (0 = Live)", Group = "Microstructure", DefaultValue = 0)]
        public double SpreadPips { get; set; }

        [Parameter("Entry Slippage (Pips)", Group = "Microstructure", DefaultValue = 0.5)]
        public double SlippageInPips { get; set; }

        [Parameter("Exit Slippage (Pips)", Group = "Microstructure", DefaultValue = 1.0)]
        public double SlippageOutPips { get; set; }

        [Parameter("Log Key", Group = "Hotkeys", DefaultValue = Key.Space)]
        public Key LogKey { get; set; }
        #endregion

        #region UI Elements & Lines
        private ChartHorizontalLine _reqEntryLine, _reqSlLine, _reqTpLine;
        private ChartHorizontalLine _realEntryLine, _realSlLine, _realTpLine;

        private TextBlock _uiSession, _uiSpread;
        private TextBlock _uiTheoR, _uiRealR, _uiBleed, _uiInflation, _uiSize, _uiHint;
        #endregion

        #region Telemetry State
        private bool _isLong;
        private double _currentSpreadPips;
        private double _theoR, _realR, _bleedPct, _riskInflation;
        private double _adjustedVolume;
        #endregion

        protected override void Initialize()
        {
            InitializeLines();
            InitializeHUD();

            // Event Subscriptions
            Chart.ObjectUpdated += OnChartObjectUpdated;
            Chart.KeyDown += OnChartKeyDown;

            UpdateTelemetry();
        }

        public override void Calculate(int index)
        {
            // Update live spread on incoming ticks if tracking is active
            if (SpreadPips == 0 && IsLastBar)
            {
                UpdateTelemetry();
            }
        }

        #region Core Math & Logic
        private void UpdateTelemetry()
        {
            if (_reqEntryLine == null || _reqSlLine == null || _reqTpLine == null) return;

            double reqEntry = _reqEntryLine.Y;
            double reqSl = _reqSlLine.Y;
            double reqTp = _reqTpLine.Y;

            _isLong = reqTp > reqEntry;

            // Live or Fixed Spread
            _currentSpreadPips = SpreadPips == 0 ? Symbol.Spread / Symbol.PipSize : SpreadPips;

            double spreadCost = _currentSpreadPips * Symbol.PipSize;
            double slipInCost = SlippageInPips * Symbol.PipSize;
            double slipOutCost = SlippageOutPips * Symbol.PipSize;

            // Calculate Realized Levels
            double realEntry = _isLong ? reqEntry + spreadCost + slipInCost : reqEntry - spreadCost - slipInCost;
            double realSl = _isLong ? reqSl - slipOutCost : reqSl + slipOutCost;
            double realTp = _isLong ? reqTp - slipOutCost : reqTp + slipOutCost;

            // Update physical broker-reality lines
            _realEntryLine.Y = realEntry;
            _realSlLine.Y = realSl;
            _realTpLine.Y = realTp;

            // Expectancy Math
            double theoRisk = Math.Abs(reqEntry - reqSl);
            double theoReward = Math.Abs(reqTp - reqEntry);
            _theoR = theoRisk > 0 ? (theoReward / theoRisk) : 0;

            double realRisk = Math.Abs(realEntry - realSl);
            double realReward = Math.Abs(realTp - realEntry);
            _realR = realRisk > 0 ? (realReward / realRisk) : 0;

            _bleedPct = _theoR > 0 ? ((_theoR - _realR) / _theoR) * 100 : 0;
            _riskInflation = (realRisk > 0 && theoRisk > 0) ? ((realRisk / theoRisk) - 1.0) * 100 : 0;

            // Dynamic Sizing based on Realized Risk
            double riskMoney = Account.Balance * (RiskPercent / 100.0);
            double pipRisk = realRisk / Symbol.PipSize;
            
            if (pipRisk > 0)
            {
                double riskPerVolume = pipRisk * Symbol.PipValue;
                double rawVolume = riskMoney / riskPerVolume;
                _adjustedVolume = Symbol.NormalizeVolumeInUnits(rawVolume, RoundingMode.Down);
            }

            UpdateHUD();
        }
        #endregion

        #region Event Handlers
        private void OnChartObjectUpdated(ChartObjectUpdatedEventArgs args)
        {
            // React immediately when the user drags a requested line
            if (args.ChartObject.Name == "ReqEntry" || args.ChartObject.Name == "ReqSL" || args.ChartObject.Name == "ReqTP")
            {
                UpdateTelemetry();
            }
        }

        private void OnChartKeyDown(ChartKeyboardEventArgs args)
        {
            if (args.Key == LogKey)
            {
                ExportToJournal();
                FlashHUD();
            }
        }
        #endregion

        #region CSV Exporter
        private void ExportToJournal()
        {
            try
            {
                string docsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                string filePath = Path.Combine(docsPath, "Execution_Journal.csv");

                bool writeHeader = !File.Exists(filePath);

                using (StreamWriter writer = File.AppendText(filePath))
                {
                    if (writeHeader)
                    {
                        writer.WriteLine("Time,Symbol,Session,Direction,ReqEntry,RealEntry,SpreadPips,SlipInPips,TheoR,RealR,BleedPct,Volume");
                    }

                    string session = GetSessionTag();
                    string direction = _isLong ? "LONG" : "SHORT";

                    writer.WriteLine($"{Server.Time:yyyy-MM-dd HH:mm:ss},{Symbol.Name},{session},{direction},{_reqEntryLine.Y},{_realEntryLine.Y},{_currentSpreadPips},{SlippageInPips},{_theoR:F2},{_realR:F2},{_bleedPct:F2},{_adjustedVolume}");
                }
                
                Print($"[Journal] Trade logged to {filePath}");
            }
            catch (Exception ex)
            {
                Print($"Error writing to CSV: {ex.Message}");
            }
        }
        #endregion

        #region Initialization Helpers
        private void InitializeLines()
        {
            double price = Symbol.Ask;
            double offset = 10 * Symbol.PipSize;

            _reqEntryLine = Chart.DrawHorizontalLine("ReqEntry", price, Color.Gray, 2, LineStyle.Solid);
            _reqSlLine = Chart.DrawHorizontalLine("ReqSL", price - offset, Color.Maroon, 2, LineStyle.Solid);
            _reqTpLine = Chart.DrawHorizontalLine("ReqTP", price + (offset * 2), Color.DarkGreen, 2, LineStyle.Solid);

            _reqEntryLine.IsInteractive = true;
            _reqSlLine.IsInteractive = true;
            _reqTpLine.IsInteractive = true;

            _realEntryLine = Chart.DrawHorizontalLine("RealEntry", price, Color.DodgerBlue, 1, LineStyle.Lines);
            _realSlLine = Chart.DrawHorizontalLine("RealSL", price, Color.Red, 1, LineStyle.Lines);
            _realTpLine = Chart.DrawHorizontalLine("RealTP", price, Color.Teal, 1, LineStyle.Lines);
            
            // Read-only broker lines
            _realEntryLine.IsInteractive = false;
            _realSlLine.IsInteractive = false;
            _realTpLine.IsInteractive = false;
        }

        private void InitializeHUD()
        {
            var panel = new StackPanel { Orientation = Orientation.Vertical, Margin = new Thickness(10) };

            panel.AddChild(new TextBlock { Text = "/// EXECUTION TELEMETRY & EXPECTANCY", ForegroundColor = Color.White, FontWeight = FontWeight.Bold, Margin = new Thickness(0,0,0,10) });

            _uiSession = new TextBlock { ForegroundColor = Color.LightGray };
            _uiSpread = new TextBlock { ForegroundColor = Color.LightGray, Margin = new Thickness(0,0,0,10) };
            
            _uiTheoR = new TextBlock { ForegroundColor = Color.Silver };
            _uiRealR = new TextBlock { FontWeight = FontWeight.Bold };
            _uiBleed = new TextBlock { ForegroundColor = Color.Orange, Margin = new Thickness(0,0,0,10) };
            
            _uiInflation = new TextBlock { ForegroundColor = Color.Red };
            _uiSize = new TextBlock { ForegroundColor = Color.Yellow, FontWeight = FontWeight.Bold, FontSize = 12, Margin = new Thickness(0,0,0,15) };
            
            _uiHint = new TextBlock { Text = $"[PRESS {LogKey.ToString().ToUpper()} TO EXPORT TO JOURNAL]", ForegroundColor = Color.DimGray, FontSize = 10 };

            panel.AddChild(_uiSession);
            panel.AddChild(_uiSpread);
            panel.AddChild(_uiTheoR);
            panel.AddChild(_uiRealR);
            panel.AddChild(_uiBleed);
            panel.AddChild(_uiInflation);
            panel.AddChild(_uiSize);
            panel.AddChild(_uiHint);

            var border = new Border
            {
                BackgroundColor = Color.FromArgb(200, 15, 15, 15),
                BorderColor = Color.FromArgb(100, 50, 50, 50),
                BorderThickness = new Thickness(1),
                CornerRadius = 3,
                Padding = new Thickness(15),
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top,
                Child = panel
            };

            Chart.AddControl(border);
        }

        private void UpdateHUD()
        {
            _uiSession.Text = $"Symbol: {Symbol.Name}  |  Session: {GetSessionTag()}";
            _uiSpread.Text = $"Modeled Spread: {_currentSpreadPips:F1} pips";
            
            _uiTheoR.Text = $"Theoretical Screen R: {_theoR:F2}R";
            _uiRealR.Text = $"Realized Net R: {_realR:F2}R";
            _uiRealR.ForegroundColor = _realR > 1.0 ? Color.Teal : Color.Red;
            
            _uiBleed.Text = $"Expectancy Bleed: -{_bleedPct:F1}%";
            _uiInflation.Text = $"Risk Inflation Tax: +{_riskInflation:F1}%";
            _uiSize.Text = $"Adjusted Position Size: {_adjustedVolume} Units";
        }

        private void FlashHUD()
        {
            _uiHint.ForegroundColor = Color.LimeGreen;
            Timer.Start(TimeSpan.FromMilliseconds(300));
        }

        protected override void OnTimer()
        {
            _uiHint.ForegroundColor = Color.DimGray;
            Timer.Stop();
        }
        #endregion

        #region Utilities
        private string GetSessionTag()
        {
            var hour = Server.Time.TimeOfDay.Hours;
            if (hour >= 8 && hour < 13) return "London";
            if (hour >= 13 && hour < 17) return "London/NY Overlap";
            if (hour >= 17 && hour < 22) return "NY";
            return "Asian";
        }
        #endregion
    }
}

Re: Execution journal: measuring slippage by pair and session

Posted: Mon Sep 21, 2026 1:41 pm
by PTScalper
Why this is the ultimate environment for this tool:

Event-Driven Math: Notice the Chart.ObjectUpdated subscription. The instant you click and drag a structural entry line, the script recalculates the broker's reality, updates the read-only dashed lines, and refreshes the HUD seamlessly. No more polling or waiting for ticks.

Proper Component UI: The StackPanel inside a translucent Border creates a clean, flicker-free WPF overlay. You can easily drag it out to a separate window if you detach your cTrader charts.

Enterprise File Scopes: MQL forces files into terminal-specific sandboxes. By declaring AccessRights.FileSystem in cAlgo, we write Execution_Journal.csv directly to your My Documents folder. If you want to connect this to your local LLM setup or a C# microservice pushing to a SQL database, the file path is static, shared, and globally accessible.