Advertisement IC Markets

Raw vs standard on AUDUSD: 30-day cost race

Compare ECN/Raw spread brokers, analyze execution speeds, report slippage, and evaluate commission structures for high-frequency traders.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

MQL4 Implementation

Save this script as TrueExecutionCost.mq4 inside your MT4 MQL4/Indicators/ directory.

Code: Select all

#property copyright "Custom"
#property link      ""
#property version   "1.00"
#property strict
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1 clrGray
#property indicator_color2 clrOrange
#property indicator_width1 1
#property indicator_width2 2
#property indicator_label1 "Raw Spread (Pips)"
#property indicator_label2 "All-In Cost (Pips)"

//--- Inputs
input string   InpCostsGroup        = "--- Cost Settings ---";
input double   InpCommissionPerLot  = 6.0;   // Round-turn Commission per Lot ($)
input double   InpPipValuePerLot    = 10.0;  // Pip Value per Lot ($)
input string   InpTimeGroup         = "--- Time Offset (Hours) ---";
input int      InpBrokerGmtOffset   = 2;     // Broker Server GMT Offset (e.g., 2 or 3)
input int      InpLondonGmtOffset   = 0;     // London GMT Offset (0 in Winter, 1 in Summer)

//--- Buffers
double SpreadBuffer[];
double AllInCostBuffer[];

double g_pipSize;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   IndicatorDigits(2);
   
   SetIndexBuffer(0, SpreadBuffer);
   SetIndexStyle(0, DRAW_LINE);
   
   SetIndexBuffer(1, AllInCostBuffer);
   SetIndexStyle(1, DRAW_LINE);
   
   // Determine 1 pip based on instrument digits
   g_pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10.0 : _Point;
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Comment(""); // Clear chart HUD
}

//+------------------------------------------------------------------+
//| 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[])
{
   if(rates_total <= 0 || g_pipSize <= 0) return(0);

   // Only calculate on live incoming ticks
   int lastBar = rates_total - 1;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   
   double commPips = (InpPipValuePerLot > 0) ? (InpCommissionPerLot / InpPipValuePerLot) : 0.0;
   double currentSpread = (ask - bid) / g_pipSize;
   double allInCost = currentSpread + commPips;

   SpreadBuffer[lastBar]    = currentSpread;
   AllInCostBuffer[lastBar] = allInCost;

   // Update HUD Dashboard
   UpdateDashboard(currentSpread, commPips, allInCost);

   return(rates_total);
}

//+------------------------------------------------------------------+
//| Session Evaluation & Dashboard HUD                               |
//+------------------------------------------------------------------+
void UpdateDashboard(double spread, double comm, double totalCost)
{
   datetime serverTime = TimeCurrent();
   datetime londonTime = serverTime - (InpBrokerGmtOffset * 3600) + (InpLondonGmtOffset * 3600);

   MqlDateTime dt;
   TimeToStruct(londonTime, dt);
   int totalMinutes = (dt.hour * 60) + dt.min;

   string sessionText = "Off-Peak / Outside Window";

   // Asian Session: 00:30 (30m) to 03:00 (180m)
   if(totalMinutes >= 30 && totalMinutes <= 180)
      sessionText = "Asian (00:30 - 03:00 London)";
   // London Session: 07:00 (420m) to 10:00 (600m)
   else if(totalMinutes >= 420 && totalMinutes <= 600)
      sessionText = "London (07:00 - 10:00 London)";

   string dashboard = StringFormat(
      "=== TRUE EXECUTION COST ===\n" +
      "Session Window   : %s\n" +
      "London Time      : %02d:%02d\n" +
      "Real-Time Spread : %.2f pips\n" +
      "Commission (RT)  : %.2f pips\n" +
      "Total All-In Cost: %.2f pips",
      sessionText, dt.hour, dt.min, spread, comm, totalCost
   );

   Comment(dashboard);
}
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: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

MQL5 Implementation

Save this script as TrueExecutionCost.mq5 inside your MT5 MQL5/Indicators/ directory.

Code: Select all

#property copyright "Custom"
#property link      ""
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   2

#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
#property indicator_label1  "Raw Spread (Pips)"

#property indicator_type2   DRAW_LINE
#property indicator_color2  clrOrange
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2
#property indicator_label2  "All-In Cost (Pips)"

//--- Inputs
input group "Cost Settings"
input double   InpCommissionPerLot  = 6.0;   // Round-turn Commission per Lot ($)
input double   InpPipValuePerLot    = 10.0;  // Pip Value per Lot ($)

input group "Time Offset (Hours)"
input int      InpBrokerGmtOffset   = 2;     // Broker Server GMT Offset (e.g., 2 or 3)
input int      InpLondonGmtOffset   = 0;     // London GMT Offset (0 in Winter, 1 in Summer)

//--- Buffers
double SpreadBuffer[];
double AllInCostBuffer[];

double g_pipSize;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   IndicatorSetInteger(INDICATOR_DIGITS, 2);

   SetIndexBuffer(0, SpreadBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, AllInCostBuffer, INDICATOR_DATA);

   // Determine 1 pip based on instrument digits
   g_pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10.0 : _Point;

   // Empty value configuration so historical bars without live ticks stay unplotted
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Comment(""); // Clear chart HUD
}

//+------------------------------------------------------------------+
//| 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[])
{
   if(rates_total <= 0 || g_pipSize <= 0) return(0);

   // Initialize historical bars to empty value when the indicator loads
   if(prev_calculated == 0)
   {
      ArrayInitialize(SpreadBuffer, 0.0);
      ArrayInitialize(AllInCostBuffer, 0.0);
   }

   int lastBar = rates_total - 1;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   double commPips = (InpPipValuePerLot > 0) ? (InpCommissionPerLot / InpPipValuePerLot) : 0.0;
   double currentSpread = (ask - bid) / g_pipSize;
   double allInCost = currentSpread + commPips;

   SpreadBuffer[lastBar]    = currentSpread;
   AllInCostBuffer[lastBar] = allInCost;

   // Update HUD Dashboard
   UpdateDashboard(currentSpread, commPips, allInCost);

   return(rates_total);
}

//+------------------------------------------------------------------+
//| Session Evaluation & Dashboard HUD                               |
//+------------------------------------------------------------------+
void UpdateDashboard(double spread, double comm, double totalCost)
{
   datetime serverTime = TimeCurrent();
   datetime londonTime = serverTime - (InpBrokerGmtOffset * 3600) + (InpLondonGmtOffset * 3600);

   MqlDateTime dt;
   TimeToStruct(londonTime, dt);
   int totalMinutes = (dt.hour * 60) + dt.min;

   string sessionText = "Off-Peak / Outside Window";

   // Asian Session: 00:30 (30m) to 03:00 (180m)
   if(totalMinutes >= 30 && totalMinutes <= 180)
      sessionText = "Asian (00:30 - 03:00 London)";
   // London Session: 07:00 (420m) to 10:00 (600m)
   else if(totalMinutes >= 420 && totalMinutes <= 600)
      sessionText = "London (07:00 - 10:00 London)";

   string dashboard = StringFormat(
      "=== TRUE EXECUTION COST ===\n" +
      "Session Window   : %s\n" +
      "London Time      : %02d:%02d\n" +
      "Real-Time Spread : %.2f pips\n" +
      "Commission (RT)  : %.2f pips\n" +
      "Total All-In Cost: %.2f pips",
      sessionText, dt.hour, dt.min, spread, comm, totalCost
   );

   Comment(dashboard);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

Implementation Details

Live Feed Handling: MetaTrader bar history stores integer points from historical candle closes rather than sub-pip tick floating spreads. Both scripts initialize historical bars to empty/zero and plot the subwindow line forward only on incoming live market ticks to ensure calculations remain uncorrupted.

Pip Normalization: Both scripts detect 3-digit and 5-digit quotes (_Digits == 3 || _Digits == 5) and scale _Point by 10.0 so fractional pip calculations are displayed in standard pips.

Time Alignment: Most retail MetaTrader brokers run server clocks set to Eastern European Time (GMT+2 in winter, GMT+3 in summer). The inputs InpBrokerGmtOffset and InpLondonGmtOffset allow exact conversion to London clock time to match the session boundaries you tracked.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

Here is the updated MQL5 implementation.

To handle high-frequency tick data safely without freezing your terminal, this version introduces two critical structural changes:

File Handle Management: Instead of opening and closing the CSV file on every single tick (which causes severe I/O lag), the file is opened once when you attach the indicator and held in memory. FileFlush() is used after every write so you can open the CSV in Excel while MT5 is still running, without data corruption.

Alert Rate-Limiting: If the spread hovers above your threshold, generating a popup alert on every single tick will instantly crash your terminal. A cooldown timer (default 60 seconds) is applied to the visual popups, while the CSV continues to log every violating tick silently.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

Updated MQL5 Script (TrueExecutionCost_Logger.mq5)

Code: Select all

#property copyright "Custom"
#property link      ""
#property version   "1.10"
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   2

#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
#property indicator_label1  "Raw Spread (Pips)"

#property indicator_type2   DRAW_LINE
#property indicator_color2  clrOrange
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2
#property indicator_label2  "All-In Cost (Pips)"

//--- Inputs
input group "Cost Settings"
input double   InpCommissionPerLot  = 6.0;   // Round-turn Commission per Lot ($)
input double   InpPipValuePerLot    = 10.0;  // Pip Value per Lot ($)

input group "Time Offset (Hours)"
input int      InpBrokerGmtOffset   = 2;     // Broker Server GMT Offset
input int      InpLondonGmtOffset   = 0;     // London GMT Offset (0=Winter, 1=Summer)

input group "Spike Alerts & Tick Logging"
input double   InpSpikeThreshold    = 1.5;   // Spread Spike Threshold (Pips)
input bool     InpEnableAlerts      = true;  // Enable Visual Popup Alerts
input int      InpAlertCooldown     = 60;    // Alert Cooldown (Seconds)
input bool     InpEnableLogging     = true;  // Enable CSV Tick Logging
input bool     InpLogAsianOnly      = true;  // Only Log/Alert during Asian Session
input string   InpLogFileName       = "SpreadSpikes.csv"; // CSV File Name

//--- Buffers
double SpreadBuffer[];
double AllInCostBuffer[];

double g_pipSize;
int    g_fileHandle = INVALID_HANDLE;
datetime g_lastAlertTime = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   IndicatorSetInteger(INDICATOR_DIGITS, 2);

   SetIndexBuffer(0, SpreadBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, AllInCostBuffer, INDICATOR_DATA);

   g_pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10.0 : _Point;

   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);

   // Initialize CSV Logging
   if(InpEnableLogging)
   {
      // Open with FILE_SHARE_READ so you can view the file in Excel while MT5 runs
      g_fileHandle = FileOpen(InpLogFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_SHARE_READ, ",");
      if(g_fileHandle != INVALID_HANDLE)
      {
         FileSeek(g_fileHandle, 0, SEEK_END);
         // Write header if the file is brand new
         if(FileSize(g_fileHandle) == 0)
         {
            FileWrite(g_fileHandle, "Time(Broker)", "Time(London)", "Session", "Ask", "Bid", "Spread(Pips)");
            FileFlush(g_fileHandle);
         }
      }
      else
      {
         Print("Failed to open CSV file for logging. Error: ", GetLastError());
      }
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Comment(""); // Clear chart HUD
   
   if(g_fileHandle != INVALID_HANDLE)
   {
      FileClose(g_fileHandle);
   }
}

//+------------------------------------------------------------------+
//| 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[])
{
   if(rates_total <= 0 || g_pipSize <= 0) return(0);

   if(prev_calculated == 0)
   {
      ArrayInitialize(SpreadBuffer, 0.0);
      ArrayInitialize(AllInCostBuffer, 0.0);
   }

   int lastBar = rates_total - 1;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   double commPips = (InpPipValuePerLot > 0) ? (InpCommissionPerLot / InpPipValuePerLot) : 0.0;
   double currentSpread = (ask - bid) / g_pipSize;
   double allInCost = currentSpread + commPips;

   SpreadBuffer[lastBar]    = currentSpread;
   AllInCostBuffer[lastBar] = allInCost;

   string activeSession = GetCurrentSession();
   
   // Handle Alerts and Logging
   ProcessSpikeTracking(currentSpread, ask, bid, activeSession);

   // Update HUD
   UpdateDashboard(currentSpread, commPips, allInCost, activeSession);

   return(rates_total);
}

//+------------------------------------------------------------------+
//| Evaluates tick data for alerts and CSV logging                   |
//+------------------------------------------------------------------+
void ProcessSpikeTracking(double spread, double ask, double bid, string sessionText)
{
   if(spread < InpSpikeThreshold) return;
   
   bool isAsianSession = (StringFind(sessionText, "Asian") >= 0);
   
   // Enforce session restriction if enabled
   if(InpLogAsianOnly && !isAsianSession) return;

   datetime currentTime = TimeCurrent();

   // 1. Visual Alert (Rate-Limited)
   if(InpEnableAlerts && (currentTime - g_lastAlertTime >= InpAlertCooldown))
   {
      Alert(StringFormat("SPREAD SPIKE [%s]: %.2f pips (Ask: %.5f / Bid: %.5f)", _Symbol, spread, ask, bid));
      g_lastAlertTime = currentTime;
   }

   // 2. CSV Tick Logging (Every Violating Tick)
   if(InpEnableLogging && g_fileHandle != INVALID_HANDLE)
   {
      datetime londonTime = currentTime - (InpBrokerGmtOffset * 3600) + (InpLondonGmtOffset * 3600);
      
      string strBrokerTime = TimeToString(currentTime, TIME_DATE|TIME_SECONDS);
      string strLondonTime = TimeToString(londonTime, TIME_DATE|TIME_SECONDS);
      
      FileWrite(g_fileHandle, strBrokerTime, strLondonTime, sessionText, 
                DoubleToString(ask, _Digits), DoubleToString(bid, _Digits), DoubleToString(spread, 2));
                
      // Force disk write immediately so data isn't lost in a crash
      FileFlush(g_fileHandle); 
   }
}

//+------------------------------------------------------------------+
//| Retrieves the current session name based on offset               |
//+------------------------------------------------------------------+
string GetCurrentSession()
{
   datetime serverTime = TimeCurrent();
   datetime londonTime = serverTime - (InpBrokerGmtOffset * 3600) + (InpLondonGmtOffset * 3600);

   MqlDateTime dt;
   TimeToStruct(londonTime, dt);
   int totalMinutes = (dt.hour * 60) + dt.min;

   if(totalMinutes >= 30 && totalMinutes <= 180)
      return "Asian Open (00:30-03:00)";
   if(totalMinutes >= 420 && totalMinutes <= 600)
      return "London Open (07:00-10:00)";

   return "Off-Peak Window";
}

//+------------------------------------------------------------------+
//| Dashboard HUD Rendering                                          |
//+------------------------------------------------------------------+
void UpdateDashboard(double spread, double comm, double totalCost, string sessionText)
{
   datetime serverTime = TimeCurrent();
   datetime londonTime = serverTime - (InpBrokerGmtOffset * 3600) + (InpLondonGmtOffset * 3600);
   MqlDateTime dt; TimeToStruct(londonTime, dt);

   string dashboard = StringFormat(
      "=== TRUE EXECUTION COST ===\n" +
      "Session Window   : %s\n" +
      "London Time      : %02d:%02d\n" +
      "Real-Time Spread : %.2f pips\n" +
      "Commission (RT)  : %.2f pips\n" +
      "Total All-In Cost: %.2f pips\n" +
      "---------------------------\n" +
      "Logging Active   : %s (Threshold: %.1f)",
      sessionText, dt.hour, dt.min, spread, comm, totalCost,
      (InpEnableLogging ? "YES" : "NO"), InpSpikeThreshold
   );

   Comment(dashboard);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

Where to Find Your Logged Data

MetaTrader runs a strictly sandboxed file system for security. Your CSV will not save to your desktop. To find the exported file:

1.) Open MT5 and go to File > Open Data Folder.

2.) Navigate into MQL5 > Files.

3.) You will find SpreadSpikes.csv here.

Because the code uses FILE_SHARE_READ, you can open this CSV in Excel while MT5 is running. As the Asian session unfolds, you can click "Refresh" in Excel to see the new ticks populate in real-time.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

Here is the exact equivalent converted for MetaTrader 4 (MQL4).

Because MT4 handles indicator arrays (buffers) and file I/O slightly differently than MT5, this version has been adjusted so that historical bars remain properly empty, live ticks are plotted on the current [0] index (MT4's default timeseries behavior), and the CSV file is safely shared so you can watch it update in Excel.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

MQL4 Implementation (TrueExecutionCost_Logger.mq4)

Save this inside your MT4 MQL4/Indicators/ directory.

Code: Select all

#property copyright "Custom"
#property link      ""
#property version   "1.10"
#property strict
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1  clrGray
#property indicator_color2  clrOrange
#property indicator_width1  1
#property indicator_width2  2
#property indicator_label1  "Raw Spread (Pips)"
#property indicator_label2  "All-In Cost (Pips)"

//--- Inputs
input string   InpCostsGroup        = "--- Cost Settings ---";
input double   InpCommissionPerLot  = 6.0;   // Round-turn Commission per Lot ($)
input double   InpPipValuePerLot    = 10.0;  // Pip Value per Lot ($)

input string   InpTimeGroup         = "--- Time Offset (Hours) ---";
input int      InpBrokerGmtOffset   = 2;     // Broker Server GMT Offset
input int      InpLondonGmtOffset   = 0;     // London GMT Offset (0=Winter, 1=Summer)

input string   InpSpikeGroup        = "--- Spike Alerts & Logging ---";
input double   InpSpikeThreshold    = 1.5;   // Spread Spike Threshold (Pips)
input bool     InpEnableAlerts      = true;  // Enable Visual Popup Alerts
input int      InpAlertCooldown     = 60;    // Alert Cooldown (Seconds)
input bool     InpEnableLogging     = true;  // Enable CSV Tick Logging
input bool     InpLogAsianOnly      = true;  // Only Log/Alert during Asian Session
input string   InpLogFileName       = "SpreadSpikes_MT4.csv"; // CSV File Name

//--- Buffers
double SpreadBuffer[];
double AllInCostBuffer[];

double g_pipSize;
int    g_fileHandle = INVALID_HANDLE;
datetime g_lastAlertTime = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   IndicatorDigits(2);
   
   // Set up Spread Buffer
   SetIndexBuffer(0, SpreadBuffer);
   SetIndexStyle(0, DRAW_LINE);
   SetIndexEmptyValue(0, EMPTY_VALUE); // Prevents drawing flat zero lines on history
   
   // Set up All-In Cost Buffer
   SetIndexBuffer(1, AllInCostBuffer);
   SetIndexStyle(1, DRAW_LINE);
   SetIndexEmptyValue(1, EMPTY_VALUE);

   // Calculate fractional pip size
   g_pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10.0 : _Point;

   // Initialize CSV Logging
   if(InpEnableLogging)
   {
      // Open with FILE_SHARE_READ so you can view it in Excel while MT4 runs
      g_fileHandle = FileOpen(InpLogFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_SHARE_READ, ",");
      if(g_fileHandle != INVALID_HANDLE)
      {
         FileSeek(g_fileHandle, 0, SEEK_END);
         // Write header if the file is brand new
         if(FileSize(g_fileHandle) == 0)
         {
            FileWrite(g_fileHandle, "Time(Broker)", "Time(London)", "Session", "Ask", "Bid", "Spread(Pips)");
            FileFlush(g_fileHandle);
         }
      }
      else
      {
         Print("Failed to open CSV file for logging. Error: ", GetLastError());
      }
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Comment(""); // Clear chart HUD
   
   if(g_fileHandle != INVALID_HANDLE)
   {
      FileClose(g_fileHandle);
   }
}

//+------------------------------------------------------------------+
//| 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[])
{
   if(rates_total <= 0 || g_pipSize <= 0) return(0);

   // On first load, fill the entire history with EMPTY_VALUE so no line is drawn
   if(prev_calculated == 0)
   {
      ArrayInitialize(SpreadBuffer, EMPTY_VALUE);
      ArrayInitialize(AllInCostBuffer, EMPTY_VALUE);
   }

   // MQL4 indicator arrays are Timeseries by default. Index [0] is ALWAYS the live bar.
   double commPips = (InpPipValuePerLot > 0) ? (InpCommissionPerLot / InpPipValuePerLot) : 0.0;
   double currentSpread = (Ask - Bid) / g_pipSize;
   double allInCost = currentSpread + commPips;

   // Update the live bar only
   SpreadBuffer[0]    = currentSpread;
   AllInCostBuffer[0] = allInCost;

   string activeSession = GetCurrentSession();
   
   // Handle Alerts and Logging
   ProcessSpikeTracking(currentSpread, Ask, Bid, activeSession);

   // Update HUD
   UpdateDashboard(currentSpread, commPips, allInCost, activeSession);

   return(rates_total);
}

//+------------------------------------------------------------------+
//| Evaluates tick data for alerts and CSV logging                   |
//+------------------------------------------------------------------+
void ProcessSpikeTracking(double spread, double currentAsk, double currentBid, string sessionText)
{
   if(spread < InpSpikeThreshold) return;
   
   bool isAsianSession = (StringFind(sessionText, "Asian") >= 0);
   
   // Enforce session restriction if enabled
   if(InpLogAsianOnly && !isAsianSession) return;

   datetime currentTime = TimeCurrent();

   // 1. Visual Alert (Rate-Limited)
   if(InpEnableAlerts && (currentTime - g_lastAlertTime >= InpAlertCooldown))
   {
      Alert(StringFormat("SPREAD SPIKE [%s]: %.2f pips (Ask: %.5f / Bid: %.5f)", _Symbol, spread, currentAsk, currentBid));
      g_lastAlertTime = currentTime;
   }

   // 2. CSV Tick Logging (Every Violating Tick)
   if(InpEnableLogging && g_fileHandle != INVALID_HANDLE)
   {
      datetime londonTime = currentTime - (InpBrokerGmtOffset * 3600) + (InpLondonGmtOffset * 3600);
      
      string strBrokerTime = TimeToString(currentTime, TIME_DATE|TIME_SECONDS);
      string strLondonTime = TimeToString(londonTime, TIME_DATE|TIME_SECONDS);
      
      FileWrite(g_fileHandle, strBrokerTime, strLondonTime, sessionText, 
                DoubleToString(currentAsk, _Digits), DoubleToString(currentBid, _Digits), DoubleToString(spread, 2));
                
      // Force disk write immediately so data isn't lost if terminal crashes
      FileFlush(g_fileHandle); 
   }
}

//+------------------------------------------------------------------+
//| Retrieves the current session name based on offset               |
//+------------------------------------------------------------------+
string GetCurrentSession()
{
   datetime serverTime = TimeCurrent();
   datetime londonTime = serverTime - (InpBrokerGmtOffset * 3600) + (InpLondonGmtOffset * 3600);

   MqlDateTime dt;
   TimeToStruct(londonTime, dt);
   int totalMinutes = (dt.hour * 60) + dt.min;

   if(totalMinutes >= 30 && totalMinutes <= 180)
      return "Asian Open (00:30-03:00)";
   if(totalMinutes >= 420 && totalMinutes <= 600)
      return "London Open (07:00-10:00)";

   return "Off-Peak Window";
}

//+------------------------------------------------------------------+
//| Dashboard HUD Rendering                                          |
//+------------------------------------------------------------------+
void UpdateDashboard(double spread, double comm, double totalCost, string sessionText)
{
   datetime serverTime = TimeCurrent();
   datetime londonTime = serverTime - (InpBrokerGmtOffset * 3600) + (InpLondonGmtOffset * 3600);
   MqlDateTime dt; TimeToStruct(londonTime, dt);

   string dashboard = StringFormat(
      "=== TRUE EXECUTION COST ===\n" +
      "Session Window   : %s\n" +
      "London Time      : %02d:%02d\n" +
      "Real-Time Spread : %.2f pips\n" +
      "Commission (RT)  : %.2f pips\n" +
      "Total All-In Cost: %.2f pips\n" +
      "---------------------------\n" +
      "Logging Active   : %s (Threshold: %.1f)",
      sessionText, dt.hour, dt.min, spread, comm, totalCost,
      (InpEnableLogging ? "YES" : "NO"), InpSpikeThreshold
   );

   Comment(dashboard);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by PTScalper »

Where to find the MT4 Data Export

MetaTrader 4 sandboxes its file system differently than standard Windows applications.

1.) Open MT4 and go to File > Open Data Folder.

2.) Navigate to MQL4 > Files.

3.) You will find SpreadSpikes_MT4.csv here.

Because FileFlush() and FILE_SHARE_READ are utilized, the CSV file can remain open in Excel on your other monitor. As the rollover/Asian session executes, you can periodically hit "Refresh" in Excel to see the broker's spread spikes populate in real time without having to detach the indicator.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
LondonScalper
Posts: 965
Joined: Sat Sep 05, 2026 7:54 am

Re: Raw vs standard on AUDUSD: 30-day cost race

Post by LondonScalper »

The compounding frame is fair, and I understated it in the write-up. Thirty-five pips at half a lot looks small on a monthly statement; annualised at the same pace it is real money, and at full lots it stops being a rounding error.

Where I still land differently is which gap I care about most. The London window on those 118 tickets was a tidy 0.3-pip edge for raw. The 00:30–01:30 stretch was nearly double that, and that is the hour that decided the experiment for me. If someone only trades AUDUSD after 07:00 London, I would not fight them for keeping a standard account. My book is not that book.

I did not leave the standard login open as a control after the month. Once the fills looked identical and the cost line did not, keeping both terminals was just more noise on the desk.
Post Reply