2026-01-14 17:13:26 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| TrainPriceActionNN.mq5 |
|
|
|
|
|
//| Copyright 2025, Google Gemini |
|
|
|
|
|
//| https://www.mql5.com |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#property copyright "Copyright 2025, Google Gemini"
|
|
|
|
|
#property link "https://www.google.com"
|
2026-01-21 13:07:32 +03:00
|
|
|
#property version "3.00"
|
2026-01-14 17:13:26 +03:00
|
|
|
#property script_show_inputs
|
|
|
|
|
|
|
|
|
|
#include <NeuralNet.mqh>
|
|
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
input int InpEpochs = 500;
|
|
|
|
|
input int InpLookback = 144;
|
2026-01-26 17:17:54 +03:00
|
|
|
input string InpHiddenLayerConfig = "256,128,64";
|
2026-01-21 13:07:32 +03:00
|
|
|
input double InpTrainingRate = 0.001;
|
2026-01-21 15:59:36 +03:00
|
|
|
input double InpTrainingDecay = 0.98;
|
2026-01-21 13:07:32 +03:00
|
|
|
input double InpLambda = 0.0001; // L2 Regularization (Weight Decay)
|
|
|
|
|
input double InpJitter = 0.05; // Nudge magnitude when stuck
|
2026-01-21 15:39:31 +03:00
|
|
|
input int InpMaxBars = 5000000;
|
2026-01-21 13:07:32 +03:00
|
|
|
input int InpPredictionHorizon = 144;
|
|
|
|
|
// Session Times (Broker Server Hour)
|
|
|
|
|
input int InpAsianStart = 0;
|
|
|
|
|
input int InpAsianEnd = 8;
|
|
|
|
|
input int InpLondonStart = 8;
|
|
|
|
|
input int InpLondonEnd = 16;
|
|
|
|
|
input int InpNYStart = 13;
|
|
|
|
|
input int InpNYEnd = 22;
|
2026-01-14 17:13:26 +03:00
|
|
|
|
|
|
|
|
CNeuralNet *Net;
|
2026-01-16 10:51:51 +03:00
|
|
|
int GlobalTopology[];
|
2026-01-21 13:07:32 +03:00
|
|
|
int handleATR_D1, handleEMA50, handleEMA200;
|
|
|
|
|
|
|
|
|
|
struct Sample {
|
|
|
|
|
double inputs[];
|
|
|
|
|
double target;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct DaySession {
|
|
|
|
|
double aH, aL;
|
|
|
|
|
double lH, lL;
|
|
|
|
|
double nH, nL;
|
|
|
|
|
bool aValid, lValid, nValid;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Map: DayIndex (Time/86400) -> Stats
|
|
|
|
|
// Since time is large, we map [0..NumDays].
|
|
|
|
|
// We need 'StartDayTime' for index 0.
|
|
|
|
|
DaySession sessions[];
|
|
|
|
|
datetime baseTime;
|
|
|
|
|
|
|
|
|
|
void ComputeSessions(const MqlRates &rates[], int total)
|
|
|
|
|
{
|
|
|
|
|
// 1. Find Date Range
|
|
|
|
|
datetime first = rates[total-1].time; // Oldest
|
|
|
|
|
datetime last = rates[0].time; // Newest
|
|
|
|
|
|
|
|
|
|
// Align to Day Start
|
|
|
|
|
first = first - (first % 86400);
|
|
|
|
|
baseTime = first;
|
|
|
|
|
|
|
|
|
|
int numDays = (int)((last - first) / 86400) + 2; // +buffer
|
|
|
|
|
ArrayResize(sessions, numDays);
|
|
|
|
|
|
|
|
|
|
// Initialize
|
|
|
|
|
for(int k=0; k<numDays; k++) {
|
|
|
|
|
sessions[k].aH = -1; sessions[k].aL = 999999;
|
|
|
|
|
sessions[k].lH = -1; sessions[k].lL = 999999;
|
|
|
|
|
sessions[k].nH = -1; sessions[k].nL = 999999;
|
|
|
|
|
sessions[k].aValid=false; sessions[k].lValid=false; sessions[k].nValid=false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Print("Pre-calculating sessions for ", numDays, " days...");
|
|
|
|
|
|
|
|
|
|
// Scan all bars
|
|
|
|
|
for(int i=0; i<total; i++) {
|
|
|
|
|
datetime t = rates[i].time;
|
|
|
|
|
int dayIdx = (int)((t - baseTime) / 86400);
|
|
|
|
|
if(dayIdx < 0 || dayIdx >= numDays) continue;
|
|
|
|
|
|
|
|
|
|
MqlDateTime dt; TimeToStruct(t, dt);
|
|
|
|
|
int h = dt.hour;
|
|
|
|
|
double H = rates[i].high;
|
|
|
|
|
double L = rates[i].low;
|
|
|
|
|
|
|
|
|
|
// Update stats for that day
|
|
|
|
|
// Asian
|
|
|
|
|
if(h >= InpAsianStart && h < InpAsianEnd) {
|
|
|
|
|
if(H > sessions[dayIdx].aH || sessions[dayIdx].aH == -1) sessions[dayIdx].aH = H;
|
|
|
|
|
if(L < sessions[dayIdx].aL) sessions[dayIdx].aL = L;
|
|
|
|
|
sessions[dayIdx].aValid = true;
|
|
|
|
|
}
|
|
|
|
|
// London
|
|
|
|
|
if(h >= InpLondonStart && h < InpLondonEnd) {
|
|
|
|
|
if(H > sessions[dayIdx].lH || sessions[dayIdx].lH == -1) sessions[dayIdx].lH = H;
|
|
|
|
|
if(L < sessions[dayIdx].lL) sessions[dayIdx].lL = L;
|
|
|
|
|
sessions[dayIdx].lValid = true;
|
|
|
|
|
}
|
|
|
|
|
// NY
|
|
|
|
|
if(h >= InpNYStart && h < InpNYEnd) {
|
|
|
|
|
if(H > sessions[dayIdx].nH || sessions[dayIdx].nH == -1) sessions[dayIdx].nH = H;
|
|
|
|
|
if(L < sessions[dayIdx].nL) sessions[dayIdx].nL = L;
|
|
|
|
|
sessions[dayIdx].nValid = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-14 17:13:26 +03:00
|
|
|
|
|
|
|
|
void OnStart()
|
|
|
|
|
{
|
2026-01-21 13:07:32 +03:00
|
|
|
Print("--- Advanced Training: v3.0 Optimized ---");
|
|
|
|
|
Print( "InpEpochs: ", InpEpochs,
|
|
|
|
|
"\nInpHiddenLayerConfig: ", InpHiddenLayerConfig,
|
|
|
|
|
"\nInpTrainingRate: ", InpTrainingRate,
|
|
|
|
|
"\nInpTrainingDecay: ", InpTrainingDecay,
|
|
|
|
|
"\nInpLookback: " ,InpLookback,
|
|
|
|
|
"\nInpMaxBars: " , InpMaxBars,
|
|
|
|
|
"\nInpPredictionHorizon: " , InpPredictionHorizon);
|
|
|
|
|
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
handleATR_D1 = iATR(_Symbol, PERIOD_D1, 14);
|
2026-01-25 16:49:49 +00:00
|
|
|
handleEMA50 = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
|
handleEMA200 = iMA(_Symbol, PERIOD_H1, 200, 0, MODE_EMA, PRICE_CLOSE);
|
2026-01-14 17:13:26 +03:00
|
|
|
|
|
|
|
|
MqlRates rates[];
|
|
|
|
|
ArraySetAsSeries(rates, true);
|
2026-01-21 13:07:32 +03:00
|
|
|
int needed = InpMaxBars + InpPredictionHorizon + InpLookback + 5000;
|
2026-01-25 16:49:49 +00:00
|
|
|
int copied = CopyRates(_Symbol, PERIOD_H1, 0, needed, rates);
|
2026-01-21 13:07:32 +03:00
|
|
|
if(copied < 5000) return;
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
double ema50[], ema200[];
|
|
|
|
|
ArraySetAsSeries(ema50, true);
|
|
|
|
|
ArraySetAsSeries(ema200, true);
|
|
|
|
|
CopyBuffer(handleEMA50, 0,0,copied, ema50);
|
|
|
|
|
CopyBuffer(handleEMA200, 0,0,copied, ema200);
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-25 16:49:49 +00:00
|
|
|
// --- Log Training Data Info ---
|
|
|
|
|
datetime startT = rates[copied-1].time;
|
|
|
|
|
datetime endT = rates[0].time;
|
|
|
|
|
double totalDays = (double)(endT - startT) / 86400.0;
|
|
|
|
|
|
|
|
|
|
Print("--------------------------------------------------");
|
|
|
|
|
PrintFormat("Training Data Loaded: %d Bars", copied);
|
|
|
|
|
Print("Date Range: " + TimeToString(startT) + " to " + TimeToString(endT));
|
|
|
|
|
PrintFormat("Total Duration: %.2f Days", totalDays);
|
|
|
|
|
Print("--------------------------------------------------");
|
|
|
|
|
Print("Inputs Reference:");
|
|
|
|
|
PrintFormat("MaxBars: %d", InpMaxBars);
|
|
|
|
|
PrintFormat("Prediction Horizon: %d bars", InpPredictionHorizon);
|
|
|
|
|
PrintFormat("Lookback: %d bars", InpLookback);
|
|
|
|
|
PrintFormat("Hidden Layers: %s", InpHiddenLayerConfig);
|
|
|
|
|
PrintFormat("Training Rate: %.4f (Decay: %.3f)", InpTrainingRate, InpTrainingDecay);
|
|
|
|
|
Print("--------------------------------------------------");
|
|
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
// Precalc Sessions
|
|
|
|
|
ComputeSessions(rates, copied);
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
int featuresPerBar = 17;
|
|
|
|
|
int inputSize = InpLookback * featuresPerBar;
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
string result[];
|
|
|
|
|
int hiddenLayerCount = StringSplit(InpHiddenLayerConfig, ',', result);
|
|
|
|
|
ArrayResize(GlobalTopology, 1 + hiddenLayerCount + 1);
|
|
|
|
|
GlobalTopology[0] = inputSize;
|
|
|
|
|
for(int i=0; i<hiddenLayerCount; i++) GlobalTopology[i+1] = (int)StringToInteger(result[i]);
|
|
|
|
|
GlobalTopology[ArraySize(GlobalTopology)-1] = 1;
|
|
|
|
|
|
2026-01-26 17:17:54 +03:00
|
|
|
Net = new CNeuralNet(GlobalTopology, ACT_LEAKY_RELU);
|
2026-01-21 13:07:32 +03:00
|
|
|
Net.SetLearningRate(InpTrainingRate);
|
|
|
|
|
Net.SetLambda(InpLambda);
|
2026-01-14 17:13:26 +03:00
|
|
|
|
|
|
|
|
Sample samples[];
|
2026-01-21 13:07:32 +03:00
|
|
|
int pos=0, neg=0, flat=0;
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
int startIdx = InpPredictionHorizon;
|
2026-01-14 17:13:26 +03:00
|
|
|
int endIdx = copied - InpLookback - 1;
|
|
|
|
|
if(endIdx - startIdx > InpMaxBars) endIdx = startIdx + InpMaxBars;
|
|
|
|
|
|
|
|
|
|
for(int i = startIdx; i <= endIdx; i++)
|
|
|
|
|
{
|
2026-01-21 13:07:32 +03:00
|
|
|
// Stride: Train every 20 minutes (Minute 0, 20, 40)
|
|
|
|
|
MqlDateTime dt; TimeToStruct(rates[i].time, dt);
|
2026-01-25 16:49:49 +00:00
|
|
|
//if(dt.min % 20 != 0) continue;
|
2026-01-21 13:07:32 +03:00
|
|
|
|
|
|
|
|
// -- D1 ATR --
|
|
|
|
|
double d1ATR_Arr[1];
|
|
|
|
|
if(CopyBuffer(handleATR_D1, 0, rates[i].time, 1, d1ATR_Arr)<=0) continue;
|
|
|
|
|
double dailyATR = d1ATR_Arr[0];
|
|
|
|
|
|
2026-01-25 16:49:49 +00:00
|
|
|
double targetTP = 1.0 * dailyATR;
|
|
|
|
|
double targetSL = 0.5 * dailyATR;
|
2026-01-21 13:07:32 +03:00
|
|
|
double entryPrice = rates[i].close;
|
|
|
|
|
double label = 0;
|
|
|
|
|
|
2026-01-25 16:49:49 +00:00
|
|
|
int firstBuyTP = 99999, firstBuySL = 99999;
|
|
|
|
|
int firstSellTP = 99999, firstSellSL = 99999;
|
2026-01-21 13:07:32 +03:00
|
|
|
|
|
|
|
|
for(int k=1; k<=InpPredictionHorizon; k++) {
|
2026-01-25 16:49:49 +00:00
|
|
|
int f = i-k; if(f<0) break;
|
|
|
|
|
double h = rates[f].high;
|
|
|
|
|
double l = rates[f].low;
|
|
|
|
|
|
|
|
|
|
if(firstBuyTP==99999 && h >= entryPrice + targetTP) firstBuyTP = k;
|
|
|
|
|
if(firstBuySL==99999 && l <= entryPrice - targetSL) firstBuySL = k;
|
|
|
|
|
|
|
|
|
|
if(firstSellTP==99999 && l <= entryPrice - targetTP) firstSellTP = k;
|
|
|
|
|
if(firstSellSL==99999 && h >= entryPrice + targetSL) firstSellSL = k;
|
2026-01-21 13:07:32 +03:00
|
|
|
}
|
|
|
|
|
|
2026-01-25 16:49:49 +00:00
|
|
|
if(firstBuyTP < firstBuySL) label = 1.0; // Buy Win
|
|
|
|
|
else if(firstSellTP < firstSellSL) label = -1.0; // Sell Win
|
|
|
|
|
else label = 0.0;
|
2026-01-21 13:07:32 +03:00
|
|
|
|
|
|
|
|
// -- Features --
|
|
|
|
|
double inputs[];
|
|
|
|
|
ArrayResize(inputs, inputSize);
|
|
|
|
|
|
|
|
|
|
for(int k=0; k<InpLookback; k++) {
|
|
|
|
|
int p = i + k;
|
|
|
|
|
if(p >= copied) break;
|
|
|
|
|
int off = k * featuresPerBar;
|
|
|
|
|
|
|
|
|
|
double o = rates[p].open;
|
|
|
|
|
double c = rates[p].close;
|
|
|
|
|
double h = rates[p].high;
|
|
|
|
|
double l = rates[p].low;
|
|
|
|
|
double pH = rates[p+1].high;
|
|
|
|
|
double pL = rates[p+1].low;
|
|
|
|
|
|
2026-01-26 17:17:54 +03:00
|
|
|
inputs[off+0] = (c - o)/o * 100.0;
|
|
|
|
|
inputs[off+1] = (h - o)/o * 100.0;
|
|
|
|
|
inputs[off+2] = (l - o)/o * 100.0;
|
|
|
|
|
inputs[off+3] = ((h - pH)/pH) * 100.0;
|
|
|
|
|
inputs[off+4] = ((l - pL)/pL) * 100.0;
|
2026-01-25 16:49:49 +00:00
|
|
|
inputs[off+5] = (rates[p].tick_volume - rates[p+1].tick_volume) / (rates[p+1].tick_volume + 1.0) * 10.0; // Completed Bar Volume difference normalized
|
2026-01-26 17:17:54 +03:00
|
|
|
inputs[off+6] = ((c - ema50[p])/ema50[p]) * 100.0;
|
|
|
|
|
inputs[off+7] = ((c - ema200[p])/ema200[p]) * 100.0;
|
2026-01-21 13:07:32 +03:00
|
|
|
|
|
|
|
|
// -- Optimized Session Lookup --
|
|
|
|
|
datetime pTime = rates[p].time;
|
|
|
|
|
int dayIdx = (int)((pTime - baseTime) / 86400);
|
|
|
|
|
MqlDateTime pDt; TimeToStruct(pTime, pDt);
|
|
|
|
|
int pHour = pDt.hour;
|
|
|
|
|
|
|
|
|
|
// Asian: If pTime >= AsianEnd, use Today. Else Yesterday.
|
|
|
|
|
double aH, aL;
|
|
|
|
|
int aIdx = (pHour >= InpAsianEnd) ? dayIdx : dayIdx - 1;
|
|
|
|
|
if(aIdx>=0 && sessions[aIdx].aValid) { aH=sessions[aIdx].aH; aL=sessions[aIdx].aL; }
|
|
|
|
|
else { aH=rates[p].high; aL=rates[p].low; } // Fallback
|
|
|
|
|
|
|
|
|
|
// London
|
|
|
|
|
double lH, lL;
|
|
|
|
|
int lIdx = (pHour >= InpLondonEnd) ? dayIdx : dayIdx - 1;
|
|
|
|
|
if(lIdx>=0 && sessions[lIdx].lValid) { lH=sessions[lIdx].lH; lL=sessions[lIdx].lL; }
|
|
|
|
|
else { lH=rates[p].high; lL=rates[p].low; }
|
|
|
|
|
|
|
|
|
|
// NY
|
|
|
|
|
double nH, nL;
|
|
|
|
|
int nIdx = (pHour >= InpNYEnd) ? dayIdx : dayIdx - 1;
|
|
|
|
|
if(nIdx>=0 && sessions[nIdx].nValid) { nH=sessions[nIdx].nH; nL=sessions[nIdx].nL; }
|
|
|
|
|
else { nH=rates[p].high; nL=rates[p].low; }
|
|
|
|
|
|
2026-01-26 17:17:54 +03:00
|
|
|
inputs[off+8] = ((c - aH)/aH) * 100.0;
|
|
|
|
|
inputs[off+9] = ((c - aL)/aH) * 100.0;
|
|
|
|
|
inputs[off+10] = ((c - lH)/lH) * 100.0;
|
|
|
|
|
inputs[off+11] = ((c - lL)/lH) * 100.0;
|
|
|
|
|
inputs[off+12] = ((c - nH)/nH) * 100.0;
|
|
|
|
|
inputs[off+13] = ((c - nL)/nH) * 100.0;
|
2026-01-21 13:07:32 +03:00
|
|
|
|
|
|
|
|
inputs[off+14] = (double)pDt.day_of_week / 7.0;
|
|
|
|
|
inputs[off+15] = (double)pDt.hour / 24.0;
|
|
|
|
|
inputs[off+16] = (double)pDt.min / 60.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Sample s;
|
|
|
|
|
s.target = label;
|
|
|
|
|
ArrayResize(s.inputs, inputSize);
|
|
|
|
|
ArrayCopy(s.inputs, inputs);
|
|
|
|
|
|
|
|
|
|
int n=ArraySize(samples);
|
|
|
|
|
ArrayResize(samples, n+1);
|
|
|
|
|
samples[n] = s;
|
|
|
|
|
|
|
|
|
|
if(label > 0.1) pos++; else if(label < -0.1) neg++; else flat++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrintFormat("Collected: Pos=%d Neg=%d Flat=%d", pos, neg, flat);
|
|
|
|
|
|
|
|
|
|
// Balance
|
|
|
|
|
int targetCount = (pos + neg) / 2;
|
|
|
|
|
Sample balanced[];
|
|
|
|
|
for(int k=0; k<ArraySize(samples); k++) {
|
|
|
|
|
if(samples[k].target == 0) {
|
|
|
|
|
if(MathRand()%100 < 100.0 * targetCount/flat) {
|
|
|
|
|
int b=ArraySize(balanced); ArrayResize(balanced, b+1);
|
|
|
|
|
balanced[b]=samples[k];
|
2026-01-14 17:13:26 +03:00
|
|
|
}
|
|
|
|
|
} else {
|
2026-01-21 13:07:32 +03:00
|
|
|
int b=ArraySize(balanced); ArrayResize(balanced, b+1);
|
|
|
|
|
balanced[b]=samples[k];
|
2026-01-14 17:13:26 +03:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-21 13:07:32 +03:00
|
|
|
PrintFormat("Balanced to %d", ArraySize(balanced));
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
// Train
|
|
|
|
|
int total = ArraySize(balanced);
|
|
|
|
|
int nTrain = (int)(total * 0.8);
|
|
|
|
|
int indices[]; ArrayResize(indices, nTrain);
|
|
|
|
|
for(int k=0; k<nTrain; k++) indices[k]=k;
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
double bestValAcc = 0;
|
2026-01-21 15:51:26 +03:00
|
|
|
double lastValAcc = 0;
|
2026-01-21 13:07:32 +03:00
|
|
|
int stuckCount = 0;
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
for(int e=0; e<InpEpochs; e++) {
|
|
|
|
|
// Shuffle training indices
|
|
|
|
|
for(int k=nTrain-1; k>0; k--) { int r=MathRand()%(k+1); int t=indices[k]; indices[k]=indices[r]; indices[r]=t; }
|
2026-01-16 16:42:06 +03:00
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
for(int k=0; k<nTrain; k++) {
|
|
|
|
|
double t[1]; t[0]=balanced[indices[k]].target;
|
|
|
|
|
Net.FeedForward(balanced[indices[k]].inputs);
|
|
|
|
|
Net.BackProp(t);
|
2026-01-16 16:42:06 +03:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
// Calculate Validation Accuracy
|
|
|
|
|
int corr=0, tot=0;
|
|
|
|
|
for(int v=nTrain; v<total; v++) {
|
|
|
|
|
Net.FeedForward(balanced[v].inputs);
|
|
|
|
|
double res[]; Net.GetResults(res);
|
|
|
|
|
if(MathAbs(balanced[v].target)>0.1) {
|
|
|
|
|
tot++;
|
|
|
|
|
if(balanced[v].target * res[0] > 0) corr++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
double valAcc = (tot>0)?(double)corr/tot*100:0;
|
|
|
|
|
|
|
|
|
|
if(e%10==0 || e==InpEpochs-1) {
|
|
|
|
|
PrintFormat("Epoch %d Val: %.1f%% (Best: %.1f%%) - LR: %.6f", e, valAcc, bestValAcc, Net.eta);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 17:17:54 +03:00
|
|
|
// Handle "Best Model" Persistence
|
|
|
|
|
if(valAcc > bestValAcc + 0.05) {
|
|
|
|
|
bestValAcc = valAcc;
|
|
|
|
|
SaveModelWithHeader("PriceActionNN_v3.bin");
|
|
|
|
|
PrintFormat("--- NEW BEST MODEL SAVED! Accuracy: %.2f%%", bestValAcc);
|
|
|
|
|
stuckCount = 0;
|
|
|
|
|
} else {
|
|
|
|
|
stuckCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If no improvement for 30 epochs relative to current state, NUDGE
|
|
|
|
|
if(stuckCount >= 30 && InpJitter > 0) {
|
|
|
|
|
PrintFormat("STUCK DETECTED! (Current: %.2f%% stayed flat). Nudging weights...", valAcc);
|
|
|
|
|
Net.Jitter(InpJitter);
|
|
|
|
|
stuckCount = 0;
|
|
|
|
|
Net.SetLearningRate(Net.eta * 1.5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Net.SetLearningRate(Net.eta * InpTrainingDecay);
|
2026-01-14 17:13:26 +03:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 13:07:32 +03:00
|
|
|
string fullPath = TerminalInfoString(TERMINAL_COMMONDATA_PATH) + "\\Files\\PriceActionNN_v3.bin";
|
2026-01-21 15:39:31 +03:00
|
|
|
Print("--------------------------------------------------");
|
|
|
|
|
Print("TRAINING FINISHED.");
|
|
|
|
|
PrintFormat("Peak Validation Accuracy Achieved: %.2f%%", bestValAcc);
|
|
|
|
|
PrintFormat("Topology Used: %d inputs, %s hidden, 1 output", GlobalTopology[0], InpHiddenLayerConfig);
|
|
|
|
|
Print("Final Best Model at: " + fullPath);
|
|
|
|
|
Print("--------------------------------------------------");
|
2026-01-14 17:13:26 +03:00
|
|
|
|
2026-01-25 16:49:49 +00:00
|
|
|
SaveModelWithHeader("PriceActionNN_v3.bin");
|
|
|
|
|
|
|
|
|
|
delete Net; Net = NULL;
|
|
|
|
|
IndicatorRelease(handleATR_D1);
|
|
|
|
|
IndicatorRelease(handleEMA50);
|
|
|
|
|
IndicatorRelease(handleEMA200);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnDeinit(const int reason)
|
|
|
|
|
{
|
|
|
|
|
if(CheckPointer(Net) == POINTER_DYNAMIC) {
|
|
|
|
|
Print("Script stopped/interrupted. Saving current weights...");
|
|
|
|
|
SaveModelWithHeader("PriceActionNN_v3.bin");
|
|
|
|
|
delete Net;
|
|
|
|
|
Net = NULL;
|
|
|
|
|
}
|
2026-01-21 13:07:32 +03:00
|
|
|
IndicatorRelease(handleATR_D1);
|
|
|
|
|
IndicatorRelease(handleEMA50);
|
|
|
|
|
IndicatorRelease(handleEMA200);
|
2026-01-14 17:13:26 +03:00
|
|
|
}
|
2026-01-25 16:49:49 +00:00
|
|
|
|
|
|
|
|
void SaveModelWithHeader(string filename)
|
|
|
|
|
{
|
|
|
|
|
int handle = FileOpen(filename, FILE_WRITE|FILE_BIN|FILE_COMMON);
|
|
|
|
|
if(handle == INVALID_HANDLE) { Print("Error opening file for write: ", filename); return; }
|
|
|
|
|
|
|
|
|
|
// Header Data
|
|
|
|
|
int version = 2; // Version 2 supports Metadata
|
|
|
|
|
FileWriteInteger(handle, version);
|
|
|
|
|
FileWriteInteger(handle, InpLookback);
|
|
|
|
|
FileWriteInteger(handle, ArraySize(GlobalTopology));
|
|
|
|
|
for(int i=0; i<ArraySize(GlobalTopology); i++) FileWriteInteger(handle, GlobalTopology[i]);
|
|
|
|
|
|
|
|
|
|
// Risk Settings
|
|
|
|
|
FileWriteDouble(handle, 1.0); // Target Multiplier
|
|
|
|
|
FileWriteDouble(handle, 0.5); // Stop Multiplier
|
|
|
|
|
|
|
|
|
|
// Session Times (Only used in Advanced, but kept for consistent header structure or omitted?
|
|
|
|
|
// PriceActionNN simple version doesn't use Session Inputs as strictly.
|
|
|
|
|
// Actually, `TrainPriceActionNN` DOES use sessions (Lines 23-28).
|
|
|
|
|
FileWriteInteger(handle, InpAsianStart);
|
|
|
|
|
FileWriteInteger(handle, InpAsianEnd);
|
|
|
|
|
FileWriteInteger(handle, InpLondonStart);
|
|
|
|
|
FileWriteInteger(handle, InpLondonEnd);
|
|
|
|
|
FileWriteInteger(handle, InpNYStart);
|
|
|
|
|
FileWriteInteger(handle, InpNYEnd);
|
|
|
|
|
|
|
|
|
|
if(Net.Save(handle)) Print("Saved Model + Metadata to ", filename);
|
|
|
|
|
else Print("Error saving weights!");
|
|
|
|
|
|
|
|
|
|
FileClose(handle);
|
|
|
|
|
}
|