Adds the two Expert Advisors from Part 3, which were not previously in
the repository:
Experts/Kronos/KronosForecast.mq5 visualize-only EA, draws the
forecast candles to the right of live price on each new bar
Experts/Kronos/KronosSignalEA.mq5 Strategy Tester EA, trades a
deliberately trivial threshold rule over a precomputed dump
Switches the forecast dump to the shared Common\Files folder. A
Strategy Tester agent is sandboxed to its own private MQL5\Files
directory and cannot read what the script wrote to the terminal's
Files folder, so KronosForecastDump, KronosEvalQuality and
KronosSignalEA now all open the CSV with FILE_COMMON.
Other changes:
- KronosEvalQuality: drop the unused rw_hit accumulator and correct
the comment that described it as reported separately
- KronosForecastDump: shorten two over-long block comments
- Engine headers and KronosVerifyEncoder: align //--- block comments
flush-left and tidy trailing comment columns, no logic changes
- README: document the evaluation workflow, the Common\Files
requirement, and the measured result
190 lines
7.3 KiB
MQL5
190 lines
7.3 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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 <Trade\Trade.mqh>
|
|
|
|
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);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|