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.
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).
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.
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):
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.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.weekend = day_of_week >= 5. day_of_week is documented Mon=0..Sun=6, so Sat=5,
Sun=6; no new column needed.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.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.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.