175 lines
6.1 KiB
MQL5
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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|