//+------------------------------------------------------------------+ //| KronosSignalEA.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 input string InpInFile = "kronos_eval\\eurusd_h1_forecasts.csv"; // dump file (Common\Files) input int InpPredLen = 16; // horizon count P stored in the dump input int InpHorizon = 16; // which horizon's forecast to trade (1..P) input double InpThreshold = 0.0010; // min predicted move (fraction, e.g. 0.0010 = 0.10%) input double InpLots = 0.10; // fixed position size input ulong InpMagic = 920607; // EA magic number CTrade g_trade; //--- dump held as parallel arrays, ascending by time datetime g_time[]; double g_last_close[]; double g_fc[]; // flattened [row*P + (h-1)] forecast close int g_rows = 0; int g_hold = 0; // bars remaining to hold the open position //+------------------------------------------------------------------+ //| Load the dump CSV into parallel arrays. | //+------------------------------------------------------------------+ bool LoadDump() { const int P = InpPredLen; //--- FILE_COMMON is essential here: a tester agent sees only its own private //--- Files folder, so the shared Common\Files is the only readable location. int fh = FileOpen(InpInFile, FILE_READ | FILE_CSV | FILE_ANSI | FILE_COMMON, ','); if(fh == INVALID_HANDLE) { PrintFormat("Cannot open %s (err %d). Run KronosForecastDump first.", InpInFile, GetLastError()); return false; } int expected = 2 + 2 * P; // time,last_close, fc_c1..cP, ac_c1..cP ArrayResize(g_time, 0); ArrayResize(g_last_close, 0); ArrayResize(g_fc, 0); g_rows = 0; while(!FileIsEnding(fh)) { string time_s = FileReadString(fh); if(time_s == "") break; if(time_s == "time") // header { for(int k = 1; k < expected && !FileIsLineEnding(fh); k++) FileReadString(fh); continue; } double last_close = (double)FileReadString(fh); double fc[]; ArrayResize(fc, P); for(int h = 0; h < P; h++) fc[h] = (double)FileReadString(fh); for(int h = 0; h < P; h++) FileReadString(fh); // skip realized actuals (scoring only) int idx = g_rows; ArrayResize(g_time, idx + 1); ArrayResize(g_last_close, idx + 1); ArrayResize(g_fc, (idx + 1) * P); g_time[idx] = StringToTime(time_s); g_last_close[idx] = last_close; for(int h = 0; h < P; h++) g_fc[idx * P + h] = fc[h]; g_rows++; } FileClose(fh); PrintFormat("Loaded %d forecast rows from %s", g_rows, InpInFile); return g_rows > 0; } //+------------------------------------------------------------------+ //| Find the dump row whose time == bar_time (exact match, the dump | //| is keyed on the eval-bar time). Linear from a remembered cursor | //| since the tester advances forward in time. | //+------------------------------------------------------------------+ int g_cursor = 0; int FindRow(datetime bar_time) { while(g_cursor < g_rows && g_time[g_cursor] < bar_time) g_cursor++; if(g_cursor < g_rows && g_time[g_cursor] == bar_time) return g_cursor; return -1; } //+------------------------------------------------------------------+ int OnInit() { const int P = InpPredLen; if(InpHorizon < 1 || InpHorizon > P) { Print("Horizon must be in 1..PredLen"); return INIT_PARAMETERS_INCORRECT; } g_trade.SetExpertMagicNumber(InpMagic); if(!LoadDump()) return INIT_FAILED; return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| New-bar detection: act once per completed bar, at its open. | //+------------------------------------------------------------------+ datetime g_last_bar = 0; void OnTick() { datetime t = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE); if(t == g_last_bar) return; g_last_bar = t; OnBar(); } //+------------------------------------------------------------------+ //| Current position direction held by THIS EA on this symbol: +1 | //| long, -1 short, 0 flat. Reading the terminal each bar instead | //| of a shadow flag keeps the EA in step with the real position | //| and prevents duplicate same-bar orders. | //+------------------------------------------------------------------+ int CurrentDir() { if(!PositionSelect(_Symbol)) return 0; if(PositionGetInteger(POSITION_MAGIC) != (long)InpMagic) return 0; long ptype = PositionGetInteger(POSITION_TYPE); return (ptype == POSITION_TYPE_BUY) ? +1 : -1; } //+------------------------------------------------------------------+ //| Bar logic. The forecast keyed at the PREVIOUS bar (the just- | //| closed bar) is the one we may act on now, at this bar's open, | //| so the decision never uses information from the bar it trades on.| //+------------------------------------------------------------------+ void OnBar() { //--- the just-closed bar is index 1; its time keys the forecast we trade now datetime closed_time = (datetime)iTime(_Symbol, _Period, 1); int r = FindRow(closed_time); int dir = CurrentDir(); // terminal truth, not a shadow flag //--- time-based exit: decrement hold, close when it runs out. If we hold //--- nothing (a prior close already settled), keep the counter at zero. if(dir != 0) { g_hold--; if(g_hold <= 0) { CloseAll(); dir = 0; // position is now flat for the rest of this bar } } else g_hold = 0; if(r < 0) return; // no forecast for this bar double last_close = g_last_close[r]; double fc_h = g_fc[r * InpPredLen + (InpHorizon - 1)]; if(last_close <= 0.0) return; double pred_ret = (fc_h - last_close) / last_close; int want = 0; if(pred_ret > InpThreshold) want = +1; else if(pred_ret < -InpThreshold) want = -1; if(want == 0 || want == dir) return; // no signal, or already in the wanted direction (no dup order) //--- flip or fresh entry: close any opposite position first, then open once if(dir != 0) CloseAll(); if(want > 0) g_trade.Buy(InpLots); else g_trade.Sell(InpLots); g_hold = InpHorizon; // reset hold for the new position } //+------------------------------------------------------------------+ //| Close any position this EA owns on the current symbol. | //+------------------------------------------------------------------+ void CloseAll() { if(PositionSelect(_Symbol)) { if(PositionGetInteger(POSITION_MAGIC) == (long)InpMagic) g_trade.PositionClose(_Symbol); } } //+------------------------------------------------------------------+