fix(sl-hunting): SLH-021 + coverage floor 74% (+ 1 Oct comparison) - #195
Merged
Merged
Conversation
…de its pass
A trade's journal row is opened when its inference pass is HARVESTED. When the
pass entered and the per-poll stop closed the trade before that, after_exit had
no row to close, the stop had already invalidated the pass, and the harvest saw
a stale pass with a FLAT position. SLH-012 keys on self.pos.active, so the pass
was discarded, and the trade was missing from both the journal and the decisions
log. The runner log shows it on 10 Aug, 4 Sep, 15 Sep and 30 Sep 2026, each an
AI_STOP three to four seconds after the fill; three of those came after SLH-012.
Bookkeeping only -- nothing places, refuses or sizes an order differently:
* after_exit: with no row open and an unconsumed executor last_entry_order
(only the in-flight pass can have placed it), park the closed position, the
entry order, the exit context, SLH-020's _trade_realized_before as the P&L
basis, the mirror's entry price quality (read before the basket close
resets it) and whether the mirror survived. Parking consumes the order.
* _consume_agent_decision: take the parked close before anything can return,
so it can never attach to a later pass. Record a stale pass with a parked
close (WARNING) instead of discarding it, log its decision, and open + close
the row from the parked facts and the pass's own decision and frames. A
surviving mirror defers the close exactly as the ordinary path does, and a
second open in the same pass never overwrites a deferred parked row.
* _journal_open_row takes an optional parked trade; the live path is
unchanged.
Known limit: a parked trade whose pass is never harvested (the worker stopped
first) is still lost.
Tests: five on the SLH-012 stub and four on the REAL worker, including the 30 Sep
sequence (enter, invalidate, AI_STOP, stale harvest), which journals the pass's
own setup and this trade's basket P&L with 250 of earlier P&L seeded. Ten code
mutations, all caught; control passes. Master 648 OK, SL Hunting 283.
Also a doc-only addendum for 1 Oct: both books sold per the note and both were
stopped by BankNIFTY refusing to fall (+0.62% at IH's 10:01 cut). No knowledge
change: the mechanism is R:R-BAIT + THIRD-INDEX LAG + v4i, the cross-index form
can't be priced (no BankNIFTY history), and the nearest proxy -- entries against
the cross-index bias, 55 trades, +17,876.75 -- isn't robust either way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI type-checks nifty_multi_strategy_master.py in its own mypy invocation, which the local gate had not been running. `executor` comes from getattr(..., None), so mypy could not prove it non-None in the `finally:` that consumes the entry order. Return early when there is no executor as well as no order -- the same behaviour, now stated where mypy can see it. All ten SLH-021 mutations re-run, all caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At the operator's direction. CI measured 74.6% on main at 2c3f4c7 (PR #194) and 74.7% on this PR, identically on Python 3.12 and 3.13, so 74.0 keeps roughly 0.6pp of margin (about 175 statements and branches) -- the same deliberate pressure the 73.0 floor applied at 0.8pp. The floor only moves up, and only on a CI figure. Updated everywhere it is pinned: pyproject fail_under (with its history), the repository-policy assertion, the threshold script's docstring, CLAUDE.md, AGENTS.md, README and the testing-and-ci LLD. Negative-tested: with pyproject back at 73.0 the policy test fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three parts:
SLH-021: a trade that opens and closes inside its own decision call was never journaled
The bug. A trade's journal row is written when its decision call finishes (the "harvest"). When the call places an entry and the per-poll stop closes it before that:
after_exithas no open row to close.self.pos.active), so it throws the call away.So the trade is missing from both the journal and the decisions log. The runner log shows this four times, each an AI_STOP 3–4 seconds after the fill:
Three of those came after SLH-012. Every one of them is a quick loss, so the journal has been making the book look better than it is.
The fix.
after_exit: when no journal row is open but the executor still holds an unconsumedlast_entry_order, the trade must come from the call still in flight, which is the only thing that can place an order. It saves ("parks") what the journal row will need:It then clears the entry order so it can't leak into a later row.
Harvest (
_consume_agent_decision):Known limit: a parked trade whose call is never harvested, because the worker stopped first, is still lost.
1 Oct comparison (doc only)
Both books sold per the note, and both were stopped by BankNIFTY refusing to fall.
Why nothing was encoded:
Coverage floor: 73% → 74%
main(2c3f4c7, after fix(cpr-ai): prompt v3 - drop the veto examples the backtest refutes #194) and 74.7% on this PR, the same on Python 3.12 and 3.13. That leaves about 0.6 points of margin, roughly 175 statements and branches. That's the same deliberate pressure the 73% floor applied at 0.8 points.fail_under(with a dated note in its history comment), the repository-policy test, the threshold script's docstring, CLAUDE.md, AGENTS.md, README and the testing-and-ci design doc.The CI mypy failure (fixed)
nifty_multi_strategy_master.pyby itself. Locally I had only run the first, which is how this slipped through._park_close_inside_pass,executorcomes fromgetattr(..., None), so mypy couldn't prove it was non-None in thefinally:block.Test plan
python -m mypyon 82 files, thenpython -m mypy nifty_multi_strategy_master.py). Repository-policy tests: 17 passed. Pre-commit (all files) passes.🤖 Generated with Claude Code