z=+2.81 and z=+3.70) silently failed this guard with zero log output.
A walkthrough of what happens between a z-score crossing threshold and a real order placed at the broker — including the 27 May fix.
For EURUSD, the z-score comes from a rolling-OLS lag model on US-DE 2Y spread changes, evaluated on 15-minute bars. For USDJPY, it's a 30-day rolling z-score of the US-JP 2Y spread level, evaluated hourly. Different signal pipelines, same downstream logic.
The check fires on cadence — EURUSD every 15 minutes at :01, :16, :31, :46, USDJPY hourly at :02. If z stays below threshold, the check logs "No signal" and exits. If z crosses threshold, we move to the session test.
state["position_signal"] as +1 or −1 respectively.
The filters run in order. Each must pass before the next is evaluated:
z=+2.81 and z=+3.70) silently failed this guard with zero log output.
df.iloc[-2] — the just-completed prior 15M bar with a full window of price action — instead of df.iloc[-1], the in-progress current bar. Second: if the guard ever fires again, the log will record the z-score, direction, pull in pips, and full bar OHLC. No more silent misses, ever.
0.786 is the deepest commonly-used Fibonacci ratio. The model uses it to wait for a near-full retracement of the signal bar's range before entering. For a LONG signal, this means waiting for price to dip back down toward the bar's low. For a SHORT, waiting for price to rally up toward the bar's high.
The retracement target is computed from the just-completed bar's close, high, and low. Once armed, fast-polling starts at 1-second cadence, waiting up to 6 hours for price to tap the target.
This is the part that trips people up. The model does NOT draw the Fib across the whole bar (high to low). It uses only the upper portion for a SHORT — from the body close up to the high (wick top). Most charting tools default to spanning the full candle, which puts the 0.786 level at a completely different price.
SHORT recipe:
1. Anchor 0% at the body CLOSE (1.16686)
2. Anchor 100% at the wick HIGH (1.16745)
3. Read the 0.786 level → 1.16732
4. That's the model's entry target.
For a LONG, flip it: 0% at close, 100% at the wick LOW, read the 0.786 level below the close.
Fast polling runs in a daemon thread separate from the signal check scheduler. Every second, v3 queries mt5.symbol_info_tick() for the latest bid/ask and compares to the armed target. The check is direction-aware: for LONG, it watches the ask to dip to target; for SHORT, the bid to rally to target.
The polling cadence was bumped from 5 seconds to 1 second after the first v3-era trade slipped 5 pips on a fast move. Sub-second checks would cost CPU without meaningfully improving fills since broker tick streams update at roughly 100ms cadence anyway.
If 6 hours pass without a tap, the arm expires. No trade entered. No telegram. v3 logs "6h expiry" and disarms cleanly.
TP and SL are computed as fixed percentages of the actual fill price (not the Fib target). For EURUSD: TP at 0.20% in favor, SL at 0.25% against. For USDJPY: TP at 0.70%, SL at 0.40%. Same percentages used in the validated backtest.
The brackets live at the broker. If v3 disconnects, crashes, or the VPS reboots, MT5 still enforces TP/SL automatically. v3's job at that point is just to detect that the position closed (via positions_get) and write the close to trade history with the real P&L from MT5's deal records.