217 lines
9.3 KiB
MQL5
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);
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|