264 lines
9.3 KiB
MQL5
264 lines
9.3 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Catch22EA.mq5|
|
|
//| MMQ — Muhammad Minhas Qamar |
|
|
//| www.mql5.com/en/articles/23488 |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "MMQ — Muhammad Minhas Qamar"
|
|
#property link "https://www.mql5.com/en/articles/23488"
|
|
#property version "1.00"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Catch22\Catch22Features.mqh>
|
|
#include <Math\Alglib\alglib.mqh>
|
|
|
|
//--- vol-regime prediction (must match the Lab that exported the model)
|
|
input int InpWindow = 128; // catch22 rolling window (match Lab)
|
|
input string InpModelFile = "Catch22\\model_combined.txt"; // exported forest
|
|
//--- regime filter: run the baseline strategy ONLY when the predicted
|
|
//--- next-window volatility regime is one the user has enabled.
|
|
input bool InpTradeLOW = false; // trade in LOW-vol regime
|
|
input bool InpTradeMED = true; // trade in MED-vol regime
|
|
input bool InpTradeHIGH = true; // trade in HIGH-vol regime
|
|
input bool InpUseFilter = true; // false => trade every regime (baseline control)
|
|
//--- baseline strategy (a plain MA cross; the article compares it with
|
|
//--- and without the catch22 regime filter)
|
|
input int InpFastMA = 20; // fast EMA period
|
|
input int InpSlowMA = 50; // slow EMA period
|
|
input int InpATRperiod = 14; // ATR for SL/TP
|
|
input double InpSLmult = 2.0; // SL in ATR
|
|
input double InpTPmult = 3.0; // TP in ATR
|
|
input double InpLots = 0.10; // fixed lot
|
|
input ulong InpMagic = 20260712;
|
|
|
|
//--- vol-regime classes (identical to the Lab)
|
|
#define LAB_LOW 0
|
|
#define LAB_MED 1
|
|
#define LAB_HIGH 2
|
|
#define NCLASSES 3
|
|
|
|
//--- globals
|
|
CTrade g_trade;
|
|
CCatch22FeatureBuilder g_fb;
|
|
CDecisionForestShell g_model;
|
|
int g_hATR=INVALID_HANDLE;
|
|
int g_hFast=INVALID_HANDLE;
|
|
int g_hSlow=INVALID_HANDLE;
|
|
bool g_ready=false;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Read the whole exported model file into a string. |
|
|
//+------------------------------------------------------------------+
|
|
bool LoadModelString(const string fname,string &out)
|
|
{
|
|
//--- Read as raw bytes from the shared Common\Files folder (FILE_COMMON),
|
|
//--- where the Lab exported the model. The tester runs in a sandbox with
|
|
//--- its own private Files\, so a plain read would not find a
|
|
//--- terminal-local model. ALGLIB's serialized forest is space/newline-
|
|
//--- separated tokens whose separators matter, so binary (not text) read
|
|
//--- is required to preserve them exactly.
|
|
int h=FileOpen(fname,FILE_READ|FILE_BIN|FILE_COMMON);
|
|
if(h==INVALID_HANDLE)
|
|
{
|
|
PrintFormat("Cannot open model file %s (err %d)",fname,GetLastError());
|
|
return false;
|
|
}
|
|
ulong sz=FileSize(h);
|
|
uchar bytes[];
|
|
ArrayResize(bytes,(int)sz);
|
|
uint read=FileReadArray(h,bytes,0,(int)sz);
|
|
FileClose(h);
|
|
if(read!=(uint)sz)
|
|
{
|
|
PrintFormat("Model read short: %u of %I64u bytes",read,sz);
|
|
return false;
|
|
}
|
|
out=CharArrayToString(bytes,0,(int)sz,CP_ACP);
|
|
return StringLen(out)>0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| EA init: build the feature engine (COMBINED arm, to match the |
|
|
//| exported model), load and deserialize the trained forest. |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
if(!g_fb.Init(_Symbol,_Period,InpWindow,ARM_COMBINED))
|
|
{
|
|
Print("feature builder init failed");
|
|
return INIT_FAILED;
|
|
}
|
|
g_hATR =iATR(_Symbol,_Period,InpATRperiod);
|
|
g_hFast=iMA(_Symbol,_Period,InpFastMA,0,MODE_EMA,PRICE_CLOSE);
|
|
g_hSlow=iMA(_Symbol,_Period,InpSlowMA,0,MODE_EMA,PRICE_CLOSE);
|
|
if(g_hATR==INVALID_HANDLE || g_hFast==INVALID_HANDLE || g_hSlow==INVALID_HANDLE)
|
|
return INIT_FAILED;
|
|
|
|
string s;
|
|
if(!LoadModelString(InpModelFile,s))
|
|
{
|
|
Print("Model not found. Run Catch22Lab first to export it.");
|
|
return INIT_FAILED;
|
|
}
|
|
CAlglib::DFUnserialize(s,g_model);
|
|
g_trade.SetExpertMagicNumber(InpMagic);
|
|
g_ready=true;
|
|
PrintFormat("Catch22EA ready: model loaded (%d chars), window=%d",
|
|
StringLen(s),InpWindow);
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| EA deinit: release handles. |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
g_fb.Deinit();
|
|
if(g_hATR!=INVALID_HANDLE)
|
|
IndicatorRelease(g_hATR);
|
|
if(g_hFast!=INVALID_HANDLE)
|
|
IndicatorRelease(g_hFast);
|
|
if(g_hSlow!=INVALID_HANDLE)
|
|
IndicatorRelease(g_hSlow);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| True once per newly-closed bar. |
|
|
//+------------------------------------------------------------------+
|
|
bool NewBar()
|
|
{
|
|
static datetime last=0;
|
|
datetime t=iTime(_Symbol,_Period,0);
|
|
if(t==last)
|
|
return false;
|
|
last=t;
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Classify the most recent CLOSED bar (shift 1) with the forest and|
|
|
//| return the predicted volatility-regime class, or -1 if the |
|
|
//| feature row cannot be built. |
|
|
//+------------------------------------------------------------------+
|
|
int Predict(void)
|
|
{
|
|
double row[];
|
|
if(!g_fb.Build(1,row)) // shift 1 = last closed bar
|
|
return -1;
|
|
if(ArraySize(row)!=CCatch22FeatureBuilder::DimOf(ARM_COMBINED))
|
|
return -1;
|
|
for(int j=0;j<ArraySize(row);j++)
|
|
if(!MathIsValidNumber(row[j]))
|
|
return -1;
|
|
|
|
double y[];
|
|
CAlglib::DFProcess(g_model,row,y); // class probabilities
|
|
int pred=0;
|
|
double best=y[0];
|
|
for(int k=1;k<NCLASSES;k++)
|
|
if(y[k]>best)
|
|
{
|
|
best=y[k];
|
|
pred=k;
|
|
}
|
|
return pred;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Current ATR on the last closed bar. |
|
|
//+------------------------------------------------------------------+
|
|
double CurrentATR()
|
|
{
|
|
double a[];
|
|
ArraySetAsSeries(a,true);
|
|
if(CopyBuffer(g_hATR,0,1,1,a)<1)
|
|
return 0.0;
|
|
return a[0];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Is trading permitted in the predicted regime? With the filter |
|
|
//| off, every regime is allowed (this is the baseline control the |
|
|
//| article compares against). |
|
|
//+------------------------------------------------------------------+
|
|
bool RegimeAllowed(int regime)
|
|
{
|
|
if(!InpUseFilter)
|
|
return true;
|
|
if(regime==LAB_LOW)
|
|
return InpTradeLOW;
|
|
if(regime==LAB_MED)
|
|
return InpTradeMED;
|
|
if(regime==LAB_HIGH)
|
|
return InpTradeHIGH;
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Baseline MA-cross signal on the last two closed bars: |
|
|
//| +1 = fast crossed above slow, -1 = crossed below, 0 = none. |
|
|
//+------------------------------------------------------------------+
|
|
int MASignal()
|
|
{
|
|
double f[],s[];
|
|
ArraySetAsSeries(f,true);
|
|
ArraySetAsSeries(s,true);
|
|
if(CopyBuffer(g_hFast,0,1,2,f)<2)
|
|
return 0;
|
|
if(CopyBuffer(g_hSlow,0,1,2,s)<2)
|
|
return 0;
|
|
bool up =(f[1]<=s[1] && f[0]>s[0]);
|
|
bool down=(f[1]>=s[1] && f[0]<s[0]);
|
|
if(up)
|
|
return +1;
|
|
if(down)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| On each new bar: predict the next-window vol regime, and if the |
|
|
//| regime filter allows trading, act on the baseline MA-cross with |
|
|
//| ATR-based SL/TP. The catch22 model gates WHEN the baseline runs. |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
if(!g_ready)
|
|
return;
|
|
if(!NewBar())
|
|
return;
|
|
if(PositionSelect(_Symbol))
|
|
return; // one position at a time
|
|
|
|
//--- predict the volatility regime of the coming window
|
|
int regime=Predict();
|
|
if(regime<0)
|
|
return;
|
|
if(!RegimeAllowed(regime))
|
|
return; // filtered out by the catch22 model
|
|
|
|
//--- baseline entry decision
|
|
int sig=MASignal();
|
|
if(sig==0)
|
|
return;
|
|
|
|
double atr=CurrentATR();
|
|
if(atr<=0.0)
|
|
return;
|
|
double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
|
|
double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
|
|
|
|
if(sig>0)
|
|
{
|
|
double sl=bid-InpSLmult*atr;
|
|
double tp=bid+InpTPmult*atr;
|
|
g_trade.Buy(InpLots,_Symbol,ask,sl,tp,"c22_regime");
|
|
}
|
|
else
|
|
{
|
|
double sl=ask+InpSLmult*atr;
|
|
double tp=ask-InpTPmult*atr;
|
|
g_trade.Sell(InpLots,_Symbol,bid,sl,tp,"c22_regime");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|