//+------------------------------------------------------------------+ //| Catch22Features.mqh | //| 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 //+------------------------------------------------------------------+ //| Shared feature builder for the ablation study. Both the Lab | //| script (offline training) and the EA (live inference) build | //| their feature vectors HERE so the columns are guaranteed | //| identical — the single most important correctness constraint of | //| a train-offline / trade-live pipeline. | //| | //| Three "arms" select which block of columns is produced: | //| ARM_CLASSIC : classic indicators only (baseline control) | //| ARM_CATCH22 : the 22 catch22 features | //| ARM_COMBINED : classic + catch22 | //+------------------------------------------------------------------+ enum ENUM_C22_ARM { ARM_CLASSIC=0, // classic indicators only ARM_CATCH22, // catch22 only ARM_COMBINED // both }; //--- number of classic indicator features produced by this module #define C22_NCLASSIC 8 //+------------------------------------------------------------------+ //| CCatch22FeatureBuilder | //| Owns the indicator handles and a CCatch22 engine. Call Init() | //| once, then Build() per bar index to fill a feature vector for | //| the chosen arm. Column count is Dim(arm). | //+------------------------------------------------------------------+ class CCatch22FeatureBuilder { private: string m_symbol; ENUM_TIMEFRAMES m_tf; int m_window; // rolling window for catch22 (bars of returns) ENUM_C22_ARM m_arm; //--- classic indicator handles int m_hRSI; int m_hATR; int m_hMAfast; int m_hMAslow; int m_hStd; CCatch22 m_c22; //--- fill the classic block for the window ENDING at bar 'shift' bool BuildClassic(int shift,double &out[]); //--- fill the catch22 block from returns of the window ending at 'shift' bool BuildCatch22(int shift,double &out[]); public: CCatch22FeatureBuilder(void); ~CCatch22FeatureBuilder(void); bool Init(const string symbol,ENUM_TIMEFRAMES tf, int window,ENUM_C22_ARM arm); void Deinit(void); //--- number of feature columns for the current arm int Dim(void) const; static int DimOf(ENUM_C22_ARM arm); //--- build the full feature vector for the bar at 'shift'. Returns //--- false if the required history is not yet available. bool Build(int shift,double &out[]); //--- column name for diagnostics / CSV headers / importance labels string ColumnName(int col) const; }; //+------------------------------------------------------------------+ //| Construct with empty handles. | //+------------------------------------------------------------------+ CCatch22FeatureBuilder::CCatch22FeatureBuilder(void) { m_hRSI=INVALID_HANDLE; m_hATR=INVALID_HANDLE; m_hMAfast=INVALID_HANDLE; m_hMAslow=INVALID_HANDLE; m_hStd=INVALID_HANDLE; m_window=128; m_arm=ARM_COMBINED; } //+------------------------------------------------------------------+ //| Release handles on destruction. | //+------------------------------------------------------------------+ CCatch22FeatureBuilder::~CCatch22FeatureBuilder(void) { Deinit(); } //+------------------------------------------------------------------+ //| Number of feature columns for a given arm. | //+------------------------------------------------------------------+ static int CCatch22FeatureBuilder::DimOf(ENUM_C22_ARM arm) { switch(arm) { case ARM_CLASSIC: return C22_NCLASSIC; case ARM_CATCH22: return CATCH22_N; case ARM_COMBINED: return C22_NCLASSIC+CATCH22_N; } return 0; } //+------------------------------------------------------------------+ //| Dimension for the configured arm. | //+------------------------------------------------------------------+ int CCatch22FeatureBuilder::Dim(void) const { return DimOf(m_arm); } //+------------------------------------------------------------------+ //| Create indicator handles and store configuration. | //+------------------------------------------------------------------+ bool CCatch22FeatureBuilder::Init(const string symbol,ENUM_TIMEFRAMES tf, int window,ENUM_C22_ARM arm) { m_symbol=symbol; m_tf=tf; m_window=window; m_arm=arm; //--- classic indicators are only needed for the classic/combined arms if(arm==ARM_CLASSIC || arm==ARM_COMBINED) { m_hRSI =iRSI(symbol,tf,14,PRICE_CLOSE); m_hATR =iATR(symbol,tf,14); m_hMAfast=iMA(symbol,tf,20,0,MODE_EMA,PRICE_CLOSE); m_hMAslow=iMA(symbol,tf,50,0,MODE_EMA,PRICE_CLOSE); m_hStd =iStdDev(symbol,tf,20,0,MODE_SMA,PRICE_CLOSE); if(m_hRSI==INVALID_HANDLE || m_hATR==INVALID_HANDLE || m_hMAfast==INVALID_HANDLE || m_hMAslow==INVALID_HANDLE || m_hStd==INVALID_HANDLE) return false; } return true; } //+------------------------------------------------------------------+ //| Release indicator handles. | //+------------------------------------------------------------------+ void CCatch22FeatureBuilder::Deinit(void) { if(m_hRSI!=INVALID_HANDLE) { IndicatorRelease(m_hRSI); m_hRSI=INVALID_HANDLE; } if(m_hATR!=INVALID_HANDLE) { IndicatorRelease(m_hATR); m_hATR=INVALID_HANDLE; } if(m_hMAfast!=INVALID_HANDLE) { IndicatorRelease(m_hMAfast); m_hMAfast=INVALID_HANDLE; } if(m_hMAslow!=INVALID_HANDLE) { IndicatorRelease(m_hMAslow); m_hMAslow=INVALID_HANDLE; } if(m_hStd!=INVALID_HANDLE) { IndicatorRelease(m_hStd); m_hStd=INVALID_HANDLE; } } //+------------------------------------------------------------------+ //| Classic indicator block, read at bar 'shift' (already-closed | //| bar). Eight interpretable features a discretionary trader would | //| have on the chart: RSI, ATR (normalized), MA spread & slopes, | //| return, rolling volatility, and price-vs-MA stretch. | //+------------------------------------------------------------------+ bool CCatch22FeatureBuilder::BuildClassic(int shift,double &out[]) { //--- dynamic, series-ordered buffers so index 0 == the bar at 'shift' //--- (newest of the copied range) and index 1 == the bar before it. double rsi[],atr[],maf[],mas[],std[],close[]; ArraySetAsSeries(rsi,true); ArraySetAsSeries(atr,true); ArraySetAsSeries(maf,true); ArraySetAsSeries(mas,true); ArraySetAsSeries(std,true); ArraySetAsSeries(close,true); if(CopyBuffer(m_hRSI,0,shift,1,rsi)<1) return false; if(CopyBuffer(m_hATR,0,shift,1,atr)<1) return false; if(CopyBuffer(m_hMAfast,0,shift,2,maf)<2) return false; if(CopyBuffer(m_hMAslow,0,shift,2,mas)<2) return false; if(CopyBuffer(m_hStd,0,shift,1,std)<1) return false; if(CopyClose(m_symbol,m_tf,shift,3,close)<3) return false; double price=close[0]; double atrn =(price>0.0)?atr[0]/price:0.0; // ATR as fraction of price double ret =(close[1]>0.0)?MathLog(close[0]/close[1]):0.0; double maspread=(price>0.0)?(maf[0]-mas[0])/price:0.0; double mafslope=(price>0.0)?(maf[0]-maf[1])/price:0.0; double masslope=(price>0.0)?(mas[0]-mas[1])/price:0.0; double stretch =(atr[0]>0.0)?(price-maf[0])/atr[0]:0.0; // price-vs-MA in ATRs double volrel =(price>0.0)?std[0]/price:0.0; ArrayResize(out,C22_NCLASSIC); out[0]=rsi[0]/100.0; // scale RSI to [0,1] out[1]=atrn; out[2]=ret; out[3]=maspread; out[4]=mafslope; out[5]=masslope; out[6]=stretch; out[7]=volrel; return true; } //+------------------------------------------------------------------+ //| catch22 block: build a window of log returns ending at bar | //| 'shift' and run the CCatch22 engine over it. | //+------------------------------------------------------------------+ bool CCatch22FeatureBuilder::BuildCatch22(int shift,double &out[]) { int need=m_window+1; double close[]; if(CopyClose(m_symbol,m_tf,shift,need,close) newest double ret[]; ArrayResize(ret,m_window); for(int i=0;i=0 && col