//+------------------------------------------------------------------+ //| TrainPriceActionNN.mq5 | //| Copyright 2025, Google Gemini | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Google Gemini" #property link "https://www.google.com" #property version "3.00" #property script_show_inputs #include input int InpEpochs = 500; input int InpLookback = 144; input string InpHiddenLayerConfig = "256,128,64"; input double InpTrainingRate = 0.001; input double InpTrainingDecay = 0.98; input double InpLambda = 0.0001; // L2 Regularization (Weight Decay) input double InpJitter = 0.05; // Nudge magnitude when stuck input int InpMaxBars = 5000000; 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; CNeuralNet *Net; int GlobalTopology[]; 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) 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; } } } void OnStart() { Print("--- Advanced Training: v3.0 Optimized ---"); Print( "InpEpochs: ", InpEpochs, "\nInpHiddenLayerConfig: ", InpHiddenLayerConfig, "\nInpTrainingRate: ", InpTrainingRate, "\nInpTrainingDecay: ", InpTrainingDecay, "\nInpLookback: " ,InpLookback, "\nInpMaxBars: " , InpMaxBars, "\nInpPredictionHorizon: " , InpPredictionHorizon); handleATR_D1 = iATR(_Symbol, PERIOD_D1, 14); handleEMA50 = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE); handleEMA200 = iMA(_Symbol, PERIOD_H1, 200, 0, MODE_EMA, PRICE_CLOSE); MqlRates rates[]; ArraySetAsSeries(rates, true); int needed = InpMaxBars + InpPredictionHorizon + InpLookback + 5000; int copied = CopyRates(_Symbol, PERIOD_H1, 0, needed, rates); if(copied < 5000) return; double ema50[], ema200[]; ArraySetAsSeries(ema50, true); ArraySetAsSeries(ema200, true); CopyBuffer(handleEMA50, 0,0,copied, ema50); CopyBuffer(handleEMA200, 0,0,copied, ema200); // --- 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("--------------------------------------------------"); // Precalc Sessions ComputeSessions(rates, copied); int featuresPerBar = 17; int inputSize = InpLookback * featuresPerBar; string result[]; int hiddenLayerCount = StringSplit(InpHiddenLayerConfig, ',', result); ArrayResize(GlobalTopology, 1 + hiddenLayerCount + 1); GlobalTopology[0] = inputSize; for(int i=0; i InpMaxBars) endIdx = startIdx + InpMaxBars; for(int i = startIdx; i <= endIdx; i++) { // Stride: Train every 20 minutes (Minute 0, 20, 40) MqlDateTime dt; TimeToStruct(rates[i].time, dt); //if(dt.min % 20 != 0) continue; // -- D1 ATR -- double d1ATR_Arr[1]; if(CopyBuffer(handleATR_D1, 0, rates[i].time, 1, d1ATR_Arr)<=0) continue; double dailyATR = d1ATR_Arr[0]; double targetTP = 1.0 * dailyATR; double targetSL = 0.5 * dailyATR; double entryPrice = rates[i].close; double label = 0; int firstBuyTP = 99999, firstBuySL = 99999; int firstSellTP = 99999, firstSellSL = 99999; for(int k=1; k<=InpPredictionHorizon; k++) { 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; } if(firstBuyTP < firstBuySL) label = 1.0; // Buy Win else if(firstSellTP < firstSellSL) label = -1.0; // Sell Win else label = 0.0; // -- Features -- double inputs[]; ArrayResize(inputs, inputSize); for(int k=0; k= 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; 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; 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 inputs[off+6] = ((c - ema50[p])/ema50[p]) * 100.0; inputs[off+7] = ((c - ema200[p])/ema200[p]) * 100.0; // -- 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; } 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; 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; k0; k--) { int r=MathRand()%(k+1); int t=indices[k]; indices[k]=indices[r]; indices[r]=t; } for(int k=0; k0.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); } // 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); } string fullPath = TerminalInfoString(TERMINAL_COMMONDATA_PATH) + "\\Files\\PriceActionNN_v3.bin"; 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("--------------------------------------------------"); 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; } IndicatorRelease(handleATR_D1); IndicatorRelease(handleEMA50); IndicatorRelease(handleEMA200); } 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