geforkt von animatedread/Warrior_EA
Three related changes, all aimed at work being repeated at a frequency nobody chose. 1. OnDeinit gets a tester/optimizer fast path. Everything in the live teardown exists to leave a CHART clean and a live model's state on disk. An optimization agent has neither. It was still running, on EVERY pass: a per-signal arrow-sidecar WRITE (ShutdownChartCleanup -> PersistAndClearChartSignals) plus two full chart-object scans plus a ChartRedraw. At optimization scale that is hundreds of thousands of pointless file writes per agent, against a ~4,500 ms budget MetaTrader force-terminates on - the shape of thing that stalls an agent rather than failing it. The fast path keeps MarkShutdown() and FlushTrainRun() (so a killed pass never leaves a half-written era) and still calls dbm.Deinit() and Expert.Deinit() - leaking the signal tree or a handle across passes is its own way to accumulate into a stall. The two now-unreachable !isTesterRun guards further down are folded away. 2. All four tester handlers are present and documented by WHERE THEY RUN. OnTesterInit/OnTesterPass/OnTesterDeinit run in the CONTROLLING TERMINAL once per session; only OnTester runs on the agent, per pass. OnTesterPass was missing entirely - added empty and deliberately so: it only fires for passes that shipped FrameAdd() data, which this EA never sends, and reading frames there would put per-pass work on the terminal's critical path. Declared so that adding frame-sending later fails loudly instead of silently dropping every frame. 3. Expert_EveryTick is now actually enforced. It was passed to Expert.Init() and only ever reached StartIndex() - which bar a signal READS. The whole pipeline still ran on every quote. It now gates m_signal.SetDirection() in CExpertCustom::Processing(): that call drives Direction(), which is a TRANSACTION (NN forward passes, DB rows, chart arrows, one-shot vote state), and re-running it on every tick of a 4-hour bar repeats all of it. Scoped deliberately. Everything after that line still runs per tick - CheckReverse/CheckClose/CheckTrailingStop and pending-order maintenance are risk management, and a stop that only trails at bar boundaries is a different strategy, not a faster one. The scheduled close-all in OnTick() matches a +-1 MINUTE window, so bar-gating it on H4 would step straight over the thing 100% of label timeouts already resolve against. g_riskBudget.Update() also stays at quote frequency, by design. System/NewBar.mqh becomes CNewBar, a class. The free function it replaced had zero callers and kept its watermark in a `static`: ONE watermark shared by every caller, so the first caller each tick consumed the transition and every other caller was told "no new bar" for a bar that had just opened. Per-instance state fixes that; first observation counts as new, so a fresh attach acts immediately instead of idling up to a full bar. Compile-verified in _claude_stage: 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
64 Zeilen
3,4 KiB
MQL5
64 Zeilen
3,4 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| NewBar.mqh |
|
|
//| AnimateDread |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "AnimateDread"
|
|
#property link "https://www.mql5.com"
|
|
#ifndef WARRIOR_SYSTEM_NEWBAR_MQH
|
|
#define WARRIOR_SYSTEM_NEWBAR_MQH
|
|
//+------------------------------------------------------------------+
|
|
//| "HAS A NEW BAR OPENED SINCE I LAST ASKED?" |
|
|
//| |
|
|
//| A CLASS, not the free function this file used to hold, and the |
|
|
//| reason is the one piece of state involved: the last bar time. A |
|
|
//| free function has to keep that in a `static`, which makes it ONE |
|
|
//| watermark shared by every caller - so the first caller each tick |
|
|
//| consumes the transition and every other caller is told "no new |
|
|
//| bar" for a bar that just opened. That is silent and direction- |
|
|
//| dependent, which is the worst shape a bug can have. One instance |
|
|
//| per consumer, each with its own watermark, and the question is |
|
|
//| answerable by as many callers as want to ask it. |
|
|
//| |
|
|
//| FIRST CALL COUNTS AS NEW. A gate built on this runs the moment |
|
|
//| the EA attaches rather than sitting idle until the next bar |
|
|
//| closes - on H4 that would be up to four hours of a live EA |
|
|
//| deliberately doing nothing. |
|
|
//+------------------------------------------------------------------+
|
|
class CNewBar
|
|
{
|
|
private:
|
|
datetime m_lastTime;
|
|
bool m_seen; // false until the first successful read
|
|
|
|
public:
|
|
CNewBar(void) : m_lastTime(0), m_seen(false) {}
|
|
//--- Reset the watermark, so the next call reports a new bar again.
|
|
void Reset(void) { m_lastTime = 0; m_seen = false; }
|
|
//--- The bar time this instance last accepted (0 = never read one).
|
|
datetime LastBarTime(void) const { return m_lastTime; }
|
|
//--- True exactly once per opened bar of `symbol`/`period` (defaults: the chart's own).
|
|
bool IsNewBar(const string symbol = NULL, const ENUM_TIMEFRAMES period = PERIOD_CURRENT)
|
|
{
|
|
string sym = (symbol == NULL || symbol == "") ? _Symbol : symbol;
|
|
datetime cTime[];
|
|
ArraySetAsSeries(cTime, true);
|
|
//--- CopyTime can fail or come back empty (history not yet synced right after attach, or a gap
|
|
//--- on an illiquid symbol). Indexing cTime[0] unguarded would raise "array out of range" and
|
|
//--- abort the whole tick. Answering "not a new bar" is the safe direction to fail: the caller
|
|
//--- skips this tick's bar-boundary work and asks again on the next one.
|
|
if(CopyTime(sym, period, 0, 1, cTime) <= 0 || ArraySize(cTime) == 0)
|
|
return false;
|
|
if(!m_seen)
|
|
{
|
|
m_seen = true;
|
|
m_lastTime = cTime[0];
|
|
return true; // first observation - see the header note
|
|
}
|
|
if(cTime[0] <= m_lastTime)
|
|
return false;
|
|
m_lastTime = cTime[0];
|
|
return true;
|
|
}
|
|
};
|
|
#endif // WARRIOR_SYSTEM_NEWBAR_MQH
|