forked from 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.
237 lines
11 KiB
MQL5
237 lines
11 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| WarriorEnums.mqh |
| |
//| AnimateDread |
| |
//| |
| |
//| EVERY INPUT THIS EA TAKES IS AN ENUM, and each enum's VALUE IS |
| |
//| ITS PAYLOAD. SL_ATR_x2 is literally 2, RISK_PCT_3 is literally 3, |
| |
//| so a consumer writes `m_atr * SL_Mode` with no lookup table and |
| |
//| no switch that can fall out of step with the list. The MT5 |
| |
//| optimizer steps over members, so a sweep can only ever produce a |
| |
//| legal configuration - which is the real reason for the dropdowns. |
| |
//| |
| |
//| ORDINALS ARE PINNED AND NEVER REUSED. MT5 stores an input's VALUE |
| |
//| in the .set file and in each chart's .chr, and does not validate |
| |
//| it against the current enum. Renumbering a member therefore |
| |
//| silently re-points every saved configuration at a different |
| |
//| setting. Gaps in the sequence are deliberate headstones for |
| |
//| members that were removed; leave them. |
| |
//+------------------------------------------------------------------+
| |
#ifndef WARRIOR_SIMPLE_ENUMS_MQH
| |
#define WARRIOR_SIMPLE_ENUMS_MQH
| |
| |
//--- Which way this chart is allowed to trade. LONG_ONLY exists because an index is not symmetric;
| |
//--- it is a legitimate setting, not a crutch. Value 1 was the default a stale tester preset once
| |
//--- supplied to every run, turning a two-sided EA long-only for days, so read the init echo.
| |
enum WARRIOR_DIRECTION
| |
{
| |
DIR_BOTH = 0, // Both directions
| |
DIR_LONG = 1, // Long only
| |
DIR_SHORT = 2, // Short only
| |
};
| |
| |
//--- Position sizing. 1 is deliberately vacant: it was INTELLIGENT, which staked real money on an
| |
//--- uncalibrated confidence number (and in an earlier form increased size after a losing streak).
| |
//--- It is not coming back, and the hole stops a saved "1" from selecting something else.
| |
enum WARRIOR_MONEY_MODE
| |
{
| |
MONEY_FIXED_RISK = 0, // Risk a fixed % of balance
| |
MONEY_FIXED_LOT = 2, // Fixed lot size
| |
};
| |
| |
//--- Risk per trade, as a whole percent of balance. The value IS the percent.
| |
enum WARRIOR_RISK_PCT
| |
{
| |
RISK_1 = 1, // 1%
| |
RISK_2 = 2, // 2%
| |
RISK_3 = 3, // 3%
| |
RISK_5 = 5, // 5%
| |
};
| |
| |
//--- Risk per trade in HUNDREDTHS of a percent of equity - the value / 100 IS the percent. A new
| |
//--- enum, not new members of WARRIOR_RISK_PCT: that one's values are whole percents, and a saved
| |
//--- "RiskPercent=1" re-read as 0.01% would size every trade at a hundredth of what was chosen. The
| |
//--- input it feeds has a new name for the same reason - an old preset simply does not bind to it.
| |
//--- The prop-firm book runs 0.25%: four correlated index charts under a 0.75% open-risk cap.
| |
enum WARRIOR_RISK
| |
{
| |
RISK_0_10 = 10, // 0.10%
| |
RISK_0_25 = 25, // 0.25% (the validated dip-buy book)
| |
RISK_0_50 = 50, // 0.50%
| |
RISK_0_75 = 75, // 0.75%
| |
RISK_1_00 = 100, // 1.00%
| |
RISK_2_00 = 200, // 2.00%
| |
};
| |
| |
//--- The stop, in ATR from entry. The value IS the multiple.
| |
enum WARRIOR_SL_MODE
| |
{
| |
SL_ATR_x1 = 1, // ATR x 1
| |
SL_ATR_x2 = 2, // ATR x 2
| |
SL_ATR_x3 = 3, // ATR x 3
| |
SL_ATR_x4 = 4, // ATR x 4
| |
};
| |
| |
//--- The target, in ATR from entry. 0 = none, run it on the trail alone.
| |
enum WARRIOR_TP_MODE
| |
{
| |
TP_NONE = 0, // No target (trail only)
| |
TP_ATR_x1 = 1, // ATR x 1
| |
TP_ATR_x2 = 2, // ATR x 2
| |
TP_ATR_x3 = 3, // ATR x 3
| |
TP_ATR_x4 = 4, // ATR x 4
| |
TP_ATR_x6 = 6, // ATR x 6
| |
TP_ATR_x8 = 8, // ATR x 8
| |
TP_ATR_x10 = 10, // ATR x 10
| |
};
| |
| |
//--- The trailing stop, in ATR behind price. The value IS the multiple; 0 disables it.
| |
enum WARRIOR_TRAIL_MODE
| |
{
| |
TRAIL_NONE = 0, // No trailing stop
| |
TRAIL_ATR_x1 = 1, // Trail at ATR x 1
| |
TRAIL_ATR_x2 = 2, // Trail at ATR x 2
| |
TRAIL_ATR_x3 = 3, // Trail at ATR x 3
| |
};
| |
| |
//--- THE VOTE THRESHOLD, as a percent of the ensemble's weight.
| |
//---
| |
//--- ⚠ WHAT THIS MEANS DEPENDS ON THE ROSTER, and that is the single easiest way to misread this EA.
| |
//--- CExpertSignal::Direction() divides the weighted sum by the NUMBER OF FILTERS THAT ANSWERED, so
| |
//--- one unopposed voter at full weight scores 100/N. With 19 filters registered that is 5.3%, and a
| |
//--- threshold of 15% therefore demands three agreeing voters, not one. Change the roster and every
| |
//--- rung on this list means something different. The EA prints the resolved ladder at init for
| |
//--- exactly this reason - read that, not this comment.
| |
//---
| |
//--- The sub-5% rungs exist because a large roster pushes the whole useful range down there.
| |
enum WARRIOR_THRESHOLD
| |
{
| |
THR_1 = 1, // 1%
| |
THR_2 = 2, // 2%
| |
THR_3 = 3, // 3%
| |
THR_4 = 4, // 4%
| |
THR_5 = 5, // 5%
| |
THR_10 = 10, // 10%
| |
THR_15 = 15, // 15%
| |
THR_20 = 20, // 20%
| |
THR_25 = 25, // 25%
| |
THR_30 = 30, // 30%
| |
THR_40 = 40, // 40%
| |
THR_50 = 50, // 50%
| |
};
| |
| |
//--- THE PROBABILITY CUT for anything that outputs a calibrated probability - the neural module.
| |
//--- Stored as integer percent, read as value/100.0.
| |
//---
| |
//--- 50 IS "NO CUT" AND IS THE RIGHT DEFAULT until a trained model's calibration has been measured
| |
//--- on the instrument it will trade. A threshold chosen before a calibration curve exists is a
| |
//--- guess wearing a setting's clothes, and it will look like a decision in the .set file forever.
| |
//---
| |
//--- ⚠ FOR THE FRACTAL NET, EXPECT THE USEFUL CUT TO BE LOW, and to act as a floor rather than a
| |
//--- trigger: measured mean R by predicted-probability quintile was -0.230 / -0.089 / -0.026 /
| |
//--- +0.002 / +0.017 across 20 series. Nearly all of the separation is in refusing the bottom, not
| |
//--- in selecting the top - so raising this hunting for a "high conviction" band is chasing a part
| |
//--- of the curve that was measured flat.
| |
enum WARRIOR_CONFIDENCE
| |
{
| |
CONF_50 = 50, // 0.50 - no cut
| |
CONF_55 = 55, // 0.55
| |
CONF_60 = 60, // 0.60
| |
CONF_65 = 65, // 0.65
| |
CONF_70 = 70, // 0.70
| |
CONF_75 = 75, // 0.75
| |
CONF_80 = 80, // 0.80
| |
CONF_90 = 90, // 0.90
| |
};
| |
//--- A MODULE'S WEIGHT IN THE VOTE, as a percent. The value IS the percent, so WEIGHT_100 is 1.0.
| |
//---
| |
//--- These are SWEPT BY THE OPTIMISER, alongside the threshold and the ATR multiples, and that is
| |
//--- the deliberate division of labour with the database: the journal sets the 49 PATTERN weights
| |
//--- from measured expectancy (far too many to optimise), the optimiser sets the 12 MODULE weights
| |
//--- (few enough to be tractable, and coarse enough that a genetic pass can explore them).
| |
//---
| |
//--- WEIGHT_0 IS THE USEFUL ONE. A module at zero is switched off without being deleted, so the
| |
//--- optimiser can discover that an indicator contributes nothing - which is the question this EA
| |
//--- has never been able to ask, because every module has always voted at weight 1.0 by default.
| |
enum WARRIOR_WEIGHT
| |
{
| |
WEIGHT_0 = 0, // 0.0 - silent
| |
WEIGHT_25 = 25, // 0.25
| |
WEIGHT_50 = 50, // 0.50
| |
WEIGHT_75 = 75, // 0.75
| |
WEIGHT_100 = 100, // 1.00 - the default
| |
WEIGHT_150 = 150, // 1.50
| |
WEIGHT_200 = 200, // 2.00
| |
};
| |
//--- MOVE THE STOP TO ENTRY once the trade is this many TENTHS of R in front. The value IS the
| |
//--- tenths, so BE_AT_0R5 is 5 and the consumer divides by 10.
| |
//---
| |
//--- ⚠ THIS IS NOT A TRAILING STOP AND MUST NOT BE CONFUSED WITH ONE. Measured 2026-09-11: every
| |
//--- ATR-trail setting made results WORSE (+150.67 -> +90.56 at 1 ATR, worse as it widened),
| |
//--- because a trail keeps moving and exits the winner on its first pullback. Breakeven moves
| |
//--- ONCE and then stands still, so it scratches the loser without capping the winner. On 5,491
| |
//--- traded-side firings the counterfactual was mean R -0.132 -> +0.216 at +0.5R.
| |
enum WARRIOR_BREAKEVEN
| |
{
| |
BE_OFF = 0, // Never move the stop to entry
| |
BE_AT_0R5 = 5, // +0.5 R
| |
BE_AT_0R7 = 7, // +0.7 R
| |
BE_AT_1R0 = 10, // +1.0 R
| |
BE_AT_1R5 = 15, // +1.5 R
| |
};
| |
//--- MANAGEMENT MODEL CUT. At +0.5R the model is asked "does this continue?"; below this
| |
//--- probability the trade is closed at the crossing instead of being left to the stop. The value
| |
//--- IS the percent. MGMT_OFF disables both the model and its training walk.
| |
//---
| |
//--- ⚠ 46% of crossings continue and 54% retrace, so a cut of 50 acts on almost every trade while
| |
//--- a cut of 30 acts only where the model is confident. Which is right depends on the model's
| |
//--- calibration, which is why this is an input and not a constant - and why the net prints its
| |
//--- held-out AUC: a cut applied to a coin-flip model is just a random early exit.
| |
enum WARRIOR_MGMT_CUT
| |
{
| |
MGMT_OFF = 0, // No management model
| |
MGMT_CUT30 = 30, // exit when P(continue) < 0.30
| |
MGMT_CUT40 = 40, // < 0.40
| |
MGMT_CUT50 = 50, // < 0.50
| |
MGMT_CUT60 = 60, // < 0.60
| |
};
| |
//--- FADE THE CROWD. When at least this many filters vote the SAME way, take the OTHER side; when
| |
//--- fewer do, stand aside entirely. The value IS the voter count.
| |
//---
| |
//--- WHY THIS EXISTS. Measured on 32,409 traded bar-sides (4 majors, H4, 2015-2026) the ensemble's
| |
//--- conviction is monotonically ANTI-predictive: agree=12 -0.27R, agree=13 -0.61R, in both halves,
| |
//--- on every symbol. Priced exactly from the journal as a symmetric-1R fade (wins iff the traded
| |
//--- side touched -1R before +1R, read from mfeR, ties scored AGAINST the fade): agree>=12 gives
| |
//--- +0.294R in-sample and +0.323R out of sample, every symbol, every t >= 3.5. The mechanism is
| |
//--- plain: these are lagging indicators, the vote fires after the move, and when ALL of them agree
| |
//--- the move is mature. Unanimity is exhaustion.
| |
//--- USE WITH StopMode == TargetMode (1R/1R). The measurement was symmetric; an asymmetric fade is
| |
//--- a different trade that has not been priced.
| |
enum WARRIOR_FADE
| |
{
| |
FADE_OFF = 0, // Normal voting
| |
FADE_AGREE_11 = 11, // Fade when >= 11 filters agree (+0.15R measured)
| |
FADE_AGREE_12 = 12, // Fade when >= 12 agree (+0.29R measured)
| |
FADE_AGREE_13 = 13, // Fade only unanimity (+0.6R measured, rare)
| |
};
| |
//--- THE DIP-BUY'S TWO MEASURED FORMS (Signals\SignalDipBuy.mqh has the numbers): the daily RSI(2)
| |
//--- Connors form, and the z-score form that the seven-style scan found to be the deepest and the
| |
//--- only one with H4 cadence (~15 trades a month on SP500).
| |
enum WARRIOR_DIP_ENTRY
| |
{
| |
DIP_RSI2 = 0, // RSI(2) at or below RsiEntry (the daily Connors form)
| |
DIP_ZSCORE = 1, // close below the 20-bar mean by DipZ standard deviations
| |
};
| |
//--- THE MIND (Mind\WarriorMind.mqh): the trader behind the signal. It reads the closed signal bar
| |
//--- through several modalities, books every trade with what it saw, and reviews that book.
| |
//--- OBSERVE changes nothing that is traded. The default: run a backtest, then read the book.
| |
//--- SIZE trades that the journal rates below the book's average get LESS risk (never more).
| |
//--- SIZE_GATE as SIZE, and a setup the journal rates a coin flip or worse is skipped.
| |
//--- Both acting modes stay silent until MindMinTrades have closed.
| |
enum WARRIOR_MIND
| |
{
| |
MIND_OFF = 0, // Off
| |
MIND_OBSERVE = 1, // Observe: book every trade with its context, change nothing
| |
MIND_SIZE = 2, // Size: journal review scales risk down, never up
| |
MIND_SIZE_GATE = 3, // Size + stand aside when the journal says coin flip
| |
};
| |
#endif // WARRIOR_SIMPLE_ENUMS_MQH
|