Warrior_EA/Warrior_EA.mq5
AnimateDread e8452913c0 feat: add swing-context feature with confirmed zigzag pivot
Introduce m_useSwingContext flag and FindConfirmedZigZagPivot method to compute normalized swing direction/magnitude/age features from the existing ADZigZag indicator. Only pivots that are at least m_swingConfirmationBars old are trusted, preventing lookahead bias. The SWING_SCAN_CAP_BARS macro limits backward scan depth. Default is off.
2026-07-19 11:04:38 -04:00

1060 lines
46 KiB
MQL5

//+------------------------------------------------------------------+
//| 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 |
//| <terminal data folder>\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 <typename TSignal>
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<CSignalPAI>(maxRetryOnError, EnablePAI);
CSignalCONV *CONV = CreateSignalWithRetry<CSignalCONV>(maxRetryOnError, EnableCONV);
CSignalLSTM *LSTM = CreateSignalWithRetry<CSignalLSTM>(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<CSignalNewsFilter>(maxRetryOnError, EnableNewsFilter);
CSignalSessionFilter *sessionFilter = CreateSignalWithRetry<CSignalSessionFilter>(maxRetryOnError, EnableSessionFilter);
CSignalITF *ITF = CreateSignalWithRetry<CSignalITF>(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<CSignalMarketDepth>(maxRetryOnError, g_marketDepthAvailable);
CSignalRiskGuard *riskGuard = CreateSignalWithRetry<CSignalRiskGuard>(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;
}
//+------------------------------------------------------------------+