Trading Bot HQModels & runsTestsResearchShadow paper

Declared scan: session structure (the twice-surviving signal class)

Date declared: 2026-07-24, before any computation. Split: Research only (BTCUSD 2017-08-17 → 2022-12-31). Layer 3, Edge Lab. Evidence basis: time-of-day filters are the only signal class that held every time it appeared — run 17 H3 (09-16 UTC filter +0.024R net, tail −5.80→−3.44R), run 18 H3, and the mentor's own P&L concentration (09-16 UTC, both accounts). Lineage: hybrid (mentor session observation + our mechanization).

Explicit non-goal: this does not resurrect disp_follow_btc_v1 (Validation FAIL is final). The depth-2 arm studies the event class on the research split — knowledge for future candidates, not a rerun of a dead spec.

Conditions

Base (unsigned vol structure, 16 hypotheses): 1. asia_block (hour 00–07 UTC) — 4 horizons 2. europe_block (hour 08–15) — 4 horizons 3. us_block (hour 16–23) — 4 horizons 4. weekend (Sat/Sun) — 4 horizons

Depth-2 (signed, 8 hypotheses — declared per the Research Program combination rule): 5. disp4_in_window: disp4-class displacement event (frozen scan definition) with event close inside 09:00–15:59 UTC — signed by bar direction — 4 horizons 6. disp4_out_window: same event outside that window — 4 horizons

Combination rule reading (declared): the interaction is real only if disp4_in_window beats BOTH disp4_out_window AND the unconditioned disp4 record (+0.288/+0.456/+0.381/ +0.238 ATR at 15m/30m/1h/4h, on file) by more than the bootstrap CI overlap — otherwise it is a filter shrinking n, and gets logged as such.

Horizons: 15, 30, 60, 240 min. Universe, nulls, engine defaults as prior scans; shuffle null meaningful for the signed arms (per-event direction varies), degenerate for the unsigned base (on record). 24 hypotheses (cumulative 93 → 117 of 2,000).

Pre-declared reading

Honest prior: the base session vol curve is real but well-known physics (log it, condition on it, never trade it alone); weekend suppression likewise. The genuinely open question is #5 vs #6: whether the window where humans (and the mentor) concentrate P&L also concentrates event continuation — if yes, every future continuation-class prereg inherits a session term with sourced numbers; if no, session filters remain a tail-risk tool only (their twice-observed role) and nothing more.

Results (filled 2026-07-24, single ledger-logged run; 24 hypotheses, cumulative 117/2000)

Base session/calendar structure (conditions 1-4): CONFIRMED as pre-declared honest prior — real, well-known physics, not a standalone edge. Depth-2 interaction (conditions 5-6): NOT an interaction — fails the pre-declared combination rule at all 4 horizons; logged as a filter shrinking n, as the declaration specified for this outcome. Full table in results/session-structure-btcusd.json; new additive feature column close_hour_utc added to tradingbot.edgelab.features.build_features (FEATURE_VERSION 1.2.0 → 1.3.0, additive-only, same convention as the H4-ignition columns); runner scripts/run_session_structure_scan.py; 171 tests green (170 + 1 new feature-correctness test for close_hour_utc).

Grid convention / window mapping, stated explicitly: the M5 feature store is OPEN-labeled (a bar's index timestamp is its open; it covers [index, index+5min) and CLOSES at index+5min, same convention as the H4-ignition columns). "Event close time inside 09:00–15:59 UTC" is therefore not the same predicate as hour_of_day (the bar's OPEN hour): the one bar per hour whose open sits at :55 past the hour closes in the next UTC hour. close_hour_utc = (index + 5min).hour was added as its own column rather than re-derived per scan. This makes the window boundary exact and reveals a clean edge case: a disp4 event bar opening at 15:55 closes at 16:00 and is correctly out of window even though its open hour (15) would put it in europe_block; one opening at 08:55 closes at 09:00 and is correctly in window even though its open hour (8) would also read as europe_block-adjacent. Base conditions 1–4, by contrast, use the existing hour_of_day (open-hour) column unchanged — the declaration specifies event-close-time mapping only for the depth-2 arms (5–6), and at n≈187–188k per 8-hour block the one-bar boundary distinction is immaterial. disp4_in_window (n=191) + disp4_out_window (n=326) = 517, exactly the unconditioned disp4 event count on file — confirming the in/out split is an exact, gapless partition of the same 517-event population, not a re-derivation that lost or duplicated events.

Base conditions (unsigned, universe = full valid store: atr14>0, forward-complete, n=563,540 — no baseline_bp restriction, since these are unconditional vol-structure facts, not displacement events):

Condition n Abs mean ATR vs unconditional (rand-p): 15m / 30m / 1h / 4h
asia_block (00–07 UTC) 187,364 0.859/0.877 p=1.000 · 1.207/1.239 p=1.000 · 1.705/1.761 p=1.000 · 3.568/3.687 p=1.000
europe_block (08–15 UTC) 188,011 0.910/0.877 p=.000 · 1.289/1.239 p=.000 · 1.840/1.761 p=.000 · 3.924/3.687 p=.000
us_block (16–23 UTC) 188,165 0.864/0.877 p=1.000 · 1.219/1.239 p=1.000 · 1.738/1.761 p=1.000 · 3.570/3.687 p=1.000
weekend (Sat/Sun) 161,351 0.881/0.877 p=.159 · 1.237/1.239 p=.632 · 1.744/1.761 p=.999 · 3.597/3.687 p=1.000

(No signed stats: unsigned conditions, no direction_col — shuffle null not computed, degenerate for this condition class as declared on record.)

europe_block (08–15 UTC) is the single session significantly elevated above the unconditional baseline at every horizon (rand-p=0.000, all 1000/1000 random draws below it) — +3.8%/+4.1%/+4.5%/+6.4% relative to unconditional at 15m/30m/1h/4h. asia_block and us_block are both mildly suppressed at every horizon (rand-p=1.000: never once exceeded across 1000 draws). weekend is essentially flat — a marginal suppression signal only at 15m (rand-p=.159) that fades to indistinguishable-from-baseline by 1h–4h (rand-p .999/1.000). This matches the pre-declared honest prior exactly: real, session-hour-shaped volatility structure (the Europe/London-into-NY overlap), not itself a standalone edge.

Depth-2 arms (signed by direction_sign, universe = baseline_bp >= 7, exact disp4 event definition from displacement-repro.json):

Condition n Signed mean ATR (shuffle-p): 15m / 30m / 1h / 4h
disp4_in_window (09:00–15:59 UTC close) 191 [UNDERPOWERED] +0.227 (.238) / +0.511 (.023) / +0.439 (.103) / +0.032 (.932)
disp4_out_window (outside that window) 326 +0.324 (.057) / +0.424 (.029) / +0.346 (.117) / +0.358 (.299)
disp4 (unconditioned, on file, n=517)¹ 517 +0.288 / +0.456 / +0.381 / +0.238

¹ Verified against reports/scan-displacement-events/results-btcusd.json rather than trusting the transcribed numbers, per instruction: disp4.horizons.{15m,30m,1h,4h} .signed_mean_atr = 0.2880014773686192 / 0.45616207948020565 / 0.3805019418217511 / 0.23800679608659406 — matches the declaration's +0.288/+0.456/+0.381/+0.238 exactly.

Combination-rule evaluation (mechanical, bootstrap CI overlap as the criterion):

Horizon in_window signed [90% boot CI] out_window signed [CI] unconditioned signed [CI] in beats both?
15m +0.227 [−0.084, 0.567] +0.324 [0.040, 0.642] +0.288 [0.061, 0.342] No — in_window's point estimate is the lowest of the three; CIs fully overlap
30m +0.511 [0.130, 0.907] +0.424 [0.101, 0.729] +0.456 [0.224, 0.709] No — nominal edge, but in_window's CI entirely contains both other CIs
1h +0.439 [−0.003, 0.902] +0.346 [0.018, 0.697] +0.381 [0.114, 0.635] No — same pattern; total overlap
4h +0.032 [−0.690, 0.714] +0.358 [−0.189, 0.957] +0.238 [−0.224, 0.675] No — in_window's point estimate is again the lowest of the three

Verdict per the declared reading: disp4_in_window never clears the pre-declared bar ("beats BOTH disp4_out_window AND the unconditioned disp4 record... by more than the bootstrap CI overlap") at any of the 4 horizons. At 15m and 4h its point estimate is below both comparators, the opposite of the hoped-for direction. At 30m and 1h its point estimate is nominally the highest of the three, but its own 90% bootstrap CI is wide enough to fully contain the other two distributions' CIs — there is no statistical daylight between "the session window amplifies continuation" and "n=191 is a noisy small draw from the same 517-event population that also produced the unconditioned and out-of-window numbers." Per the pre-declared combination rule, this is not an interaction: it is a filter shrinking n, and is logged as such, exactly the outcome the declaration flagged as the alternative to a real effect.

Year-block signs (signed_by_year_atr, 2017–2022, 6 blocks) for the depth-2 arms:

Horizon disp4_in_window (+/6) disp4_out_window (+/6)
15m 3/6 (2018,2020,2021+) 5/6 (all but 2017)
30m 5/6 (all but 2022) 5/6 (all but 2017)
1h 4/6 (2017,2018,2020,2021+) 4/6 (2018,2020,2021,2022+)
4h 3/6 (2018,2020+) 4/6 (2018,2019,2020,2021+)

disp4_out_window is, if anything, more consistently sign-stable than disp4_in_window (5/6 vs 3/6 at 15m, tied elsewhere) — a second, independent line of evidence against the window concentrating the effect, on top of the CI-overlap verdict above.

Ambiguity resolutions (explicit):

  1. Event-close windowing requires a new column, not an existing one. hour_of_day is the bar's OPEN hour; "event close inside 09:00–15:59 UTC" needed the bar's CLOSE hour, which only differs on the :55-past-the-hour bar. Resolved by adding close_hour_utc (additive, FEATURE_VERSION bump) rather than approximating with hour_of_day.
  2. Base conditions (1–4) use hour_of_day (open-hour), not close_hour_utc. The declaration specifies close-time mapping only for the depth-2 arms; base conditions are plain calendar/session blocks with 8-hour-wide boundaries where open vs. close hour is immaterial at n≈187–188k per block. Kept on the existing, precedented column.
  3. weekend = day_of_week >= 5. day_of_week is documented Mon=0..Sun=6, so Sat=5, Sun=6; no new column needed.
  4. Block boundaries are inclusive and exactly partition 24 hours (00–07 / 08–15 / 16–23), matching the declaration's literal ranges with no gaps or overlaps.
  5. Base-condition universe is the plain valid universe (atr14>0, forward-complete), not baseline_bp>=7. These are unconditional session-structure facts, not displacement events; restricting to baseline_bp>=7 would be an undeclared change to what condition 1–4 measure.
  6. Depth-2 universe/event definition reused verbatim from engine/scans/configs/displacement-repro.json (baseline_bp>=7; body_fraction>=0.6 and compression_bp<=baseline_bp and disp_mult>=4 and direction_sign!=0), per the task instruction — no re-derivation, no parameter changes.
  7. In/out window partition is a strict complement (close_hour_utc>=9 and close_hour_utc<=15 vs. the negation), confirmed exact by the 191+326=517 event-count identity above.

Null-battery caveat on record (as declared): shuffle null is not computed at all for conditions 1–4 (no direction_col — unsigned, degenerate by construction, consistent with the rhythm/H4-ignition precedents); it is meaningful and computed for conditions 5–6 (per-event direction varies via direction_sign), where it stays weak-to-marginal throughout (0.023–0.932 across both arms) — consistent with, not independent proof against, the CI-overlap verdict above.

Consequence for the session-structure branch / Research Program: the base session-vol curve is confirmed real (europe_block elevated, asia/us_block suppressed, weekend mildly suppressed short-horizon only) — log it, condition on it, never trade it alone, exactly the pre-declared reading. The genuinely open question — whether the 09:00–15:59 UTC window where the mentor's and human P&L concentrate also concentrates displacement continuation — resolves no on this sample: disp4_in_window does not beat disp4_out_window or the unconditioned disp4 record by more than sampling noise at any horizon, and is in fact nominally weaker at the two horizons (15m, 4h) where the unconditioned disp4 signal itself is most established. Session filters remain, on the evidence accumulated so far, a tail-risk / drag-reduction tool only (their twice-observed role from run 17/18 H3) — no future continuation-class prereg should inherit a session term from this result. This does not reopen or resurrect disp_follow_btc_v1 (Validation FAIL stands; the explicit non-goal holds): the depth-2 arms here ran on the research split as a pure event-study question, not an execution retest. disp4_in_window is additionally underpowered by the declared min_events=200 threshold (n=191), so a larger sample (e.g. ETHUSDT replication of this same split) could in principle move the picture — but as pre-registered, the verdict for this run is: base structure confirmed as known physics, depth-2 interaction refuted as a filter-not-interaction.