//+------------------------------------------------------------------+ //| 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."); } //+------------------------------------------------------------------+