Status: built and run 2026-09-06; verdict: FAIL (see research/2026-09-06-entry5m-screen.md). Date: 2026-09-06. Approach: 1 (pyakao first on five years of Tickstory data behind pre-registered criteria; a Pine strategy only if the rule survives).
ZTD (AVWAP-Z x PBK Zero-Touch Divergence) is the bias layer: an H1 divergence fire says which way to trade and roughly how far. This project adds the entry layer Daniel described: on the 5m chart, wait for a pullback against the bias, then enter on the structure break the pullback formed, with the stop behind the pullback and the target at the H1 zero-touch extreme.
The question the project answers is narrow: does "ZTD H1 bias + any 5m pullback + pivot-break stop entry" make money on five years of data, under criteria written down before the run? Everything else (Pine strategy, alerts, sizing polish) is conditional on a pass.
Decisions already taken by Daniel (2026-09-06), reproduced so nothing drifts:
| Question | Decision |
|---|---|
| Setup shape | Structure break after pullback, in the bias direction |
| Bias window | From the divergence fire until target or contradiction (with a bar timeout) |
| Bias target | The H1 zero-touch extreme between range 1 and range 2 ("the range-1 low/high") |
| Pullback level | No level; any 5m pullback qualifies |
| 5m mechanics | Pivot break, stop order; stop-loss behind the pullback extreme; one entry per pullback |
| Order of work | pyakao first, Pine second |
In scope:
pyakao/src/pyakao/entry5m/: bias (H1 ZTD -> bias windows on the 5m clock), setup (5m pivots, pullbacks, orders), execution (fills, exits, R accounting), report (the pre-registered table), screen CLI.pyakao/src/pyakao/ztd.py): per-bar contradiction events and the zero-touch extreme, exported on ZtdRow.Out of scope for this spec: the Pine strategy (section 10 sketches it; it gets its own short spec if section 8 passes), the 15m variant (same code with a different resample, run only as a secondary report), trailing stops, pyramiding, session filters, the ZTD Pine script itself (untouched).
D:/tickstory/History data/<SYMBOL>_mt5_bars.csv, loaded with pyakao.feeds.tickstory.load_tickstory(path, timeframe="5m"), the same loader and server-offset handling the ZMS screen uses.| Symbol | Years | Role |
|---|---|---|
| EURUSD | 5 (2021-03 .. 2026-03) | primary |
| GBPUSD | 5 | reported, but counted as the same test as EURUSD (correlated USD pair) |
| XAUUSD | 5 | independent market |
| EURJPY | 5 | non-USD control |
pyakao.zms.htf.build_htf(bars, "60"), which also indexes the last CLOSED H1 bar for every 5m bar. The bias only ever reads closed H1 bars.entry5m/bias.py)Input. The H1 bar series and a ZtdConfig (defaults, tf_ok=True). ZtdEngine.update is driven once per closed H1 bar.
ZTD port additions (in ztd.py). ZtdRow gains four fields the Pine script computes but does not plot:
contraB, contraU (int 0/1): the "contradicted now" events (bear_contra_now / bull_contra_now, already inside ZtdRuleOut).midLoB, midHiU (float, na when not applicable): the zero-touch extreme. midLoB is the lowest bar low seen while the bear side is in state 2 (locked: after the zero touch, before range 2 opens), reset when the side leaves state 2 for state 1 or 0; carried unchanged through state 3 so it is readable on the fire bar. midHiU mirrors with highs. The golden test keeps comparing only the exported plot titles, so these extra fields do not disturb parity.Bias record. Bias(side, start_h1, start_5m, ref, ext, target, deadline_h1, kind):
side: -1 for sigBear (RDIV-/HDIV-), +1 for sigBull.ref: fRefB / fRefU (range-1 extreme), informational.ext: the range-2 extreme fCurB / fCurU; updated when the port reports an extension (ext != 0 on the same side).target: midLoB for a short bias, midHiU for a long bias, read on the fire bar. If it is na (cannot happen after a valid fire, but guard it) the fire is ignored and counted in the report as "no target".kind: RDIV when abs(sig) == 2, else HDIV (same code as the combo study).deadline_h1: fire bar index + timeout_h1 (default 96). Every extension on the same side sets deadline_h1 = ext bar index + timeout_h1. Extensions do not restart the bias or move the target.Activation on the 5m clock. A bias fired on H1 bar k becomes active on the first 5m bar whose open time is at or after the close time of H1 bar k. Identical for every later H1 event (contradiction, timeout, opposite fire): the event is known at the H1 close and takes effect on the next 5m bar.
End of a bias, whichever comes first:
low <= target; long: high >= target). Ends at that bar's close (any open trade was already exited at the target inside that bar, section 6).contraB / contraU on the bias side at an H1 close.deadline_h1.sigBull while a short bias is active replaces it (and vice versa). The new bias starts normally.One bias per side at a time; a same-side fire while that side is active replaces the record (new ref, ext, target, deadline). The report counts how each bias ended.
entry5m/setup.py)All rules stated for a short bias; the long side mirrors every high/low, min/max and inequality. Bars are closed 5m bars; nothing looks at bar i+1 when deciding on bar i.
Pivots. pivot_high(N) at bar p: high[p] is greater than or equal to the highs of the N bars before it and strictly greater than the highs of the N bars after it (Pine ta.pivothigh(N, N) semantics, validated 2026-09-07); it is confirmed at bar p+N. Default N = 3. Pivot lows mirror.
Pullback. A confirmed pivot high at bar p is a pullback for the short bias if:
origin = argmin(low[q..p]) where q is the bar after the previous confirmed pivot high since the bias started, or the bias start bar if there is none, and p - origin >= min_pb_bars (default 3).Origin low = the swing the pullback formed; its break is the structure break Daniel trades.
Order. On the close of the confirmation bar c = p+N:
sell stop at low[origin] - tick;high[p] + tick;(entry - target) / (stop - entry); if < min_rr (default 1.0), no order (counted as "rr rejected");low of any bar in (p, c] is already <= entry the level is gone: no order (counted as "already broken").Pending-order life. One pending order at a time. It is cancelled when:
high > high[p] (pullback extreme taken out) before the fill;pending_max_bars (default 48 5m bars = 4 hours) elapse without a fill.Position. One open position per instrument. While a position is open no order is placed; pullbacks that confirm meanwhile are counted as "skipped, in position". After an exit the bias, if still active, can produce the next entry from the next pullback.
entry5m/execution.py)A small purpose-built broker, not the ZMS Broker (it carries ZMS-specific pyramiding, ratchets and sizing modes that this study does not use).
low <= entry. Fill price = min(open, entry) - slip (a gap below the entry fills at the open). slip = slippage_ticks * mintick from pyakao.feeds.base.spec_for(symbol), the same constants ZMS used.high >= stop, the trade is a loss at the stop (conservative, same as the combo study). If a later bar hits both stop and target, it is a loss.high >= stop, exit at stop + slip. Target: low <= target, exit at target (limit, no slippage). Bias end (contradiction, timeout, opposite fire): exit at the close of the 5m bar on which the end takes effect, minus slip. Time stop: max_hold_bars (default 0 = off) bars after the fill, exit at close. A pending order may still fill during the 5m bar on which an H1 bias end takes effect; the position is then closed at that bar's close as a bias-end exit.R = pnl / (stop - entry) in risk units, plus MFE and MAE in R. Money accounting is a fixed 1 R = 1% of a notional 10,000 account, only to feed the existing ZMS metric helpers; R is the unit that is judged.entry5m/report.py, entry5m/screen.py)python -m pyakao.entry5m.screen --symbols EURUSD,GBPUSD,XAUUSD,EURJPY runs the primary cell on each symbol and prints one table with, per symbol:
A second table, clearly labelled sensitivity, not judged, repeats net R / PF / trades for N in {2, 3, 5} and min_rr in {1.0, 1.5}. It exists to show fragility, not to pick a cell.
--tf 15 reruns the same code on a 15m resample as a secondary report.
The primary cell is: N = 3, min_pb_bars = 3, timeout_h1 = 96, min_rr = 1.0, pending_max_bars = 48, max_hold_bars = 0, ZTD defaults. Nothing in this cell is tuned after seeing results.
The rule passes only if ALL of the following hold on at least two of the three independent groups {EURUSD+GBPUSD (judged on EURUSD; GBPUSD net R must not be negative), XAUUSD, EURJPY}:
Anything else is a fail, and the rule as specified is dead. A fail is not followed by a parameter search; if Daniel wants a variant, it gets its own pre-registration before running. The sensitivity table is reported either way.
midLoB equals the minimum low over the state-2 bars for every fire in a synthetic run, and that contraB is set exactly on bars where flagBear later reports the contradiction bit.ZtdRow fields ignored by design).A strategy script in entry5m/ on the 5m chart that computes the ZTD H1 core through request.security on "60" and reproduces sections 4-6 with strategy.entry(... stop=...) and strategy.exit. It would be verified against pyakao's trade list bar-for-bar on the TradingView window before any live use. Not started until section 8 passes.
use_volume default. The two feeds still differ bar by bar, so any parity check between pyakao and TradingView on H1 must use the CDP export, as the ZTD golden test does.