mql5/Experts/NeuralNetEA.mq5

271 lignes
9,7 Kio
MQL5
Brut Lien permanent Vue normale Historique

2026-01-16 11:34:58 +03:00
//+------------------------------------------------------------------+
//| NeuralNetEA.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:37 +03:00
#property version "3.00"
2026-01-16 16:42:10 +03:00
2026-01-16 11:34:58 +03:00
#include <Trade\Trade.mqh>
#include <NeuralNet.mqh>
2026-01-21 13:07:37 +03:00
input string InpModelFile = "PriceActionNN_v3.bin";
input double InpConfidence = 0.8;
input double InpLotSize = 0.1;
2026-01-16 11:34:58 +03:00
input int InpMagic = 123456;
2026-01-25 16:54:03 +00:00
// --- Model Parameters (Loaded from File) ---
int gLookback;
int gAsianStart, gAsianEnd;
int gLondonStart, gLondonEnd;
int gNYStart, gNYEnd;
double gTargetTP_Mult, gTargetSL_Mult;
2026-01-16 11:34:58 +03:00
CTrade Trade;
CNeuralNet *Net;
int GlobalTopology[];
datetime lastBarTime = 0;
2026-01-21 13:07:37 +03:00
int handleEMA50, handleEMA200, handleATR_D1;
2026-01-16 11:34:58 +03:00
2026-01-25 16:54:03 +00:00
// Forward Decl
bool LoadModelWithHeader(string filename);
2026-01-16 11:34:58 +03:00
int OnInit()
{
Trade.SetExpertMagicNumber(InpMagic);
2026-01-21 13:07:37 +03:00
handleEMA50 = iMA(_Symbol, _Period, 50, 0, MODE_EMA, PRICE_CLOSE);
handleEMA200 = iMA(_Symbol, _Period, 200, 0, MODE_EMA, PRICE_CLOSE);
handleATR_D1 = iATR(_Symbol, PERIOD_D1, 14);
if(handleEMA50 == INVALID_HANDLE || handleEMA200 == INVALID_HANDLE || handleATR_D1 == INVALID_HANDLE) return(INIT_FAILED);
2026-01-16 11:34:58 +03:00
2026-01-25 16:54:03 +00:00
if(!LoadModelWithHeader(InpModelFile)) {
Print("Fatal Error: Could not load model and metadata.");
return(INIT_FAILED);
}
Print("Model Loaded Successfully.");
PrintFormat("Config: Lookback=%d, Risk=%.1f/%.1f ATR", gLookback, gTargetSL_Mult, gTargetTP_Mult);
2026-01-16 11:34:58 +03:00
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
if(CheckPointer(Net) != POINTER_INVALID) delete Net;
2026-01-21 13:07:37 +03:00
IndicatorRelease(handleEMA50);
IndicatorRelease(handleEMA200);
IndicatorRelease(handleATR_D1);
2026-01-16 11:34:58 +03:00
}
2026-01-21 13:07:37 +03:00
// Optimized GetSession: Only scans necessary period
void GetSession(MqlRates &rates[], int currentIdx, int startHour, int endHour, double &H, double &L)
{
// Need Past Finished Session.
// currentIdx is index in 'rates'. rates[0] is newest.
// If current Hour >= endHour, we scan today (until index reaches endHour).
// If current Hour < endHour, we scan Yesterday.
// Simplification for EA:
// Just scan 24h back from currentIdx, and verify Hour check.
H = -1; L = 999999;
bool found = false;
datetime t = rates[currentIdx].time;
MqlDateTime dt; TimeToStruct(t, dt);
int curH = dt.hour;
int scanStart = 0; // bars back from currentIdx
// Logic:
// If currently In Session or Before Session -> We need YESTERDAY's session.
// If currently After Session -> We need TODAY's session.
bool lookForYesterday = (curH < endHour);
// We scan back up to 2880 bars (2 days) to be safe
for(int k=1; k<2000; k++) {
int idx = currentIdx + k;
if(idx >= ArraySize(rates)) break;
datetime subT = rates[idx].time;
MqlDateTime subDt; TimeToStruct(subT, subDt);
int h = subDt.hour;
// Check if this bar belongs to the Target Session
if(h >= startHour && h < endHour) {
// Found a bar in session.
// Is it the RIGHT day?
// If we lookForYesterday, day_of_year must be != dt.day_of_year
// (Edge case: New Year)
bool isSameDay = (subDt.day_of_year == dt.day_of_year && subDt.year == dt.year);
if(lookForYesterday && isSameDay) continue; // Skip today's bars
// If we are here, it's valid.
if(rates[idx].high > H || H == -1) H = rates[idx].high;
if(rates[idx].low < L) L = rates[idx].low;
found = true;
} else {
// If we were inside the session loop and now we are OUT (h < startHour), we are done?
// Careful with day boundaries.
if(found) {
// We found session bars, now we exited the session window. It's fully scanned.
break;
}
}
}
if(H == -1) { H=rates[currentIdx].high; L=rates[currentIdx].low; } // Fallback
}
2026-01-16 11:34:58 +03:00
void OnTick()
{
datetime time = iTime(_Symbol, _Period, 0);
if(time == lastBarTime) return;
lastBarTime = time;
2026-01-26 17:17:50 +03:00
int count = gLookback + 2000;
2026-01-16 11:34:58 +03:00
MqlRates rates[];
ArraySetAsSeries(rates, true);
2026-01-21 13:07:37 +03:00
double ema50[], ema200[];
ArraySetAsSeries(ema50, true);
ArraySetAsSeries(ema200, true);
if(CopyRates(_Symbol, _Period, 0, count, rates) < count ||
2026-01-25 16:54:03 +00:00
CopyBuffer(handleEMA50, 0, 0, gLookback+1, ema50) < gLookback ||
CopyBuffer(handleEMA200, 0, 0, gLookback+1, ema200) < gLookback) return;
2026-01-21 13:07:37 +03:00
// Pre-calc sessions for Current Time (Index 0)
int featuresPerBar = 17;
2026-01-25 16:54:03 +00:00
int inputSize = gLookback * featuresPerBar;
2026-01-21 13:07:37 +03:00
double inputs[];
2026-01-16 11:34:58 +03:00
ArrayResize(inputs, inputSize);
2026-01-21 13:07:37 +03:00
// Cache to avoid rescanning if we are in same context?
double prevAH=-1.0;
double prevAL=0.0, prevLH=0.0, prevLL=0.0, prevNH=0.0, prevNL=0.0;
int prevHour = -1;
2026-01-25 16:54:03 +00:00
for(int k=0; k<gLookback; k++) {
2026-01-21 13:07:37 +03:00
int off = k * featuresPerBar;
double o = rates[k].open;
double c = rates[k].close;
double h = rates[k].high;
double l = rates[k].low;
double pH = rates[k+1].high;
double pL = rates[k+1].low;
inputs[off+0] = (c - o)/o * 1000.0;
inputs[off+1] = (h - o)/o * 1000.0;
inputs[off+2] = (l - o)/o * 1000.0;
inputs[off+3] = ((h - pH)/pH) * 1000.0;
inputs[off+4] = ((l - pL)/pL) * 1000.0;
2026-01-21 15:39:36 +03:00
inputs[off+5] = (rates[k+1].tick_volume - rates[k+2].tick_volume) / (rates[k+2].tick_volume + 1.0) * 10.0; // Completed Bar Volume difference normalized
2026-01-21 13:07:37 +03:00
inputs[off+6] = ((c - ema50[k])/ema50[k]) * 1000.0;
inputs[off+7] = ((c - ema200[k])/ema200[k]) * 1000.0;
// Session Logic
datetime t = rates[k].time;
MqlDateTime dt; TimeToStruct(t, dt);
double aH, aL, lH, lL, nH, nL;
// Optimization: If hour is same as prev k (very likely for M1), levels are same.
if(dt.hour == prevHour && prevAH != -1) {
aH = prevAH; aL = prevAL;
lH = prevLH; lL = prevLL;
nH = prevNH; nL = prevNL;
} else {
2026-01-25 16:54:03 +00:00
GetSession(rates, k, gAsianStart, gAsianEnd, aH, aL);
GetSession(rates, k, gLondonStart, gLondonEnd, lH, lL);
GetSession(rates, k, gNYStart, gNYEnd, nH, nL);
2026-01-21 13:07:37 +03:00
prevHour = dt.hour;
prevAH=aH; prevAL=aL; prevLH=lH; prevLL=lL; prevNH=nH; prevNL=nL;
}
inputs[off+8] = ((c - aH)/aH) * 1000.0;
inputs[off+9] = ((c - aL)/aH) * 1000.0;
inputs[off+10] = ((c - lH)/lH) * 1000.0;
inputs[off+11] = ((c - lL)/lH) * 1000.0;
inputs[off+12] = ((c - nH)/nH) * 1000.0;
inputs[off+13] = ((c - nL)/nH) * 1000.0;
inputs[off+14] = (double)dt.day_of_week / 7.0;
inputs[off+15] = (double)dt.hour / 24.0;
inputs[off+16] = (double)dt.min / 60.0;
2026-01-16 11:34:58 +03:00
}
Net.FeedForward(inputs);
2026-01-21 13:07:37 +03:00
double results[]; Net.GetResults(results);
2026-01-16 11:34:58 +03:00
double signal = results[0];
2026-01-21 13:07:37 +03:00
// Get current and previous D1 ATR
double atrArray[2];
if(CopyBuffer(handleATR_D1, 0, 0, 2, atrArray) < 2) return;
double currentDayATR = atrArray[0];
double prevDayATR = atrArray[1];
// Use larger of current or previous day's ATR
2026-01-25 16:54:03 +00:00
// Use larger of current or previous day's ATR
2026-01-26 17:17:50 +03:00
CopyBuffer(handleATR_D1, 0, 0, 2, atrArray);
2026-01-25 16:54:03 +00:00
double atrToUse = (atrArray[0] > atrArray[1]) ? atrArray[0] : atrArray[1];
double distTP = atrToUse * gTargetTP_Mult;
double distSL = atrToUse * gTargetSL_Mult;
2026-01-21 13:07:37 +03:00
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
2026-01-25 16:54:03 +00:00
Print("Signal: ", signal, " | ATR: ", DoubleToString(atrToUse, _Digits), " | TP: ", DoubleToString(distTP, _Digits), " SL: ", DoubleToString(distSL, _Digits));
2026-01-21 13:07:37 +03:00
if(signal > InpConfidence && !PositionSelect(_Symbol)) {
2026-01-25 16:54:03 +00:00
Trade.Buy(InpLotSize, _Symbol, 0, ask - distSL, ask + distTP, "NN Buy");
2026-01-16 11:34:58 +03:00
}
2026-01-21 13:07:37 +03:00
else if(signal < -InpConfidence && !PositionSelect(_Symbol)) {
2026-01-25 16:54:03 +00:00
Trade.Sell(InpLotSize, _Symbol, 0, bid + distSL, bid - distTP, "NN Sell");
2026-01-16 11:34:58 +03:00
}
}
2026-01-25 16:54:03 +00:00
// Load Model with Header
bool LoadModelWithHeader(string filename)
{
int handle = FileOpen(filename, FILE_READ|FILE_BIN|FILE_COMMON);
if(handle == INVALID_HANDLE) { Print("Error opening model file."); return false; }
int version = FileReadInteger(handle);
if(version != 2) { Print("Error: Model version match (Expected 2)"); FileClose(handle); return false; }
gLookback = FileReadInteger(handle);
int topologyCnt = FileReadInteger(handle);
ArrayResize(GlobalTopology, topologyCnt);
for(int i=0; i<topologyCnt; i++) GlobalTopology[i] = FileReadInteger(handle);
gTargetTP_Mult = FileReadDouble(handle);
gTargetSL_Mult = FileReadDouble(handle);
gAsianStart = FileReadInteger(handle);
gAsianEnd = FileReadInteger(handle);
gLondonStart = FileReadInteger(handle);
gLondonEnd = FileReadInteger(handle);
gNYStart = FileReadInteger(handle);
gNYEnd = FileReadInteger(handle);
// Create Net with loaded Topology
if(CheckPointer(Net) != POINTER_INVALID) delete Net;
Net = new CNeuralNet(GlobalTopology);
// Load Weights
if(!Net.Load(handle, GlobalTopology)) { FileClose(handle); return false; }
FileClose(handle);
return true;
}