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