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