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
9 changes: 5 additions & 4 deletions qt/intraday_group_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
plot_quintile_nav,
plot_spread_curve,
)
from qt.intraday_tail_framework import limit_basis_phrase


def _write_figures(
Expand Down Expand Up @@ -184,10 +185,10 @@ def _feasibility_lines(result) -> list[str]:
cov = result.limit_coverage
lines.extend([
"- **enabled** (`intraday.price_limit_check=true`): a buy is blocked at the "
"raw upper limit and a sell at the raw lower limit, comparing the price "
f"that actually EXECUTES — the selected execution minute on the "
f"`{cfg.intraday.execution_price_basis}` basis — to the raw `stk_limit` "
"band (RAW-vs-RAW; never qfq / daily close / a daily-close-derived flag).",
"raw upper limit and a sell at the raw lower limit, comparing "
f"{limit_basis_phrase(cfg.intraday.execution_price_basis)} — to the raw "
"`stk_limit` band (RAW-vs-RAW; never qfq / daily close / a "
"daily-close-derived flag).",
"- a limit-up minute is LOCKED (every print at the limit, VWAP equals it up "
"to rounding, buy blocked) or OPENED (prints landed below it, which is "
"evidence a fill was achievable, buy allowed). The executed price separates "
Expand Down
54 changes: 34 additions & 20 deletions qt/intraday_tail_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,9 @@ def _load_price_limits(
Returns ``(limits_df, stk_limit_gap_fetches)``. The limits flow through the
SAME P4 read-through cache as the daily endpoints (no new endpoint); a
fully-covered window costs zero gap fetches. The cache stores RAW
``up_limit`` / ``down_limit`` only — the model compares them to the RAW price
that EXECUTES at the selected minute (the bar VWAP under the default basis),
never qfq / daily close.
``up_limit`` / ``down_limit`` only; for what they are compared against, see
:func:`limit_basis_phrase` — deliberately not restated here, because a second
statement of the same fact is a second thing to get wrong.
"""
if cfg.intraday is None or not cfg.intraday.price_limit_check:
return None, 0
Expand Down Expand Up @@ -723,22 +723,37 @@ def _append_liquidity_section(lines: list[str], result: I5aResult) -> None:
lines.append("")


def limit_basis_lines(execution_price_basis: str) -> list[str]:
"""Report prose stating what the I5b price-limit gate ACTUALLY compares.
def limit_basis_phrase(execution_price_basis: str) -> str:
"""THE ONE sentence fragment naming what the price-limit gate compares.

Every place that describes the gate — the feasibility section, the report's
Limitations bullet, the group report, and any future one — composes this
rather than authoring its own sentence.

Extracted so it is testable. The gate reads the price that EXECUTES — which
since PR #75 is the bar VWAP by default, not the bar close — and an earlier
revision of this text kept claiming "the raw 1min close" after the gate input
had changed. A report that misstates the check it performed is worse than no
report, so the wording is derived from the active basis and pinned by test.
That indirection is the actual fix, and it replaced a weaker one. When PR #75
moved the gate from the bar close to the executed price, three independently
written sentences described it; the first correction pass fixed two, and a
regex guard was added to catch the rest. Review then demonstrated that seven
plausible rewordings ("its last print", "the final tick", "1-minute" instead
of "1min") all escape that regex — which is the general fate of a lexical
guard aimed at a semantic claim. A regex cannot assert "no other sentence
says this"; having no other sentence can. The guard is kept, scoped to what
it genuinely does: catching a literal revert.
"""
return (
f"the price that actually EXECUTES — the selected execution minute on the "
f"`{execution_price_basis}` basis"
)


def limit_basis_lines(execution_price_basis: str) -> list[str]:
"""Report prose stating what the I5b price-limit gate ACTUALLY compares."""
return [
f"- **comparison basis**: the price that actually EXECUTES — the selected "
f"execution minute on the `{execution_price_basis}` basis — vs the "
f"symbol/date **raw** `stk_limit` band. NOT qfq, NOT the daily close, NOT "
f"a daily-close-derived limit flag. (The intraday cache stores unadjusted "
f"bars and `stk_limit` is raw, and a bar VWAP is raw amount over raw "
f"volume, so the comparison is RAW-vs-RAW either way.)",
f"- **comparison basis**: {limit_basis_phrase(execution_price_basis)} — vs "
f"the symbol/date **raw** `stk_limit` band. NOT qfq, NOT the daily close, "
f"NOT a daily-close-derived limit flag. (The intraday cache stores "
f"unadjusted bars and `stk_limit` is raw, and a bar VWAP is raw amount "
f"over raw volume, so the comparison is RAW-vs-RAW either way.)",
"- **why the executed price and not the bar close**: a limit-up minute has "
"two shapes. LOCKED (封死涨停) — every print is at the limit, so the VWAP "
"equals it up to rounding and the buy must be blocked. OPENED (盘中打开) — "
Expand Down Expand Up @@ -962,10 +977,9 @@ def _write_report(result: I5aResult, *, elapsed: float) -> None:
lines.append(
f"- **Execution-time feasibility (I5b)**: a missing/NaN execution bar "
f"blocks BOTH directions; on top of that, raw `stk_limit` blocks buys at "
f"the upper limit and sells at the lower limit, comparing the price that "
f"actually EXECUTES — the selected execution minute on the "
f"`{ec.execution_price_basis}` basis — to raw limits (see the section "
f"above). Remaining gap: only the price-limit and bar-existence "
f"the upper limit and sells at the lower limit, comparing "
f"{limit_basis_phrase(ec.execution_price_basis)} — to raw limits (see the "
f"section above). Remaining gap: only the price-limit and bar-existence "
f"constraints are modeled; partial-fill / liquidity / volume caps at the "
f"execution minute are not."
)
Expand Down
109 changes: 106 additions & 3 deletions tests/test_i5b_execution_feasibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,17 @@ def test_no_module_repeats_the_pre_pr75_close_based_limit_claim():
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. The
honest fix for a claim that gets restated in prose in N places is to assert no
(N+1)th copy exists, rather than to enumerate the ones we happen to know.
`_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.
"""
import re
from pathlib import Path
Expand Down Expand Up @@ -517,3 +525,98 @@ def test_no_module_repeats_the_pre_pr75_close_based_limit_claim():
f"the default basis). Derive the wording from execution_price_basis "
f"instead of restating it:\n " + "\n ".join(hits)
)


def test_every_gate_description_composes_the_single_phrase():
"""All three descriptions of the gate compose one phrase; none re-authors it.

This is the guard the regex above cannot be: a lexical scan enumerates
wordings, whereas having exactly one authored sentence leaves nothing to
reword. If a future edit writes its own sentence instead of composing
`limit_basis_phrase`, that sentence stops containing the phrase and this
fails.
"""
from types import SimpleNamespace

from qt.intraday_group_report import _feasibility_lines
from qt.intraday_tail_framework import limit_basis_lines, limit_basis_phrase

cfg = load_config(str(_I5B_CONFIG))
basis = cfg.intraday.execution_price_basis
phrase = limit_basis_phrase(basis)
assert f"`{basis}`" in phrase # the phrase is basis-derived, not a constant

# 1. the tail report's feasibility section
assert phrase in " ".join(limit_basis_lines(basis))

# 2. the group report's feasibility section
result = SimpleNamespace(
config=cfg,
price_limit_check=True,
limit_coverage={"required": 1, "present": 1, "missing": 0},
stk_limit_gap_fetches=0,
groups=(
SimpleNamespace(
group=1, up_limit_blocked_buys=0, down_limit_blocked_sells=0,
missing_limit_rows=0, opened_limit_up_minutes=0,
opened_limit_down_minutes=0, missing_adj_factor_pairs=0,
),
),
)
assert phrase in " ".join(_feasibility_lines(result))

# 3. the tail report's Limitations bullet -- the one the first pass missed.
# Read from source rather than rendering, since building a full I5aResult
# here would test the dataclass, not the sentence.
import inspect

import qt.intraday_tail_framework as tf

src = inspect.getsource(tf._write_report)
assert "limit_basis_phrase(ec.execution_price_basis)" in src, (
"the Limitations bullet must COMPOSE limit_basis_phrase, not restate the "
"comparison in its own words"
)


def test_composed_gate_sentences_are_grammatical_in_every_context():
"""The phrase is an appositive: every host sentence must close its em-dash.

Composing one fragment into several sentences trades three ways to state a
fact wrong for one way to punctuate it wrong. The first version of this
refactor did exactly that -- it dropped the CLOSING em-dash in two of the
three hosts, leaving "...on the `bar_vwap` basis to the raw stk_limit band"
with an unclosed dash. Rendered output was NOT byte-identical to the previous
release, contrary to what the change claimed, and a rendering probe caught it.

So: wherever the phrase is embedded mid-sentence, the em-dash that opens the
appositive inside it must be matched by one after it.
"""
from types import SimpleNamespace

from qt.intraday_group_report import _feasibility_lines
from qt.intraday_tail_framework import limit_basis_phrase

cfg = load_config(str(_I5B_CONFIG))
phrase = limit_basis_phrase(cfg.intraday.execution_price_basis)
assert "—" in phrase, "the phrase itself opens an appositive with an em-dash"

result = SimpleNamespace(
config=cfg, price_limit_check=True,
limit_coverage={"required": 1, "present": 1, "missing": 0},
stk_limit_gap_fetches=0,
groups=(SimpleNamespace(
group=1, up_limit_blocked_buys=0, down_limit_blocked_sells=0,
missing_limit_rows=0, opened_limit_up_minutes=0,
opened_limit_down_minutes=0, missing_adj_factor_pairs=0),),
)
for line in _feasibility_lines(result):
if phrase in line:
tail = line.split(phrase, 1)[1]
assert tail.lstrip().startswith("—"), (
f"the phrase is embedded mid-sentence but its appositive is never "
f"closed; the text reads '...{phrase[-30:]}{tail[:40]}'"
)
break
else:
raise AssertionError("the group report no longer composes the phrase at all")