208 lines
7.8 KiB
MQL5
#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
|