//+------------------------------------------------------------------+ //| Warrior_EA | //| AnimateDread | //| | //+------------------------------------------------------------------+ //--- database classes #include "Database\DatabaseManager.mqh" //--- available custom classes #include "Expert\ExpertCustom.mqh" #include "System\PrintVerbose.mqh" #include "System\StatusLabel.mqh" //--- available signals #include "Signals\Signals.mqh" //--- available trailing #include "Trailing\Trailing.mqh" //--- available money management #include "Money\Money.mqh" //--- Inputs #include "Variables\Inputs.mqh" //--- Variables #include "Variables\Variables.mqh" //--- Control panel GUI (standard MQL5 Controls library) #include "Panel\ControlPanel.mqh" //+------------------------------------------------------------------+ //| The 5 CustomIndicators\*.mq5 files (ADCumulativeDelta, | //| ADShorteningOfThrust, ADWyckoffEventStream, | //| ADWyckoffFailedStructure, ADWyckoffSignificantBarInversion) are | //| loaded via CiCustom/IND_CUSTOM (see ExpertSignalAIBase.mqh), which | //| - like #import for DLLs - resolves them from | //| \MQL5\Indicators\ at call time. MQL5 no | //| longer allows a running program to write outside its own | //| MQL5\Files\ sandbox (mirroring the WarriorCPU.dll/WarriorDML.dll | //| restriction in AI\Network.mqh), so auto-extracting them there from | //| an embedded #resource is no longer possible. Compile each | //| CustomIndicators\*.mq5 once in MetaEditor (or copy the already- | //| compiled .ex5) directly into MQL5\Indicators\ alongside this EA | //| before enabling any EnableAD* input - InitADCumulativeDelta() etc. | //| below will simply fail CiCustom::Create() with a clear log message | //| if the matching .ex5 isn't there. | //+------------------------------------------------------------------+ // CExpertCustom Expert; CDatabaseManager dbm(); //+------------------------------------------------------------------+ //| Pointers to whichever AI signal instances this run actually | //| created (per AIType - PAI/CONV/LSTM, any subset), so the control | //| panel can drive training/weight actions on exactly the signal(s) | //| in play this run and never touch another config's files. | //+------------------------------------------------------------------+ #define MAX_AI_SIGNALS 3 CExpertSignalAIBase *g_aiSignals[MAX_AI_SIGNALS]; int g_aiSignalCount = 0; void RegisterAISignal(CExpertSignalAIBase *sig) { if(sig == NULL || g_aiSignalCount >= MAX_AI_SIGNALS) return; g_aiSignals[g_aiSignalCount++] = sig; } //+------------------------------------------------------------------+ //| Control panel: a CAppDialog-based GUI (see Panel\ControlPanel.mqh) | //| with show/hide signals, start/pause/stop training, and save/load/ | //| delete-reset weights buttons for the currently-active AI | //| signal(s) only. The dialog's own caption bar provides the show/ | //| hide (minimize) control - no separate toggle button needed. | //+------------------------------------------------------------------+ //--- default spawn position: top-right corner, clear of the status label text block (top-left) so the //--- two don't overlap on first run - the panel is fully draggable afterwards via its caption bar, //--- so this is only a starting point, not a constraint. #define CP_Y0 10 #define CP_RIGHT_MARGIN 80 CControlPanel ExtPanel; bool g_signalsVisible = true; //--- tracks the last known AlgoTrading permission state (terminal "Algo Trading" toggle AND this //--- EA's own "Allow Algo Trading" property) so a change is logged exactly once, not spammed every tick bool g_lastAlgoTradingAllowed = true; //--- OnDeinit() is not guaranteed to run on a terminal crash/power loss/forced kill, so weights //--- would otherwise only be as fresh as the last fully-completed training era. A periodic //--- autosave closes that gap without depending on UseDatabaseRanking's timer. #define AUTOSAVE_INTERVAL_SECONDS 300 datetime g_lastAutosave = 0; //--- summarizes state across all currently-active AI signals for button labels; //--- "paused"/"stopped" only report true if EVERY active signal agrees, so a mixed state //--- (e.g. AIType=All with one signal paused and one running) still shows an actionable label bool AllTrainingPaused(void) { if(g_aiSignalCount == 0) return false; for(int i = 0; i < g_aiSignalCount; i++) if(!g_aiSignals[i].IsTrainingPaused()) return false; return true; } bool AllTrainingStopped(void) { if(g_aiSignalCount == 0) return false; for(int i = 0; i < g_aiSignalCount; i++) if(!g_aiSignals[i].IsTrainingStopped()) return false; return true; } void ApplySignalsVisibility(void) { for(int i = ObjectsTotal(0, 0, OBJ_ARROW) - 1; i >= 0; i--) { string name = ObjectName(0, i, 0, OBJ_ARROW); ObjectSetInteger(0, name, OBJPROP_TIMEFRAMES, g_signalsVisible ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); } ChartRedraw(0); } void ToggleSignalsVisibility(void) { g_signalsVisible = !g_signalsVisible; ApplySignalsVisibility(); } //--- keeps every button's label in sync with live training/signal-visibility state; safe/cheap to //--- call after every panel action void RefreshControlPanelLabels(void) { ExtPanel.SetSignalsText(g_signalsVisible ? "Hide Signals" : "Show Signals"); bool noAI = (g_aiSignalCount == 0); ExtPanel.SetPauseText(noAI ? "Pause Training (n/a)" : (AllTrainingPaused() ? "Resume Training" : "Pause Training")); ExtPanel.SetStopText(noAI ? "Stop Training (n/a)" : (AllTrainingStopped() ? "Start Training" : "Stop Training")); ChartRedraw(0); } //--- creates the control panel dialog once, from OnInit() - the standard CAppDialog usage pattern //--- (create in OnInit, destroy in OnDeinit; see Controls\Dialog.mqh). CAppDialog::Destroy(REASON_PROGRAM) //--- calls ExpertRemove() once the dialog has a valid PROGRAM_EXPERT type (i.e. on any call after the //--- first successful Create()), so it must never be called speculatively/defensively before Create() - //--- doing so would silently detach this EA from the chart the next time this function ran. bool CreateControlPanel(void) { ResetLastError(); long chartWidth = ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int panelX1 = (chartWidth > CP_PANEL_W + CP_RIGHT_MARGIN + 20) ? (int)(chartWidth - CP_PANEL_W - CP_RIGHT_MARGIN) : 10; if(!ExtPanel.Create(0, "WarriorCP", 0, panelX1, CP_Y0, panelX1 + CP_PANEL_W, CP_Y0 + CP_PANEL_H)) { Print(__FUNCTION__ + ": failed to create control panel, error " + IntegerToString(GetLastError())); return false; } if(!ExtPanel.Run()) { Print(__FUNCTION__ + ": failed to run control panel, error " + IntegerToString(GetLastError())); return false; } ExtPanel.ForceMaximize(); RefreshControlPanelLabels(); return true; } //--- performs whatever button action ExtPanel recorded (see ConsumeAction() in ControlPanel.mqh); //--- a no-op when nothing was clicked since the last call void HandleControlPanelAction(ENUM_CP_ACTION action) { switch(action) { case CP_ACTION_TOGGLE_SIGNALS: ToggleSignalsVisibility(); RefreshControlPanelLabels(); break; case CP_ACTION_TOGGLE_PAUSE: { bool pause = !AllTrainingPaused(); for(int i = 0; i < g_aiSignalCount; i++) if(pause) g_aiSignals[i].PauseTraining(); else g_aiSignals[i].ResumeTraining(); RefreshControlPanelLabels(); break; } case CP_ACTION_TOGGLE_STOP: { bool doStop = !AllTrainingStopped(); for(int i = 0; i < g_aiSignalCount; i++) if(doStop) g_aiSignals[i].StopTraining(); else g_aiSignals[i].StartTraining(); RefreshControlPanelLabels(); break; } case CP_ACTION_SAVE: for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].SaveWeightsNow(); break; case CP_ACTION_LOAD: for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].LoadWeightsNow(); break; case CP_ACTION_RESET: for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].ResetWeights(); RefreshControlPanelLabels(); break; default: break; } } // Helper function to pause execution for a random duration between 1 to 3 seconds void RandomSleep() { Sleep(MathRand() % 2000 + 1000); // Sleeps between 1000ms (1s) and 3000ms (3s) } //+------------------------------------------------------------------+ //| Verifies real Depth of Market data is actually available for | //| `symbol` before anything else (including neural network init) | //| depends on it. MarketBookAdd() succeeding only means the | //| SUBSCRIPTION was accepted - some brokers/symbols accept the | //| subscription but never actually stream book data, so this also | //| polls MarketBookGet() briefly for a genuinely non-empty snapshot | //| before trusting it. Alerts the user (popup) and cleans up its own | //| subscription on any failure so the caller can simply treat | //| EnableMarketDepth as false for the rest of this run. | //+------------------------------------------------------------------+ bool CheckMarketDepthAvailability(string symbol) { if(!MarketBookAdd(symbol)) { Alert("Warrior EA: Market Depth (DOM) is not available for " + symbol + " - the order-book confirmation filter will be omitted for this run. Your broker/account may not provide Depth of Market for this symbol."); Print("Warrior EA: MarketBookAdd(" + symbol + ") failed, error " + IntegerToString(GetLastError()) + " - EnableMarketDepth will be treated as false."); return false; } MqlBookInfo book[]; bool populated = false; for(int attempt = 0; attempt < 10 && !populated; attempt++) { if(MarketBookGet(symbol, book) && ArraySize(book) > 0) populated = true; else Sleep(200); } if(!populated) { MarketBookRelease(symbol); Alert("Warrior EA: Market Depth (DOM) subscribed for " + symbol + " but returned no data - your broker likely does not provide real Depth of Market for this symbol. The order-book confirmation filter will be omitted for this run."); Print("Warrior EA: MarketBookGet(" + symbol + ") returned no levels after 2s - EnableMarketDepth will be treated as false."); return false; } Print("Warrior EA: Market Depth (DOM) available for " + symbol + " - order-book confirmation filter enabled."); return true; } // Helper function to retry signal creation with error handling template TSignal* CreateSignalWithRetry(int maxRetries, bool enableFlag) { if(!enableFlag) return NULL; TSignal* signal = NULL; for(int tries = 0; tries < maxRetries; ++tries) { signal = new TSignal; if(signal == NULL) { Print("Initialization of signal failed, retrying..."); RandomSleep(); } else break; } if(signal == NULL) { Print("Failed to create and initialize signal after retries"); } return signal; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| IMPORTANT: no failure branch below (nor in any helper it calls - | //| AddFilterToSignal(), InitializeSignal(), InitializeTrailing(), | //| InitializeMoneyManagement()) may call Expert.Deinit() before | //| returning INIT_FAILED/false. MQL5 ALWAYS calls this EA's own | //| OnDeinit(REASON_INITFAILED) automatically once OnInit() returns | //| anything other than INIT_SUCCEEDED, and OnDeinit() already calls | //| Expert.Deinit() itself. Expert.Deinit() tears down `signal` and, | //| through it, every registered AI signal (PAI/CONV/LSTM - see | //| g_aiSignals' declaration comment) - calling it a second time here | //| would free those objects while g_aiSignals[] still points at them, | //| and OnDeinit()'s own PersistOnShutdown() loop over g_aiSignals[] | //| would then dereference already-freed pointers. This is exactly | //| what "invalid pointer access" during OnDeinit() after a failed | //| OnInit() means if it ever recurs - the fix is to remove whichever | //| inline Expert.Deinit() call was re-added, not to guard the loop. | //+------------------------------------------------------------------+ int OnInit() { //--- clears out whatever status label text was left over from before this OnInit() ran (stale text //--- from a prior "warm" re-init - e.g. an input-parameter change, which reuses this same running //--- instance rather than a fresh one - would otherwise sit unchanged and look like nothing is //--- happening) so it's obvious the moment training/signal init actually resumes producing new status text SetStatusLabel("Warrior EA: initializing..."); int maxRetryOnError = 5; string functionName = __FUNCTION__; // Initialize random seed based on the number of milliseconds since the system started MathSrand(GetTickCount()); // Initialize expert bool expertInitialized = false; for(int tries = 0; !expertInitialized && tries < 5; ++tries) { if(!Expert.Init(Symbol(), Period(), Expert_EveryTick, Expert_MagicNumber)) { Print(functionName + ": Failed initializing expert, retrying..."); RandomSleep(); } else { expertInitialized = true; break; } } if(!expertInitialized) { Print(functionName + ": Failed to initialize expert after retries"); return INIT_FAILED; } Expert.OnChartEventProcess(true); //--- Market Depth availability check - deliberately BEFORE any signal (and therefore any neural //--- network) is created below, per the same "before initializing neurons" requirement as the //--- AI models themselves. g_marketDepthAvailable stays false (its declaration default) if //--- EnableMarketDepth is off, so nothing below needs to special-case that. if(EnableMarketDepth) g_marketDepthAvailable = CheckMarketDepthAvailability(_Symbol); // Creating signal PrintVerbose("Initializing Signal..."); CExpertSignalCustom* signal = NULL; for(int tries = 0; signal == NULL && tries < 5; ++tries) { signal = new CExpertSignalCustom; if(signal == NULL) { Print(functionName + ": Failed to initialize Signal, retrying..."); RandomSleep(); } else { break; } } if(signal == NULL) { Print(functionName + ": Failed to initialize Signal after retries"); return INIT_FAILED; } InitializeSignal(signal); // Initializing Database if(UseDatabaseRanking) { bool dbInitialized = false; string databaseFolderStructure[] = {eaName, "Databases", "Signals"}; const string dbName = Symbol() + "_" + IntegerToString(Period()) + ".db"; const string dbVersion = "2.0"; PrintVerbose("Initializing Database..."); for(int tries = 0; !dbInitialized && tries < 5; ++tries) { if(!dbm.Init(dbVersion, databaseFolderStructure, dbName)) { Print(functionName + ": Failed to initialize Database, retrying..."); RandomSleep(); } else { dbInitialized = true; break; } } if(!dbInitialized) { Print(functionName + ": Failed to initialize Database after retries"); return INIT_FAILED; } } //+------------------------------------------------------------------+ //| AIType selects which of the 3 AI signal models this run trades/ | //| trains, or all 3 at once under HYBRID: | //| - PAI (CSignalPAI): plain multi-layer Perceptron - input layer | //| feeds straight into the tapering Dense hidden-layer stack, no | //| Conv/Pool/LSTM stage. The baseline/cheapest model. | //| - CONV (CSignalCONV): Conv+Pool front-end ahead of the same | //| tapering Dense stack - looks for local price-action patterns | //| (candlestick/short-range shapes) before the dense layers see | //| them. | //| - LSTM (CSignalLSTM): a single LSTM layer ahead of the same | //| tapering Dense stack - genuine forget/input/output-gated | //| recurrence (see AI\Network.mqh's CNeuronLSTM/CNeuronLSTMOCL), | //| for sequential/regime-dependent structure the other two can't | //| see across bars. | //| All 3 share one CExpertSignalCustom "signal" (see | //| InitializeSignal()/AddFilterToSignal() below) via the standard | //| MQL5 wizard signal-aggregation pattern: each is added as a | //| weighted filter, and the aggregate signal's Direction()/SLTP just | //| blends whichever of them are active. Concurrency model: MQL5 is | //| single-threaded per chart - OnTick()/OnTimer() never run | //| re-entrantly, so PAI/CONV/LSTM never race each other inside this | //| EA's own code; PollTraining() below just calls each in turn every | //| timer tick. The one real concurrency-relevant boundary is the | //| native compute backend (WarriorCPU.dll/WarriorDML.dll, see | //| AI\Network.mqh) - each CNet (there can be up to 6 alive at once | //| under HYBRID: 3 signals x live+shadow net each) gets its OWN | //| opaque per-instance context handle with no shared/global DLL | //| state, so a fault or watchdog-kill against one can never poison | //| another's calls. Each CNet's WarriorCPU.dll worker pool is sized | //| directly off the single global TargetCPULoad input, undivided, | //| even though several pools can be alive at once - see | //| AI\Network.mqh's TargetCPULoad declaration comment for why that's | //| safe: MQL5's single execution thread per chart means only one | //| pool is EVER actively computing at a time, so there is no real | //| contention to divide the budget across. | //| Memory ownership: PAI/CONV/LSTM are allocated here with `new` and | //| registered into g_aiSignals[] for the control panel's benefit, but | //| g_aiSignals[] does NOT own them - AddFilterToSignal() below adds | //| each to `signal`'s own filter array (CExpertSignal::AddFilter()), | //| which frees its elements on destruction; `signal` itself is owned | //| by Expert (InitializeSignal() -> Expert.InitSignal()). So the | //| actual free happens via Expert.Deinit() (see OnDeinit() below) | //| tearing down signal -> its filter array -> PAI/CONV/LSTM, exactly | //| once each - g_aiSignals[] is never delete'd directly (grep confirms| //| this file has no `delete g_aiSignals` anywhere), so there is no | //| double-free risk from the two arrays holding the same pointers. | //+------------------------------------------------------------------+ EnablePAI = (AIType == 0 || AIType == 3); EnableCONV = (AIType == 1 || AIType == 3); EnableLSTM = (AIType == 2 || AIType == 3); // Creating instances of signals CSignalPAI *PAI = CreateSignalWithRetry(maxRetryOnError, EnablePAI); CSignalCONV *CONV = CreateSignalWithRetry(maxRetryOnError, EnableCONV); CSignalLSTM *LSTM = CreateSignalWithRetry(maxRetryOnError, EnableLSTM); //--- register whichever AI signal instances this run created, so the control panel can drive //--- training/weight actions on exactly this run's current config (never another AIType's files) g_aiSignalCount = 0; if(EnablePAI && PAI != NULL) RegisterAISignal(PAI); if(EnableCONV && CONV != NULL) RegisterAISignal(CONV); if(EnableLSTM && LSTM != NULL) RegisterAISignal(LSTM); CSignalNewsFilter *newsFilter = CreateSignalWithRetry(maxRetryOnError, EnableNewsFilter); CSignalSessionFilter *sessionFilter = CreateSignalWithRetry(maxRetryOnError, EnableSessionFilter); CSignalITF *ITF = CreateSignalWithRetry(maxRetryOnError, EnableITF); //--- g_marketDepthAvailable is the OnInit-time verified result (see CheckMarketDepthAvailability() //--- above), not the raw EnableMarketDepth input - only created when real DOM data was confirmed. CSignalMarketDepth *marketDepth = CreateSignalWithRetry(maxRetryOnError, g_marketDepthAvailable); CSignalRiskGuard *riskGuard = CreateSignalWithRetry(maxRetryOnError, EnableRiskGuard); if((EnableITF && ITF == NULL) || (EnablePAI && PAI == NULL) || (EnableCONV && CONV == NULL) || (EnableLSTM && LSTM == NULL) || (EnableNewsFilter && newsFilter == NULL) || (EnableSessionFilter && sessionFilter == NULL) || (g_marketDepthAvailable && marketDepth == NULL) || (EnableRiskGuard && riskGuard == NULL)) { Print("Critical signal initialization failed, cannot proceed"); return INIT_FAILED; } // Set filter parameters if(EnableRiskGuard) { riskGuard.SetMaxDailyLossPct(MaxDailyLossPct); riskGuard.SetMaxDrawdownPct(MaxDrawdownPct); } if(EnableSessionFilter) { sessionFilter.TradeLondonSession(SF_trade_LondonSession); sessionFilter.TradeNewYorkSession(SF_trade_NewYorkSession); sessionFilter.TradeTokyoSession(SF_trade_TokyoSession); } if(EnableITF) { ITF.GoodHourOfDay(ITF_GoodHourOfDay); ITF.BadHoursOfDay(ITF_BadHoursOfDay); ITF.GoodDayOfWeek(ITF_GoodDayOfWeek); ITF.BadDaysOfWeek(ITF_BadDaysOfWeek); } if(EnableNewsFilter) { newsFilter.SetMinImpact(NF_MinImpact); newsFilter.SetLookbackMinutes(NF_LookMinutes); } if(g_marketDepthAvailable) { marketDepth.DepthLevels(DOM_DepthLevels); marketDepth.ImbalanceScale(DOM_ImbalanceScale / 100.0); marketDepth.MaxSpreadMultiple(DOM_MaxSpreadMultiple); } if(AIType == 0 || AIType == 3) { PAI.InitialNeuronsCount(InitialNeurons); PAI.OutputNeuronsCount(OutputNeuronsCount); PAI.MinNeuronsCount(MinNeuronsCount); PAI.HiddenLayersCount(HiddenLayersCount); PAI.NeuronsReduction(NeuronsReduction); PAI.HistoryBars(ind_Periods); PAI.StudyPeriod(StudyPeriods); PAI.StopTrainWR(MinWR); PAI.MinDirectionalRecall(MinRecall); PAI.ClassSampleWeight(ClassSampleWeight / 10.0); PAI.FocalLossGamma(FocalLossGamma / 10.0); PAI.SwingConfirmationBars(SwingConfirmationBars); PAI.MaxErasPerRun(MaxErasPerRun); PAI.TrainRetryCooldownSeconds(TrainRetryCooldownSec); PAI.OOSSplit(OOSSplit); if(!UseDatabaseRanking) PAI.Weight(1); PAI.UseVolumes(EnableVolume); PAI.UseTime(EnableTime); PAI.UseATR(EnableATR); PAI.UseSwingContext(EnableSwingContext); PAI.UseNews(EnableNews); PAI.NewsFeatureWindowMinutes(NewsFeatureWindowMinutes); PAI.UseADCumulativeDelta(EnableADCumulativeDelta); PAI.UseADShorteningOfThrust(EnableADShorteningOfThrust); PAI.UseADWyckoffEventStream(EnableADWyckoffEventStream); PAI.UseADWyckoffFailedStructure(EnableADWyckoffFailedStructure); PAI.UseADWyckoffSignificantBarInversion(EnableADWyckoffSignificantBarInversion); PAI.AutoTuneIndicators(AutoTuneIndicators); PAI.IndicatorTuneTrials(IndicatorTuneTrials); } if(AIType == 1 || AIType == 3) { CONV.InitialNeuronsCount(InitialNeurons); CONV.OutputNeuronsCount(OutputNeuronsCount); CONV.MinNeuronsCount(MinNeuronsCount); CONV.HiddenLayersCount(HiddenLayersCount); CONV.NeuronsReduction(NeuronsReduction); CONV.HistoryBars(ind_Periods); CONV.StudyPeriod(StudyPeriods); CONV.StopTrainWR(MinWR); CONV.MinDirectionalRecall(MinRecall); CONV.ClassSampleWeight(ClassSampleWeight / 10.0); CONV.FocalLossGamma(FocalLossGamma / 10.0); CONV.SwingConfirmationBars(SwingConfirmationBars); CONV.MaxErasPerRun(MaxErasPerRun); CONV.TrainRetryCooldownSeconds(TrainRetryCooldownSec); CONV.OOSSplit(OOSSplit); if(!UseDatabaseRanking) CONV.Weight(1); if(AIType == 3) CONV.Pattern_0(10); CONV.UseVolumes(EnableVolume); CONV.UseTime(EnableTime); CONV.UseATR(EnableATR); CONV.UseSwingContext(EnableSwingContext); CONV.UseNews(EnableNews); CONV.NewsFeatureWindowMinutes(NewsFeatureWindowMinutes); CONV.UseADCumulativeDelta(EnableADCumulativeDelta); CONV.UseADShorteningOfThrust(EnableADShorteningOfThrust); CONV.UseADWyckoffEventStream(EnableADWyckoffEventStream); CONV.UseADWyckoffFailedStructure(EnableADWyckoffFailedStructure); CONV.UseADWyckoffSignificantBarInversion(EnableADWyckoffSignificantBarInversion); CONV.AutoTuneIndicators(AutoTuneIndicators); CONV.IndicatorTuneTrials(IndicatorTuneTrials); } if(AIType == 2 || AIType == 3) { LSTM.InitialNeuronsCount(InitialNeurons); LSTM.OutputNeuronsCount(OutputNeuronsCount); LSTM.MinNeuronsCount(MinNeuronsCount); LSTM.HiddenLayersCount(HiddenLayersCount); LSTM.NeuronsReduction(NeuronsReduction); LSTM.HistoryBars(ind_Periods); LSTM.StudyPeriod(StudyPeriods); LSTM.StopTrainWR(MinWR); LSTM.MinDirectionalRecall(MinRecall); LSTM.ClassSampleWeight(ClassSampleWeight / 10.0); LSTM.FocalLossGamma(FocalLossGamma / 10.0); LSTM.SwingConfirmationBars(SwingConfirmationBars); LSTM.MaxErasPerRun(MaxErasPerRun); LSTM.TrainRetryCooldownSeconds(TrainRetryCooldownSec); LSTM.OOSSplit(OOSSplit); if(!UseDatabaseRanking) LSTM.Weight(1); if(AIType == 3) LSTM.Pattern_0(10); LSTM.UseVolumes(EnableVolume); LSTM.UseTime(EnableTime); LSTM.UseATR(EnableATR); LSTM.UseSwingContext(EnableSwingContext); LSTM.UseNews(EnableNews); LSTM.NewsFeatureWindowMinutes(NewsFeatureWindowMinutes); LSTM.UseADCumulativeDelta(EnableADCumulativeDelta); LSTM.UseADShorteningOfThrust(EnableADShorteningOfThrust); LSTM.UseADWyckoffEventStream(EnableADWyckoffEventStream); LSTM.UseADWyckoffFailedStructure(EnableADWyckoffFailedStructure); LSTM.UseADWyckoffSignificantBarInversion(EnableADWyckoffSignificantBarInversion); LSTM.AutoTuneIndicators(AutoTuneIndicators); LSTM.IndicatorTuneTrials(IndicatorTuneTrials); } // Add filters PrintVerbose("Initializing Signal filters..."); //--- added exactly once, before the DB retry loop below - these calls don't depend on DB success at //--- all (every pointer here was already validated non-NULL above), but living inside the loop body //--- meant a DB open/transaction failure that triggered a retry would re-run AddFilterToSignal() and //--- register the same filter pointer a second time in signal's CArrayObj; since that array frees its //--- elements on destruction, a duplicate entry means the same pointer gets deleted twice on shutdown //--- (heap corruption), which could easily explain instability across a later remove/re-add cycle. bool filtersAdded = true; filtersAdded &= (EnableITF ? AddFilterToSignal(signal, ITF) : true); filtersAdded &= (EnableSessionFilter ? AddFilterToSignal(signal, sessionFilter) : true); filtersAdded &= (g_marketDepthAvailable ? AddFilterToSignal(signal, marketDepth) : true); filtersAdded &= (EnableNewsFilter ? AddFilterToSignal(signal, newsFilter) : true); filtersAdded &= (EnableRiskGuard ? AddFilterToSignal(signal, riskGuard) : true); filtersAdded &= (EnablePAI ? AddFilterToSignal(signal, PAI) : true); filtersAdded &= (EnableCONV ? AddFilterToSignal(signal, CONV) : true); filtersAdded &= (EnableLSTM ? AddFilterToSignal(signal, LSTM) : true); if(!filtersAdded) { Print(functionName + ": Error loading filters"); return INIT_FAILED; } bool filterSuccess = false; for(int tries = 0; tries < maxRetryOnError; ++tries) { if(UseDatabaseRanking && !dbm.OpenDatabase()) { Print(functionName + ": Error opening database, retrying..."); RandomSleep(); continue; } if(UseDatabaseRanking && !dbm.BeginTransaction()) { Print(functionName + ": Error starting transaction, retrying..."); dbm.CloseDatabase(); // Ensure the database is closed before retry RandomSleep(); continue; } if(UseDatabaseRanking && (!dbm.CommitTransaction() || !dbm.CloseDatabase())) { Print(functionName + ": Error committing transaction or closing database, retrying..."); RandomSleep(); continue; } filterSuccess = true; break; // Success if all operations complete without error } if(!filterSuccess) { Print(functionName + ": Failed after all retries"); return INIT_FAILED; // Return failure if retries are exhausted } // Trailing logic PrintVerbose("Initializing Trailing..."); bool trailingInitialized = false; for(int tries = 0; !trailingInitialized && tries < maxRetryOnError; ++tries) { if(!InitializeTrailing()) { Print(functionName + ": Failed to initialize Trailing, retrying..."); RandomSleep(); } else { trailingInitialized = true; break; } } if(!trailingInitialized) { Print(functionName + ": Failed to initialize Trailing after retries"); return INIT_FAILED; } // Creation of money object bool moneyManagementInitialized = false; for(int tries = 0; !moneyManagementInitialized && tries < maxRetryOnError; ++tries) { if(!InitializeMoneyManagement()) { Print(functionName + ": Failed to initialize Money Management, retrying..."); RandomSleep(); } else { moneyManagementInitialized = true; break; } } if(!moneyManagementInitialized) { Print(functionName + ": Failed to initialize Money Management after retries"); return INIT_FAILED; } // Check all trading objects parameters PrintVerbose("Validating settings..."); bool settingsValidated = false; for(int tries = 0; !settingsValidated && tries < maxRetryOnError; ++tries) { if(!Expert.ValidationSettings()) { Print(functionName + ": Failed to validate settings, retrying..."); RandomSleep(); } else { settingsValidated = true; break; } } if(!settingsValidated) { Print(functionName + ": Failed to validate settings after retries"); return INIT_FAILED; } // Tuning of all necessary indicators PrintVerbose("Initializing Indicators..."); bool indicatorsInitialized = false; for(int tries = 0; !indicatorsInitialized && tries < maxRetryOnError; ++tries) { if(!Expert.InitIndicators()) { Print(functionName + ": Failed to initialize Indicators, retrying..."); RandomSleep(); } else { indicatorsInitialized = true; break; } } if(!indicatorsInitialized) { Print(functionName + ": Failed to initialize Indicators after retries"); return INIT_FAILED; } // setting timer: always on (short interval) so control-panel upkeep and other periodic checks run // on a fixed schedule regardless of tick activity - a quiet/after-hours symbol can go long stretches // without a single OnTick() call, and self-healing logic that only lives in OnTick() would never run // during that stretch. The (much less frequent) DB-ranking work is throttled separately inside // OnTimer() below rather than by EventSetTimer's own interval, since MQL5 only supports one timer // interval per program. if(UseDatabaseRanking) Expert.OnTimerProcess(true); bool timerSet = false; // 250ms (was 5s, via EventSetTimer's whole-second granularity): Train() only does up to // TRAIN_TIME_BUDGET_MS (80ms) of work per call, then yields back here - at a 5s interval that's // ~80ms busy / 5000ms elapsed, i.e. training sat idle ~98% of the time whenever it was being // driven by this timer (quiet symbol/no ticks) instead of real tick flow, which is why an era was // taking ~2 minutes wall-clock for well under 2s of actual compute. EventSetMillisecondTimer is // needed for sub-second resolution; EventSetTimer only accepts whole seconds. DB-ranking work // below is paced by its own g_lastDbRankingRun/DB_RANKING_INTERVAL_SECONDS check, not by this // interval, so it still only runs ~hourly regardless of this change. int timerInterval_ms = 250; for(int tries = 0; !timerSet && tries < maxRetryOnError; ++tries) { if(!EventSetMillisecondTimer(timerInterval_ms)) { Print(functionName + ": Error creating timer, retrying..."); RandomSleep(); } else { timerSet = true; break; } } if(!timerSet) { Print(functionName + ": Failed to set timer after retries"); return INIT_FAILED; } // Initialization successful PrintVerbose("Initialization successful"); if(!CreateControlPanel()) Print(functionName + ": WARNING - control panel failed to initialize; trading/training continue normally, " "but no GUI panel will be available for this run"); //--- one-time cleanup: an earlier build used Comment() plus a separate background rectangle object //--- that turned out to render ON TOP of the text (Comment() has no built-in background/styling //--- parameters at all) - delete any leftover from a prior run now that status text is a single //--- self-contained OBJ_LABEL (see SetStatusLabel()) with its own BGCOLOR fill instead if(ObjectFind(0, "WarriorCommentBG") >= 0) ObjectDelete(0, "WarriorCommentBG"); //--- required for CAppDialog's caption-bar drag to work at all - without it, the chart never delivers //--- CHARTEVENT_MOUSE_MOVE and the panel silently ignores drag attempts ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); g_lastAlgoTradingAllowed = (bool)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && (bool)MQLInfoInteger(MQL_TRADE_ALLOWED); if(!g_lastAlgoTradingAllowed) Print(functionName + ": WARNING - AlgoTrading is currently disabled (terminal or EA); signals will still train but no orders will be sent until it is re-enabled"); return INIT_SUCCEEDED; } // Called before optimization/backtesting in the strategy tester int OnTesterInit() { IsBacktesting = true; return(INIT_SUCCEEDED); } // Called after EA optimization in the strategy tester void OnTesterDeinit() { dbm.Deinit(); IsBacktesting = false; OnDeinit(0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Maps a terminal deinit reason code to a short label for logging, | //| so operators can tell a routine recompile/parameter change apart | //| from a terminal shutdown or the EA actually being removed. | //+------------------------------------------------------------------+ string DeinitReasonToString(const int reason) { switch(reason) { case REASON_PROGRAM: return "EA stopped by ExpertRemove()/self"; case REASON_REMOVE: return "EA removed from chart"; case REASON_RECOMPILE: return "EA recompiled"; case REASON_CHARTCHANGE: return "chart symbol/period changed"; case REASON_CHARTCLOSE: return "chart closed"; case REASON_PARAMETERS: return "input parameters changed"; case REASON_ACCOUNT: return "account changed"; case REASON_TEMPLATE: return "template applied"; case REASON_INITFAILED: return "OnInit() failed"; case REASON_CLOSE: return "terminal closed"; default: return "unknown (" + IntegerToString(reason) + ")"; } } void OnDeinit(const int reason) { string reasonStr = DeinitReasonToString(reason); Print(__FUNCTION__ + ": shutting down - reason: " + reasonStr); //--- matches the MarketBookAdd() in CheckMarketDepthAvailability() (OnInit) - only subscribed at //--- all when g_marketDepthAvailable ended up true for this run. if(g_marketDepthAvailable) MarketBookRelease(_Symbol); //--- persist every active AI signal's current in-memory weights/state before anything is torn down, //--- so a terminal restart, chart re-add, or template swap resumes from here rather than from the //--- last fully-completed training era only. Best-effort: a save failure is logged but must not //--- block the rest of shutdown. for(int i = 0; i < g_aiSignalCount; i++) { if(!g_aiSignals[i].PersistOnShutdown()) Print(__FUNCTION__ + ": WARNING - failed to persist weights for signal index " + IntegerToString(i) + " on shutdown (reason: " + reasonStr + ")"); } g_aiSignalCount = 0; EventKillTimer(); dbm.Deinit(); //--- destroy the control panel's own UI BEFORE Expert.Deinit() below - each active AI signal's //--- destructor (~CExpertSignalAIBase) calls PurgeChart(), which does ObjectsDeleteAll(0) and force- //--- deletes every object on the chart, including the panel's buttons/labels. Tearing the panel down //--- first lets CAppDialog remove its own objects cleanly instead of Destroy() running against //--- objects that were already deleted out from under it. ExtPanel.Destroy(reason); Expert.Deinit(); //--- ObjectsDeleteAll(0) above already deleted the status label along with every other chart object, //--- but this explicit clear is kept as a belt-and-suspenders guard in case that call path changes - //--- without it, whatever status line (e.g. "Warrior EA: initializing..." or the last training- //--- progress line) was showing could stay on the chart forever after the EA is removed. ClearStatusLabel(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //--- Expert.OnTimer() does the (comparatively expensive) DB-ranking work, originally paced by its //--- own 1-hour EventSetTimer() interval; now that the timer itself runs every 5s (see OnInit), pace //--- that work manually instead so it still only actually runs about once an hour. #define DB_RANKING_INTERVAL_SECONDS 3600 datetime g_lastDbRankingRun = 0; void OnTimer() { //--- keeps training progressing on wall-clock time even with no ticks at all (market closed) - //--- OnTickHandler's own scheduling only ever runs when a tick actually arrives for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].PollTraining(); if(!UseDatabaseRanking) return; datetime now = TimeCurrent(); if(g_lastDbRankingRun != 0 && now - g_lastDbRankingRun < DB_RANKING_INTERVAL_SECONDS) return; g_lastDbRankingRun = now; Expert.OnTimer(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CheckAlgoTradingState(void) { bool allowed = (bool)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && (bool)MQLInfoInteger(MQL_TRADE_ALLOWED); if(allowed != g_lastAlgoTradingAllowed) { if(allowed) Print(__FUNCTION__ + ": AlgoTrading re-enabled - order placement resumed (training/signals were unaffected while disabled)"); else Print(__FUNCTION__ + ": AlgoTrading disabled (terminal toggle off, or EA's own permission revoked) - no new orders will be sent until re-enabled; training/signal generation continues unaffected"); g_lastAlgoTradingAllowed = allowed; } } void AutosaveWeightsIfDue(void) { datetime now = TimeCurrent(); if(now - g_lastAutosave < AUTOSAVE_INTERVAL_SECONDS) return; g_lastAutosave = now; for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].SaveWeightsNow(); } void OnTick() { CheckAlgoTradingState(); AutosaveWeightsIfDue(); Expert.OnTick(); //--- new arrows are always created visible; if signals are currently hidden, re-hide //--- any that were drawn this tick (cheap - only runs while the toggle is in the "hidden" state) if(!g_signalsVisible) ApplySignalsVisibility(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- canonical CAppDialog usage (Controls\Dialog.mqh): forward every event to the dialog first, since //--- that's what drives its own click/drag hit-testing (via CHARTEVENT_MOUSE_MOVE) as well as our //--- buttons' EVENT_MAP handlers (see ControlPanel.mqh) - then pick up whatever button action, if any, //--- that just recorded. ExtPanel.ChartEvent(id, lparam, dparam, sparam); HandleControlPanelAction(ExtPanel.ConsumeAction()); Expert.OnChartEvent(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool AddFilterToSignal(CExpertSignalCustom * signal, CExpertSignalCustom * filter) { if(filter == NULL) { Print(__FUNCTION__ + "Error creating filters"); return false; } return signal.AddFilter(filter); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool InitializeSignal(CExpertSignalCustom * signal_obj) { if(signal_obj == NULL) { Print(__FUNCTION__ + ": error creating signal"); return false; } Expert.InitSignal(signal_obj); signal_obj.Entry_Multiplier(Entry_Multiplier); signal_obj.Expiration(Signal_Expiration); signal_obj.Periods(ind_Periods); signal_obj.SLAtrMultiplier(SL_Atr_Multiplier); signal_obj.MinRiskRewardRatio(Min_Risk_Reward_Ratio); signal_obj.UseAISLTP(SLTP_Source == SLTP_AI_CONFIDENCE); signal_obj.ConfidenceSource((int)Confidence_Source); signal_obj.UseAIExit(Use_AI_Exit); signal_obj.AIExitThreshold((double)AI_Exit_Threshold / 100.0); if(AIType == 3) { signal_obj.ThresholdOpen(20); signal_obj.ThresholdClose(20); } else { signal_obj.ThresholdOpen(10); signal_obj.ThresholdClose(10); } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ // Initialize Trailing bool InitializeTrailing() { if(TrailingStrategy == TRAILING_STRATEGY_NONE) { // No trailing strategy selected return true; } else if(TrailingStrategy == TRAILING_STRATEGY_ATR_x1 || TrailingStrategy == TRAILING_STRATEGY_ATR_x2 || TrailingStrategy == TRAILING_STRATEGY_ATR_x3) { // ATR Trailing Strategy double multiplier = 0; if(TrailingStrategy == TRAILING_STRATEGY_ATR_x1) multiplier = 1; else if(TrailingStrategy == TRAILING_STRATEGY_ATR_x2) multiplier = 2; else if(TrailingStrategy == TRAILING_STRATEGY_ATR_x3) multiplier = 3; CTrailingATR *trailing = new CTrailingATR; if(trailing == NULL) { Print(__FUNCTION__ + ": error creating trailing"); return false; } // Set ATR Multiplier trailing.Multiplier(multiplier); if(!Expert.InitTrailing(trailing)) { Print(__FUNCTION__ + ": error initializing trailing"); return false; } } // Add more trailing strategies if needed return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool InitializeMoneyManagement() { string functionName = __FUNCTION__; if(MM_STRATEGY == FIXED_RISK) { CMoneyFixedRisk *money = new CMoneyFixedRisk; if(money == NULL) { Print(functionName + ": error creating money"); return false; } if(!Expert.InitMoney(money)) { Print(functionName + ": error initializing money"); return false; } money.Percent(Money_Risk_Percent); } else if(MM_STRATEGY == FIXED_LOT) { CMoneyFixedLot *money = new CMoneyFixedLot; if(money == NULL) { Print(functionName + ": error creating money"); return false; } if(!Expert.InitMoney(money)) { Print(functionName + ": error initializing money"); return false; } money.Lots(Money_FixLot_Lots); } else if(MM_STRATEGY == INTELLIGENT) { CMoneyIntelligent *money = new CMoneyIntelligent; if(money == NULL) { Print(functionName + ": error creating money"); return false; } if(!Expert.InitMoney(money)) { Print(functionName + ": error initializing money"); return false; } money.Percent(Money_Risk_Percent); money.UseAIConfidenceLotSizing(Use_AI_Lot_Sizing); money.ConfidenceSource((int)Confidence_Source); } // Add more money management strategies if needed return true; } //+------------------------------------------------------------------+