kronos-mql5/Scripts/Kronos/KronosForecastDump.mq5
2026-07-05 05:06:39 +00:00

217 lines
9.3 KiB
MQL5

//+------------------------------------------------------------------+
//| KronosForecastDump.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
#include <Kronos\KronosInference.mqh>
//--- tokenizer config (config_tokenizer.json)
#define KR_TOK_DIR "kronos_weights\\tokenizer\\"
#define KR_TOK_DM 256
#define KR_TOK_HEADS 4
#define KR_TOK_ENC 4
#define KR_TOK_DEC 4
#define KR_TOK_FF 512
//--- predictor config (config_predictor.json)
#define KR_PRED_DIR "kronos_weights\\predictor\\"
#define KR_PRED_DM 512
#define KR_PRED_HEADS 8
#define KR_PRED_LAYERS 8
#define KR_PRED_FF 1024
enum ENUM_VOL_MODE
{
VOL_TICK_DERIVED = 0, // tick_volume + derived amount (typical_price * tick_volume)
VOL_ZERO_FILL = 1 // zero-fill volume and amount
};
input datetime InpStartDate = D'2025.04.01 00:00'; // eval window start (first eval bar)
input datetime InpEndDate = D'2025.06.01 00:00'; // eval window end (last eval bar)
input int InpStep = 1; // dump density (evaluate every Nth bar)
input int InpLookback = 256; // context bars (<=512)
input int InpPredLen = 16; // forecast horizon (bars)
input ENUM_VOL_MODE InpVolMode = VOL_TICK_DERIVED; // volume/amount handling
input double InpTemperature = 1.0; // sampling temperature (greedy ignores)
input int InpTopK = 0; // top-k (greedy ignores)
input double InpTopP = 0.9; // nucleus top-p (greedy ignores)
input int InpSampleCount = 1; // averaged sample paths (1 for greedy)
input bool InpGreedy = true; // greedy => deterministic / reproducible
input string InpOutFile = "kronos_eval\\eurusd_h1_forecasts.csv"; // output (MQL5/Files)
CKronosModel g_model;
//+------------------------------------------------------------------+
//| Assemble the raw (L,6) window for an eval bar whose CLOSED bars |
//| occupy rates[] indices [base .. base+L-1] (oldest first). |
//| Mirrors the window-build in KronosForecast.mq5 exactly. |
//+------------------------------------------------------------------+
void BuildWindow(const MqlRates &rates[], int base, int L, matrix &raw, datetime &ctx_time[])
{
raw = matrix::Zeros((ulong)L, KR_NFEAT);
ArrayResize(ctx_time, L);
for(int i = 0; i < L; i++)
{
int src = base + i;
double o = rates[src].open, h = rates[src].high, lo = rates[src].low, c = rates[src].close;
double vol = 0.0, amt = 0.0;
if(InpVolMode == VOL_TICK_DERIVED)
{
vol = (double)rates[src].tick_volume;
double typical = (o + h + lo + c) / 4.0;
amt = vol * typical;
}
raw[i][0] = o;
raw[i][1] = h;
raw[i][2] = lo;
raw[i][3] = c;
raw[i][4] = vol;
raw[i][5] = amt;
ctx_time[i] = rates[src].time;
}
}
//+------------------------------------------------------------------+
//| Build the full (L+P,5) stamp matrix: context stamps + projected |
//| future bar stamps. Mirrors KronosForecast.mq5. |
//+------------------------------------------------------------------+
void BuildStamps(const datetime &ctx_time[], int L, int P, int secs, matrix &full_stamp)
{
full_stamp = matrix::Zeros((ulong)(L + P), 5);
for(int i = 0; i < L; i++)
{
int st[];
KronosStamp(ctx_time[i], st);
for(int j = 0; j < 5; j++)
full_stamp[i][j] = st[j];
}
datetime last_time = ctx_time[L - 1];
for(int i = 0; i < P; i++)
{
datetime ft = last_time + (datetime)((i + 1) * secs);
int st[];
KronosStamp(ft, st);
for(int j = 0; j < 5; j++)
full_stamp[L + i][j] = st[j];
}
}
//+------------------------------------------------------------------+
//| Script entry: load model, walk history, dump forecasts. |
//+------------------------------------------------------------------+
void OnStart()
{
const int L = InpLookback, P = InpPredLen;
if(L < 1 || L > 512) { Print("Lookback must be in 1..512"); return; }
if(P < 1) { Print("PredLen must be >= 1"); return; }
if(InpStep < 1) { Print("Step must be >= 1"); return; }
if(InpStartDate >= InpEndDate) { Print("StartDate must be before EndDate"); return; }
if(!g_model.Init(KR_TOK_DIR, KR_TOK_ENC, KR_TOK_DEC, KR_TOK_DM, KR_TOK_HEADS, KR_TOK_FF,
KR_PRED_DIR, KR_PRED_LAYERS, KR_PRED_DM, KR_PRED_HEADS, KR_PRED_FF, 512))
{
Print("Kronos model failed to load. Check kronos_weights/ under MQL5/Files/.");
return;
}
Print("Kronos model loaded. Building forecast dump...");
int secs = PeriodSeconds(_Period);
//--- Pull enough history: from L bars before the start, through P bars after
//--- the end (so every eval bar in [start,end] has both a full context AND P
//--- realized future bars to score against). Oldest-first (no series flag).
datetime from = InpStartDate - (datetime)((L + 2) * secs);
datetime to = InpEndDate + (datetime)((P + 2) * secs);
MqlRates rates[];
int got = CopyRates(_Symbol, _Period, from, to, rates);
if(got <= 0)
{ PrintFormat("CopyRates failed (err %d). Load H1 history for %s.", GetLastError(), _Symbol); return; }
PrintFormat("History: %d bars from %s to %s", got,
TimeToString(rates[0].time), TimeToString(rates[got - 1].time));
//--- open output (truncate + header)
int fh = FileOpen(InpOutFile, FILE_WRITE | FILE_CSV | FILE_ANSI, ',');
if(fh == INVALID_HANDLE)
{ PrintFormat("Cannot open %s for write (err %d)", InpOutFile, GetLastError()); return; }
WriteHeader(fh, P);
//--- Identify eval bars: a bar index t is an eval bar if
//--- rates[t].time in [start,end], it has L closed bars before it
//--- (indices t-L..t-1) and P realized bars after it (t+1..t+P).
//--- The context window is rates[t-L .. t-1]; the "current" bar is t (its close
//--- is last_close); forecasts cover t+1..t+P. Using bars strictly before t as
//--- context and t's close as the anchor avoids any look-ahead.
int rows_written = 0;
ulong t0 = GetTickCount64();
for(int t = L; t + P < got; t++)
{
if(rates[t].time < InpStartDate || rates[t].time > InpEndDate)
continue;
if(((t - L) % InpStep) != 0)
continue;
matrix raw;
datetime ctx_time[];
BuildWindow(rates, t - L, L, raw, ctx_time); // closed bars t-L..t-1
matrix full_stamp;
BuildStamps(ctx_time, L, P, secs, full_stamp);
matrix forecast; // (P,6) raw units
if(!g_model.Predict(raw, full_stamp, P, InpTemperature, InpTopK, InpTopP,
InpSampleCount, InpGreedy, forecast))
{ PrintFormat("Predict failed at %s", TimeToString(rates[t].time)); continue; }
WriteRow(fh, rates, t, P, forecast);
rows_written++;
if((rows_written % 25) == 0)
{
double secs_elapsed = (double)(GetTickCount64() - t0) / 1000.0;
PrintFormat(" %d rows | %s | %.1fs elapsed (%.2fs/forecast)",
rows_written, TimeToString(rates[t].time),
secs_elapsed, secs_elapsed / rows_written);
}
}
FileClose(fh);
double total = (double)(GetTickCount64() - t0) / 1000.0;
PrintFormat("Done. %d forecasts written to MQL5/Files/%s in %.1fs.",
rows_written, InpOutFile, total);
}
//+------------------------------------------------------------------+
//| CSV header: time, last_close, fc_c1..cP, ac_c1..cP. |
//+------------------------------------------------------------------+
void WriteHeader(int fh, int P)
{
string cols = "time,last_close";
for(int h = 1; h <= P; h++)
cols += StringFormat(",fc_c%d", h);
for(int h = 1; h <= P; h++)
cols += StringFormat(",ac_c%d", h);
FileWrite(fh, cols);
}
//+------------------------------------------------------------------+
//| One row: eval-bar time, its close, forecast closes 1..P, then the|
//| REALIZED future closes 1..P (rates[t+1..t+P]). Stored for scoring|
//| only — never an input to any forecast/trade decision. |
//+------------------------------------------------------------------+
void WriteRow(int fh, const MqlRates &rates[], int t, int P, const matrix &forecast)
{
string row = TimeToString(rates[t].time, TIME_DATE | TIME_MINUTES);
row += StringFormat(",%.6f", rates[t].close);
for(int h = 0; h < P; h++)
row += StringFormat(",%.6f", forecast[h][3]); // col 3 = close
for(int h = 1; h <= P; h++)
row += StringFormat(",%.6f", rates[t + h].close);
FileWrite(fh, row);
}
//+------------------------------------------------------------------+