Skip to content

fix(sl-hunting): SLH-021 + coverage floor 74% (+ 1 Oct comparison) - #195

Merged
DoRmAmMu1997 merged 3 commits into
mainfrom
feat/sl-hunting-v5o
Oct 1, 2026
Merged

DoRmAmMu1997 merged 3 commits into
mainfrom
feat/sl-hunting-v5o

Conversation

@DoRmAmMu1997

@DoRmAmMu1997 DoRmAmMu1997 commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

Three parts:

  • SLH-021, a bookkeeping-only code fix. It never places, refuses or sizes an order.
  • The 1 Oct trade comparison, doc only. There's no knowledge change, because the one new idea can't be measured.
  • Coverage floor raised from 73% to 74% (operator's request).

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_exit has no open row to close.
  • The stop has already marked the call stale.
  • At harvest, SLH-012 sees a stale call and a flat position (it checks 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:

  • 10 Aug 09:50:59
  • 4 Sep 09:15:48
  • 15 Sep 09:15:55
  • 30 Sep 09:22:49

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 unconsumed last_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:

    • the closed position;
    • the entry order;
    • the exit details;
    • SLH-020's starting P&L, as the basis for this trade's P&L;
    • the mirror leg's price quality at entry, read before the basket close resets it;
    • whether the mirror is still open.

    It then clears the entry order so it can't leak into a later row.

  • Harvest (_consume_agent_decision):

    • It takes the parked trade before any early return, so the trade can never attach to a later call.
    • A stale call with a parked trade is recorded with a WARNING instead of being thrown away, and its decision goes to the decisions log.
    • It opens and closes the row using the parked details plus the call's own decision and market data.
    • If the NIFTY leg was cut alone and the mirror is still open, the close waits for the mirror, as the ordinary path already does. A second entry in the same call can never overwrite that waiting row.
  • 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.

  • IH: bought puts on all three indices at 09:18, then cut at his loss limit at 10:01. His screen at that point: BankNIFTY +0.62% (54,974.40, just under the note's 55,000), NIFTY −0.12%, SENSEX +0.05%. His loss amount wasn't shown.
  • The agent: three shorts, −369.00 in total.
    • Trade 1 booked +3.75 within a minute because BankNIFTY turned up, which was the right read.
    • Trades 2 and 3 then sold into BankNIFTY's round-number rejections, counting them as confirmation. IH says those rejections are what drew in the sellers the breakout went on to hunt.

Why nothing was encoded:

  • The mechanism is already written down in R:R-BAIT AT ROUND-NUMBER REJECTIONS, THIRD-INDEX LAG and v4i.
  • The only new part — that a rejection in the index refusing your direction is bait there, not confirmation for you — can't be measured, because there's no BankNIFTY price history after 2023.
  • The closest thing I could measure was entries taken against the cross-index read: 55 trades, +17,876.75, 51% winners. The two halves split −4,675.75 / +22,552.50, so it doesn't support a rule either way.

Coverage floor: 73% → 74%

  • Why it's safe: CI measured 74.6% on 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.
  • Where it changed: pyproject's 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)

  • CI runs mypy twice: once on the package files, and once on nifty_multi_strategy_master.py by itself. Locally I had only run the first, which is how this slipped through.
  • In _park_close_inside_pass, executor comes from getattr(..., None), so mypy couldn't prove it was non-None in the finally: block.
  • The fix is an early return when there's no executor. Behaviour is unchanged, and all ten SLH-021 mutation tests were re-run and still caught.

Test plan

  • SLH-021: five tests on the SLH-012 stub, plus four on the real worker. One replays the 30 Sep sequence (enter → invalidate → AI_STOP → stale harvest). It checks that the row carries the call's own setup and reasoning and this trade's basket P&L; 250 of earlier P&L is seeded so the day's total can't pass for it.
  • Ten deliberate code mutations were all caught, and a control rewording passes.
  • Run during market hours, kept to the touched suites: master suite 648 OK; SL Hunting 283 passed; ruff clean.
  • After the two fixes: both mypy runs pass, exactly as CI runs them (python -m mypy on 82 files, then python -m mypy nifty_multi_strategy_master.py). Repository-policy tests: 17 passed. Pre-commit (all files) passes.
  • Floor negative test: with pyproject set back to 73.0, the coverage-policy test fails.
  • The full local gates (pytest, mypy, compileall, bandit, pre-commit) will run after the 15:15 square-off. Coverage is left to CI, per CLAUDE.md.

🤖 Generated with Claude Code

DoRmAmMu1997 and others added 3 commits October 1, 2026 13:11
…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>
@DoRmAmMu1997 DoRmAmMu1997 changed the title fix(sl-hunting): SLH-021 - journal a trade that opens and closes inside its pass (+ 1 Oct comparison) fix(sl-hunting): SLH-021 + coverage floor 74% (+ 1 Oct comparison) Oct 1, 2026
@DoRmAmMu1997
DoRmAmMu1997 merged commit ac9298e into main Oct 1, 2026
7 checks passed
@DoRmAmMu1997
DoRmAmMu1997 deleted the feat/sl-hunting-v5o branch October 1, 2026 08:04
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