ответвлён от mnbvc188199/Warrior_EA
- Introduced `FeatureScale.mqh` with `FeatSquash` function for stateless feature scaling. - Added `RegimeMath.mqh` class for regime arithmetic, including efficiency and variance calculations. - Documented the Mind trading logic in `MIND.md`, detailing the trading process and modes. - Created `VOLNORM_PLAN.md` and `VOLNORM_RESULTS.md` for tick-volume normalization testing. - Implemented `read_book.py` for analyzing trade book data and correlations. - Developed `volnorm.py` for testing tick-volume normalization with new and old methods.
501 строка
29 КиБ
MQL5
501 строка
29 КиБ
MQL5
//+------------------------------------------------------------------+
| |
//| Warrior_EA.mq5 |
| |
//| AnimateDread |
| |
//| |
| |
//| THE WHOLE EA, in the shape MetaEditor's wizard generates. |
| |
//| |
| |
//| CExpert |
| |
//| CWarriorVote the root signal - holds the filters |
| |
//| classic modules MA, RSI, MACD, Ichimoku, CCI, ... |
| |
//| Wyckoff modules event stream, failed structure |
| |
//| NN modules trained in-terminal, vote like the rest|
| |
//| CWarriorMoney fixed lot or fixed risk % |
| |
//| CTrailingATR ATR trailing stop |
| |
//| |
| |
//| Every filter answers LongCondition()/ShortCondition() with 0..100. |
| |
//| The root sums them, divides by how many answered, and CExpert |
| |
//| opens a trade when the result clears the threshold. That is the |
| |
//| entire decision. There is no override, no quorum counter, no |
| |
//| per-setup order shaping and no special case for the networks - a |
| |
//| net that votes is indistinguishable from an RSI that votes, which |
| |
//| is the property that makes the whole thing rankable. |
| |
//| |
| |
//| WHAT THE DATABASE IS FOR. Every filter evaluation is queued during |
| |
//| the vote and written on the timer, so each module accumulates its |
| |
//| own record. The ranking layer reads those win rates and sets each |
| |
//| module's Weight(). A module that carries nothing earns a small |
| |
//| weight and stops mattering - which is the alternative to deleting |
| |
//| it, and the reason the classic modules can be left switched on. |
| |
//| |
| |
//| THE DEFAULTS ARE THE VALIDATED BOOK (2026-09-23): the vol-gated |
| |
//| dip-buy, alone, long only, on an H4 chart of SP500, NAS100, US30 |
| |
//| or DAX40 - one chart per symbol, the SAME Magic on every chart. |
| |
//| Tester 2022-26: 4 charts, +11.6%, equity DD 2.82%, ret/DD 4.11, |
| |
//| every index and every year positive; unchanged on real ticks. See |
| |
//| STRATEGY.md. The account layer (System\AccountGuard.mqh) shares |
| |
//| an open-risk cap, a kill switch and a lock across those charts. |
| |
//| Every other module is still here and still switchable; with them |
| |
//| on, this is the old ensemble, which has never been profitable. |
| |
//+------------------------------------------------------------------+
| |
#property copyright "AnimateDread"
| |
#property version "2.00"
| |
#property description "Warrior EA - standard-library vote, NN signals, self-ranking database."
| |
| |
#include <Expert\Expert.mqh>
| |
//--- The database layer logs through System\PrintVerbose.mqh, which reads these two globals. They
| |
//--- are consts, not inputs: this EA reports through the threshold ladder and the journal summary,
| |
//--- both of which print unconditionally. The old per-tick trace was 35% of a 1.5M-line tester log
| |
//--- and told nobody anything - if something here needs explaining, it should print a sentence, not
| |
//--- a number per tick.
| |
const bool VerboseMode = false;
| |
const bool TraceMode = false;
| |
| |
#include "Enumerations\WarriorEnums.mqh"
| |
//--- CExpert with a session-aware bar clock: a daily bar that opens during the CFD maintenance hour
| |
//--- is acted on at the first tick inside a session, not discarded. See the header for the count.
| |
#include "Expert\WarriorExpert.mqh"
| |
#include "Expert\WarriorSignal.mqh"
| |
#include "Database\WarriorJournal.mqh"
| |
#include "Expert\WarriorVote.mqh"
| |
#include "Money\WarriorMoney.mqh"
| |
#include "System\AccountGuard.mqh"
| |
#include "System\TradeLog.mqh"
| |
//--- OURS, NOT THE WIZARD'S. These are the repo's own classic modules, restored from 3ed053e /
| |
//--- 095bd27. They are the MetaQuotes indicators with the one thing the stock modules lack: each
| |
//--- NAMES the pattern that matched, so the journal can key a row to it and the ranking layer can
| |
//--- give that pattern its own weight. 49 named patterns across the eleven, against 0 from the
| |
//--- stdlib versions - a CExpertSignal cannot be dynamic_cast to CWarriorSignal, so every stock
| |
//--- module voted invisibly and none of them could ever be ranked.
| |
#include "Signals\SignalMA.mqh"
| |
#include "Signals\SignalRSI.mqh"
| |
#include "Signals\SignalMACD.mqh"
| |
#include "Signals\SignalCCI.mqh"
| |
#include "Signals\SignalStoch.mqh"
| |
#include "Signals\SignalWPR.mqh"
| |
#include "Signals\SignalRVI.mqh"
| |
#include "Signals\SignalSAR.mqh"
| |
#include "Signals\SignalAO.mqh"
| |
#include "Signals\SignalAC.mqh"
| |
#include "Signals\SignalIchimoku.mqh"
| |
//--- Regime context: trending / consolidating / mean reverting, from closes alone.
| |
#include "Signals\SignalRegime.mqh"
| |
//--- The measured US-index dip-buy. Run ALONE on SP500/NAS100/US30 D1, Direction = DIR_LONG.
| |
#include "Signals\SignalDipBuy.mqh"
| |
//--- OURS. Wyckoff derives from CWarriorSignal, so it votes exactly like the modules above and is
| |
//--- additionally JOURNALLED - the vote cannot tell them apart, the database can.
| |
#include "Signals\Wyckoff\SignalWyckoffEvent.mqh"
| |
#include "Signals\Wyckoff\SignalWyckoffStructure.mqh"
| |
//--- The network. An ordinary signal module: it builds its own inputs, votes 0..100, and
| |
//--- trains itself from this chart's history the first time it runs.
| |
#include "Signals\SignalNeural.mqh"
| |
//--- ATR trailing stop - a plain CExpertTrailing, self-contained.
| |
#include "Trailing\TrailingATR.mqh"
| |
| |
//+------------------------------------------------------------------+
| |
//| Inputs - every one an enum, every value its own payload. |
| |
//+------------------------------------------------------------------+
| |
input group "=== General ==="
| |
input ulong Magic = 20260911; // Magic number
| |
input bool EveryTick = false; // Evaluate on every tick (else bar open)
| |
| |
input group "=== Vote ==="
| |
input WARRIOR_THRESHOLD ThresholdOpen = THR_15; // Vote needed to open (% of ensemble)
| |
//--- 101 = never close on a vote (the default: exits are the stop, the target or the trail). The
| |
//--- dip-buy module's exit IS a vote - its ShortCondition fires when the dip has reverted - so on an
| |
//--- index chart this is set to 50 and, with the module alone, its 100 clears it.
| |
input int ThresholdClose = 50; // Vote needed to close (101 = never)
| |
input WARRIOR_DIRECTION Direction = DIR_LONG; // Allowed direction
| |
input WARRIOR_FADE FadeCrowd = FADE_OFF; // Fade near-unanimous votes, else stand aside
| |
| |
input group "=== Risk ==="
| |
input WARRIOR_MONEY_MODE MoneyMode = MONEY_FIXED_RISK; // Position sizing
| |
input WARRIOR_RISK Risk = RISK_0_25; // Risk per trade, % of equity (when sizing by risk)
| |
input double FixedLots = 0.01; // Lot size (when sizing fixed)
| |
input WARRIOR_SL_MODE StopMode = SL_ATR_x3; // Stop loss (the dip-buy places its own 3 x Wilder ATR)
| |
input WARRIOR_TP_MODE TargetMode = TP_NONE; // Take profit
| |
input WARRIOR_TRAIL_MODE TrailMode = TRAIL_NONE; // Trailing stop
| |
input WARRIOR_BREAKEVEN BreakevenAt = BE_OFF; // Move stop to entry at this profit
| |
input WARRIOR_MGMT_CUT ManagementCut = MGMT_OFF; // Exit at +0.5R when the model says retrace
| |
| |
//--- MODULE WEIGHTS - the optimiser's half of the ranking. The journal sets pattern weights from
| |
//--- measured expectancy; these say how loudly each module's patterns are heard. WEIGHT_0 switches
| |
//--- a module off entirely, which is the setting worth sweeping: it lets a pass answer "does this
| |
//--- indicator contribute anything", a question the EA could never ask while every module voted at
| |
//--- 1.0 by default.
| |
//--- THE ACCOUNT, SHARED BY EVERY CHART WITH THIS MAGIC. Attach one chart per symbol with the same
| |
//--- Magic: the cap sums every such chart's open risk, the kill switch halts them all, and a
| |
//--- cross-chart lock stops two charts rolling to the same bar from both reading the cap before
| |
//--- either has opened. Different Magics = independent books with independent caps.
| |
input group "=== Account (shared by every chart with this Magic) ==="
| |
input double MaxOpenRiskPct = 0.75; // Open-risk cap: total risk to stops, % of equity (0 = off)
| |
input double KillSwitchDD = 4.5; // Kill switch: equity DD from peak, % (0 = off)
| |
input int FridayFlatMin = 170; // Friday flat: minutes before this symbol's Friday close (-1 = off)
| |
input bool TradeLog = true; // Write warrior_trades_<SYMBOL>.csv (common folder) at deinit
| |
| |
//--- THE MIND (Mind\WarriorMind.mqh). OBSERVE changes nothing that is traded: it reads the closed
| |
//--- signal bar through price, volume-without-the-clock, Wyckoff, cross-index and calendar
| |
//--- modalities, books every trade with that picture (Common\Files\Warrior_EA\Mind\book_*.csv) and
| |
//--- prints a review at the end. SIZE / SIZE_GATE let the journal act on itself - only ever to take
| |
//--- risk off - once MindMinTrades have closed. Run OBSERVE first, read the book, then decide.
| |
input group "=== Mind (the trader behind the signal) ==="
| |
input WARRIOR_MIND MindMode = MIND_OBSERVE; // Mind: off / observe / size / size + gate
| |
input bool MindWyckoff = true; // Mind: read the Wyckoff structure indicators
| |
input string MindPeers = "SP500,NAS100,US30,DAX40"; // Mind: cross-index breadth peers ("" = off)
| |
input int MindMinTrades = 40; // Mind: closed trades before the journal may act
| |
input int MindShrink = 20; // Mind: shrinkage - trades of evidence to half-believe a band
| |
input double MindMinScale = 0.5; // Mind: smallest risk multiple the journal may apply
| |
input int MindTrials = 50; // Mind: configurations tried, for the deflated Sharpe
| |
| |
input group "=== Module weights (optimise these) ==="
| |
input WARRIOR_WEIGHT W_MA = WEIGHT_100; // MA
| |
input WARRIOR_WEIGHT W_RSI = WEIGHT_100; // RSI
| |
input WARRIOR_WEIGHT W_MACD = WEIGHT_100; // MACD
| |
input WARRIOR_WEIGHT W_CCI = WEIGHT_100; // CCI
| |
input WARRIOR_WEIGHT W_Stoch = WEIGHT_100; // Stochastic
| |
input WARRIOR_WEIGHT W_WPR = WEIGHT_100; // Williams %R
| |
input WARRIOR_WEIGHT W_RVI = WEIGHT_100; // RVI
| |
input WARRIOR_WEIGHT W_SAR = WEIGHT_100; // Parabolic SAR
| |
input WARRIOR_WEIGHT W_AO = WEIGHT_100; // Awesome Oscillator
| |
input WARRIOR_WEIGHT W_AC = WEIGHT_100; // Accelerator
| |
input WARRIOR_WEIGHT W_Ichimoku = WEIGHT_100; // Ichimoku
| |
input WARRIOR_WEIGHT W_WyckoffEv = WEIGHT_100; // Wyckoff events
| |
input WARRIOR_WEIGHT W_WyckoffSt = WEIGHT_100; // Wyckoff structure
| |
input WARRIOR_WEIGHT W_Neural = WEIGHT_100; // Neural
| |
input WARRIOR_WEIGHT W_Regime = WEIGHT_100; // Regime
| |
input WARRIOR_WEIGHT W_DipBuy = WEIGHT_100; // Dip-buy
| |
| |
input group "=== Modules ==="
| |
input bool UseClassics = false; // Classic indicator modules
| |
input bool UseWyckoff = false; // Wyckoff modules
| |
//--- OFF BY DEFAULT so every comparison made before it still holds. Registering a fourteenth voter
| |
//--- changes the divisor in CExpertSignal::Direction(), which changes what EVERY threshold
| |
//--- percentage means - a roster change and a threshold change cannot be measured in one run.
| |
input bool UseRegime = false; // Regime module (trend/range/revert)
| |
//--- THE DIP-BUY IS NOT A VOTE MEMBER. One filter at 100 scores 7.7% on a thirteen-voter roster and
| |
//--- clears nothing; it must run ALONE (every other module off) with Direction = DIR_LONG, a wide
| |
//--- stop and no target. The production form is DIP_ZSCORE on H4 with the vol gate, on SP500, NAS100,
| |
//--- US30 and DAX40 (the defaults); the RSI(2) form is the D1 US-index variant. It does NOT work on
| |
//--- forex or metals - thirteen registered families found nothing there (research/FX_RESULTS.md).
| |
input bool UseDipBuy = true; // Index dip-buy (run alone, long only)
| |
//--- The dip's two measured forms (see the module header): RSI(2) on D1 with the 200-bar filter and
| |
//--- the SMA5 exit, or the z-score on H4 with no filter and the SMA20 exit. All four are optimiser
| |
//--- inputs; the module refuses values outside the measured neighbourhood.
| |
input WARRIOR_DIP_ENTRY DipEntry = DIP_ZSCORE; // Dip-buy: entry form
| |
input double DipZ = 1.5; // Dip-buy: z-score depth (DIP_ZSCORE)
| |
input int DipExitMA = 20; // Dip-buy: exit when close crosses above this SMA
| |
input int DipTrendMA = 0; // Dip-buy: only above this SMA (0 = no filter)
| |
input int DipMaxBars = 10; // Dip-buy: time stop in bars
| |
//--- THE VOLATILITY GATE (z form only): trade the dip only when the signal bar's Garman-Klass
| |
//--- sigma(30) sits at or above this percentile of its own whole history. Validated on H4; on D1
| |
//--- it halves the cadence below the prop floor, so switch it off there.
| |
input bool DipVolGate = true; // Dip-buy: volatility-regime gate (H4: on, D1: off)
| |
input double DipVolPct = 0.50; // Dip-buy: gate - minimum vol percentile
| |
//--- THE META-LABEL (System\DipMeta.mqh): an ALGLIB forest + MLP, trained in the terminal on every
| |
//--- past dip and what the EA's own exit made of it, says take/skip. CONF_50 = off. Walk-forward:
| |
//--- refitted every DipMetaRefit bars from history-so-far, never saved.
| |
input WARRIOR_CONFIDENCE DipMetaCut = CONF_50; // Dip-buy: skip when P(pays) is below this
| |
input int DipMetaRefit = 500; // Dip-buy: refit the meta-label every N bars
| |
input bool UseNeural = false; // Neural module (trains on first run)
| |
input WARRIOR_CONFIDENCE NeuralCut = CONF_50; // Neural: probability cut
| |
input bool UseDatabase = true; // Record every evaluation to the database
| |
//--- 🛑 THE RANKING FEEDS THE WEIGHTS - AND MEASURED, IT LOSES MONEY. Default OFF.
| |
//---
| |
//--- EURUSD D1 2015-2026, identical settings, the ONLY difference being how much of the journal the
| |
//--- ranker had to act on: with a cold database (patterns mostly keeping their AUTHORED weights,
| |
//--- 1 module ranked at the start, 12 modules / 25 patterns by the end) the run made +150.67 at
| |
//--- PF 1.33. With the same window but a journal pre-filled from 2000-2014 - fully ranked from the
| |
//--- first bar, 12 modules / 27 patterns immediately - it made +27.53 at PF 1.05.
| |
//---
| |
//--- More evidence made it WORSE, which is the signature of a measurement that does not persist:
| |
//--- a pattern's edge is real on the window it was measured on and gone on the next. Recording
| |
//--- stays ON regardless - the journal found the give-back and the 46/54 continue-vs-retrace split,
| |
//--- and neither of those was visible any other way. It is the CONTROL LOOP that is switched off,
| |
//--- not the LOGGING. Left as an input so the optimiser can re-test it rather than trusting this.
| |
input bool DB_RankingFeedsWeights = false; // Let measured expectancy set the weights
| |
| |
//--- ATR drives the stop, the target and the trail, so it is read once here and shared.
| |
input int AtrPeriod = 14; // ATR period for stop/target/trail
| |
| |
//+------------------------------------------------------------------+
| |
CWarriorExpert ExtExpert;
| |
CWarriorVote *ExtSignal = NULL;
| |
CWarriorJournal ExtJournal;
| |
CAccountGuard ExtGuard;
| |
CWarriorMind ExtMind;
| |
CDatabaseManager ExtDbm;
| |
bool ExtDbReady = false;
| |
| |
//--- Registers one filter and says so if it cannot. A filter that fails to attach silently is a
| |
//--- module missing from the vote, which changes what the threshold means without changing the log.
| |
//--- Takes the STDLIB type, so a MetaQuotes module and one of ours register identically.
| |
bool AddFilter(CExpertSignal *filter, const string name, const WARRIOR_WEIGHT weight)
| |
{
| |
if(filter == NULL)
| |
{
| |
PrintFormat("OnInit: could not create %s", name);
| |
return false;
| |
}
| |
//--- A module at WEIGHT_0 is REGISTERED AND SILENT, not skipped. That matters: the vote divides
| |
//--- by the number of filters that answered, so removing a module from the roster changes what
| |
//--- every threshold percentage means, and a sweep over weights would then be comparing runs
| |
//--- whose thresholds are not the same quantity. Registered-at-zero keeps the divisor fixed.
| |
filter.Weight((double)weight / 100.0);
| |
if(!ExtSignal.AddFilter(filter))
| |
{
| |
PrintFormat("OnInit: could not add %s to the vote", name);
| |
return false;
| |
}
| |
return true;
| |
}
| |
//+------------------------------------------------------------------+
| |
int OnInit()
| |
{
| |
if(!ExtExpert.Init(Symbol(), Period(), EveryTick, Magic))
| |
{
| |
Print("OnInit: ExtExpert.Init failed");
| |
return INIT_FAILED;
| |
}
| |
ExtSignal = new CWarriorVote;
| |
if(ExtSignal == NULL)
| |
return INIT_FAILED;
| |
ExtExpert.InitSignal(ExtSignal);
| |
ExtSignal.ThresholdOpen((int)ThresholdOpen);
| |
//--- CLOSED ONLY BY THE STOP, THE TARGET OR THE TRAIL. A vote-driven exit is a different strategy
| |
//--- from the one the entry threshold was chosen for, and mixing them makes neither measurable.
| |
ExtSignal.ThresholdClose(ThresholdClose);
| |
//--- WHICH SIDE MAY OPEN. Declared as an input since the rebuild and applied nowhere until now:
| |
//--- the gate lives on the vote's CheckOpenLong/Short, which the stdlib's reversal path also uses.
| |
ExtSignal.Allowed(Direction);
| |
//--- THE BARRIERS. StopMode and TargetMode are ATR MULTIPLES (the enum value IS the multiple), so
| |
//--- the stop is the same size in volatility terms on every symbol and in every regime - which a
| |
//--- fixed point distance is not. Set BEFORE InitIndicators, which is where the ATR is created.
| |
ExtSignal.Barriers(AtrPeriod, (double)StopMode, (double)TargetMode);
| |
ExtSignal.ManagementCut((double)ManagementCut / 100.0);
| |
ExtSignal.FadeAt((int)FadeCrowd);
| |
| |
if(UseClassics)
| |
{
| |
if(!AddFilter(new CSignalMA, "MA", W_MA)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalRSI, "RSI", W_RSI)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalMACD, "MACD", W_MACD)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalCCI, "CCI", W_CCI)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalStoch, "Stoch", W_Stoch)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalWPR, "WPR", W_WPR)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalRVI, "RVI", W_RVI)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalSAR, "SAR", W_SAR)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalAO, "AO", W_AO)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalAC, "AC", W_AC)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalIchimoku, "Ichimoku", W_Ichimoku)) return INIT_FAILED;
| |
}
| |
if(UseRegime && !AddFilter(new CSignalRegime, "Regime", W_Regime))
| |
return INIT_FAILED;
| |
if(UseDipBuy)
| |
{
| |
CSignalDipBuy *dip = new CSignalDipBuy;
| |
if(dip == NULL)
| |
return INIT_FAILED;
| |
dip.Entry(DipEntry);
| |
dip.ZEntry(DipZ);
| |
dip.ExitPeriod(DipExitMA);
| |
dip.TrendPeriod(DipTrendMA);
| |
dip.MaxBars(DipMaxBars);
| |
dip.StopAtr((double)StopMode); // the label simulates the stop the EA places
| |
dip.AtrPeriod(AtrPeriod);
| |
dip.VolGate(DipVolGate, DipVolPct);
| |
if(DipEntry == DIP_ZSCORE && DipVolGate && Period() != PERIOD_H4)
| |
PrintFormat("OnInit: ⚠ the dip-buy's vol gate was validated on H4 only; this chart is %s."
| |
" On D1 switch DipVolGate off.", EnumToString(Period()));
| |
dip.MetaCut((double)DipMetaCut / 100.0);
| |
dip.MetaRefit(DipMetaRefit);
| |
if(!AddFilter(dip, "DipBuy", W_DipBuy))
| |
return INIT_FAILED;
| |
}
| |
if(UseWyckoff)
| |
{
| |
if(!AddFilter(new CSignalWyckoffEvent, "WyckoffEvent", W_WyckoffEv)) return INIT_FAILED;
| |
if(!AddFilter(new CSignalWyckoffStructure, "WyckoffStructure", W_WyckoffSt)) return INIT_FAILED;
| |
}
| |
if(UseNeural)
| |
{
| |
CSignalNeural *net = new CSignalNeural;
| |
if(net == NULL)
| |
return INIT_FAILED;
| |
net.AtrPeriod(AtrPeriod);
| |
net.Confidence((double)NeuralCut / 100.0);
| |
if(!AddFilter(net, "Neural", W_Neural))
| |
return INIT_FAILED;
| |
}
| |
| |
CWarriorMoney *money = new CWarriorMoney;
| |
if(money == NULL)
| |
return INIT_FAILED;
| |
if(MoneyMode == MONEY_FIXED_LOT)
| |
money.FixedLot(FixedLots);
| |
else
| |
money.RiskPercent((double)Risk / 100.0);
| |
if(!ExtExpert.InitMoney(money))
| |
return INIT_FAILED;
| |
| |
//--- ONE OBJECT, TWO INDEPENDENT FEATURES. CTrailingATR carries both the ATR trail and the
| |
//--- breakeven stop, and either may be off: the stdlib's plain CExpertTrailing has neither, so
| |
//--- installing it whenever TrailMode is NONE would silently discard BreakevenAt.
| |
if(TrailMode == TRAIL_NONE && BreakevenAt == BE_OFF)
| |
{
| |
CExpertTrailing *none = new CExpertTrailing;
| |
if(none == NULL || !ExtExpert.InitTrailing(none))
| |
return INIT_FAILED;
| |
}
| |
else
| |
{
| |
CTrailingATR *trail = new CTrailingATR;
| |
if(trail == NULL)
| |
return INIT_FAILED;
| |
trail.Multiplier((double)TrailMode); // 0 = no ATR trail, breakeven only
| |
trail.BreakevenAt((double)BreakevenAt / 10.0); // the enum is TENTHS of R
| |
if(!ExtExpert.InitTrailing(trail))
| |
return INIT_FAILED;
| |
PrintFormat("OnInit: management - ATR trail %s, breakeven %s.",
| |
(TrailMode == TRAIL_NONE ? "off" : "x" + IntegerToString((int)TrailMode)),
| |
(BreakevenAt == BE_OFF ? "off" : "at +" +
| |
DoubleToString((double)BreakevenAt / 10.0, 1) + "R"));
| |
}
| |
| |
if(!ExtExpert.ValidationSettings() || !ExtExpert.InitIndicators())
| |
{
| |
Print("OnInit: validation or indicator init failed");
| |
return INIT_FAILED;
| |
}
| |
//--- THE ACCOUNT LAYER. A fixed-lot book has no risk unit, so an entry adds nothing the cap can
| |
//--- price in advance; the cap still counts what is already open.
| |
ExtGuard.Init(Symbol(), (long)Magic,
| |
(MoneyMode == MONEY_FIXED_RISK ? (double)Risk / 100.0 : 0.0),
| |
MaxOpenRiskPct, KillSwitchDD, FridayFlatMin);
| |
ExtExpert.Guard(GetPointer(ExtGuard));
| |
//--- THE MIND. After the indicators so a Wyckoff feed the modules already built is reused, not
| |
//--- built twice. It cannot stop the EA starting: a mind that fails to start is simply off.
| |
ExtMind.Bind(money);
| |
if(!ExtMind.Init(Symbol(), Period(), Magic, MindMode, MindWyckoff, MindPeers, MindMinTrades,
| |
MindShrink, MindMinScale, MindTrials))
| |
Print("OnInit: the Mind could not start - trading without it.");
| |
else
| |
ExtExpert.Mind(GetPointer(ExtMind));
| |
| |
| |
//--- THE DATABASE. Off during optimisation and forward passes on purpose: those run many agents
| |
//--- in parallel against one file, which is write contention, not a throttle problem.
| |
if(UseDatabase && !MQLInfoInteger(MQL_OPTIMIZATION) && !MQLInfoInteger(MQL_FORWARD))
| |
{
| |
//--- One database per symbol AND per timeframe. Mixing timeframes in one file would pool a
| |
//--- module's D1 record with its M5 record under the same table name, and they are not the
| |
//--- same claim - the whole point of ranking is that a module can be good on one and not the
| |
//--- other. FILE_COMMON, so the tester and the live terminal read the same corpus.
| |
string folders[] = {"Warrior_EA", "Journal"};
| |
const string dbName = Symbol() + "_" + IntegerToString(Period()) + ".db";
| |
if(ExtDbm.Init("1.0", folders, dbName) && ExtDbm.OpenDatabase())
| |
{
| |
ExtDbReady = true;
| |
ExtJournal.Bind(GetPointer(ExtDbm), (bool)MQLInfoInteger(MQL_TESTER));
| |
ExtSignal.Journal(GetPointer(ExtJournal));
| |
}
| |
else
| |
Print("OnInit: database unavailable - running without a record.");
| |
}
| |
//--- SAY OUT LOUD WHETHER THIS EA CAN TRADE AT ALL. Five switches can stop it dead and none of
| |
//--- them raise an error - the EA simply places nothing, which is indistinguishable from a
| |
//--- strategy that found no setups. This repo lost a session to exactly that (AlgoTrading off).
| |
//--- Not fatal in the tester, where some of these read differently, so it reports rather than
| |
//--- refuses - but it always reports.
| |
string tradeWhy = "";
| |
if(!TCTradingPermitted(tradeWhy))
| |
PrintFormat("OnInit: ⚠ TRADING IS NOT CURRENTLY POSSIBLE - %s", tradeWhy);
| |
ENUM_ORDER_TYPE_FILLING fill = ORDER_FILLING_FOK;
| |
string fillWhy = "";
| |
TCPickFilling(Symbol(), fill, fillWhy);
| |
PrintFormat("OnInit: filling mode %s, %s account.", EnumToString(fill),
| |
(TCIsHedging() ? "hedging" : "netting"));
| |
//--- THE ACCOUNT'S KILL SWITCH, PRINTED. This broker is a prop firm and its account carries a
| |
//--- money-mode stop-out at 95% of deposit; the tester inherits it and ENDS THE RUN the moment
| |
//--- balance crosses it - "stop out occurred on 33% of testing interval", final balance 4747.48.
| |
//--- Four symbols across three sweeps were written off as broken history or margin faults on
| |
//--- that evidence. They had simply lost 5%. Every run that ended early sat just past -250 on a
| |
//--- 5,000 deposit and every run that did not, did not.
| |
const ENUM_ACCOUNT_STOPOUT_MODE soMode = (ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);
| |
PrintFormat("OnInit: stop-out mode %s, margin call at %.2f, stop out at %.2f, balance %.2f - "
| |
"%s",
| |
EnumToString(soMode),
| |
AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL), AccountInfoDouble(ACCOUNT_MARGIN_SO_SO),
| |
AccountInfoDouble(ACCOUNT_BALANCE),
| |
(soMode == ACCOUNT_STOPOUT_MODE_MONEY
| |
? "a MONEY stop-out ends a backtest at that equity, whatever the margin level"
| |
: "percent mode - a stop-out needs the margin level to fall this low"));
| |
EventSetTimer(60);
| |
| |
//--- THE RESOLVED LADDER. The threshold is a percentage of the ensemble's weight, so what it
| |
//--- demands depends on how many filters are registered - state it rather than let it be assumed.
| |
const int members = ExtSignal.FilterCount();
| |
if(members > 0)
| |
PrintFormat("OnInit: %d filter(s) registered, so one unopposed voter at full weight scores"
| |
" %.2f%%. ThresholdOpen = %d%% therefore needs about %d of them agreeing."
| |
" ThresholdClose = %d%%, direction %s.",
| |
members, 100.0 / members, (int)ThresholdOpen,
| |
(int)MathCeil((double)ThresholdOpen * members / 100.0),
| |
(int)ThresholdClose, EnumToString(Direction));
| |
return INIT_SUCCEEDED;
| |
}
| |
//+------------------------------------------------------------------+
| |
void OnDeinit(const int reason)
| |
{
| |
EventKillTimer();
| |
if(ExtSignal != NULL)
| |
ExtSignal.ReportLadder();
| |
if(ExtDbReady)
| |
{
| |
ExtJournal.Flush();
| |
ExtJournal.Report();
| |
ExtDbm.CloseDatabase();
| |
}
| |
ExtExpert.Deinit();
| |
if(TradeLog)
| |
WriteTradeLog("warrior", Symbol(), (long)Magic);
| |
}
| |
//+------------------------------------------------------------------+
| |
void OnTick()
| |
{
| |
ExtExpert.OnTick();
| |
}
| |
//+------------------------------------------------------------------+
| |
//| The queue is drained here and nowhere else. Writing per |
| |
//| evaluation would put SQLite in the tick path; the connection is |
| |
//| opened once at init and held for the whole run. |
| |
//+------------------------------------------------------------------+
| |
//--- HOW OFTEN THE WEIGHTS ARE RE-DERIVED, in days of market time. The ranking pass is ~100 SQL
| |
//--- aggregates; run every timer tick it would dominate a decade-long pass, and run once at init
| |
//--- it would freeze the weights on whatever the first days happened to show. Daily is the natural
| |
//--- cadence for a swing EA: often enough to track a module going bad, rare enough to be free.
| |
#define WARRIOR_RERANK_DAYS 1
| |
datetime ExtLastRerank = 0;
| |
| |
void OnTimer()
| |
{
| |
if(ExtDbReady)
| |
{
| |
//--- FLUSH BEFORE RANKING, always. The ranking reads the database, not the queue, so a
| |
//--- rerank on an unflushed buffer silently scores the module on everything EXCEPT its most
| |
//--- recent evidence - which is the evidence most likely to have changed the answer.
| |
ExtJournal.Flush();
| |
const datetime now = TimeCurrent();
| |
if(now - ExtLastRerank >= WARRIOR_RERANK_DAYS * 86400)
| |
{
| |
ExtLastRerank = now;
| |
if(DB_RankingFeedsWeights)
| |
ExtSignal.Rerank(GetPointer(ExtDbm));
| |
}
| |
}
| |
ExtExpert.OnTimer();
| |
}
| |
//+------------------------------------------------------------------+
| |
void OnTrade()
| |
{
| |
ExtExpert.OnTrade();
| |
}
| |
//+------------------------------------------------------------------+
|