Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/phase_i5a_intraday_tail_framework.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
#
# Proves the shared event-driven backtest engine drives an IntradayTailEventModel
# end-to-end on REAL SH/SZ minute data: decision at 14:50, execution at the first
# valid 1min close in [14:51, 14:56:59], exec-to-exec holding returns. The score is
# valid 1min bar in [14:51, 14:56:59] priced on the active execution_price_basis
# (the bar VWAP by default), exec-to-exec holding returns. The score is
# a single PIT-safe I3 feature (intraday_ret_0930_1450), not a research alpha.
#
# Minute bars are read from the EXISTING intraday cache only (read-only; a cache
Expand Down
17 changes: 10 additions & 7 deletions config/phase_i5b_intraday_execution_feasibility.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Phase I5b — intraday execution-time price-limit feasibility (hardening, NOT research).
#
# Same intraday tail-rebalance smoke as I5a (decision 14:50, execute at the first
# valid 1min close in [14:51, 14:56:59], exec-to-exec holding returns, PIT-safe I3
# valid 1min bar in [14:51, 14:56:59] priced on the active execution_price_basis, exec-to-exec holding returns, PIT-safe I3
# score) PLUS execution-time price-limit feasibility: a buy is blocked at the raw
# upper limit and a sell at the raw lower limit, comparing the selected
# execution-minute RAW 1min close to the raw stk_limit band (NEVER qfq / daily
# close / daily-close-derived flags).
# upper limit and a sell at the raw lower limit, comparing the RAW price that
# EXECUTES at the selected minute -- the bar VWAP under the default
# execution_price_basis -- to the raw stk_limit band (NEVER qfq / daily close /
# daily-close-derived flags).
#
# Minute bars are read from the EXISTING intraday cache only (read-only; a miss is
# a hard blocker -> zero stk_mins live calls). Raw stk_limit flows through the same
Expand Down Expand Up @@ -42,8 +43,9 @@ universe:
filters:
# Selection-level daily tradability flags stay DISABLED: at the 14:50 decision
# the daily-close-derived limit flag is an EOD value not known yet. The honest
# direction-aware blocking happens at EXECUTION via raw stk_limit vs the raw
# execution-minute close (intraday.price_limit_check below). Disclosed in the report.
# direction-aware blocking happens at EXECUTION via raw stk_limit vs the RAW
# price that EXECUTES at that minute (intraday.price_limit_check below).
# Disclosed in the report.
missing_close: true
suspended: false
st: false
Expand Down Expand Up @@ -95,7 +97,8 @@ intraday:
- '14:56:59'
require_cache_coverage: true
missing_execution: block
# I5b: execution-time raw price-limit feasibility (raw 1min close vs raw stk_limit).
# I5b: execution-time raw price-limit feasibility (the RAW price that EXECUTES
# at that minute vs raw stk_limit).
price_limit_check: true
require_price_limit_coverage: true
limit_tolerance: 1.0e-06
Expand Down
3 changes: 2 additions & 1 deletion config/phase_i5c_mmp_minute_factor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ universe:
filters:
# Selection-level daily tradability flags stay DISABLED (as in I5b): the daily
# close limit flag is an EOD value not known at 14:50. The honest direction-aware
# blocking is execution-time raw stk_limit vs the raw execution-minute close.
# blocking is execution-time raw stk_limit vs the RAW price that EXECUTES at that minute
# (the bar VWAP under the default execution_price_basis).
missing_close: true
suspended: false
st: false
Expand Down
5 changes: 3 additions & 2 deletions config/phase_i5d_mmp_quintile_5y.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ universe:
filters:
# Selection-level daily tradability flags stay DISABLED (as in I5b/I5c): the
# daily close limit flag is an EOD value not known at 14:50. The honest
# direction-aware blocking is execution-time raw stk_limit vs the raw
# execution-minute close.
# direction-aware blocking is execution-time raw stk_limit vs the RAW price
# that EXECUTES at that minute (the bar VWAP under the default
# execution_price_basis).
missing_close: true
suspended: false
st: false
Expand Down
5 changes: 3 additions & 2 deletions config/phase_i5e_mmp_quintile_csi300_generalization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ universe:
filters:
# Selection-level daily tradability flags stay DISABLED (as in I5b/I5c/I5d): the
# daily close limit flag is an EOD value not known at 14:50. The honest
# direction-aware blocking is execution-time raw stk_limit vs the raw
# execution-minute close.
# direction-aware blocking is execution-time raw stk_limit vs the RAW price
# that EXECUTES at that minute (the bar VWAP under the default
# execution_price_basis).
missing_close: true
suspended: false
st: false
Expand Down
5 changes: 3 additions & 2 deletions config/phase_i5f_intraday_liquidity_diagnostics.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Phase I5f — intraday execution liquidity diagnostics (REPORT-ONLY, NOT research).
#
# Same intraday tail-rebalance smoke as I5a/I5b (decision 14:50, execute at the
# first valid 1min close in [14:51, 14:56:59], exec-to-exec holding returns,
# first valid 1min bar in [14:51, 14:56:59] priced on the active execution_price_basis, exec-to-exec holding returns,
# PIT-safe I3 score, raw stk_limit execution-time feasibility ON) PLUS an opt-in,
# REPORT-ONLY execution liquidity diagnostic. For each desired rebalance trade
# (|target_weight - current_weight|) it sizes the trade against the SELECTED
Expand Down Expand Up @@ -48,7 +48,8 @@ universe:
filters:
# Selection-level daily tradability flags stay DISABLED (as in I5b): the daily
# close limit flag is an EOD value not known at 14:50. The honest direction-aware
# blocking is execution-time raw stk_limit vs the raw execution-minute close.
# blocking is execution-time raw stk_limit vs the RAW price that EXECUTES at that minute
# (the bar VWAP under the default execution_price_basis).
missing_close: true
suspended: false
st: false
Expand Down
7 changes: 5 additions & 2 deletions qt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,11 @@ class IntradayCfg(_Strict):
# I5b execution-time price-limit feasibility. OFF by default, so every I5a /
# daily config validates and behaves unchanged. When enabled, the intraday
# tail model gates buys at the raw upper limit and sells at the raw lower
# limit, comparing the selected execution-minute RAW close to raw ``stk_limit``
# (never qfq / daily close). ``require_price_limit_coverage`` makes a missing
# limit, comparing the RAW price that EXECUTES at the selected minute -- the
# bar VWAP under the default ``execution_price_basis`` -- to raw ``stk_limit``
# (never qfq / daily close; see qt.intraday_tail_framework.limit_basis_phrase,
# the single authored statement of this, which this module cannot import
# without a cycle). ``require_price_limit_coverage`` makes a missing
# required (anchor date, symbol) limit row a hard, pre-result failure instead
# of a silent "checked" pass; ``limit_tolerance`` is the raw-price equality
# band for the at-limit test.
Expand Down
3 changes: 2 additions & 1 deletion qt/intraday_tail_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
-> deterministic PIT-safe score = the I3 ``intraday_ret_0930_1450`` feature
(only bars with available_time <= 14:50 enter it)
-> engine + IntradayTailEventModel: decision 14:50, execute at the first valid
1min close in [14:51, 14:56:59], exec-to-exec holding returns
1min bar in [14:51, 14:56:59] priced on the active execution_price_basis
(the bar VWAP by default), exec-to-exec holding returns
-> NAV / feasibility / holdings / event logs -> markdown report.

It is intentionally small: the score is a single already-PIT-safe feature solely
Expand Down
9 changes: 9 additions & 0 deletions runtime/backtest/event_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ class IntradayTailEventModel:
symbol/date's raw ``stk_limit`` band. A buy is blocked at the upper limit, a
sell at the lower limit, directionally.

That sentence is authored once, in
``qt.intraday_tail_framework.limit_basis_phrase``, and every report composes
it. This docstring cannot import it — ``qt.intraday_tail_framework`` already
imports this class, so the reverse would cycle — so it is one of the two
restatements that survive by necessity. It is covered instead by the repo-wide
scan in ``tests/test_i5b_execution_feasibility.py``, which fails if any file
claims this gate compares a CLOSE. Change the behaviour and that scan will
point here.

Why the gate reads the VWAP and not the bar close. A limit-up execution minute
has exactly two shapes, and they are the feasibility question:

Expand Down
96 changes: 54 additions & 42 deletions tests/test_i5b_execution_feasibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,55 +476,67 @@ def test_group_report_feasibility_prose_names_the_active_execution_basis():
assert "adj_factor" in text


def test_no_module_repeats_the_pre_pr75_close_based_limit_claim():
"""Guard the DEFECT CLASS, not just the two places already fixed.

The gate input moved from the bar close to the executed price in PR #75. The
first correction pass fixed the prose in the two obvious spots and MISSED a
third copy in `_write_report`'s "Limitations" section, so a run would have
emitted two contradictory descriptions of the same check in one document.

Review found it, and found why the other tests could not: none of them render
`_write_report` at all, so that location was structurally unreachable.

SCOPE, stated honestly because the first version of this docstring overstated
it. This catches a LITERAL revert -- a bad merge or rebase restoring the old
sentence, which is the failure that actually happened. It does NOT catch a
reworded restatement: review wrote seven plausible ones ("its last print",
"the final tick", "1-minute" for "1min") and all seven escape this regex. No
lexical guard can assert "no other sentence makes this claim"; English has too
many ways to make it. The durable fix is structural and is the reason
`limit_basis_phrase` exists -- with one authored sentence there is no second
one to reword. This test guards that one sentence against being un-written.
def test_no_file_anywhere_claims_the_gate_compares_a_close():
"""Repo-wide: NO file may state that the I5b gate compares a close.

The first version of this scanned only the two report modules. Review then
found SIX more live instances it could not reach even in principle -- one in
`qt/config.py` and five in the I5b/c/d/e/f config YAMLs, which are exactly what
an operator reads before enabling the feature, a higher-stakes readership than
a docstring. Scoping a guard to the files you already fixed guarantees it only
ever confirms what you already know.

Two restatements survive by necessity and are covered by this scan rather than
by composition: `runtime/backtest/event_models.py` and `qt/config.py` both sit
UPSTREAM of `qt.intraday_tail_framework` in the import graph (it imports them),
so neither can call `limit_basis_phrase` without a cycle.

Deliberately NOT matched: `bar_close` the basis name, "that bar's single
closing tick" the basis description, and the DAILY universe tradability filter
(`universe/filters.py`, `data/clean/tradability.py`), which is a separate and
older mechanism that genuinely does compare a raw daily close -- see
RUNBOOK.md. Those are correct and must not be swept up.

Scope, stated honestly: this catches a claim written with the word "close"
near "execution minute". It does NOT catch a reworded synonym ("its last
print", "the final tick") -- review demonstrated seven such escapes. No lexical
guard can assert "no sentence anywhere makes this claim". Composition is the
real answer where the import graph allows it; this scan is the net under the
places where it does not.
"""
import re
from pathlib import Path

import qt.intraday_group_report as gr
import qt.intraday_tail_framework as tf

# Phrasings that assert the LIMIT COMPARISON reads a close. "bar_close" the
# basis name and "that bar's single closing tick" the basis description are
# both legitimate and deliberately not matched.
root = Path(__file__).resolve().parent.parent
forbidden = re.compile(
r"execution-minute\s+(\*\*raw\*\*\s+)?(1min\s+)?close"
r"|1min\s+close\s+to\s+the\s+raw"
r"|raw\s+execution-minute\s+close",
r"execution[- ]minute[^.\n]{0,40}\bclose\b"
r"|\b1min close\b"
r"|raw\s+close\s+to\s+(the\s+)?raw\s+`?`?stk_limit",
re.IGNORECASE,
)
for mod in (tf, gr):
src = Path(mod.__file__).read_text()
hits = [
f"line {i}: {ln.strip()}"
for i, ln in enumerate(src.splitlines(), 1)
if forbidden.search(ln)
]
assert not hits, (
f"{Path(mod.__file__).name} still claims the price-limit gate compares "
f"a CLOSE. The gate reads the price that executes (the bar VWAP under "
f"the default basis). Derive the wording from execution_price_basis "
f"instead of restating it:\n " + "\n ".join(hits)
)
targets = [root / "qt" / "config.py", *sorted((root / "config").glob("*.yaml"))]
targets += sorted((root / "qt").glob("intraday*.py"))
targets += [root / "runtime" / "backtest" / "event_models.py",
root / "runtime" / "intraday_execution.py"]
# The exec-basis modules describe the same execution anchor from the factor
# side. They are accurate today; they are listed BEFORE they go stale, which
# is the whole lesson of how this guard had to be widened in the first place.
targets += sorted((root / "qt").glob("exec_basis*.py"))
targets += [root / "qt" / "exec_forward_returns.py"]

hits = []
for f in targets:
if not f.exists():
continue
for i, line in enumerate(f.read_text().splitlines(), 1):
if forbidden.search(line):
hits.append(f"{f.relative_to(root)}:{i}: {line.strip()}")
assert not hits, (
"these files claim the I5b price-limit gate compares a CLOSE. It compares "
"the price that EXECUTES (the bar VWAP under the default basis). Where the "
"import graph allows it, compose limit_basis_phrase; where it does not, "
"point at it:\n " + "\n ".join(hits)
)


def test_every_gate_description_composes_the_single_phrase():
Expand Down