#property strict #include "../Include/CMarketModelFeatures.mqh" input string BuildOutputDirectory="DualEA\\model_build\\universal_h1_logistic_v1"; input string TrainingSymbols="EURUSD,GBPUSD,USDCHF,USDJPY,USDCAD,AUDUSD,NZDUSD,EURGBP,EURJPY,GBPJPY,XAUUSD,XAGUSD,XAUEUR,AUS200,GER40,NAS100,UK100,US30,US500,BRENT,WTI,BTCUSD,ETHUSD"; input int DecisionStrideBars=1; input int TrainingEpochs=300; input double LearningRate=0.08; input double L2Penalty=0.01; input datetime TrainingStart=D'2024.01.01'; input datetime TrainingEnd=D'2026.07.01'; input datetime ValidationEnd=D'2026.09.01'; input datetime TestEnd=D'2026.10.01'; #define MODEL_FEATURES 12 #define ROW_FEATURES 16 struct STrainingRow { int symbol_index; datetime time; datetime target_end; int direction; int split; int label; double net_return; double entry_spread; double exit_spread; double x[ROW_FEATURES]; }; STrainingRow rows[]; string training_symbols[],rejected_symbols[]; double means[],scales[],weights[],effective_weights[]; double effective_intercept=0; int symbol_counts[],symbol_positives[]; double intercept=0.0; int counts[3],positives[3]; bool trained=false; double Logistic(double score) { if(score>=0.0) return 1.0/(1.0+MathExp(-MathMin(score,700.0))); double e=MathExp(MathMax(score,-700.0)); return e/(1.0+e); } double Prediction(const STrainingRow &row) { double score=effective_intercept; for(int j=0;j=1500) break; Sleep(250); } } double point=SymbolInfoDouble(symbol,SYMBOL_POINT); int local_counts[3],local_positive[3]; ArrayInitialize(local_counts,0);ArrayInitialize(local_positive,0); int start=used; int accepted_index=ArraySize(training_symbols); if(n>=1500 && point>0) { ArrayResize(rows,used+n*2); for(int d=15;d+4=TestEnd) continue; bool continuous=true; for(int k=d;k=boundary) continue; for(int direction=0;direction<2;direction++) { double f[]; if(!MarketModelFeaturesFromRates(rates,d-1,direction,point,decision,f) || ArraySize(f)!=MODEL_FEATURES) continue; double entry_bid=rates[d].open,exit_bid=rates[d+4].open; double entry_ask=entry_bid+rates[d].spread*point,exit_ask=exit_bid+rates[d+4].spread*point; if(entry_ask<=0 || exit_ask<=0) continue; double target=direction==0 ? MathLog(exit_bid/entry_ask) : MathLog(entry_bid/exit_ask); if(!MathIsValidNumber(target)) continue; rows[used].symbol_index=accepted_index; rows[used].time=decision;rows[used].target_end=end; rows[used].direction=direction;rows[used].split=split; rows[used].net_return=target;rows[used].label=target>0 ? 1 : 0; rows[used].entry_spread=rates[d].spread;rows[used].exit_spread=rates[d+4].spread; for(int j=0;j=1000 && local_positive[0]>=20 && local_counts[0]-local_positive[0]>=20; if(!eligible) { used=start;ArrayResize(rows,used); int rejected=ArraySize(rejected_symbols);ArrayResize(rejected_symbols,rejected+1);rejected_symbols[rejected]=symbol; PrintFormat("[MODEL-TRAIN SKIP] %s insufficient real history/class counts: bars=%d train=%d val=%d test=%d",symbol,n,local_counts[0],local_counts[1],local_counts[2]); continue; } ArrayResize(training_symbols,accepted_index+1);training_symbols[accepted_index]=symbol; ArrayResize(symbol_counts,(accepted_index+1)*3);ArrayResize(symbol_positives,(accepted_index+1)*3); for(int split=0;split<3;split++) { counts[split]+=local_counts[split];positives[split]+=local_positive[split]; symbol_counts[accepted_index*3+split]=local_counts[split];symbol_positives[accepted_index*3+split]=local_positive[split]; PrintFormat("[MODEL-TRAIN DATA] %s %s rows=%d positive=%d negative=%d",symbol,SplitName(split),local_counts[split],local_positive[split],local_counts[split]-local_positive[split]); } ArrayResize(rows,used); } string feature_names[]; if(ArraySize(training_symbols)<2 || !UniversalModelFeatureNames(training_symbols,feature_names)) return false; int width=ArraySize(feature_names); ArrayResize(means,width);ArrayResize(scales,width);ArrayResize(weights,width);ArrayResize(effective_weights,width); return true; } void FitTrainingOnly() { int width=ArraySize(weights); ArrayInitialize(means,0.0);ArrayInitialize(scales,0.0);ArrayInitialize(weights,0.0); for(int i=0;i=0 && rows[i].symbol_index!=symbol_index)) continue; double p=Prediction(rows[i]); p=MathMax(1e-12,MathMin(1.0-1e-12,p)); int y=rows[i].label; loss-=y*MathLog(p)+(1-y)*MathLog(1-p); brier+=MathPow(p-y,2); baseline_loss-=y*MathLog(prior)+(1-y)*MathLog(1-prior); baseline_brier+=MathPow(prior-y,2); if((p>=0.5 ? 1 : 0)==y) correct++; ranked[slot][0]=p; ranked[slot][1]=y; slot++; } ArraySort(ranked); double positive_rank_sum=0; int begin=0; while(begin0.5) positive_rank_sum+=average_rank; begin=end; } double pos=expected_positive,neg=expected_count-expected_positive; double auc=pos>0 && neg>0 ? (positive_rank_sum-pos*(pos+1)/2)/(pos*neg) : 0; string auc_json=pos>0 && neg>0 ? Number(auc) : "null"; PrintFormat("[MODEL-TRAIN METRICS] %s AUC=%s logloss=%.6f baseline=%.6f accuracy=%.6f",SplitName(split),auc_json,loss/slot,baseline_loss/slot,(double)correct/slot); return "{\"rows\":"+IntegerToString(slot)+",\"positive\":"+IntegerToString(expected_positive)+",\"negative\":"+IntegerToString(expected_count-expected_positive)+",\"auc\":"+auc_json+",\"log_loss\":"+Number(loss/slot)+",\"constant_train_prior_log_loss\":"+Number(baseline_loss/slot)+",\"constant_train_prior_brier\":"+Number(baseline_brier/slot)+",\"constant_train_prior_accuracy\":"+Number(prior>=0.5 ? pos/slot : neg/slot)+",\"brier_score\":"+Number(brier/slot)+",\"accuracy\":"+Number((double)correct/slot)+"}"; } string StringArrayJSON(const string &values[]) { string output="["; for(int j=0;j24 || TrainingEpochs<1 || TrainingEpochs>2000 || !MathIsValidNumber(LearningRate) || !MathIsValidNumber(L2Penalty) || LearningRate<=0 || LearningRate>1 || L2Penalty<0) return INIT_PARAMETERS_INCORRECT; if(!LoadRealRows()) {Print("[MODEL-TRAIN FAIL] Real history/class-count requirements were not met.");return INIT_FAILED;} FitTrainingOnly(); if(!MathIsValidNumber(intercept)) return INIT_FAILED; for(int j=0;j