2026-09-11 06:16:08 -04:00 | | | //+------------------------------------------------------------------+
|
| | | //| WarriorMoney.mqh |
|
| | | //| AnimateDread |
|
| | | //| |
|
| | | //| Position sizing: the standard library's shape, with the one hole |
|
| | | //| in it closed. |
|
| | | //| |
|
| | | //| 🛑 THE HOLE. MQL5's own CMoneyFixedRisk ends with |
|
| | | //| |
|
| | | //| if(lot < minvol) lot = minvol; |
|
| | | //| |
|
| | | //| so when the risk budget buys LESS than one minimum lot, it trades |
|
| | | //| a minimum lot anyway. The requested risk is silently exceeded, |
|
| | | //| by an arbitrary factor that is largest exactly when the stop is |
|
| | | //| widest - and nothing in the log says so. On an index with a 0.10 |
|
| | | //| minimum that can be several times the intended risk. |
|
| | | //| |
|
| | | //| Here a trade that cannot be sized within its risk is REFUSED. A |
|
| | | //| skipped trade is a missing row in the results; an oversized one |
|
| | | //| is a wrong number in them, and only the second kind is dangerous. |
|
| | | //| This repo closed the same hole once before, in 77e8080. |
|
| | | //| |
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | //| SIZED ON EQUITY, NOT BALANCE (2026-09-23). A prop account is |
|
| | | //| judged on equity, and with several charts sharing one account the |
|
| | | //| balance ignores what the other charts' open positions have |
|
| | | //| already lost. The reference dip EA sized on equity; a flat single |
|
| | | //| chart sees no difference. |
|
| | | //| |
|
2026-09-11 06:16:08 -04:00 | | | //| NO RISK BUDGET, NO LATCH, NO PERSISTED STATE. CRiskBudget writes |
|
| | | //| a permanent halt to disk that survives between backtests, so one |
|
| | | //| breaching run silently truncates every later run on that symbol. |
|
| | | //| Account protection belongs in a veto filter that starts fresh |
|
| | | //| each pass, not in the lot-size calculation. |
|
| | | //+------------------------------------------------------------------+
|
| | | #ifndef WARRIOR_SIMPLE_MONEY_MQH
|
| | | #define WARRIOR_SIMPLE_MONEY_MQH
|
| | |
|
| | | #include <Expert\ExpertMoney.mqh>
|
| | |
|
| | | class CWarriorMoney : public CExpertMoney
|
| | | {
|
| | | protected:
|
| | | bool m_fixedLot; // true = trade m_lots flat, ignore m_percent
|
| | | double m_lots;
|
| | | int m_refused; // how many entries could not be sized - reported at deinit
|
2026-09-30 18:36:33 -04:00 | | | double m_scale; // multiple of the base risk for the NEXT entry, 0 < scale <= 1
|
2026-09-11 06:16:08 -04:00 | | |
|
| | | double Size(ENUM_ORDER_TYPE type, double price, double sl);
|
| | |
|
| | | public:
|
2026-09-30 18:36:33 -04:00 | | | CWarriorMoney(void) : m_fixedLot(false), m_lots(0.01), m_refused(0), m_scale(1.0) {}
|
2026-09-11 06:16:08 -04:00 | | | ~CWarriorMoney(void) {}
|
| | |
|
| | | void FixedLot(const double lots) { m_fixedLot = true; m_lots = lots; }
|
| | | void RiskPercent(const double p) { m_fixedLot = false; m_percent = p; }
|
| | | int Refused(void) const { return m_refused; }
|
2026-09-30 18:36:33 -04:00 | | | //--- The Mind's hook. It may only take risk OFF (clamped to (0, 1]); a fixed-lot book has no risk
|
| | | //--- unit to scale and ignores it. Set per entry and reset by its owner - never persisted.
|
| | | void RiskScale(const double s) { m_scale = MathMax(0.05, MathMin(1.0, s)); }
|
| | | double RiskScale(void) const { return m_scale; }
|
2026-09-11 06:16:08 -04:00 | | |
|
| | | virtual double CheckOpenLong(double price, double sl) override
|
| | | { return Size(ORDER_TYPE_BUY, price, sl); }
|
| | | virtual double CheckOpenShort(double price, double sl) override
|
| | | { return Size(ORDER_TYPE_SELL, price, sl); }
|
| | | };
|
| | | //+------------------------------------------------------------------+
|
| | | double CWarriorMoney::Size(ENUM_ORDER_TYPE type, double price, double sl)
|
| | | {
|
| | | if(m_symbol == NULL)
|
| | | return 0.0;
|
| | | const double minvol = m_symbol.LotsMin();
|
| | | const double maxvol = m_symbol.LotsMax();
|
| | | const double step = m_symbol.LotsStep();
|
| | |
|
| | | if(m_fixedLot)
|
| | | {
|
| | | double lot = m_lots;
|
| | | if(lot < minvol) lot = minvol;
|
| | | if(lot > maxvol) lot = maxvol;
|
| | | return lot;
|
| | | }
|
| | | //--- NO STOP MEANS NO RISK UNIT, so there is nothing to size against. The stdlib answers this
|
| | | //--- case with "the largest lot the margin allows", which is the opposite of a risk rule.
|
| | | if(sl == 0.0 || price <= 0.0)
|
| | | {
|
| | | m_refused++;
|
| | | return 0.0;
|
| | | }
|
| | | //--- Loss per one lot between entry and stop, asked of the broker rather than derived from the
|
| | | //--- point value - it is the only source that knows this symbol's contract and currency.
|
| | | const double lossPerLot = -m_account.OrderProfitCheck(m_symbol.Name(), type, 1.0, price, sl);
|
| | | if(lossPerLot <= 0.0)
|
| | | {
|
| | | //--- An inverted or zero-distance stop. Refuse: dividing by it produces either a negative lot
|
| | | //--- or an enormous one, and both have reached live accounts in this repo's history.
|
| | | m_refused++;
|
| | | return 0.0;
|
| | | }
|
2026-09-30 18:36:33 -04:00 | | | const double riskMoney = m_account.Equity() * m_percent / 100.0 * m_scale;
|
2026-09-11 06:16:08 -04:00 | | | double lot = (step > 0.0) ? MathFloor(riskMoney / lossPerLot / step) * step
|
| | | : riskMoney / lossPerLot;
|
| | | if(lot < minvol)
|
| | | {
|
| | | //--- THE REFUSAL. Rounding up to the minimum here is what the stdlib does and what this class
|
| | | //--- exists not to do. Said out loud, throttled to once per 50, because a silent skip is how
|
| | | //--- "the EA barely traded" becomes a mystery instead of a message.
|
| | | m_refused++;
|
| | | if(m_refused % 50 == 1)
|
| | | PrintFormat("CWarriorMoney: refused - %.2f%% of %.2f is %.2f, and one minimum lot (%.2f)"
|
| | | " would risk %.2f. Widen the risk, tighten the stop, or accept fewer trades;"
|
| | | " trading a minimum lot here would exceed the stated risk. (%d so far)",
|
 feat(warrior): the vol-gated dip-buy book, ported into Warrior_EA
Warrior's defaults are now the validated book: DIP_ZSCORE alone, long only, H4,
risk 0.25%, one chart per index with a shared Magic.
- System/BarCache.mqh: whole-history closed bars, Wilder ATR, GK sigma and the
expanding vol percentile (no 1024-bar stdlib ceiling)
- System/AccountGuard.mqh: open-risk cap, kill switch, cross-chart lock and
Friday flat, shared through terminal globals by Magic
- CWarriorExpert: guard on every tick; a transient open failure retries the bar
- CWarriorSignal::SetupStop: the dip owns its 3 x Wilder ATR stop from the bid
- SignalDipBuy: no entry vote while holding (a still-dipping time exit never
closed, and Processing re-entered on the exit bar); no entry on a stop bar
- WarriorMoney sizes on equity; WARRIOR_RISK allows fractional risk
- TradeLog + research/compare_ea.py: trade-for-trade check vs WarriorDipZ -
SP500/US30/DAX40 identical to the cent, NAS100 96.9% (stale-quote timer fills)
- research/nn_cross_index.py: pre-registered cross-index NN meta-label - FAIL
(AUC 0.564, CI [0.498, 0.630]); DipMetaCut stays off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 21:14:53 -04:00 | | | m_percent, m_account.Equity(), riskMoney, minvol, minvol * lossPerLot,
|
2026-09-11 06:16:08 -04:00 | | | m_refused);
|
| | | return 0.0;
|
| | | }
|
| | | if(lot > maxvol)
|
| | | lot = maxvol;
|
| | | return lot;
|
| | | }
|
| | | #endif // WARRIOR_SIMPLE_MONEY_MQH
|