Save this in MQL4\Experts. The CSV will output to File -> Open Data Folder -> MQL4\Files\Execution_KPI_Log.csv. MQL4 handles error codes differently (requiring GetLastError()) and relies on OrderSelect post-fill to retrieve the exact entry price.
Code: Select all
//+------------------------------------------------------------------+
//| ExecutionKPILogger.mq4|
//+------------------------------------------------------------------+
#property strict
input double InpVolume = 0.1; // Volume (Lots)
string csv_filename = "Execution_KPI_Log.csv";
int OnInit()
{
CreateButton("btnBuy", "BUY (Track KPI)", 20, 20, clrWhite, clrSeaGreen);
CreateButton("btnSell", "SELL (Track KPI)", 160, 20, clrWhite, clrFireBrick);
if(!FileIsExist(csv_filename))
{
int handle = FileOpen(csv_filename, FILE_WRITE|FILE_TXT|FILE_ANSI);
if(handle != INVALID_HANDLE)
{
FileWrite(handle, "Timestamp,Symbol,Side,SpreadAtClick(Pips),RequestedPrice,FillPrice,Slippage(Pips),Latency(ms),Status,RejectReason");
FileClose(handle);
}
}
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
ObjectDelete(0, "btnBuy");
ObjectDelete(0, "btnSell");
}
void CreateButton(string name, string text, int x, int y, color txt_color, color bg_color)
{
ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, 130);
ObjectSetInteger(0, name, OBJPROP_YSIZE, 35);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, txt_color);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg_color);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_LOWER);
ObjectSetInteger(0, name, OBJPROP_STATE, false);
}
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_CLICK)
{
if(sparam == "btnBuy")
{
ExecuteAndLog(OP_BUY);
ObjectSetInteger(0, "btnBuy", OBJPROP_STATE, false);
}
else if(sparam == "btnSell")
{
ExecuteAndLog(OP_SELL);
ObjectSetInteger(0, "btnSell", OBJPROP_STATE, false);
}
}
}
void ExecuteAndLog(int type)
{
ulong start_mcs = GetMicrosecondCount();
datetime click_time = TimeCurrent();
RefreshRates(); // Vital in MQL4 to ensure Ask/Bid aren't stale before order
double req_price = (type == OP_BUY) ? Ask : Bid;
double pip_size = (Digits == 3 || Digits == 5) ? Point * 10 : Point;
double spread_pips = (Ask - Bid) / pip_size;
// Blocking execution call
int ticket = OrderSend(Symbol(), type, InpVolume, req_price, 1000, 0, 0, "KPI_Logger", 999111, 0, (type==OP_BUY)?clrBlue:clrRed);
ulong latency_mcs = GetMicrosecondCount() - start_mcs;
double latency_ms = latency_mcs / 1000.0;
bool success = (ticket > 0);
double fill_price = 0;
double slippage = 0;
int err_code = 0;
if(success)
{
if(OrderSelect(ticket, SELECT_BY_TICKET))
{
fill_price = OrderOpenPrice();
slippage = (type == OP_BUY)
? (fill_price - req_price) / pip_size
: (req_price - fill_price) / pip_size;
}
}
else
{
err_code = GetLastError();
}
string status = success ? "Filled" : "Rejected";
string reason = success ? "" : IntegerToString(err_code);
string side_str = (type == OP_BUY) ? "Buy" : "Sell";
int handle = FileOpen(csv_filename, FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI);
if(handle != INVALID_HANDLE)
{
FileSeek(handle, 0, SEEK_END);
string log_line = StringFormat("%s,%s,%s,%.1f,%f,%f,%.1f,%.1f,%s,%s",
TimeToString(click_time, TIME_DATE|TIME_SECONDS),
Symbol(), side_str, spread_pips, req_price, fill_price,
slippage, latency_ms, status, reason);
FileWrite(handle, log_line);
FileClose(handle);
}
PrintFormat("Execution: %s | Latency: %.1fms | Slippage: %.1f pips", status, latency_ms, slippage);
}