//+------------------------------------------------------------------+ //| Warrior_EA | //| AnimateDread | //| | //+------------------------------------------------------------------+ //--- MQL5 Market rule IV: "Products must not contain calls to any DLL". Uncomment the next line before //--- compiling the Market-submission .ex5 - it compiles the WarriorDML.dll/WarriorCPU.dll #import blocks //--- and every call into them out of AI\NeuronDirectML.mqh entirely (see that file), leaving OpenCL then //--- plain-MQL5 CPU (AI\NeuronCPU.mqh) as the only compute tiers - zero DLL calls in the resulting //--- binary. Leave commented out for the private/prop-firm deployment build, which keeps the DirectML/ //--- CPU-DLL fallback tier for hosts without OpenCL. //#define WARRIOR_MARKET_BUILD //--- MARKET build only: embed every custom indicator this EA calls via CiCustom so one self-contained //--- .ex5 ships to the Market. Paths embed straight from the terminal's standard MQL5\Indicators\ folder //--- (leading backslash = MQL5 root) - the SAME place the indicators already live for the private build's //--- runtime load, so you maintain ONE copy. The indicators must be compiled x64/non-AVX (CLI compile, or //--- disable AVX in the compiler options) or the embed fails with error 414. IMPORTANT: the leading- //--- backslash root only resolves when the compiler knows the MQL5 tree - build the Market .ex5 from //--- INSIDE the terminal (MQL5\Experts\...) or via the MetaEditor CLI with an include path. Compiling //--- from an external folder in the GUI makes the root fall back to the MQL5\Files\ sandbox and the embed //--- can't find the files. The private build uses bare names (no resources), so it builds from anywhere. //--- The "::" reference paths are built by WARRIOR_CI() in Variables\IndicatorResources.mqh - keep in sync. #ifdef WARRIOR_MARKET_BUILD #resource "\\Indicators\\ADCumulativeDelta.ex5" #resource "\\Indicators\\ADShorteningOfThrust.ex5" #resource "\\Indicators\\ADWyckoffEventStream.ex5" #resource "\\Indicators\\ADWyckoffFailedStructure.ex5" #resource "\\Indicators\\ADWyckoffSignificantBarInversion.ex5" #resource "\\Indicators\\ADZigZag.ex5" #resource "\\Indicators\\ADMovingAverage.ex5" #endif //--- Inputs FIRST so the EA's own grouped inputs lead the Inputs tab. Safe because Inputs.mqh depends //--- only on Enumerations\InputEnums.mqh (which carries a guarded ENUM_OPTIMIZATION copy) - no AI //--- header needed. The AI\Network.mqh optimizer/CPU inputs are declared in that header (parsed later) //--- and so render at the END, under Inputs.mqh's "NN Optimizer / Performance" divider. #include "Variables\Inputs.mqh" //--- database classes #include "Database\DatabaseManager.mqh" #include "Database\TradeJournalManager.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" //--- Variables #include "Variables\Variables.mqh" //--- Control panel GUI (standard MQL5 Controls library) #include "Panel\ControlPanel.mqh" //+------------------------------------------------------------------+ //| The CustomIndicators\*.mq5 files (ADCumulativeDelta, | //| ADShorteningOfThrust, ADWyckoffEventStream, | //| ADWyckoffFailedStructure, ADWyckoffSignificantBarInversion, | //| ADZigZag, and the unified ADMovingAverage) are loaded via | //| CiCustom/IND_CUSTOM (see ExpertSignalAIBase.mqh / SignalMA.mqh). | //| Name resolution is build-conditional - see | //| Variables\IndicatorResources.mqh (WARRIOR_CI): | //| - MARKET build (WARRIOR_MARKET_BUILD): each indicator is EMBEDDED | //| as a #resource (block above) from the standard MQL5\Indicators\ | //| folder and called directly via its "::" resource path, so one | //| self-contained .ex5 ships to the Market with no external files. | //| A resource indicator is NOT extracted to disk. Same single copy | //| the private build already loads at runtime - compile x64/non-AVX | //| (CLI, or disable AVX) or the embed fails w/ err 414, and build | //| the Market .ex5 from inside the terminal tree (or CLI /inc) so | //| the leading-backslash MQL5 root resolves (an external GUI compile| //| falls back to the MQL5\Files\ sandbox and can't find them). | //| - Private build: referenced by bare name and loaded from | //| \Indicators\ at call time (faster than unpacking a | //| resource; dev terminals already have them deployed). Init*() | //| fails CiCustom::Create() with a clear log line if a matching | //| .ex5 isn't present. | //+------------------------------------------------------------------+ // CExpertCustom Expert; CDatabaseManager dbm(); CTradeJournalManager journal; //+------------------------------------------------------------------+ //| Pointers to whichever AI signal instances this run actually | //| created (per AIType - MLP/CONV/LSTM/HYBRID), 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 //--- Printed at OnInit so tester logs prove which binary is actually running. #define WARRIOR_BUILD_TAG "hotreload-arrows-v1" CExpertSignalAIBase *g_aiSignals[MAX_AI_SIGNALS]; int g_aiSignalCount = 0; int ResolveDenseTopologyLayers(AI_CHOICE aiType) { switch(aiType) { case MLP_4L: return 4; case MLP_3L: return 3; case CONV_2L: case LSTM_2L: case HYBRID_2L: return 2; default: return 3; } } 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; #define SIGNAL_VISIBILITY_STATE_SUFFIX ".sigvis" string SignalsVisibilityStateFile(void) { return eaName + "_" + Symbol() + "_" + IntegerToString(Period()) + SIGNAL_VISIBILITY_STATE_SUFFIX; } bool LoadSignalsVisibilityState(void) { if(MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_FORWARD)) return false; string stateFile = SignalsVisibilityStateFile(); if(!FileIsExist(stateFile, FILE_COMMON)) return false; int handle = FileOpen(stateFile, FILE_COMMON | FILE_BIN | FILE_READ | FILE_SHARE_READ | FILE_SHARE_WRITE); if(handle == INVALID_HANDLE) return false; int storedVisible = FileReadInteger(handle); FileClose(handle); g_signalsVisible = (storedVisible != 0); return true; } bool SaveSignalsVisibilityState(void) { if(MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_FORWARD)) return true; string stateFile = SignalsVisibilityStateFile(); int handle = FileOpen(stateFile, FILE_COMMON | FILE_BIN | FILE_WRITE | FILE_SHARE_READ | FILE_SHARE_WRITE); if(handle == INVALID_HANDLE) { Print(__FUNCTION__ + ": failed to open " + stateFile + " for write, error " + IntegerToString(GetLastError())); return false; } FileWriteInteger(handle, g_signalsVisible ? 1 : 0, INT_VALUE); FileClose(handle); return true; } //--- true while Show Signals has queued a rescan on one or more g_aiSignals and is waiting for all of //--- them to finish (see ToggleSignalsVisibility/FinalizeSignalsRescanIfDone) - each signal's own rescan //--- is now chunked across PollTraining's timer slices (CExpertSignalAIBase::AdvanceChartSignalRescan) //--- instead of blocking the button click, so visibility can only be (re)applied and the "shown" Alert //--- fired once every instance's RescanPending() has cleared. bool g_signalsRescanPending = false; //--- 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. //--- Fires on NEW BAR CLOSE, not a fixed wall-clock interval (a prior 300s timer saved up to ~12x more //--- often than an H1 chart ever has new state to persist - nothing changes between bar closes, since //--- both training/era-end saves and OnlineLearnStep's continual-learning updates are themselves bar- //--- driven). Every save atomically renames the SAME shared FILE_COMMON model file a Strategy Tester //--- backtest may be concurrently reading via FileCopy (see CopyFileWithRetry's declaration comment) - //--- cutting write frequency to the real update cadence directly shrinks that collision window instead //--- of just papering over it with more retries. datetime g_lastAutosaveBarTime = 0; //--- last observed AllTrainingDeployed() value, so OnTimer() can spot training deploying itself (plateau //--- ladder / era cap) and resync the panel's button labels exactly once on the transition bool g_lastDeployedState = false; //--- 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; } //--- "deployed" = every active signal has finalised a model and is running live inference rather than //--- training. This is the state that makes Pause/Stop meaningless (there is no run to pause or stop), //--- so it drives BOTH the Deploy button's own label and the n/a labels on those two - see //--- RefreshControlPanelLabels(). bool AllTrainingDeployed(void) { if(g_aiSignalCount == 0) return false; for(int i = 0; i < g_aiSignalCount; i++) if(!g_aiSignals[i].TrainingComplete()) return false; return true; } //--- true only while at least one signal is still trainable AND has never checkpointed an era that //--- cleared the per-class recall floor - i.e. deploying right now would ship a model that ignores Buy //--- or Sell. Same bar the plateau ladder's automatic deploy refuses to cross on its own. bool AnyDeployWouldSkipRecallFloor(void) { for(int i = 0; i < g_aiSignalCount; i++) if(!g_aiSignals[i].TrainingComplete() && !g_aiSignals[i].HasRecallPassingCheckpoint()) return true; return false; } 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; //--- Hide->Show is also the operator's manual "these arrows look stale" refresh: rescan each deployed //--- model against recent history BEFORE re-showing, so Show Signals reveals a fresh set instead of //--- just re-exposing whatever old render the .arrows sidecar happened to hold (see //--- CExpertSignalAIBase::StartChartSignalRescan/AdvanceChartSignalRescan). Skipped on Hide - nothing //--- to refresh when hiding, so that path stays instant. //--- Rescans are QUEUED here, not run to completion - each is a real per-bar inference pass over up to //--- SIGNAL_RESCAN_LOOKBACK_BARS bars, chunked across PollTraining's timer slices so the click handler //--- never blocks. Visibility is applied and the "shown" Alert fires later, once //--- FinalizeSignalsRescanIfDone() sees every instance's RescanPending() clear (called from OnTimer). if(g_signalsVisible) { bool anyQueued = false; for(int i = 0; i < g_aiSignalCount; i++) if(g_aiSignals[i].StartChartSignalRescan()) anyQueued = true; g_signalsRescanPending = anyQueued; if(anyQueued) { //--- Immediate feedback that the click registered - the real "Hide Signals" label only lands //--- once FinalizeSignalsRescanIfDone() runs RefreshControlPanelLabels() below. ExtPanel.SetSignalsText("Scanning..."); return; // ApplySignalsVisibility()/labels/Alert deferred to FinalizeSignalsRescanIfDone() } } ApplySignalsVisibility(); SaveSignalsVisibilityState(); } //--- Called every OnTimer tick while g_signalsRescanPending: applies visibility and fires the "shown" //--- Alert only once every queued rescan (see ToggleSignalsVisibility) has finished, since the arrows //--- being toggled visible don't exist yet until each instance's AdvanceChartSignalRescan completes. void FinalizeSignalsRescanIfDone(void) { if(!g_signalsRescanPending) return; for(int i = 0; i < g_aiSignalCount; i++) if(g_aiSignals[i].RescanPending()) return; // at least one instance still scanning - check again next tick g_signalsRescanPending = false; ApplySignalsVisibility(); RefreshControlPanelLabels(); Alert("Warrior EA: signal arrows shown"); } //--- 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); //--- The four training buttons describe ONE state machine, so their labels are derived together //--- rather than independently - otherwise the panel offers actions that silently do nothing. //--- Deployed is the state that matters most: with a finalised model there is no run left to pause or //--- stop, and the only meaningful move is to put it back into training (which is what Deploy toggles //--- to). See CExpertSignalAIBase::DeployNow/RetrainDeployed. bool deployed = !noAI && AllTrainingDeployed(); ExtPanel.SetPauseText(noAI ? "Pause Training (n/a)" : deployed ? "Pause Training (deployed)" : (AllTrainingPaused() ? "Resume Training" : "Pause Training")); ExtPanel.SetStopText(noAI ? "Stop Training (n/a)" : deployed ? "Stop Training (deployed)" : (AllTrainingStopped() ? "Start Training" : "Stop Training")); ExtPanel.SetDeployText(noAI ? "Deploy Model (n/a)" : (deployed ? "Retrain Model" : "Deploy Model")); ChartRedraw(0); } //--- CAppDialog is user-draggable; a drag near an edge followed by shrinking the chart (or dragging //--- past the visible area) can leave it partially or fully off-screen with no way to grab it back. //--- Clamps it back inside the current chart bounds whenever the chart is resized/scrolled. void ClampControlPanelToChart(void) { long chartWidth = ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); long chartHeight = ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(chartWidth <= 0 || chartHeight <= 0) return; int x = ExtPanel.Left(); int y = ExtPanel.Top(); int w = ExtPanel.Width(); int h = ExtPanel.Height(); int maxX = (int)chartWidth - w; int maxY = (int)chartHeight - h; int clampedX = (maxX < 0) ? 0 : MathMin(MathMax(x, 0), maxX); int clampedY = (maxY < 0) ? 0 : MathMin(MathMax(y, 0), maxY); if(clampedX != x || clampedY != y) ExtPanel.Move(clampedX, clampedY); } //--- 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(); ClampControlPanelToChart(); //--- seed the transition watcher (see OnTimer) so a model that is ALREADY deployed at attach time //--- doesn't register as a fresh transition on the first timer tick g_lastDeployedState = AllTrainingDeployed(); RefreshControlPanelLabels(); return true; } //--- Blocking on purpose, unlike the Alert() calls below: this only ever runs in direct response to the //--- trader clicking a destructive (red) panel button, so pausing for their yes/no is expected UX, not //--- an unwanted stall of live trade management - MessageBox() briefly blocks the chart's UI thread, //--- which is exactly what a "are you sure?" gate needs. bool ConfirmDestructiveAction(string message) { return MessageBox(message + "\n\nThis action cannot be undone.", "Warrior EA - Confirm", MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2) == IDYES; } //--- 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(); //--- If a rescan got queued (Show Signals with a deployed model to re-infer from), the label //--- refresh + Alert are deferred to FinalizeSignalsRescanIfDone() - firing "shown" here would lie //--- about arrows that don't exist on the chart yet. Hide, or a Show with nothing to rescan, //--- completes synchronously so it's safe to report right away. if(!g_signalsRescanPending) { RefreshControlPanelLabels(); Alert("Warrior EA: signal arrows " + (g_signalsVisible ? "shown" : "hidden")); } break; case CP_ACTION_TOGGLE_PAUSE: { //--- A deployed model has no training run to pause - say so instead of silently doing nothing. if(AllTrainingDeployed()) { Alert("Warrior EA: the model is deployed - there is no training run to pause.\nUse \"Retrain Model\" first if you want to train it further."); break; } bool pause = !AllTrainingPaused(); for(int i = 0; i < g_aiSignalCount; i++) if(pause) g_aiSignals[i].PauseTraining(); else g_aiSignals[i].ResumeTraining(); RefreshControlPanelLabels(); Alert("Warrior EA: training " + (pause ? "paused" : "resumed")); break; } case CP_ACTION_TOGGLE_STOP: { //--- Same as Pause: Stop/Start operate on a training run, and a deployed model isn't one. //--- Routing this to RetrainDeployed() instead would silently do the Deploy button's job. if(AllTrainingDeployed()) { Alert("Warrior EA: the model is deployed and already running live inference, not training.\nUse \"Retrain Model\" to put it back into training."); break; } bool doStop = !AllTrainingStopped(); for(int i = 0; i < g_aiSignalCount; i++) if(doStop) g_aiSignals[i].StopTraining(); else g_aiSignals[i].StartTraining(); RefreshControlPanelLabels(); Alert("Warrior EA: training " + (doStop ? "stopped" : "restarted")); break; } case CP_ACTION_TOGGLE_DEPLOY: { if(g_aiSignalCount == 0) { Alert("Warrior EA: no AI signal is active - nothing to deploy (set the AI algorithm input to something other than Disabled)."); break; } //--- RETRAIN direction: put the finalised model back into training, continuing from its own //--- weights. Reversible, non-destructive (the weights on disk stay), so no confirmation. if(AllTrainingDeployed()) { for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].RetrainDeployed(); RefreshControlPanelLabels(); Alert("Warrior EA: retraining the deployed model - it continues from its current weights.\nUse \"Delete & Reset Weights\" instead to start from scratch."); break; } //--- DEPLOY direction. The plateau ladder refuses to auto-deploy a model that never cleared the //--- per-class recall floor (it would be one that ignores Buy or Sell); a manual deploy is the //--- operator's call, but they should make it knowingly - so this is the one case that asks. if(AnyDeployWouldSkipRecallFloor() && !ConfirmDestructiveAction("No training era has cleared the per-class recall floor yet, so this model may " "be ignoring Buy or Sell entirely.\n\nDeploy it anyway as the final model?")) { Alert("Warrior EA: deploy cancelled - training continues"); break; } int deployed = 0; for(int i = 0; i < g_aiSignalCount; i++) if(g_aiSignals[i].DeployNow()) deployed++; RefreshControlPanelLabels(); if(deployed == 0) Alert("Warrior EA: could not deploy - the AI signal is not initialised yet."); else Alert("Warrior EA: model deployed (" + IntegerToString(deployed) + " signal(s)).\nTraining stopped; it now runs live inference. Click \"Retrain Model\" to train it further."); break; } case CP_ACTION_SAVE: for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].SaveWeightsNow(); Alert("Warrior EA: weights saved"); break; case CP_ACTION_LOAD: for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].LoadWeightsNow(); //--- A reload carries the saved file's own "deployed" flag, so it can flip the whole training //--- state machine (loading a finalised model makes Deploy read "Retrain Model", and Pause/Stop //--- read "(deployed)"). Resync the labels or the panel would keep offering stale actions. RefreshControlPanelLabels(); Alert("Warrior EA: weights reloaded from disk"); break; case CP_ACTION_RESET: if(!ConfirmDestructiveAction("Delete the saved AI weights and restart training from era 0?")) { Alert("Warrior EA: weights reset cancelled"); break; } for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].ResetWeights(); RefreshControlPanelLabels(); Alert("Warrior EA: weights reset - training restarts from era 0"); break; case CP_ACTION_REPORT: { if(!UseDatabaseRanking) { Print(__FUNCTION__ + ": trade journal report requires \"Weight filters by DB win-rate\" (UseDatabaseRanking) to be enabled"); Alert("Warrior EA: trade journal report requires \"Weight filters by DB win-rate\" to be enabled"); break; } string reportPath, reportError; if(journal.GenerateReport(reportPath, reportError)) { Print(__FUNCTION__ + ": trade journal report ready - " + reportPath); Alert("Warrior EA: trade journal report exported - see the Experts log for the file path"); } else { Print(__FUNCTION__ + ": could not generate trade journal report - " + reportError); Alert("Warrior EA: could not export trade journal report - " + reportError); } break; } //--- separate from CP_ACTION_RESET on purpose: resetting AI weights (a routine, frequent action //--- while tuning) must never cost the trader their accumulated pattern-confidence/trade-journal //--- history, and vice versa - these are two independent "start fresh" decisions now. case CP_ACTION_RESET_DB: if(UseDatabaseRanking) { if(!ConfirmDestructiveAction("Delete the trade-journal and pattern-confidence database?")) { Alert("Warrior EA: database reset cancelled"); break; } dbm.ResetDatabase(); Alert("Warrior EA: database reset"); } else { Print(__FUNCTION__ + ": database reset requires \"Weight filters by DB win-rate\" (UseDatabaseRanking) to be enabled"); Alert("Warrior EA: database reset requires \"Weight filters by DB win-rate\" to be enabled"); } 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) } //+------------------------------------------------------------------+ //| Config fingerprint appended to the pattern-confidence/trade- | //| journal database's filename, so a topology or feature-set change | //| that would produce a differently-shaped/behaving model gets its | //| own database instead of silently reusing pattern-weight/journal | //| history that no longer matches what's actually trading now. Same | //| FNV-1a scheme CExpertSignalAIBase uses for its own .nnw cache-key | //| fingerprint (Expert\ExpertSignalAIBase.mqh), computed here from | //| the raw inputs directly since the DB opens before any AI signal | //| object exists to ask. | //+------------------------------------------------------------------+ string ComputeDbConfigFingerprint() { //--- InitialNeurons is gone from this key: the first-layer width is now DERIVED from the input width //--- and the study period (CExpertSignalAIBase::ComputeFirstLayerWidth), not chosen, and every //--- determinant of it that IS a user choice is already hashed here. Dropping the term re-keys //--- existing databases once - which is correct, because a model an order of magnitude smaller //--- genuinely is a different config and should not inherit the old one's pattern win-rate history. string fp = StringFormat("%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d", (int)AIType, (int)OutputNeuronsCount, (int)TrainingOptimizer, (int)ind_Periods, (int)StudyPeriods, EnableVolume, EnableTime, EnableATR, EnableMA, (int)PeriodMA, (int)MA_Type, EnableRSI, (int)PeriodRSI, EnableSwingContext, EnableNews, EnableADCumulativeDelta, EnableADShorteningOfThrust, EnableADWyckoffEventStream) + StringFormat("|%d|%d|%d|%d|%d|%d", EnableADWyckoffFailedStructure, EnableADWyckoffSignificantBarInversion, (int)LstmHiddenSize, (int)ConvFilterCount, EnableMAFeature, EnableRSIFeature); //--- MACD/Ichimoku, appended ONLY WHEN ENABLED - same rule, and same reason, as the matching block in //--- CExpertSignalAIBase::BuildConfigFingerprint(): appending unconditionally would re-key every existing //--- database the moment this shipped, orphaning the accumulated per-pattern win-rate history of configs //--- that use neither. A run that enables either one genuinely IS a different config (new voting signals //--- mean new pattern rows; new input features mean a differently-shaped model) and gets its own DB. if(EnableMACD || EnableMACDFeature) fp += StringFormat("|MACD:%d:%d:%d:%d:%d", EnableMACD, EnableMACDFeature, (int)MACD_PeriodFast, (int)MACD_PeriodSlow, (int)MACD_PeriodSignal); if(EnableIchimoku || EnableIchimokuFeature) fp += StringFormat("|ICHI:%d:%d:%d:%d:%d", EnableIchimoku, EnableIchimokuFeature, (int)Ichimoku_PeriodTenkan, (int)Ichimoku_PeriodKijun, (int)Ichimoku_PeriodSenkou); uint fpHash = 2166136261; int fpLen = StringLen(fp); for(int fpi = 0; fpi < fpLen; fpi++) { fpHash ^= (uint)StringGetCharacter(fp, fpi); fpHash *= 16777619; } return StringFormat("%08x", fpHash); } //+------------------------------------------------------------------+ //| 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)) { Print("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); Print("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 (MLP/CONV/LSTM/HYBRID - 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..."); Print(__FUNCTION__ + ": build tag " + WARRIOR_BUILD_TAG); PrintFormat("%s: trade settings snapshot - AIType=%d Entry_Multiplier=%d SL_Mode=%d TP_Mode=%d Min_Risk_Reward_Ratio=%g TrailingStrategy=%d MM_STRATEGY=%d", __FUNCTION__, (int)AIType, (int)Entry_Multiplier, (int)SL_Mode, (int)TP_Mode, (double)Min_Risk_Reward_Ratio, (int)TrailingStrategy, (int)MM_STRATEGY); LoadSignalsVisibilityState(); 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"}; //--- fingerprinted so a topology/feature-set change that would produce a differently-shaped or //--- differently-behaving model gets its own database, instead of silently mixing pattern-weight/ //--- trade-journal history from an incompatible prior config into the one now trading. const string dbName = Symbol() + "_" + IntegerToString(Period()) + "_" + ComputeDbConfigFingerprint() + ".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; } //--- Trade journal shares UseDatabaseRanking's DB connection/lifecycle rather than adding a //--- second always-on toggle - see Database\TradeJournalManager.mqh's class comment. if(!journal.Init(GetPointer(dbm), Expert_MagicNumber)) { Print(functionName + ": Failed to initialize trade journal table"); return INIT_FAILED; } } //+------------------------------------------------------------------+ //| AIType selects which single AI architecture this run trades/ | //| trains: MLP, CONV, LSTM, or HYBRID. HYBRID is the fused stacked | //| model, not a 2-of-3 ensemble. | //| - 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. | //| HYBRID uses the same AI voting path as the individual models, but | //| inside one stacked topology: Conv+Pool front-end, then LSTM, then | //| the common dense taper. Concurrency model: MQL5 is | //| single-threaded per chart - OnTick()/OnTimer() never run | //| re-entrantly, so AI signal evaluation never races 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 2 alive at once | //| per AI signal: live+shadow net) 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 | //| to a fixed small thread count (CPU_THREADS_PER_NETWORK, | //| AI\Network.mqh) rather than to a share of the machine, even though | //| several pools can be alive at once - see that constant's comment. | //| Within a chart that is free: MQL5's single execution thread per | //| chart means only one pool is EVER actively computing at a time. | //| Across charts it is what keeps N charts from each claiming the | //| whole box, without depending on how many are attached yet. | //| Memory ownership: AI signal(s) 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 -> the AI signal object, | //| exactly once - g_aiSignals[] is never delete'd directly (grep | //| 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 == MLP_3L || AIType == MLP_4L); EnableCONV = (AIType == CONV_2L); EnableLSTM = (AIType == LSTM_2L); EnableHYBRID = (AIType == HYBRID_2L); // Creating instances of signals CSignalPAI *PAI = CreateSignalWithRetry(maxRetryOnError, EnablePAI); CSignalCONV *CONV = CreateSignalWithRetry(maxRetryOnError, EnableCONV); CSignalLSTM *LSTM = CreateSignalWithRetry(maxRetryOnError, EnableLSTM); CSignalHYBRID *HYBRID = CreateSignalWithRetry(maxRetryOnError, EnableHYBRID); //--- 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); if(EnableHYBRID && HYBRID != NULL) RegisterAISignal(HYBRID); CSignalMA *MA = CreateSignalWithRetry(maxRetryOnError, EnableMA); CSignalRSI *RSI = CreateSignalWithRetry(maxRetryOnError, EnableRSI); CSignalMACD *MACD = CreateSignalWithRetry(maxRetryOnError, EnableMACD); CSignalIchimoku *Ichimoku = CreateSignalWithRetry(maxRetryOnError, EnableIchimoku); 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) || (EnableMA && MA == NULL) || (EnableRSI && RSI == NULL) || (EnableMACD && MACD == NULL) || (EnableIchimoku && Ichimoku == NULL) || (EnablePAI && PAI == NULL) || (EnableCONV && CONV == NULL) || (EnableLSTM && LSTM == NULL) || (EnableHYBRID && HYBRID == 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(EnableMA) { MA.PeriodMA(PeriodMA); MA.Method(MA_Type); if(!UseDatabaseRanking) MA.Weight(1); } if(EnableRSI) { RSI.PeriodRSI(PeriodRSI); if(!UseDatabaseRanking) RSI.Weight(1); } if(EnableMACD) { MACD.PeriodFast(MACD_PeriodFast); MACD.PeriodSlow(MACD_PeriodSlow); MACD.PeriodSignal(MACD_PeriodSignal); if(!UseDatabaseRanking) MACD.Weight(1); } if(EnableIchimoku) { Ichimoku.PeriodTenkan(Ichimoku_PeriodTenkan); Ichimoku.PeriodKijun(Ichimoku_PeriodKijun); Ichimoku.PeriodSenkou(Ichimoku_PeriodSenkou); if(!UseDatabaseRanking) Ichimoku.Weight(1); } 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(EnablePAI) { int denseLayers = ResolveDenseTopologyLayers(AIType); PAI.OutputNeuronsCount(OutputNeuronsCount); PAI.HiddenLayersCount(denseLayers); PAI.HistoryBars(ind_Periods); PAI.StudyPeriod(StudyPeriods); PAI.MinDirectionalRecall(MinRecall); PAI.LogitPriorStrength(AILogitPriorStrength / 100.0); PAI.UseLogitAdjustedLoss(EnableLogitAdjustedLoss); PAI.LogitAdjustTau(LogitAdjustTau / 100.0); PAI.OversampleParity(OversampleParity / 100.0); PAI.SignalClusterWindow(SignalClusterWindow); PAI.FocalLossGamma(FocalLossGamma / 10.0); PAI.EnableMinorityReplay(EnableMinorityReplay); PAI.ConstrainReplay(ConstrainReplay); PAI.FreezePriorCalibration(FreezePriorCalibration); PAI.UseStaticPrior(UseStaticPrior); PAI.SwingConfirmationBars(SwingConfirmationBars); PAI.EnableOnlineLearning(EnableOnlineLearning); PAI.MaxErasPerRun(MaxErasPerRun); PAI.OOSSplit(OOSSplit); if(!UseDatabaseRanking) PAI.Weight(1); PAI.UseVolumes(EnableVolume); PAI.UseTime(EnableTime); PAI.UseATR(EnableATR); PAI.UseMA(EnableMAFeature); PAI.UseRSI(EnableRSIFeature); PAI.UseMACD(EnableMACDFeature); PAI.UseIchimoku(EnableIchimokuFeature); 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(EnableCONV) { int denseLayers = ResolveDenseTopologyLayers(AIType); CONV.OutputNeuronsCount(OutputNeuronsCount); CONV.HiddenLayersCount(denseLayers); CONV.ConvFilterCount(ConvFilterCount); CONV.HistoryBars(ind_Periods); CONV.StudyPeriod(StudyPeriods); CONV.MinDirectionalRecall(MinRecall); CONV.LogitPriorStrength(AILogitPriorStrength / 100.0); CONV.UseLogitAdjustedLoss(EnableLogitAdjustedLoss); CONV.LogitAdjustTau(LogitAdjustTau / 100.0); CONV.OversampleParity(OversampleParity / 100.0); CONV.SignalClusterWindow(SignalClusterWindow); CONV.FocalLossGamma(FocalLossGamma / 10.0); CONV.EnableMinorityReplay(EnableMinorityReplay); CONV.ConstrainReplay(ConstrainReplay); CONV.FreezePriorCalibration(FreezePriorCalibration); CONV.UseStaticPrior(UseStaticPrior); CONV.SwingConfirmationBars(SwingConfirmationBars); CONV.EnableOnlineLearning(EnableOnlineLearning); CONV.MaxErasPerRun(MaxErasPerRun); CONV.OOSSplit(OOSSplit); if(!UseDatabaseRanking) CONV.Weight(1); CONV.UseVolumes(EnableVolume); CONV.UseTime(EnableTime); CONV.UseATR(EnableATR); CONV.UseMA(EnableMAFeature); CONV.UseRSI(EnableRSIFeature); CONV.UseMACD(EnableMACDFeature); CONV.UseIchimoku(EnableIchimokuFeature); 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(EnableLSTM) { int denseLayers = ResolveDenseTopologyLayers(AIType); LSTM.OutputNeuronsCount(OutputNeuronsCount); LSTM.HiddenLayersCount(denseLayers); LSTM.LstmHiddenSize(LstmHiddenSize); LSTM.HistoryBars(ind_Periods); LSTM.StudyPeriod(StudyPeriods); LSTM.MinDirectionalRecall(MinRecall); LSTM.LogitPriorStrength(AILogitPriorStrength / 100.0); LSTM.UseLogitAdjustedLoss(EnableLogitAdjustedLoss); LSTM.LogitAdjustTau(LogitAdjustTau / 100.0); LSTM.OversampleParity(OversampleParity / 100.0); LSTM.SignalClusterWindow(SignalClusterWindow); LSTM.FocalLossGamma(FocalLossGamma / 10.0); LSTM.EnableMinorityReplay(EnableMinorityReplay); LSTM.ConstrainReplay(ConstrainReplay); LSTM.FreezePriorCalibration(FreezePriorCalibration); LSTM.UseStaticPrior(UseStaticPrior); LSTM.SwingConfirmationBars(SwingConfirmationBars); LSTM.EnableOnlineLearning(EnableOnlineLearning); LSTM.MaxErasPerRun(MaxErasPerRun); LSTM.OOSSplit(OOSSplit); if(!UseDatabaseRanking) LSTM.Weight(1); LSTM.UseVolumes(EnableVolume); LSTM.UseTime(EnableTime); LSTM.UseATR(EnableATR); LSTM.UseMA(EnableMAFeature); LSTM.UseRSI(EnableRSIFeature); LSTM.UseMACD(EnableMACDFeature); LSTM.UseIchimoku(EnableIchimokuFeature); 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); } if(EnableHYBRID) { int denseLayers = ResolveDenseTopologyLayers(AIType); HYBRID.OutputNeuronsCount(OutputNeuronsCount); HYBRID.HiddenLayersCount(denseLayers); HYBRID.ConvFilterCount(ConvFilterCount); HYBRID.LstmHiddenSize(LstmHiddenSize); HYBRID.HistoryBars(ind_Periods); HYBRID.StudyPeriod(StudyPeriods); HYBRID.MinDirectionalRecall(MinRecall); HYBRID.LogitPriorStrength(AILogitPriorStrength / 100.0); HYBRID.UseLogitAdjustedLoss(EnableLogitAdjustedLoss); HYBRID.LogitAdjustTau(LogitAdjustTau / 100.0); HYBRID.OversampleParity(OversampleParity / 100.0); HYBRID.SignalClusterWindow(SignalClusterWindow); HYBRID.FocalLossGamma(FocalLossGamma / 10.0); HYBRID.EnableMinorityReplay(EnableMinorityReplay); HYBRID.ConstrainReplay(ConstrainReplay); HYBRID.FreezePriorCalibration(FreezePriorCalibration); HYBRID.UseStaticPrior(UseStaticPrior); HYBRID.SwingConfirmationBars(SwingConfirmationBars); HYBRID.EnableOnlineLearning(EnableOnlineLearning); HYBRID.MaxErasPerRun(MaxErasPerRun); HYBRID.OOSSplit(OOSSplit); if(!UseDatabaseRanking) HYBRID.Weight(1); HYBRID.UseVolumes(EnableVolume); HYBRID.UseTime(EnableTime); HYBRID.UseATR(EnableATR); HYBRID.UseMA(EnableMAFeature); HYBRID.UseRSI(EnableRSIFeature); HYBRID.UseMACD(EnableMACDFeature); HYBRID.UseIchimoku(EnableIchimokuFeature); HYBRID.UseSwingContext(EnableSwingContext); HYBRID.UseNews(EnableNews); HYBRID.NewsFeatureWindowMinutes(NewsFeatureWindowMinutes); HYBRID.UseADCumulativeDelta(EnableADCumulativeDelta); HYBRID.UseADShorteningOfThrust(EnableADShorteningOfThrust); HYBRID.UseADWyckoffEventStream(EnableADWyckoffEventStream); HYBRID.UseADWyckoffFailedStructure(EnableADWyckoffFailedStructure); HYBRID.UseADWyckoffSignificantBarInversion(EnableADWyckoffSignificantBarInversion); HYBRID.AutoTuneIndicators(AutoTuneIndicators); HYBRID.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 &= (EnableMA ? AddFilterToSignal(signal, MA) : true); filtersAdded &= (EnableRSI ? AddFilterToSignal(signal, RSI) : true); filtersAdded &= (EnableMACD ? AddFilterToSignal(signal, MACD) : true); filtersAdded &= (EnableIchimoku ? AddFilterToSignal(signal, Ichimoku) : 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); filtersAdded &= (EnableHYBRID ? AddFilterToSignal(signal, HYBRID) : 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; // 500ms: Train() only does up to TRAIN_TIME_BUDGET_MS (80ms) of work per call, then yields back // here. This interval drives training only on quiet symbols / no-tick stretches (real tick flow // drives it independently), so it just needs to be short enough that a quiet era doesn't crawl - // it was 5s originally (era took ~2min for <2s of compute), then 250ms. 250ms made the on-chart // control panel laggy/hard to drag: every 250ms the timer ran ~80ms of compute AND a ChartRedraw, // which competed with the user's drag events. 500ms halves that redraw/compute contention (smooth // drag) while a quiet era still advances at ~80ms busy / 500ms = a healthy duty cycle. EventSet- // MillisecondTimer 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 = 500; 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() { // OnTesterInit/OnTesterDeinit fire once per tester session, even for a single (non-optimization) // backtest - and by the time this runs, that pass's own OnDeinit() has ALREADY executed (the // terminal calls it automatically at the end of every pass), including its own dbm.Deinit(). // Calling dbm.Deinit()/OnDeinit(0) again here duplicated the whole shutdown path (panel/arrow // cleanup, weight save, MarketBookRelease) a second time with a hardcoded/wrong reason code - // just reset the tester-only flag here. IsBacktesting = false; } //+------------------------------------------------------------------+ //| Tester function for smooth linear equity optimization | //| Output Range: 0.0 (Worst/Failed) to 100.0 (Perfect Linear Curve) | //+------------------------------------------------------------------+ double OnTester() { // 1. Enforce minimum performance thresholds double totalTrades = TesterStatistics(STAT_TRADES); if(totalTrades < 30) return(0.0); double netProfit = TesterStatistics(STAT_PROFIT); if(netProfit <= 0) return(0.0); double maxDrawdownPct = TesterStatistics(STAT_EQUITY_DDREL_PERCENT); if(maxDrawdownPct > 15.0) return(0.0); // 2. Extract key performance components for linearity proxy double profitFactor = TesterStatistics(STAT_PROFIT_FACTOR); double recoveryFactor = TesterStatistics(STAT_RECOVERY_FACTOR); double sharpeRatio = TesterStatistics(STAT_SHARPE_RATIO); if(profitFactor <= 0 || recoveryFactor <= 0) return(0.0); if(sharpeRatio < 0) sharpeRatio = 0.01; // 3. Trade density multiplier double tradeDensity = 1.0 - MathExp(-0.01 * (double)totalTrades); // 4. Mathematical combination proxy targeting visual linearity double rawScore = profitFactor * recoveryFactor * sharpeRatio; // FIX: Convert percent to decimal (divide by 100) before penalization double drawdownDecimal = maxDrawdownPct / 100.0; rawScore /= (1.0 + (drawdownDecimal * 0.5)); // Apply trade density rawScore *= tradeDensity; // 5. Normalize the score to a 0.0 - 100.0 scale double finalScore = 100.0 * (1.0 - MathExp(-0.12 * rawScore)); if(finalScore > 100.0) finalScore = 100.0; if(finalScore < 0.0) finalScore = 0.0; return(finalScore); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| 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) { static bool s_deinitInProgress = false; if(s_deinitInProgress) return; s_deinitInProgress = true; // Stop timer callbacks first so no more periodic work is queued while teardown runs. EventKillTimer(); string reasonStr = DeinitReasonToString(reason); Print(__FUNCTION__ + ": shutting down - reason: " + reasonStr); bool isTesterRun = (MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_FORWARD)); if(!isTesterRun) SaveSignalsVisibilityState(); bool preserveChartArrows = (reason == REASON_RECOMPILE || reason == REASON_PARAMETERS || reason == REASON_CHARTCHANGE || reason == REASON_TEMPLATE); if(preserveChartArrows) Print(__FUNCTION__ + ": preserving signal arrows on chart for warm reload reason."); //--- Clear the VISIBLE EA objects FIRST and fast - status label, then control panel, then arrows - //--- BEFORE the slow/fragile weight save below. On the CPU-DLL box the recursive Net.Save (two full nets //--- per signal) and the net teardown in Expert.Deinit() can run long enough for MT5 to force-terminate //--- OnDeinit ("Abnormal termination"); whatever was ordered AFTER that point never ran, which is what //--- left the status label stuck on the chart. ClearStatusLabel() is a single fast op and goes ABSOLUTELY //--- first (it was the reported straggler) so it happens even if a later step stalls; the panel and arrow //--- purge follow while still cheap. Only then the heavy persistence runs, best-effort, last. ClearStatusLabel(); //--- Destroy the control panel's own UI before object teardown so CAppDialog removes its own objects //--- cleanly (see Controls\Dialog.mqh) rather than racing the purges below. if(!isTesterRun) ExtPanel.Destroy(reason); for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].StopTraining(); for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].ShutdownChartCleanup(preserveChartArrows); // persist arrows, optionally keep them visible on warm reload //--- matches the MarketBookAdd() in CheckMarketDepthAvailability() (OnInit) - released only if subscribed. if(g_marketDepthAvailable) MarketBookRelease(_Symbol); //--- Persist every active AI signal's current in-memory weights/state, so a terminal restart, recompile, //--- chart re-add or template swap resumes from here rather than from the last fully-completed training //--- era only. Best-effort and LAST: if it stalls/faults the chart is already clean (cleanup ran above) //--- AND the last completed era is already on disk (every era end and the periodic autosave both persist //--- independently), and CNet::Save is atomic so a killed write can never damage the existing .nnw. //--- NOTE: an earlier revision skipped this entirely for in-process reloads (REASON_RECOMPILE / //--- CHARTCHANGE / PARAMETERS) on the theory that an over-budget save left the CPU-DLL holding the old //--- net's tensors and made the reload fail to allocate layer 0. That theory was wrong - the real cause of //--- "loaded 0 of N layers (failed at layer 0)" was CLayer::CreateElement no longer overriding //--- CArrayObj::CreateElement, so the read path failed before touching any backend (see AI\Network.mqh). //--- With that fixed there is no reason to throw away the in-progress era on every recompile. if(!isTesterRun) { for(int i = 0; i < g_aiSignalCount; i++) { if(!g_aiSignals[i].PersistWeightsOnShutdown()) Print(__FUNCTION__ + ": WARNING - failed to persist weights for signal index " + IntegerToString(i) + " on shutdown (reason: " + reasonStr + ")"); } } g_aiSignalCount = 0; dbm.Deinit(); Expert.Deinit(); //--- belt-and-suspenders: the status label was already cleared first, but re-clear in case a later path //--- (e.g. Expert.Deinit's destructors) drew anything, so nothing is left on the chart after removal. ClearStatusLabel(); s_deinitInProgress = false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //--- 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(); //--- Finish the Show Signals sequence once every instance queued by ToggleSignalsVisibility has //--- drained its chunked rescan (each is advanced one slice per PollTraining call above). FinalizeSignalsRescanIfDone(); //--- Training can finish and deploy ITSELF (the plateau ladder finalising the best checkpoint, or the //--- era-cap deploy) with no button ever pressed, and RefreshControlPanelLabels() otherwise only runs //--- in response to a click - which would leave the panel offering "Deploy Model" on an //--- already-deployed model until the user happened to click something. Watch for the transition and //--- resync once, rather than repainting every 500ms tick for nothing. bool deployedNow = AllTrainingDeployed(); if(deployedNow != g_lastDeployedState) { g_lastDeployedState = deployedNow; RefreshControlPanelLabels(); } 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) { //--- NEVER autosave inside the Strategy Tester / optimizer. The whole reason this exists is that a live //--- terminal can be killed without OnDeinit running - a tester run has no such exposure. Worse, "new //--- bar" in the tester means every SIMULATED bar, so this fired a full ~7MB model write per simulated //--- hour (thousands of writes per backtest), spamming save failures and burning the entire run's time //--- on I/O. A tester run is inference-only anyway (see m_inferenceOnly) - the weights never change, so //--- there is literally nothing to persist. if(MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_FORWARD)) return; datetime lastBarDate = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE); //--- <=0 is a transient history-sync hiccup, not "no new bar" - skip this tick and try again on the //--- next one rather than risk locking onto a bad watermark (same guard philosophy as //--- ScheduleTrainingIfNeeded's own lastBarDate read). if(lastBarDate <= 0 || lastBarDate == g_lastAutosaveBarTime) return; g_lastAutosaveBarTime = lastBarDate; for(int i = 0; i < g_aiSignalCount; i++) g_aiSignals[i].SaveWeightsNow(); } void OnTick() { CheckAlgoTradingState(); AutosaveWeightsIfDue(); Expert.OnTick(); if(UseDatabaseRanking) journal.Update(); //--- 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); //--- CHARTEVENT_CHART_CHANGE covers resize, scroll and DPI/zoom changes - anything that can //--- move the visible area out from under a dialog left near an edge from a previous drag. if(id == CHARTEVENT_CHART_CHANGE) ClampControlPanelToChart(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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.SLMode((int)SL_Mode); signal_obj.TPMode((int)TP_Mode); signal_obj.MinRiskRewardRatio(Min_Risk_Reward_Ratio); signal_obj.ConfidenceSource((int)Confidence_Source); //--- Gates AddFilter()'s DB pattern-table creation and Direction()'s per-tick DB signal buffering //--- (ExpertSignalCustom.mqh:286/555) - without this, UseDatabaseRanking only skipped the Weight(1) //--- default below and never actually populated the win-rate tables UpdateSignalsWeights() reads from. signal_obj.UseDatabase(UseDatabaseRanking); //--- Same two inputs feed BOTH engines, and now on ONE scale each. Open is purely the averaged-vote //--- threshold - there is no separate AI entry floor any more (the AI expresses its confidence as its //--- vote weight instead, see CExpertSignalAIBase::ConfidenceTier/m_pattern_0), so no //--- MinSignalConfidence() call is set on the AI signals above. Close drives the rule-based exit and //--- the AI early exit. See Min_Vote_Open's declaration comment (Variables\Inputs.mqh) for the shared //--- 0-100 conviction scale, and for why open and close must stay independent of each other. //--- No UseAIExit() call any more - the early-exit route has no separate on/off switch, because //--- Min_Vote_Close = Disabled already lands here as 1.01 and there disables it by arithmetic. signal_obj.AIExitThreshold(Min_Vote_Close / 100.0); signal_obj.ThresholdOpen((int)Min_Vote_Open); signal_obj.ThresholdClose((int)Min_Vote_Close); //--- HYBRID is now one fused signal, so no separate AI quorum is needed here. 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; } } else if(TrailingStrategy == TRAILING_STRATEGY_INTELLIGENT) { // Confidence-adaptive ATR trailing (see Trailing\TrailingIntelligent.mqh) - the ATR // multiple is set per-check from live AI confidence, so no fixed Multiplier() here. CTrailingIntelligent *trailing = new CTrailingIntelligent; if(trailing == NULL) { Print(__FUNCTION__ + ": error creating intelligent trailing"); return false; } if(!Expert.InitTrailing(trailing)) { Print(__FUNCTION__ + ": error initializing intelligent 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); //--- Intelligent MM is AI-driven by definition now (the old Use_AI_Lot_Sizing toggle was //--- removed as redundant) - always scale risk% by confidence via the Kelly path. money.UseAIConfidenceLotSizing(true); money.ConfidenceSource((int)Confidence_Source); } // Add more money management strategies if needed return true; } //+------------------------------------------------------------------+