forked from animatedread/Warrior_EA
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.
1060 lines
46 KiB
MQL5
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;
|
|
}
|
|
//+------------------------------------------------------------------+
|