dualea/Experts/Advisors/DualEA/Include/CMarketModelFeatures.mqh
208 lines
7.8 KiB
MQL5
Princeec13
2026-10-28 00:31:51 -04:00
#ifndef DUALEA_MARKET_MODEL_FEATURES_MQH
#define DUALEA_MARKET_MODEL_FEATURES_MQH
// Version 1: completed-bar market features, oldest bar first. No indicator
// state, gate outcomes, strategy decisions, or future observations are inputs.
#define MARKET_MODEL_FEATURE_COUNT 12
void MarketModelFeatureNames(string &names[])
{
ArrayResize(names,MARKET_MODEL_FEATURE_COUNT);
names[0]="mm_signed_logret1";
names[1]="mm_signed_logret3";
names[2]="mm_signed_logret12";
names[3]="mm_signed_zscore12";
names[4]="mm_return_std12";
names[5]="mm_range";
names[6]="mm_signed_body";
names[7]="mm_spread";
names[8]="mm_hour_sin";
names[9]="mm_hour_cos";
names[10]="mm_direction";
names[11]="mm_atr14";
}
bool MarketModelFeaturesFromRates(const MqlRates &rates[],const int closed_index,
const int direction,const double point,
const datetime decision_time,double &features[])
{
ArrayResize(features,0);
if(ArrayGetAsSeries(rates) || closed_index<14 || closed_index>=ArraySize(rates) ||
(direction!=0 && direction!=1) || !MathIsValidNumber(point) || point<=0.0 ||
decision_time<rates[closed_index].time+PeriodSeconds(PERIOD_H1))
return false;
// Only this explicit backward window is read. Later array entries may
// contain future prices during training and must never affect features.
for(int i=closed_index-14;i<=closed_index;i++)
{
if(rates[i].time<=0 || (i>closed_index-14 && rates[i].time<=rates[i-1].time) ||
!MathIsValidNumber(rates[i].open) || !MathIsValidNumber(rates[i].high) ||
!MathIsValidNumber(rates[i].low) || !MathIsValidNumber(rates[i].close) ||
rates[i].open<=0.0 || rates[i].low<=0.0 || rates[i].close<=0.0 ||
rates[i].high<rates[i].low || rates[i].high<MathMax(rates[i].open,rates[i].close) ||
rates[i].low>MathMin(rates[i].open,rates[i].close) || rates[i].spread<0)
return false;
}
double side=direction==0 ? 1.0 : -1.0;
double last=rates[closed_index].close;
double mean=0.0,return_mean=0.0;
double returns[12];
for(int i=0;i<12;i++)
{
int index=closed_index-i;
mean+=rates[index].close/12.0;
returns[i]=MathLog(rates[index].close/rates[index-1].close);
return_mean+=returns[i]/12.0;
}
double price_var=0.0,return_var=0.0;
for(int i=0;i<12;i++)
{
double delta=rates[closed_index-i].close-mean;
price_var+=delta*delta/12.0;
double return_delta=returns[i]-return_mean;
return_var+=return_delta*return_delta/12.0;
}
double atr=0.0;
for(int i=0;i<14;i++)
{
int index=closed_index-i;
double previous=rates[index-1].close;
double tr=MathMax(rates[index].high-rates[index].low,
MathMax(MathAbs(rates[index].high-previous),MathAbs(rates[index].low-previous)));
atr+=tr/14.0;
}
MqlDateTime dt;
if(!TimeToStruct(decision_time,dt)) return false;
double hour_angle=2.0*3.14159265358979323846*dt.hour/24.0;
ArrayResize(features,MARKET_MODEL_FEATURE_COUNT);
features[0]=side*returns[0];
features[1]=side*MathLog(last/rates[closed_index-3].close);
features[2]=side*MathLog(last/rates[closed_index-12].close);
features[3]=price_var>0.0 ? side*(last-mean)/MathSqrt(price_var) : 0.0;
features[4]=MathSqrt(return_var);
features[5]=(rates[closed_index].high-rates[closed_index].low)/last;
features[6]=side*(last-rates[closed_index].open)/last;
features[7]=(double)rates[closed_index].spread*point/last;
features[8]=MathSin(hour_angle);
features[9]=MathCos(hour_angle);
features[10]=side;
features[11]=atr/last;
for(int i=0;i<MARKET_MODEL_FEATURE_COUNT;i++)
if(!MathIsValidNumber(features[i])) { ArrayResize(features,0); return false; }
return true;
}
bool BuildMarketModelFeatures(const string symbol,const ENUM_TIMEFRAMES tf,
const datetime decision_time,const int direction,
double &features[])
{
ArrayResize(features,0);
int seconds=PeriodSeconds(tf);
if(seconds<=0 || decision_time<=0) return false;
int decision_shift=iBarShift(symbol,tf,decision_time,false);
if(decision_shift<0) return false;
MqlRates rates[];
ArraySetAsSeries(rates,false);
int copied=CopyRates(symbol,tf,decision_shift+1,64,rates);
if(copied<15) return false;
int last=copied-1;
if(rates[last].time+seconds>decision_time) return false;
double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
return MarketModelFeaturesFromRates(rates,last,direction,point,decision_time,features);
}
// Broker symbols retain their identity when matching the active symbol. Only
// feature identifiers are normalized; punctuation is never silently stripped
// from a broker symbol to claim support for a different instrument.
string NormalizeUniversalModelSymbol(string symbol)
{
StringToLower(symbol);
string normalized="";
for(int i=0;i<StringLen(symbol);i++)
{
ushort character=StringGetCharacter(symbol,i);
if((character>='a' && character<='z') || (character>='0' && character<='9'))
normalized+=ShortToString(character);
else
normalized+="_";
}
return normalized;
}
bool ValidateUniversalModelSymbols(const string &symbols[])
{
int count=ArraySize(symbols);
if(count<=0) return false;
for(int i=0;i<count;i++)
{
if(symbols[i]=="") return false;
string normalized=NormalizeUniversalModelSymbol(symbols[i]);
for(int j=0;j<i;j++)
if(normalized==NormalizeUniversalModelSymbol(symbols[j])) return false;
}
return true;
}
bool UniversalModelFeatureNames(const string &symbols[],string &names[])
{
ArrayResize(names,0);
if(!ValidateUniversalModelSymbols(symbols)) return false;
MarketModelFeatureNames(names);
int count=ArraySize(symbols);
ArrayResize(names,MARKET_MODEL_FEATURE_COUNT+4*count);
for(int i=0;i<count;i++)
{
int offset=MARKET_MODEL_FEATURE_COUNT+4*i;
string prefix="sym_"+NormalizeUniversalModelSymbol(symbols[i]);
names[offset]=prefix+"_flag";
names[offset+1]=prefix+"_ret1";
names[offset+2]=prefix+"_ret3";
names[offset+3]=prefix+"_zscore";
}
return true;
}
bool ExpandUniversalModelFeatures(const double &base[],const string symbol,
const string &symbols[],double &features[])
{
ArrayResize(features,0);
if(ArraySize(base)!=MARKET_MODEL_FEATURE_COUNT || !ValidateUniversalModelSymbols(symbols))
return false;
int active=-1;
for(int i=0;i<ArraySize(symbols);i++)
if(StringCompare(symbol,symbols[i],false)==0) {active=i;break;}
if(active<0) return false;
for(int i=0;i<MARKET_MODEL_FEATURE_COUNT;i++)
if(!MathIsValidNumber(base[i])) return false;
if(base[4]<0.0) return false;
int count=MARKET_MODEL_FEATURE_COUNT+4*ArraySize(symbols);
ArrayResize(features,count);
ArrayInitialize(features,0.0);
for(int i=0;i<MARKET_MODEL_FEATURE_COUNT;i++) features[i]=base[i];
int offset=MARKET_MODEL_FEATURE_COUNT+4*active;
double scale=MathMax(base[4],1e-8);
features[offset]=1.0;
features[offset+1]=base[0]/scale;
features[offset+2]=base[1]/scale;
features[offset+3]=base[3];
for(int i=0;i<count;i++)
if(!MathIsValidNumber(features[i])) {ArrayResize(features,0);return false;}
return true;
}
bool UniversalModelFeaturesFromRates(const MqlRates &rates[],const int closed_index,
const int direction,const double point,
const datetime decision_time,const string symbol,
const string &symbols[],double &features[])
{
double base[];
if(!MarketModelFeaturesFromRates(rates,closed_index,direction,point,decision_time,base))
{
ArrayResize(features,0);
return false;
}
return ExpandUniversalModelFeatures(base,symbol,symbols,features);
}
#endif