#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_timeclosed_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].highMathMin(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;idecision_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='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