Skip to content

fix(qt): report the price-limit gate input that was actually used - #76

Merged
StackOverFlow11 merged 2 commits into
mainfrom
fix/intraday-report-disclosures
Jul 21, 2026
Merged

fix(qt): report the price-limit gate input that was actually used#76
StackOverFlow11 merged 2 commits into
mainfrom
fix/intraday-report-disclosures

Conversation

@StackOverFlow11

Copy link
Copy Markdown
Owner

What was wrong

PR #75 moved the I5b price-limit gate from the execution bar's close to the price that executes (the bar VWAP under the new default basis). It updated the execution-price-basis lines in both report writers — and missed the line that describes the gate itself. Both writers kept asserting:

the selected execution-minute raw 1min close vs the symbol/date raw stk_limit band

Every run since has shipped a report describing a check it did not perform. In a project whose standing rule is 绝不静默降级, a report that misstates its own method is worse than no report.

runtime/ behaviour is unchanged by this PR. This is a disclosure fix plus three counters that were already being computed and thrown away.

What changed

The corrected claim is derived from the active execution_price_basis and states why the executed price is the faithful input: a limit-up minute is either LOCKED (封死涨停 — every print at the limit, so the VWAP equals it up to rounding and the buy must be blocked) or OPENED (盘中打开 — prints landed below it, direct evidence a fill was achievable, so the buy must go through). The bar close misclassifies both edges.

Three facts the runs already computed but never disclosed:

field why it must reach the reader
opened_limit_up_minutes / opened_limit_down_minutes the entire set on which this gate and a close-based gate disagree. A change in what does or does not trade must never be silent.
missing_adj_factor_pairs holding periods dropped because adj_factor was unusable at an anchor. Non-zero means measured coverage is short. Never defaulted to 1.0 — that would reintroduce the ex-date bias PR #75 removed.

The tail report additionally states that returns are corporate-action adjusted and gives the identity, since "exec-to-exec" alone no longer describes them.

Testability

The wording is extracted into limit_basis_lines() rather than left buried in a 200-line writer, and pinned by three tests.

Mutation evidence (actually run, not asserted):

  • Reverting the prose to the close-based claim → 2 tests FAIL:
    E  AssertionError: assert 'executes' in '- **comparison basis**: the selected
       execution-minute **raw** 1min close vs the symbol/date **raw** `stk_limit` band…'
    FAILED test_limit_basis_prose_names_the_active_execution_basis
    FAILED test_limit_basis_prose_does_not_claim_a_close_comparison_under_vwap
    
  • Dropping the new counters from the group table → the third FAILS:
    E  AssertionError: assert '| 3 | 1 | 4 |' in '## Execution-time price-limit feasibility (I5b)…'
    FAILED test_group_report_feasibility_prose_names_the_active_execution_basis
    

Why the existing I5b tests did not catch this: their fixtures price each execution bar with volume=1.0, amount=close, so VWAP == close exactly. That kept every pre-PR#75 assertion valid on both bases — and made them structurally blind to which one the gate reads. Recorded in the module docstring so the next person does not rebuild the same blind spot.

I5f re-measured at the operator's real capital

portfolio_notional 10,000,000 → 1,000,000 RMB, the stated capital ceiling.

10,000,000 (superseded) 1,000,000 (actual)
trades with a usable capacity ratio 42 42
below 1.0x capacity 20 0
ratio min / median 0.122 / 1.104 1.217 / 11.037

The earlier "about half of single-minute desired trades exceed capacity" finding described a portfolio an order of magnitude larger than any this project will run. At the real size, single-minute capacity is not binding on this universe/window. This re-scopes the motivation for a future partial-fill / volume-cap layer; it does not retire it, and it is a diagnostic, not a tradability claim.

Verified NAV decomposition (relevant to reading any I5x number)

Same window, three configurations, deterministic:

engine configuration final NAV
accepted I5b/I5f: bar_close fills, unadjusted returns 0.976448
bar_close fills, adjusted returns 1.021137 (+4.47pp)
bar_vwap fills, adjusted returns (new default) 1.019318 (−0.18pp)

The move is almost entirely the bug fix — ex-dividend drops were being booked as losses, and this window straddles the A-share annual ex-dividend peak. The VWAP fill basis itself is very slightly worse, which is the expected direction. Not a performance improvement, and it must not be read as one.

opened_limit_* are 0/0 in this window, so none of the change came from a relaxed gate; the one up-limit blocked buy is still blocked.

Gates

pytest 1731 passed (1728 baseline + 3) · ruff clean · phase0 ic_mean=0.9600, annual_return=0.8408 · 31/31 configs validate · secret scan 0 hits (token value, .config.json)

PR #75 moved the I5b gate from the execution bar's close to the price that
executes -- the bar VWAP under the new default basis -- but left both report
writers asserting "the selected execution-minute raw 1min close vs the raw
stk_limit band". Every run since has shipped a report describing a check it did
not perform, which is worse than shipping no description at all.

The prose is now derived from the active execution_price_basis and states why
the executed price is the faithful input: a limit-up minute is either LOCKED
(every print at the limit, so the VWAP equals it up to rounding and the buy must
be blocked) or OPENED (prints landed below it, direct evidence a fill was
achievable, so the buy must go through). The bar close misclassifies both edges.

Three facts the runs already computed but never disclosed now reach the reader:

  opened_limit_up_minutes / opened_limit_down_minutes -- the entire set on which
  this gate and a close-based gate disagree. A change in what does or does not
  trade must never be silent.

  missing_adj_factor_pairs -- holding periods dropped because adj_factor was
  unusable at an anchor. Non-zero means measured coverage is short; it is never
  defaulted to 1.0, which would reintroduce the ex-date bias PR #75 removed.

The tail report additionally states that returns are corporate-action adjusted
and gives the identity, since "exec-to-exec" alone no longer describes them.

The wording is extracted into limit_basis_lines() so it is testable rather than
buried in a 200-line writer, and pinned by three tests. Mutation evidence:
reverting the prose to the close-based claim fails 2 of them; dropping the new
counters from the group table fails the third.

Note for anyone extending tests/test_i5b_execution_feasibility.py: its fixtures
price each execution bar with volume=1.0 and amount=close, so VWAP == close.
That kept the pre-PR#75 assertions valid on both bases -- and made them blind to
which one the gate reads. The new tests cover that distinction directly.

Also sets the I5f diagnostic notional to 1,000,000 RMB, the operator's stated
capital ceiling. At 10,000,000 the diagnostic reported that about half of all
single-minute desired trades exceeded capacity; that described a portfolio an
order of magnitude larger than any this project will run. Re-measured at the
real size, no trade falls below capacity (min ratio 1.217, median 11.037).
…t class

Review of the first pass found the SAME stale wording surviving in
_write_report()'s "Limitations" section, untouched by that commit and identical
to what shipped before PR #75. Every I5b/I5c/I5f report would therefore have
carried two contradictory descriptions of one check in a single document: the
corrected one mid-report and the pre-#75 one at the end. The dev-facing
_load_price_limits docstring had it too.

Both now derive from the active execution_price_basis.

The more useful half is why the first pass missed it. None of the three tests
render _write_report at all -- they exercise limit_basis_lines() in isolation and
_feasibility_lines() through a stub -- so that location was structurally
unreachable by them, and enumerating the places I happened to know about was
never going to converge. The new test scans both report modules for any phrasing
that asserts the gate compares a CLOSE, so an (N+1)th copy fails rather than
ships. "bar_close" the basis name and "that bar's single closing tick" the basis
description are legitimate and deliberately not matched.

Mutation evidence: restoring the stale Limitations sentence fails the guard, and
it names the offending line:

    AssertionError: intraday_tail_framework.py still claims the price-limit gate
    compares a CLOSE. ... Derive the wording from execution_price_basis instead
    of restating it:
      line 966: f"execution-minute close to raw limits (see the section "

pytest 1732 passed (1731 + 1), ruff clean, phase0 unchanged.
@StackOverFlow11
StackOverFlow11 merged commit cfb07bc into main Jul 21, 2026
StackOverFlow11 added a commit that referenced this pull request Jul 21, 2026
… rebasis PR number (#79)

Both follow-ups now have real numbers, read from gh pr list rather than assumed:
#78 is fix/limit-basis-single-source and #79 is feat/exec-to-exec-rebasis, both
OPEN. The rebasis entry no longer says "PR not yet open".

- New entry for #78: #76's regex guard claimed no further copy of the wrong
  gate description could ship, and review disproved it by running seven
  plausible rewordings through the actual regex — all seven escape. The fix is
  structural: limit_basis_phrase() is the one authored sentence and three sites
  compose it; rendered text is unchanged; the regex stays with its scope
  corrected to catching a literal revert.
- Sharpened the second defect-class rule accordingly: the rule is not "assert no
  (N+1)th copy exists" — that is precisely what the regex failed to do — it is
  author the claim once and have every other site compose it. A regex cannot
  assert that no other sentence makes a claim; having no other sentence can.
- #79 entry gains its number, the reason for the rebasis (a minute factor is
  fixed at 14:50 and the only modellable fill is the 14:51 bar, so close-to-close
  credits a closing auction this project cannot simulate), the two coverage
  denominators kept apart as the PR keeps them, and the correctness acceptance.

Sources: gh pr view 78, gh pr view 79, RESULTS_post_pr75_2026-07-21.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant