359 lines
18 KiB
MQL5
#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<ROW_FEATURES;j++)
| |||
{
| |||
int feature=j<MODEL_FEATURES ? j : MODEL_FEATURES+4*row.symbol_index+j-MODEL_FEATURES;
| |||
score+=effective_weights[feature]*row.x[j];
| |||
}
| |||
return Logistic(score);
| |||
}
| |||
void RefreshEffectiveWeights()
| |||
{
| |||
effective_intercept=intercept;
| |||
for(int j=0;j<ArraySize(weights);j++)
| |||
{
| |||
effective_weights[j]=weights[j]/scales[j];
| |||
effective_intercept-=weights[j]*means[j]/scales[j];
| |||
}
| |||
}
| |||
string Number(double value) { return DoubleToString(value,16); }
| |||
string VectorJSON(const double &values[])
| |||
{
| |||
string result="[";
| |||
for(int j=0;j<ArraySize(values);j++) { if(j) result+=","; result+=Number(values[j]); }
| |||
return result+"]";
| |||
}
| |||
string SplitName(int split) { return split==0 ? "train" : (split==1 ? "validation" : "test"); }
| |||
bool WriteText(string name,string content)
| |||
{
| |||
int h=FileOpen(BuildOutputDirectory+"\\"+name,FILE_WRITE|FILE_TXT|FILE_ANSI,0,CP_UTF8);
| |||
if(h==INVALID_HANDLE) { Print("[MODEL-TRAIN FAIL] Cannot write ",name," error=",GetLastError()); return false; }
| |||
FileWriteString(h,content);
| |||
FileFlush(h);
| |||
FileClose(h);
| |||
return true;
| |||
}
| |||
| |||
bool LoadRealRows()
| |||
{
| |||
string requested[];
| |||
int requested_count=StringSplit(TrainingSymbols,',',requested);
| |||
int used=0;
| |||
ArrayResize(rows,0);
| |||
ArrayResize(training_symbols,0);
| |||
ArrayResize(rejected_symbols,0);
| |||
ArrayInitialize(counts,0);
| |||
ArrayInitialize(positives,0);
| |||
for(int symbol_slot=0;symbol_slot<requested_count;symbol_slot++)
| |||
{
| |||
string symbol=requested[symbol_slot];
| |||
StringTrimLeft(symbol);StringTrimRight(symbol);
| |||
if(symbol=="") continue;
| |||
bool duplicate=false;
| |||
for(int k=0;k<ArraySize(training_symbols);k++) if(training_symbols[k]==symbol) duplicate=true;
| |||
if(duplicate) continue;
| |||
MqlRates rates[];
| |||
ArraySetAsSeries(rates,false);
| |||
int n=0;
| |||
if(SymbolSelect(symbol,true))
| |||
{
| |||
// The first cross-symbol request can initiate asynchronous history loading.
| |||
for(int retry=0;retry<12;retry++)
| |||
{
| |||
n=CopyRates(symbol,PERIOD_H1,TrainingStart,TestEnd-1,rates);
| |||
if(n>=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<n;d+=DecisionStrideBars)
| |||
{
| |||
datetime decision=rates[d].time,end=rates[d+4].time;
| |||
if(decision<TrainingStart || end>=TestEnd) continue;
| |||
bool continuous=true;
| |||
for(int k=d;k<d+4;k++) if(rates[k+1].time-rates[k].time!=3600) continuous=false;
| |||
if(!continuous || rates[d].open<=0 || rates[d+4].open<=0 || rates[d].spread<0 || rates[d+4].spread<0) continue;
| |||
int split=decision<TrainingEnd ? 0 : (decision<ValidationEnd ? 1 : 2);
| |||
datetime boundary=split==0 ? TrainingEnd : (split==1 ? ValidationEnd : TestEnd);
| |||
if(end>=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<MODEL_FEATURES;j++) rows[used].x[j]=f[j];
| |||
rows[used].x[12]=1;
| |||
rows[used].x[13]=f[0]/MathMax(f[4],1e-8);
| |||
rows[used].x[14]=f[1]/MathMax(f[4],1e-8);
| |||
rows[used].x[15]=f[3];
| |||
local_counts[split]++;local_positive[split]+=rows[used].label;
| |||
used++;
| |||
}
| |||
}
| |||
}
| |||
// Eligibility must not select the universe using held-out labels or scores.
| |||
bool eligible=local_counts[0]>=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<ArraySize(rows);i++)
| |||
if(rows[i].split==0)
| |||
for(int j=0;j<ROW_FEATURES;j++)
| |||
{
| |||
int f=j<MODEL_FEATURES ? j : MODEL_FEATURES+4*rows[i].symbol_index+j-MODEL_FEATURES;
| |||
means[f]+=rows[i].x[j]/counts[0];
| |||
scales[f]+=rows[i].x[j]*rows[i].x[j]/counts[0];
| |||
}
| |||
// E[x^2]-E[x]^2 includes the inactive per-symbol zeros exactly.
| |||
for(int j=0;j<width;j++) {scales[j]=MathSqrt(MathMax(0.0,scales[j]-means[j]*means[j]));if(scales[j]<1e-12)scales[j]=1.0;}
| |||
double prior=(double)positives[0]/counts[0];
| |||
intercept=MathLog(prior/(1.0-prior));
| |||
double gradient[];ArrayResize(gradient,width);
| |||
for(int epoch=0;epoch<TrainingEpochs;epoch++)
| |||
{
| |||
RefreshEffectiveWeights();
| |||
ArrayInitialize(gradient,0.0);
| |||
double bias_gradient=0.0;
| |||
for(int i=0;i<ArraySize(rows);i++)
| |||
{
| |||
if(rows[i].split!=0) continue;
| |||
double error=Prediction(rows[i])-rows[i].label;
| |||
bias_gradient+=error/counts[0];
| |||
for(int j=0;j<ROW_FEATURES;j++)
| |||
{
| |||
int f=j<MODEL_FEATURES ? j : MODEL_FEATURES+4*rows[i].symbol_index+j-MODEL_FEATURES;
| |||
gradient[f]+=error*(rows[i].x[j]/scales[f])/counts[0];
| |||
}
| |||
}
| |||
for(int j=0;j<width;j++)
| |||
{
| |||
gradient[j]-=(means[j]/scales[j])*bias_gradient;
| |||
weights[j]-=LearningRate*(gradient[j]+L2Penalty*weights[j]);
| |||
}
| |||
intercept-=LearningRate*bias_gradient;
| |||
if(epoch%50==0) PrintFormat("[MODEL-TRAIN FIT] epoch=%d/%d real_train_rows=%d width=%d",epoch,TrainingEpochs,counts[0],width);
| |||
}
| |||
RefreshEffectiveWeights();
| |||
}
| |||
string MetricsJSON(int split,int symbol_index=-1)
| |||
{
| |||
double ranked[][2];
| |||
int expected_count=symbol_index<0 ? counts[split] : symbol_counts[symbol_index*3+split];
| |||
int expected_positive=symbol_index<0 ? positives[split] : symbol_positives[symbol_index*3+split];
| |||
if(expected_count==0)
| |||
return "{\"rows\":0,\"positive\":0,\"negative\":0,\"auc\":null,\"log_loss\":null,\"constant_train_prior_log_loss\":null,\"brier_score\":null,\"accuracy\":null}";
| |||
ArrayResize(ranked,expected_count);
| |||
double loss=0,brier=0,baseline_loss=0,baseline_brier=0;
| |||
int correct=0,slot=0;
| |||
double prior=(double)positives[0]/counts[0];
| |||
for(int i=0;i<ArraySize(rows);i++)
| |||
{
| |||
if(rows[i].split!=split || (symbol_index>=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(begin<slot)
| |||
{
| |||
int end=begin+1;
| |||
while(end<slot && ranked[end][0]==ranked[begin][0]) end++;
| |||
double average_rank=((begin+1)+end)*0.5;
| |||
for(int k=begin;k<end;k++) if(ranked[k][1]>0.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;j<ArraySize(values);j++){if(j)output+=",";output+="\""+values[j]+"\"";}
| |||
return output+"]";
| |||
}
| |||
bool SaveArtifacts()
| |||
{
| |||
FolderCreate("DualEA");FolderCreate("DualEA\\model_build");FolderCreate(BuildOutputDirectory);
| |||
string names[],base_names[];
| |||
if(!UniversalModelFeatureNames(training_symbols,names)) return false;
| |||
MarketModelFeatureNames(base_names);
| |||
string joined_names="",joined_symbols="";
| |||
for(int j=0;j<ArraySize(names);j++){if(j)joined_names+="|";joined_names+=names[j];}
| |||
for(int j=0;j<ArraySize(training_symbols);j++){if(j)joined_symbols+="|";joined_symbols+=training_symbols[j];}
| |||
string model="{\"schema\":\"market_universal_v1\",\"symbol\":\"*\",\"supported_symbols\":"+StringArrayJSON(training_symbols)+",\"rejected_symbols\":"+StringArrayJSON(rejected_symbols)+",\"timeframe\":16385,\"horizon_bars\":4,\"feature_names\":"+StringArrayJSON(names)+",\"weights\":"+VectorJSON(weights)+",\"intercept\":"+Number(intercept)+",\"scaler_mean\":"+VectorJSON(means)+",\"scaler_scale\":"+VectorJSON(scales)+",\"training_start\":\""+TimeToString(TrainingStart,TIME_DATE)+"\",\"training_end\":\""+TimeToString(TrainingEnd,TIME_DATE)+"\",\"validation_end\":\""+TimeToString(ValidationEnd,TIME_DATE)+"\",\"test_end\":\""+TimeToString(TestEnd,TIME_DATE)+"\",\"target\":\"positive_side_aware_four_bar_log_return_after_recorded_spread\",\"commission\":0,\"slippage\":0,\"decision_stride_bars\":"+IntegerToString(DecisionStrideBars)+",\"epochs\":"+IntegerToString(TrainingEpochs)+",\"learning_rate\":"+Number(LearningRate)+",\"l2\":"+Number(L2Penalty)+"}";
| |||
if(!WriteText("model_data.json",model)) return false;
| |||
string metrics="{\"train\":"+MetricsJSON(0)+",\"validation\":"+MetricsJSON(1)+",\"test\":"+MetricsJSON(2)+",\"per_symbol\":{";
| |||
for(int s=0;s<ArraySize(training_symbols);s++)
| |||
{
| |||
if(s)metrics+=",";
| |||
metrics+="\""+training_symbols[s]+"\":{\"train\":"+MetricsJSON(0,s)+",\"validation\":"+MetricsJSON(1,s)+",\"test\":"+MetricsJSON(2,s)+"}";
| |||
}
| |||
metrics+="},\"selection\":\"fixed_hyperparameters_no_validation_or_test_selection\",\"training_data\":\"broker_H1_OHLC_and_recorded_spread\",\"purge\":\"target_end_before_split_boundary\",\"supported_symbols\":"+StringArrayJSON(training_symbols)+",\"rejected_symbols\":"+StringArrayJSON(rejected_symbols)+"}";
| |||
if(!WriteText("metrics.json",metrics)) return false;
| |||
string ini="version=1\nfeature_contract=market_universal_v1\ntrained_symbol=*\nsupported_symbols="+joined_symbols+"\ntrained_timeframe=16385\nprediction_horizon_bars=4\ntraining_start="+TimeToString(TrainingStart,TIME_DATE)+"\ntraining_end="+TimeToString(TrainingEnd,TIME_DATE)+"\nvalidation_end="+TimeToString(ValidationEnd,TIME_DATE)+"\ntest_end="+TimeToString(TestEnd,TIME_DATE)+"\nfeature_count="+IntegerToString(ArraySize(names))+"\nfeature_names="+joined_names+"\nscaler_mean=";
| |||
for(int j=0;j<ArraySize(means);j++){if(j)ini+="|";ini+=Number(means[j]);}
| |||
ini+="\nscaler_scale=";
| |||
for(int j=0;j<ArraySize(scales);j++){if(j)ini+="|";ini+=Number(scales[j]);}
| |||
ini+="\ninput_name=model_input\noutput_name=probability\nlabel_mode=future_side_aware_return\n";
| |||
if(!WriteText("onnx_config.ini",ini)) return false;
| |||
int dataset=FileOpen(BuildOutputDirectory+"\\dataset.csv",FILE_WRITE|FILE_CSV|FILE_ANSI,',',CP_UTF8);
| |||
int parity=FileOpen(BuildOutputDirectory+"\\parity_samples.csv",FILE_WRITE|FILE_CSV|FILE_ANSI,',',CP_UTF8);
| |||
if(dataset==INVALID_HANDLE || parity==INVALID_HANDLE){if(dataset!=INVALID_HANDLE)FileClose(dataset);if(parity!=INVALID_HANDLE)FileClose(parity);return false;}
| |||
string header="decision_time,target_end,split,symbol,direction,label,net_log_return,entry_spread_points,exit_spread_points";
| |||
for(int j=0;j<MODEL_FEATURES;j++)header+=","+base_names[j];
| |||
header+=",active_symbol_flag,active_ret1,active_ret3,active_zscore";
| |||
FileWriteString(dataset,header+"\r\n");
| |||
string parity_header="split,decision_time,symbol,direction,expected_probability";
| |||
for(int j=0;j<ArraySize(names);j++)parity_header+=","+names[j];
| |||
FileWriteString(parity,parity_header+"\r\n");
| |||
int selected[];ArrayResize(selected,ArraySize(training_symbols)*3);ArrayInitialize(selected,0);
| |||
for(int i=0;i<ArraySize(rows);i++)
| |||
{
| |||
string symbol=training_symbols[rows[i].symbol_index];
| |||
string line=TimeToString(rows[i].time,TIME_DATE|TIME_MINUTES)+","+TimeToString(rows[i].target_end,TIME_DATE|TIME_MINUTES)+","+SplitName(rows[i].split)+","+symbol+","+IntegerToString(rows[i].direction)+","+IntegerToString(rows[i].label)+","+Number(rows[i].net_return)+","+Number(rows[i].entry_spread)+","+Number(rows[i].exit_spread);
| |||
for(int j=0;j<ROW_FEATURES;j++)line+=","+Number(rows[i].x[j]);
| |||
FileWriteString(dataset,line+"\r\n");
| |||
int group=rows[i].symbol_index*3+rows[i].split;
| |||
if(selected[group]<8)
| |||
{
| |||
string sample=SplitName(rows[i].split)+","+TimeToString(rows[i].time,TIME_DATE|TIME_MINUTES)+","+symbol+","+IntegerToString(rows[i].direction)+","+Number(Prediction(rows[i]));
| |||
for(int j=0;j<ArraySize(names);j++)
| |||
{
| |||
double value=0;
| |||
if(j<MODEL_FEATURES)value=rows[i].x[j];
| |||
else if((j-MODEL_FEATURES)/4==rows[i].symbol_index)value=rows[i].x[MODEL_FEATURES+(j-MODEL_FEATURES)%4];
| |||
sample+=","+Number(value);
| |||
}
| |||
FileWriteString(parity,sample+"\r\n");selected[group]++;
| |||
}
| |||
}
| |||
FileFlush(dataset);FileClose(dataset);FileFlush(parity);FileClose(parity);
| |||
return true;
| |||
}
| |||
int OnInit()
| |||
{
| |||
if(!MQLInfoInteger(MQL_TESTER) || _Period!=PERIOD_H1) {Print("[MODEL-TRAIN FAIL] Run only in H1 Strategy Tester.");return INIT_FAILED;}
| |||
if(!(TrainingStart<TrainingEnd && TrainingEnd<ValidationEnd && ValidationEnd<TestEnd && TestEnd<=TimeCurrent()) ||
| |||
TrainingStart%86400!=0 || TrainingEnd%86400!=0 || ValidationEnd%86400!=0 || TestEnd%86400!=0)
| |||
{
| |||
Print("[MODEL-TRAIN FAIL] Date boundaries must be ordered midnight dates, with TestEnd no later than tester time.");
| |||
return INIT_PARAMETERS_INCORRECT;
| |||
}
| |||
if(DecisionStrideBars<1 || DecisionStrideBars>24 || 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<ArraySize(weights);j++)
| |||
if(!MathIsValidNumber(weights[j]) || !MathIsValidNumber(means[j]) || !MathIsValidNumber(scales[j]) || scales[j]<=0)
| |||
{
| |||
Print("[MODEL-TRAIN FAIL] Invalid fitted coefficient/scaler at ",j);
| |||
return INIT_FAILED;
| |||
}
| |||
if(!SaveArtifacts()) return INIT_FAILED;
| |||
trained=true;
| |||
PrintFormat("[MODEL-TRAIN RESULT] rows=%d train=%d validation=%d test=%d output=%s",ArraySize(rows),counts[0],counts[1],counts[2],BuildOutputDirectory);
| |||
return INIT_SUCCEEDED;
| |||
}
| |||
void OnTick() {}
| |||
double OnTester() { return trained ? ArraySize(rows) : -1; }
|