mql5/Experts/meanSpreadReversion/meanSpreadReversion.mq5
TomasVlasak 92c7ee0d38
2026-02-08 14:17:14 +01:00

175 lines
6.1 KiB
MQL5

//--- Inputs (visible in Inputs tab)
input string InpSymbol1 = "BTCUSD"; // Symbol 1
input string InpSymbol2 = "ETHUSD"; // Symbol 2
input int InpMaxLogs = 100; // Max logs to keep (mean/std/z-score when reached)
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
ENUM_TIMEFRAMES tf = Period();
int seconds = PeriodSeconds(_Period);
EventSetTimer(seconds);
Comment("meanSpreadReversion started. Chart timeframe: ", EnumToString(tf), " (log every ", seconds, " sec)");
return(INIT_SUCCEEDED);
}
// Parse "ask-spread" and "bid-spread" from a JSON line; return mid spread (average). Returns false if parse fails.
bool ParseSpreadFromLine(const string &line, double &outAskSpread, double &outBidSpread)
{
int pa = StringFind(line, "\"ask-spread\":");
int pb = StringFind(line, "\"bid-spread\":");
if(pa < 0 || pb < 0) return false;
// Skip "\"ask-spread\":" (13 chars) to start at the number
int startA = pa + 13;
int endA = StringFind(line, ",", startA);
if(endA < 0) endA = StringLen(line);
int startB = pb + 13;
int endB = StringFind(line, "}", startB);
if(endB < 0) endB = StringLen(line);
outAskSpread = StringToDouble(StringSubstr(line, startA, endA - startA));
outBidSpread = StringToDouble(StringSubstr(line, startB, endB - startB));
return true;
}
void SavePriceToJSON(string symbol1, string symbol2)
{
double sbl1_bid = SymbolInfoDouble(symbol1, SYMBOL_BID);
double sbl1_ask = SymbolInfoDouble(symbol1, SYMBOL_ASK);
double sbl2_bid = SymbolInfoDouble(symbol2, SYMBOL_BID);
double sbl2_ask = SymbolInfoDouble(symbol2, SYMBOL_ASK);
double ask_spread = MathLog(sbl1_ask) - MathLog(sbl2_ask);
double bid_spread = MathLog(sbl1_bid) - MathLog(sbl2_bid);
double current_spread = (ask_spread + bid_spread) * 0.5;
ENUM_TIMEFRAMES tf = Period();
string tfStr = EnumToString(tf);
string json = "{";
json += "\"time\":\"" + TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES) + "\",";
json += "\"timeframe\":\"" + tfStr + "\",";
json += "\"symbol1\":\"" + symbol1 + "\",";
json += "\"symbol2\":\"" + symbol2 + "\",";
json += "\"ask1\":" + DoubleToString(sbl1_ask, 5) + ",";
json += "\"bid1\":" + DoubleToString(sbl1_bid, 5) + ",";
json += "\"ask2\":" + DoubleToString(sbl2_ask, 5) + ",";
json += "\"bid2\":" + DoubleToString(sbl2_bid, 5) + ",";
json += "\"ask-spread\":" + DoubleToString(ask_spread, 8) + ",";
json += "\"bid-spread\":" + DoubleToString(bid_spread, 8) + "}";
string filename = "prices_" + tfStr + ".json";
// Read existing lines
string lines[];
ArrayResize(lines, 0);
int file = FileOpen(filename, FILE_READ | FILE_TXT | FILE_ANSI | FILE_COMMON);
if(file != INVALID_HANDLE)
{
while(!FileIsEnding(file))
{
string ln = FileReadString(file);
StringTrimRight(ln);
StringTrimLeft(ln);
if(ln != "")
{
int n = ArraySize(lines);
ArrayResize(lines, n + 1);
lines[n] = ln;
}
}
FileClose(file);
}
// Cap: if we have >= InpMaxLogs, keep only last (InpMaxLogs-1), then add new -> exactly InpMaxLogs
if(ArraySize(lines) >= InpMaxLogs)
{
int keep = InpMaxLogs - 1;
int start = ArraySize(lines) - keep;
string trimmed[];
ArrayResize(trimmed, keep);
for(int i = 0; i < keep; i++)
trimmed[i] = lines[start + i];
ArrayResize(lines, keep);
for(int i = 0; i < keep; i++)
lines[i] = trimmed[i];
}
int n = ArraySize(lines);
ArrayResize(lines, n + 1);
lines[n] = json;
// Write file (overwrite with exactly lines[])
file = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_COMMON);
if(file == INVALID_HANDLE)
{
Print("FileOpen failed for ", filename, ", error ", GetLastError());
return;
}
for(int i = 0; i < ArraySize(lines); i++)
{
if(i > 0) FileWriteString(file, "\n");
FileWriteString(file, lines[i]);
}
FileClose(file);
Print("Logged prices to Common\\Files\\", filename, " (", ArraySize(lines), " logs)");
// When we have >= InpMaxLogs, compute mean, standard deviation, z-score
if(ArraySize(lines) >= InpMaxLogs)
{
double spreads[];
ArrayResize(spreads, ArraySize(lines));
int count = 0;
for(int i = 0; i < ArraySize(lines); i++)
{
double a = 0, b = 0;
if(ParseSpreadFromLine(lines[i], a, b))
{
spreads[count++] = (a + b) * 0.5;
}
}
if(count < 2) return;
double sum = 0;
for(int i = 0; i < count; i++) sum += spreads[i];
double mean = sum / count;
double sumSq = 0;
for(int i = 0; i < count; i++)
sumSq += (spreads[i] - mean) * (spreads[i] - mean);
double variance = sumSq / count;
double std = MathSqrt(variance);
double zScore = (std > 0) ? (current_spread - mean) / std : 0;
Print("Spread: mean=", DoubleToString(mean, 8), " std=", DoubleToString(std, 8),
" current=", DoubleToString(current_spread, 8), " z-score=", DoubleToString(zScore, 4));
}
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
Print("meanSpreadReversion stopped. Reason: ", reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Log to file once per bar — frequency = chart timeframe (e.g. M5 -> every 5 min)
static datetime lastBar = 0;
datetime thisBar = iTime(_Symbol, _Period, 0);
if(thisBar != lastBar)
{
lastBar = thisBar;
SavePriceToJSON(InpSymbol1, InpSymbol2);
}
}