//+------------------------------------------------------------------+ //| 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 #include #include //--- 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;jbest) { 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]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"); } } //+------------------------------------------------------------------+