331 lines
12 KiB
MQL5
331 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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 <Catch22\Catch22.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 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)<need)
|
|
return false;
|
|
ArraySetAsSeries(close,false); // oldest -> newest
|
|
|
|
double ret[];
|
|
ArrayResize(ret,m_window);
|
|
for(int i=0;i<m_window;i++)
|
|
{
|
|
if(close[i]<=0.0 || close[i+1]<=0.0)
|
|
{
|
|
ret[i]=0.0;
|
|
continue;
|
|
}
|
|
ret[i]=MathLog(close[i+1]/close[i]);
|
|
}
|
|
|
|
double feats[];
|
|
if(!m_c22.Compute(ret,m_window,feats))
|
|
{
|
|
//--- degenerate window: emit zeros so the row is still usable
|
|
ArrayResize(out,CATCH22_N);
|
|
ArrayInitialize(out,0.0);
|
|
return true;
|
|
}
|
|
ArrayResize(out,CATCH22_N);
|
|
for(int i=0;i<CATCH22_N;i++)
|
|
out[i]=feats[i];
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Build the full feature vector for the configured arm. |
|
|
//+------------------------------------------------------------------+
|
|
bool CCatch22FeatureBuilder::Build(int shift,double &out[])
|
|
{
|
|
double cls[],c22[];
|
|
if(m_arm==ARM_CLASSIC)
|
|
{
|
|
if(!BuildClassic(shift,out))
|
|
return false;
|
|
return true;
|
|
}
|
|
if(m_arm==ARM_CATCH22)
|
|
{
|
|
if(!BuildCatch22(shift,out))
|
|
return false;
|
|
return true;
|
|
}
|
|
//--- combined: classic block first, then catch22
|
|
if(!BuildClassic(shift,cls))
|
|
return false;
|
|
if(!BuildCatch22(shift,c22))
|
|
return false;
|
|
ArrayResize(out,C22_NCLASSIC+CATCH22_N);
|
|
for(int i=0;i<C22_NCLASSIC;i++)
|
|
out[i]=cls[i];
|
|
for(int i=0;i<CATCH22_N;i++)
|
|
out[C22_NCLASSIC+i]=c22[i];
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Human-readable column name for a feature index in the current |
|
|
//| arm (used for CSV headers and feature-importance reports). |
|
|
//+------------------------------------------------------------------+
|
|
string CCatch22FeatureBuilder::ColumnName(int col) const
|
|
{
|
|
string classic[C22_NCLASSIC]=
|
|
{
|
|
"RSI14","ATR_norm","logret","MA_spread","MAfast_slope",
|
|
"MAslow_slope","price_stretch_ATR","vol_rel"
|
|
};
|
|
|
|
if(m_arm==ARM_CLASSIC)
|
|
return (col>=0 && col<C22_NCLASSIC)?classic[col]:"?";
|
|
if(m_arm==ARM_CATCH22)
|
|
return CCatch22::Name(col);
|
|
//--- combined
|
|
if(col<C22_NCLASSIC)
|
|
return classic[col];
|
|
return CCatch22::Name(col-C22_NCLASSIC);
|
|
}
|
|
//+------------------------------------------------------------------+
|