kronos-mql5/Experts/Kronos/KronosForecast.mq5

211 lines
8.3 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| KronosForecast.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
#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
#define KR_OBJ_PREFIX "KronosFC_"
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 (no volume proxy)
};
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
input int InpTopK = 0; // top-k (0 = off)
input double InpTopP = 0.9; // nucleus top-p
input int InpSampleCount = 5; // averaged sample paths
input bool InpGreedy = false; // greedy (deterministic) vs sampling
input color InpUpColor = clrDodgerBlue; // forecast up candle
input color InpDownColor = clrTomato; // forecast down candle
CKronosModel g_model;
bool g_ready = false;
datetime g_last_bar = 0;
//+------------------------------------------------------------------+
//| Expert initialization: load the model and forecast once. |
//+------------------------------------------------------------------+
int OnInit()
{
if(InpLookback < 1 || InpLookback > 512)
{ Print("Lookback must be in 1..512"); return INIT_PARAMETERS_INCORRECT; }
if(InpPredLen < 1)
{ Print("PredLen must be >= 1"); return INIT_PARAMETERS_INCORRECT; }
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 INIT_FAILED;
}
g_ready = true;
Print("Kronos model loaded. Forecasting once per new bar.");
//--- run once immediately on attach
RunForecast();
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization: remove the forecast objects. |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
ObjectsDeleteAll(0, KR_OBJ_PREFIX);
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Tick handler: re-forecast only when a new bar has completed. |
//+------------------------------------------------------------------+
void OnTick()
{
if(!g_ready)
return;
datetime t = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
if(t == g_last_bar)
return; // only on a new completed bar
g_last_bar = t;
RunForecast();
}
//+------------------------------------------------------------------+
//| Build the raw (L,6) window and stamps, predict, draw. |
//+------------------------------------------------------------------+
void RunForecast()
{
const int L = InpLookback, P = InpPredLen;
MqlRates rates[];
ArraySetAsSeries(rates, true);
//--- index 0 is the still-forming bar; use closed bars 1..L
int need = L + 1;
int got = CopyRates(_Symbol, _Period, 0, need, rates);
if(got < need)
{ PrintFormat("CopyRates: got %d of %d bars", got, need); return; }
//--- assemble raw window oldest->newest into (L,6): open,high,low,close,vol,amount
matrix raw = matrix::Zeros((ulong)L, KR_NFEAT);
datetime ctx_time[];
ArrayResize(ctx_time, L);
for(int i = 0; i < L; i++)
{
int src = L - i; // rates[L]..rates[1] -> rows 0..L-1 (oldest first)
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;
}
//--- full stamp (L+P, 5): context times + projected future bar times
int secs = PeriodSeconds(_Period);
matrix full_stamp = matrix::Zeros((ulong)(L + P), 5);
for(int i = 0; i < L; i++)
{
int st[];
KronosStamp(ctx_time[i], st); // verified: weekday remap Sun->Mon
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];
}
//--- predict (raw units out)
matrix forecast;
if(!g_model.Predict(raw, full_stamp, P, InpTemperature, InpTopK, InpTopP,
InpSampleCount, InpGreedy, forecast))
{ Print("Predict failed"); return; }
DrawForecast(forecast, last_time, secs, rates[1].close);
}
//+------------------------------------------------------------------+
//| Draw predicted candles as chart objects to the right. |
//+------------------------------------------------------------------+
void DrawForecast(const matrix &fc, datetime last_time, int secs, double last_close)
{
ObjectsDeleteAll(0, KR_OBJ_PREFIX);
int P = (int)fc.Rows();
double prev_close = last_close;
for(int i = 0; i < P; i++)
{
datetime t = last_time + (datetime)((i + 1) * secs);
double o = fc[i][0], h = fc[i][1], lo = fc[i][2], c = fc[i][3];
color col = (c >= o) ? InpUpColor : InpDownColor;
//--- wick: high-low vertical line
string wick = StringFormat("%swick_%d", KR_OBJ_PREFIX, i);
ObjectCreate(0, wick, OBJ_TREND, 0, t, h, t, lo);
ObjectSetInteger(0, wick, OBJPROP_COLOR, col);
ObjectSetInteger(0, wick, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, wick, OBJPROP_RAY, false);
//--- body: thick open-close line
string body = StringFormat("%sbody_%d", KR_OBJ_PREFIX, i);
ObjectCreate(0, body, OBJ_TREND, 0, t, o, t, c);
ObjectSetInteger(0, body, OBJPROP_COLOR, col);
ObjectSetInteger(0, body, OBJPROP_WIDTH, 4);
ObjectSetInteger(0, body, OBJPROP_RAY, false);
//--- connector close[i-1] -> close[i] (dotted path)
string seg = StringFormat("%sseg_%d", KR_OBJ_PREFIX, i);
datetime t0 = last_time + (datetime)(i * secs);
ObjectCreate(0, seg, OBJ_TREND, 0, t0, prev_close, t, c);
ObjectSetInteger(0, seg, OBJPROP_COLOR, clrSilver);
ObjectSetInteger(0, seg, OBJPROP_STYLE, STYLE_DOT);
ObjectSetInteger(0, seg, OBJPROP_RAY, false);
prev_close = c;
}
string lbl = KR_OBJ_PREFIX + "label";
ObjectCreate(0, lbl, OBJ_TEXT, 0, last_time + (datetime)secs, fc[0][1]);
ObjectSetString(0, lbl, OBJPROP_TEXT, StringFormat("Kronos +%d", P));
ObjectSetInteger(0, lbl, OBJPROP_COLOR, clrWhite);
ChartRedraw();
PrintFormat("Forecast drawn: %d bars ahead, first close=%.5f last close=%.5f",
P, fc[0][3], fc[P - 1][3]);
}
//+------------------------------------------------------------------+