Repository navigation
insights: one track record per rule; empty side reads unknown (#962, #965) - #975
Merged
Merged
Conversation
…962, #965) #962: build_insights_report read paper.track_record by KIND, so N rules of one kind showed N identical, unnamed records and gate cards. Paper orders carry orders.rule_id (every paper order in the real paper DBs has one), so the report now splits each kind's journal by rule (paper.track_records_by_rule) and builds each record and gate from its own rule's trades. RuleTrackRecord gains rule_id and product_id; trades journalled with no rule id get one "unattributed" record per kind in scope, after the rules, with no gate. The Rules view shows id and product columns and names each gate card by kind, product and rule id (its id is now the rule id). The CLI line names rule= and product=. #965: RuleTrackRecord.avg_win/avg_loss are None when that side of the sample has no trade, decided by counting the side's trades. The payload's money(None) renders absent, the CLI prints n/a. BacktestResult and the promotion module are unchanged, so gate verdicts are check_floors on the same sample (pinned). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
Closed
…lient keeps record order (#962, #965) - realized_rr is None with no winning trade too (the zero avg_win is unknown); CLI reads "rr=n/a (no wins yet)". Gate verdicts unchanged (check_floors). - A zero-trade record prints win_rate/expectancy/profit_factor/max_dd/rr as n/a in the CLI text; --json keeps its numbers. - Structural test: the records table receives records.map( unreordered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix a stale _quantized docstring that claimed a zero-loss realized rr can be Infinity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Gatekeeper: ready for the owner to merge (head 534f134, base main 76382ee). I have not merged. Review loop. Three rounds.
Evidence on 534f134
What I checked
Issues filed
|
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.
Closes #962
Closes #965
Rule track records on the Rules view, in
keel insights summaryand in its--jsonare now one record per rule. When one side of a sample is empty, its average now reads unknown instead of $0.00.#962: one record per rule
Can trades be attributed to a rule? Yes.
orders.rule_idis set on every paper entry by a real paper cycle, and each exit copies its entry's id.~/keel/keel.db(dca and turtle_breakout) and inkeel-paperhourly.dbhas arule_id.What changed:
paper.track_records_by_rule(repo, kind)splits a kind's paper journal byorders.rule_id. The splits do not overlap, and between them they hold every trade of the kind (pinned).track_record(repo, kind)stays as the pool for the whole kind, because the journal's R match only knows the kind.RuleTrackRecordgainsrule_idandproduct_id.status="unattributed", no id, no product and no gate. They are not dropped, and they are not handed to a rule that happens to share the kind.kind · product · rule N · verdict, and the card's id ish-gatedist-<rule id>. The position-based id that PR3 added is gone.[turtle_breakout] rule=10 product=BTC-USD status=…. The unattributed record printsrule=n/a product=n/aand a one-line note.#965: an empty side is unknown, not $0.00
RuleTrackRecord.avg_winandavg_lossareNonewhen that side has no trade. This is decided by counting the side's trades, not by reading the zero.money(None)renders as "—" with state unknown.n/a.BacktestResult,stats.pyandpromotion.pyare untouched:pool_statsmultiplies the zero by a zero count, and_realized_rrreadsavg_loss_r. A test pins that each gate's(passing, blocking_reasons)equalscheck_floorson the same per-rule sample, and that the sample still holds 0 for the empty side.Rulings
One record per rule, not one per kind. The data attributes every real paper trade, and the ledger's "Follow-up 2" ruling chose per-rule when that holds. Cost if wrong: none, because a pool for the whole kind is still
track_record.Gate cards now judge each rule's own sample, so the gate figures change. The verdicts here are display only. A kind-wide pool counted every product toward one rule's
n_trades, without the Pool min_trades across same-parameter rules, with a cross-sectional diversity floor #338 diversity floor.keel rules promotedecides from backtests pluspool_statsand is untouched. Cost if wrong: on the page, rules sit further from the floor than they did before.Unattributed trades get a record per kind, not a count and not a drop. This follows the Holdings precedent (Holdings: tranches group by rule NAME, so every DCA rule shares one "dca" table #960: unattributed tranches get one table per kind, placed last). Cost if wrong: one extra row, and only on a database that has such fills.
--jsonshape: additive, plus null values.rule_id(int, or null) andproduct_id(str, or null).avg_winandavg_lossare null when that side is empty. Before, they were"0".statuscan be"unattributed".Cost if wrong: a script that does arithmetic on
avg_lossmust handle null.realized_rris unknown when either side is empty (review round 1).None: the page shows "—", and the CLI saysrr=n/a (no wins yet). With no losses it still saysn/a (no losses yet).check_floorsreadspromotion._realized_rrin R, and the engine's reason line still printsrr_r 0 < min_rr.--json,realized_rr(on the record and ingate) becomesnullfor an all-losses record. It used to be"0".rr_r 0reason line.A zero-trade record reads unknown in the CLI text too (review round 1). The page already showed "—" for these (PR3 F5). The CLI line now reads
win_rate=n/a avg_win=n/a avg_loss=n/a rr=n/a (no trades yet) expectancy=n/a profit_factor=n/a max_dd=n/a.--jsonkeeps its numbers at n=0, so the JSON change stays additive. Cost if wrong: a JSON reader still sees0.0for win_rate at n=0, next ton_trades: 0.The client's record order is pinned (review round 1). A structural test checks that
recordsSectionhandsrecords.map(straight to the table, with no sort, reverse or copy in between.Tests and gates (head 534f134)
--jsonidentity, insights: avg win / avg loss show $0.00 when that side of the sample is empty (unknown as zero) #965 ×5 including the verdict pin, payload ×2, and view header↔cell pairs plus the gate card id and summary. Review fixes, each red first: all-losses realized rr unknown (record and gate card), the zero-trade CLI line (exact full-line equality), the client record order (structural), and realized rr counting losing trades.ruff check keel tests packages,ruff format --check .and baremypyare all clean (508 files).node --checkpasses on everyjs/*.jsand onsw.js.pytest: 8329 passed, 3 skipped (head 534f134).logs/keel.logwas deleted before the run (tests: suite leaves logs/keel.log in CWD; next run fails test_timeline_export header tests #964).""Nonekeel serveon port 8893, against a scratch copy of the repo'sconfig.yamlandkeel.db(eight turtle_breakout rules in scope). The copy was seeded with attributed paper trades plus one unattributed trade. Headless Chromium, with the service worker unregistered (0 registrations) and caches cleared.turtle_breakout · BTC-USD · rule 10 · blocked, and likewise for 11, 12, 22 and 24.keel insights summary(text and--json) on the same scratch copy showsrule=…andproduct=…on every line,avg_loss=n/aandavg_win=n/aon the empty sides, andnullin the JSON.🤖 Generated with Claude Code