kronos-mql5/Scripts/Kronos/KronosEvalQuality.mq5

128 lines
5.5 KiB
MQL5
Raw Permalink Normal View History

2026-07-05 05:06:39 +00:00
//+------------------------------------------------------------------+
//| KronosEvalQuality.mq5 |
//| MMQ — Muhammad Minhas Qamar |
//| www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "MMQ — Muhammad Minhas Qamar"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#property script_show_inputs
input string InpInFile = "kronos_eval\\eurusd_h1_forecasts.csv"; // dump file (MQL5/Files)
input int InpPredLen = 16; // horizon count P in the dump
//+------------------------------------------------------------------+
//| Per-horizon running accumulators. |
//+------------------------------------------------------------------+
struct HorizonStat
{
int n; // samples
int dir_hit; // Kronos directional hits
int rw_hit; // random-walk directional hits (baseline: always "flat")
double k_abs; // sum |kronos_ret - actual_ret|
double k_sq; // sum (kronos_ret - actual_ret)^2
double rw_abs; // sum |0 - actual_ret| == sum |actual_ret|
double rw_sq; // sum actual_ret^2
};
//+------------------------------------------------------------------+
//| Script entry: load dump, score, print table. |
//+------------------------------------------------------------------+
void OnStart()
{
const int P = InpPredLen;
if(P < 1) { Print("PredLen must be >= 1"); return; }
int fh = FileOpen(InpInFile, FILE_READ | FILE_CSV | FILE_ANSI, ',');
if(fh == INVALID_HANDLE)
{ PrintFormat("Cannot open %s (err %d). Run KronosForecastDump first.", InpInFile, GetLastError()); return; }
HorizonStat st[];
ArrayResize(st, P);
for(int h = 0; h < P; h++)
{ st[h].n = 0; st[h].dir_hit = 0; st[h].rw_hit = 0;
st[h].k_abs = 0; st[h].k_sq = 0; st[h].rw_abs = 0; st[h].rw_sq = 0; }
//--- expected columns: time, last_close, fc_c1..cP, ac_c1..cP => 2 + 2P
int expected = 2 + 2 * P;
int rows = 0, skipped_header = 0;
while(!FileIsEnding(fh))
{
string time_s = FileReadString(fh);
if(time_s == "") // trailing blank
break;
if(time_s == "time") // header line
{ // consume the rest of the header row
for(int k = 1; k < expected && !FileIsLineEnding(fh); k++)
FileReadString(fh);
skipped_header++;
continue;
}
double last_close = (double)FileReadString(fh);
double fc[]; ArrayResize(fc, P);
double ac[]; ArrayResize(ac, P);
for(int h = 0; h < P; h++) fc[h] = (double)FileReadString(fh);
for(int h = 0; h < P; h++) ac[h] = (double)FileReadString(fh);
if(last_close <= 0.0)
continue;
for(int h = 0; h < P; h++)
{
double k_ret = (fc[h] - last_close) / last_close; // predicted cumulative return to h
double a_ret = (ac[h] - last_close) / last_close; // realized cumulative return to h
//--- directional accuracy (skip exact-flat actuals: no direction to call)
if(a_ret != 0.0)
{
if((k_ret > 0.0) == (a_ret > 0.0)) st[h].dir_hit++;
//--- random-walk predicts no change => never calls a direction; counts as
//--- a coin-flip baseline of 0 hits here (reported separately below)
st[h].n++;
}
double k_err = k_ret - a_ret;
st[h].k_abs += MathAbs(k_err);
st[h].k_sq += k_err * k_err;
st[h].rw_abs += MathAbs(a_ret); // baseline error == |actual return|
st[h].rw_sq += a_ret * a_ret;
}
rows++;
}
FileClose(fh);
if(rows == 0)
{ Print("No data rows parsed. Check the dump file/PredLen."); return; }
PrintFormat("=== Kronos forecast-skill report === (%d forecasts, P=%d)", rows, P);
Print("h | DirAcc | RMSE_ret RW_RMSE skill | MAE_ret RW_MAE skill");
Print("--+----------+----------------------------+---------------------------");
double agg_dir = 0; int agg_n = 0;
for(int h = 0; h < P; h++)
{
int n = st[h].n;
double diracc = (n > 0) ? (double)st[h].dir_hit / n : 0.0;
double k_rmse = MathSqrt(st[h].k_sq / rows);
double rw_rmse= MathSqrt(st[h].rw_sq / rows);
double k_mae = st[h].k_abs / rows;
double rw_mae = st[h].rw_abs / rows;
//--- skill ratio < 1 means Kronos beats random-walk on that error metric
double rmse_skill = (rw_rmse > 0) ? k_rmse / rw_rmse : 0.0;
double mae_skill = (rw_mae > 0) ? k_mae / rw_mae : 0.0;
PrintFormat("%2d| %6.2f%% | %.6f %.6f %5.3f | %.6f %.6f %5.3f",
h + 1, diracc * 100.0, k_rmse, rw_rmse, rmse_skill,
k_mae, rw_mae, mae_skill);
agg_dir += st[h].dir_hit; agg_n += n;
}
Print("--+----------+----------------------------+---------------------------");
PrintFormat("Overall directional accuracy across all horizons: %.2f%% (n=%d)",
(agg_n > 0 ? 100.0 * agg_dir / agg_n : 0.0), agg_n);
Print("Reading: DirAcc > 50%% = some directional skill; error skill < 1.000 =");
Print("Kronos beats the random-walk (no-change) baseline on that horizon.");
}
//+------------------------------------------------------------------+