//+------------------------------------------------------------------+ //| SignalRiskGuard.mqh | //| AnimateDread | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "AnimateDread" #property link "https://www.mql5.com" #include "..\Expert\ExpertSignalCustom.mqh" #include "..\Variables\RiskBudget.mqh" //+------------------------------------------------------------------+ //| Class CSignalRiskGuard. | //| Vetoes new entries (the EMPTY_VALUE convention every other filter | //| here uses, via the composite Direction()) whenever the account's | //| loss budget is exhausted. | //| | //| This class USED TO OWN the whole mechanism - thresholds, day-start| //| balance, peak equity, its own state file. It no longer does, and | //| the reason is that a CExpertSignal filter is the wrong place for | //| an account-level hard limit: | //| | //| * Direction() is called by the signal pipeline, and with the | //| shipped Expert_EveryTick=false that is ONCE PER BAR at the bar| //| open. A 4% daily limit checked once an hour on H1 is not a | //| limit. The budget is now evaluated from OnTick()/OnTimer() | //| (Variables\RiskBudget.mqh), at quote frequency, whatever | //| timeframe the signals run on. | //| * A veto can only decline to OPEN. It cannot size a trade to | //| fit the remaining allowance, and it cannot act on a position | //| that is already running toward the limit. Both of those now | //| live in CRiskBudget, which the money manager consults before | //| every lot-size calculation. | //| | //| What remains here is the entry veto alone - kept because the | //| composite-Direction convention is how every other filter blocks a | //| trade, so the journal, the panel and the vote arithmetic all | //| behave consistently when the budget halts trading. | //+------------------------------------------------------------------+ class CSignalRiskGuard : public CExpertSignalCustom { public: virtual double Direction(void); }; //+------------------------------------------------------------------+ //| CRiskBudget::Update() has already run this tick (OnTick calls it | //| before Expert.OnTick()), so this is a pure read - no account | //| queries, no file I/O, nothing that costs anything per bar. | //+------------------------------------------------------------------+ double CSignalRiskGuard::Direction(void) { if(!g_riskBudget.Enabled()) return 0.0; // permanent no-op, same convention as the other disabled filters if(g_riskBudget.Halted()) return EMPTY_VALUE; // veto: the composite Direction() turns this into "no new entries" return 0.0; } //+------------------------------------------------------------------+