catch22/Experts/Catch22/Catch22EA.mq5
2026-07-12 10:16:54 +00:00

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