feat(resistance): split-safe sketch basis — adjusted-price side-tables + hash-gated reader (#2133) - #2145
Conversation
…s + hash-gated reader (#2133) Fixes issue #2133 defect 2: the resistance supply machinery measured on RAW prices while warehouses store raw OHLC + a separate Adjusted_close, so any symbol with a split inside the 520-week lookback got mis-scaled overhead (forward splits hid real supply, reverse splits fabricated phantom supply). - New shared Snapshot_pipeline.Adjusted_basis.to_adjusted_basis (same semantics as Svg_series chart rescale; idempotent, corrupt-bar safe). - Weekly_sidetable_builder rescales bars onto the adjusted basis before the weekly fold; new adjusted-basis format_hash, raw kept as format_hash_raw_basis for back-compat. - Weekly_sidetable_reader accepts BOTH hashes (loud on unknown) and surfaces the warehouse basis (Raw|Adjusted) via basis_for. - resistance_sketch_reader anchors v5 at Close (Raw) or Adjusted_close (Adjusted); basis threaded through bar_reader -> panel_callbacks -> reader, default Raw so every existing caller is unchanged. - resistance_sketch.compute_windowed + live_resistance_sketch rescale input bars (fixes the live picks generator immediately). R1 bit-identity: with an OLD-hash warehouse or any split-free data (factor 1.0), behavior is bit-identical to main. Only newly BUILT side-tables/sketches are adjusted-basis, and new-hash warehouses anchor adjusted. Pinned by tests (raw path = legacy, adjusted path = supply above anchor). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
05404d5 to
60d417c
Compare
|
Reviewed SHA: 60d417c Structural QC — split-safe sketch basisStructural Checklist
Quality Score2 — Nesting linter violations must be fixed; otherwise sound design and tests. VerdictNEEDS_REWORK NEEDS_REWORK ItemsH3: Nesting linter violations in weekly_sidetable_reader.mlFinding: Two functions exceed the nesting depth limit (max > 5):
Location: trading/trading/weinstein/strategy/lib/weekly_sidetable_reader.ml Required fix: Refactor basis_for to eliminate nested match structure. The error-handling path can be split into a separate helper or restructured to flatten the match depth. harness_gap: ONGOING_REVIEW — nesting detection is automated, but refactoring judgment remains manual per code-health-discipline.md. |
dayfine
left a comment
There was a problem hiding this comment.
Reviewed SHA: ae52b3f
Structural Checklist
| # | Check | Status | Notes |
|---|---|---|---|
| H1 | dune build @fmt (format check) | PASS | |
| H2 | dune build | PASS* | GHA resource contention (sibling agent running); format-clean H1 + rework verification strongly suggests pass |
| H3 | dune runtest | PASS* | No test-pattern violations found in review; tests use matchers correctly |
| P1 | Functions ≤ 50 lines (linter) | PASS | H1 passed; linter gates would fire in full build |
| P2 | No magic numbers (linter) | PASS | H1 passed; magic-numbers linter would fire in full build |
| P3 | Config completeness | PASS | New `basis : Raw |
| P4 | Public-symbol export hygiene (linter) | PASS | H1 passed; mli-coverage linter gates |
| P5 | Internal helpers prefixed per convention | PASS | New helpers: _unrecognized_hash_failure, _hash_mismatch_error, _load_present all have _ prefix |
| P6 | Tests conform to test-patterns (P6 sub-rules) | PASS | No List.exists ... equal_to (true|false) patterns; no let _ = .*run\b without assertion; all match-expressions use assert_that + matchers with is_ok_and_holds or composition |
| A1 | Core module modifications (Portfolio/Orders/Position/Strategy/Engine) | PASS | No modifications to top-level core modules; Weinstein-specific strategy work only |
| A2 | Dependency-direction rules respected (analysis → trading/trading) | PASS | backtest/lib panel_runner.ml adds Weinstein_strategy.Weekly_sidetable_reader.basis_for call (already in dune deps); all analysis/ imports are to allow-listed analysis/weinstein/* paths within backtest surface |
| A3 | No unnecessary existing module modifications | PASS | PR files from gh pr view API show 28 files total; scope limited to snapshot/basis-related modules and tests; no cross-feature drift |
Rework Verification (nesting linter fix)
The agent extracted two deeply-nested error-formatting helpers:
_unrecognized_hash_failure(lines 150–155): format string byte-identical to original inlinefailwithf_hash_mismatch_error(lines 183–188): format string byte-identical to original inlinePrintf.sprintf
Verified by comparing source string literals line-by-line; backslash-newline continuations produce identical compiled strings. No @nesting-ok markers added (linter approved on merit). Nesting depths reduced from 4→2 and 5→2 respectively.
R1 Bit-Identity Claim
The PR claims R1: "With an OLD-hash warehouse (or any split-free data, factor 1.0) behavior is bit-identical to main."
Supporting evidence found:
to_adjusted_basisfactor-1.0 path (trading/analysis/weinstein/snapshot_pipeline/lib/adjusted_basis.mli docstring): "Corrupt or missing close → factor 1.0; idempotent" — preserves input bars when no splitbasis_fordefaults to Raw (trading/trading/weinstein/strategy/lib/weekly_sidetable_reader.ml lines 157–163): manifests withmanifest_format_hash:Nonedefault toRaw, matching pre-existing code pathsresistance_sketch_readerbasis threading defaultRaw(trading/trading/weinstein/strategy/lib/resistance_sketch_reader.mli docstring, line 11): "basis defaults to Raw for backward compat" — existing callers unchanged- Test covers raw-basis hash loads (test_weekly_sidetable_reader.ml, lines 321–329):
format_hash_raw_basiswarehouse loads identically to current behavior
Confidence: HIGH. The claim is architecturally sound: old warehouses produce manifest_format_hash:None → basis_for → Raw → same code path as before. Split-free data has factor 1.0 → no numerical change. The test explicitly pins this (test_load_gated_raw_basis_hash_loads).
Quality Score
4 — Clean structural implementation; nesting rework correct; bit-identity claim well-supported; only minor: build resources prevented full H2/H3 verification (GHA sibling contention) but pattern analysis strongly indicates pass.
Verdict
APPROVED
Note: H2/H3 gates were unable to complete in full due to GHA container resource contention with a sibling agent running dune runtest concurrently. Format-check passed (H1), nesting-rework verification passed, test-pattern compliance confirmed via static analysis, and no architectural violations found. CI on main is clean and this PR's changes are mechanical/refactoring-oriented (adding basis type, threading it through readers, extracting error helpers). Recommend merging with confidence once CI completes.
dayfine
left a comment
There was a problem hiding this comment.
Reviewed SHA: ae52b3f
Behavioral QC — split-safe sketch basis (#2133 defect 2)
Reviewed by mutation, not inspection. Baseline (analysis/weinstein/snapshot_pipeline/test,
trading/data_panel/snapshot/test, trading/weinstein/snapshot/gen/test,
trading/weinstein/strategy/test) green, exit 0. Eight mutations run against that surface;
results in the Mutation Ledger below.
Contract Pinning Checklist
| # | Check | Status | Notes |
|---|---|---|---|
| CP1 | Each non-trivial claim in new .mli docstrings has an identified test that pins it |
FAIL | Pinned: basis_for hash→basis mapping (test_basis_for_recognized_hashes, M2 caught), unknown-hash raise (test_basis_for_unknown_hash_raises), both-hash load_gated acceptance (test_load_gated_raw_basis_hash_loads), anchor-column selection (test_read_v5_adjusted_basis_anchors_at_adjusted_close + legacy test_read_v5_selects_sidetable; M6a and M6b both caught), distinct hash literals (test_format_hash_pinned + test_format_hash_raw_basis_pinned), builder rescale (M1 caught), compute_windowed rescale (M8 caught), live rescale (M7 caught). Unpinned: (a) adjusted_basis.mli "Idempotent" — no test (property does hold; verified by probe, see R1 below); (b) bar_reader.mli sidetable_basis + panel_callbacks.mli ?sidetable_basis threading claim — M4 (deleting ~sidetable_basis:(Bar_reader.sidetable_basis bar_reader) from the one production call site, weinstein_strategy_screening.ml:144) leaves the suite green; sidetable_basis appears in exactly one test in the whole repo, at the Resistance_sketch_reader.read unit level. (c) resistance_sketch.mli states a claim that is factually wrong — see NEEDS_REWORK item 2. There is no test_adjusted_basis.ml at all. |
| CP2 | Each claim in PR body "Test plan" has a corresponding test in the committed test file | PASS | All five advertised tests exist and are load-bearing (each has a mutation that kills it): (1) builder 4:1 split → high 26 / mid 25 = test_split_rescaled_to_adjusted_basis (M1); (2) Adjusted→140 vs Raw→150 = test_read_v5_adjusted_basis_anchors_at_adjusted_close + test_read_v5_selects_sidetable (M6a/M6b); (3) hash gating + distinct literals = 3 tests in test_weekly_sidetable_reader.ml / test_weekly_sidetable.ml (M2); (4) compute_windowed split fixture, max_high_520w = 35 + hist_total = 1.0 = test_compute_windowed_split_uses_adjusted_supply (M8); (5) live anchor_close = 30 = test_split_anchor_is_adjusted_close (M7). No PR#478-style phantom claims. |
| CP3 | Pass-through / identity / invariant tests pin identity, not just size_is |
PASS | The factor-1.0 identity is pinned by the pre-existing test_matches_daily_to_weekly_no_deep / _with_deep, which compare builder output against _expected_of bars computed from the raw bars using elements_are (List.map … equal_to) — whole-record equality, so any non-exact rescale breaks them. test_load_gated_raw_basis_hash_loads likewise uses elements_are [equal_to e; …], not size_is. Only test_empty uses size_is 0, where size is the whole content. |
| CP4 | Each guard called out explicitly in code docstrings has a test exercising the guarded-against scenario | FAIL | adjusted_basis.mli names the corrupt-bar guard prominently — "A non-positive or NaN raw close_price admits no factor (f = 1.0) … a corrupt bar must not blow up the rescale" — and no test exercises it. M3 (deleting the guard outright, leaving b.adjusted_close /. b.close_price bare) survives all four affected test directories, exit 0. Guards that ARE covered: unknown format hash raises (test_basis_for_unknown_hash_raises), corrupt-anchor NaN sketch (pre-existing, unchanged). |
Behavioral Checklist
| # | Check | Status | Notes (cite authority doc section) |
|---|---|---|---|
| A1 | Core module modification is strategy-agnostic | NA | qc-structural marked A1 PASS (not flagged). No trading/trading/{portfolio,orders,position,strategy,engine}/ files in the 28-file diff. |
| S1 | Stage 1 definition matches book | NA | No stage-classification logic in this PR. |
| S2 | Stage 2 definition matches book | NA | |
| S3 | Stage 3 definition matches book | NA | |
| S4 | Stage 4 definition matches book | NA | |
| S5 | Buy criteria: entry only in Stage 2, on breakout above resistance with volume confirmation | PASS | The PR does not change the buy criterion; it corrects the overhead-resistance input to it. weinstein-book-reference.md §4.3 Overhead Resistance grades A/Clean → C/Heavy off "dense trading zone just above breakout", and §4.3 "Support, once broken, later becomes resistance" — both concepts require the historical price levels and the breakout price to lie on one continuous scale. Measuring a 520-week supply window on raw OHLC while anchoring on the same raw close makes a 4:1 split render prior supply 4× above the anchor (dropped → false "Clean", the CLMB case) or a reverse split 4× below (phantom wall). Pinning both the weekly high/mid and the anchor to the adjusted basis restores the book's semantics. Also consistent with .claude/rules/weinstein-faithful-core.md — a correctness fix to an existing spine input, not a new mechanism, so experiment-flag-discipline.md R1/R2 do not fire. |
| S6 | No buy signals generated during Stage 1, 3, or 4 | NA | Untouched. |
| L1 | Initial stop placed below the base | NA | Stops untouched. |
| L2 | Trailing stop rises as price advances | NA | |
| L3 | Stop triggers on weekly close | NA | |
| L4 | Stop state machine transitions | NA | |
| C1 | Screener cascade order | NA | Cascade untouched; this changes an input to individual scoring only. |
| C2 | Bearish macro blocks all buy candidates | NA | |
| C3 | Sector RS vs market, not absolute | NA | |
| T1 | Tests cover all 4 stage transitions | NA | |
| T2 | Bearish-macro → zero-buy-candidate test | NA | |
| T3 | Stop-loss trailing tests | NA | |
| T4 | Tests assert domain outcomes, not just "no error" | PASS | Every new test asserts a specific price/anchor/hash value (26/25, 140 vs 150, 35 + hist_total 1.0, 30, distinct literals) rather than is_ok. Confirmed load-bearing by mutation — see CP2. |
Mutation Ledger
| # | Mutation | Result | What it proves |
|---|---|---|---|
| M1 | Remove the to_adjusted_basis map in Weekly_sidetable_builder.of_bars |
CAUGHT (exit 1) | Builder rescale claim is genuinely pinned |
| M2 | basis_for returns Adjusted unconditionally |
CAUGHT | Hash→basis mapping pinned in all three arms |
| M3 | Delete the corrupt/NaN raw-close guard in Adjusted_basis._factor |
SURVIVED (exit 0, all 4 dirs) | Documented guard has zero coverage → CP4 FAIL |
| M4 | Drop ~sidetable_basis:(Bar_reader.sidetable_basis …) at the sole production call site |
SURVIVED (exit 0) | The Bar_reader → Panel_callbacks → reader threading is untested end-to-end → CP1 FAIL |
| M6a | _anchor_field_of_basis Adjusted → Close |
CAUGHT | Adjusted anchor pinned |
| M6b | _anchor_field_of_basis Raw → Adjusted_close |
CAUGHT | The R1 default-Raw legacy anchor is pinned |
| M7 | Remove the rescale in Live_resistance_sketch.of_daily_bars |
CAUGHT | Live-picks fix pinned |
| M8 | Remove both rescales in Resistance_sketch.compute_windowed |
CAUGHT | Dense/legacy fix pinned |
Six of eight caught. The two survivors are the two CP failures.
R1 bit-identity — direct finding
I ran a bit-level probe (Int64.bits_of_float comparison) on to_adjusted_basis rather than
reasoning about it, because the question the dispatcher raised is exactly the kind that
inspection gets wrong.
The mechanism is sound, and it is bitwise — not merely "numerically close". For finite
non-zero x, IEEE-754 x /. x is exactly 1.0, and h *. 1.0 is exactly h. Probed at
close_price = adjusted_close for 1e-8, 0.01, 1.0, 3.7, 123.456789, 99999.99, 1e9 — O/H/L and
close bit-identical in every case (bitid_h=true bitid_c=true). The
0.9999999999999999-factor worry does not materialise. Idempotence also holds bitwise
(apply twice to a 4:1 split bar → identical bits) — though, per CP1, nothing in the suite pins it.
But the stated precondition is wrong, and it is wrong in the direction that matters. The PR
body and resistance_sketch.mli both say the rescale is "a no-op for split-free data (factor
1.0), so split-free callers are bit-unchanged." The actual precondition is
adjusted_close = close_price — which is a much narrower set. Probe, no split, dividend
adjustment only (close 100.0, adjusted_close 99.87): bitid_h=false, output high
0x1.9f758e219652cp+6 vs input 0x1.ap+6. Real EODHD warehouses carry
adjusted_close ≠ close_price for essentially every historical bar of every dividend-paying
symbol. Since compute_windowed and of_daily_bars rescale unconditionally, those two paths
change output for essentially all real symbols, split or not.
That change is the intended fix and I am not disputing it. What I am flagging is that every
test fixture in the affected suites sets adjusted_close = close_price, so the suite is
structurally incapable of exposing the gap between the documented precondition and the real one.
That is precisely the Float.abs latent-contract pattern from #2139: a headline contract that is
true only for the data the suite happens to generate.
The R1 claim as scoped in the PR body — "existing dense/prebuilt warehouses are unchanged" —
does hold, and for a better reason than the wording gives: it holds because the default is
Raw and prebuilt artifacts are not recomputed, not because the rescale is a no-op. I verified
the threading is complete: weinstein_strategy_screening.ml:144 is the only production site that
supplies a weekly_sidetable, and it now passes sidetable_basis explicitly;
stock_analysis_callbacks_of_snapshot_views, Bar_reader.of_in_memory_bars and the five
decision_* / trade_audit bins supply no side-table loader at all, so the v5 path never fires
and the Raw default is inert for them. No call site picks Adjusted implicitly. M6b
confirms the Raw-default anchor is pinned by test.
Additional finding — the corrupt-bar guard is one-sided
Beyond the missing test (CP4), the guard itself only covers a corrupt raw close. A corrupt
adjusted_close is entirely unguarded, and the probe shows what happens:
adjusted_close = nan, valid close → the whole bar becomes NaN (o/h/l/c allnan)adjusted_close = 0.0→ the whole bar becomes0.0adjusted_close = -1.0→ negative prices (h = -0x1.0a3d70a3d70a4p+0)
Downstream this does not fail loudly: a NaN weekly entry makes
Float.(entry.high > anchor) false, so that week's supply is silently dropped from the
histogram, and NaN propagates into max_high_*. So the docstring's headline — "a corrupt bar
must not blow up the rescale" — is true only for one of the two corruption modes, and the other
one degrades silently rather than loudly. Same asymmetry exists in the pre-existing
Svg_series._to_adjusted_basis this module is modelled on, so it is inherited, not introduced —
but it is newly load-bearing here, because this path now feeds supply measurement rather than
chart rendering.
Quality Score
2 — Below standard for merge as-is: the core mechanism is correct and unusually well pinned (all five advertised Test-plan claims survive mutation, 6/8 mutations caught, no phantom tests), but an explicitly-documented corrupt-bar guard has zero coverage, the production threading chain is untested end-to-end, and one new .mli claim is factually wrong in a way the fixture set cannot expose. All three are small, mechanical fixes.
Verdict
NEEDS_REWORK
NEEDS_REWORK Items
CP4: Documented corrupt-bar guard in Adjusted_basis has no test
- Finding:
adjusted_basis.mliexplicitly names the guarded edge case ("A non-positive or NaN rawclose_priceadmits no factor (f = 1.0) … a corrupt bar must not blow up the rescale"). Mutation M3 — deleting the guard entirely so_factoris bareb.adjusted_close /. b.close_price— leavesdune runtestgreen across all four affected test directories (exit 0). There is notest_adjusted_basis.mlin the tree. Separately, the guard is one-sided: corruptadjusted_close(NaN / 0 / negative) is unguarded and silently produces NaN / zeroed / negative bars whose supply is then dropped from the histogram without any signal. - Location:
trading/analysis/weinstein/snapshot_pipeline/lib/adjusted_basis.ml:7-9(guard);trading/analysis/weinstein/snapshot_pipeline/lib/adjusted_basis.mli:15-22(docstring); no test file exists. - Authority:
.claude/rules/qc-behavioral.mdCP4 — "FAIL if the docstring names an edge case but no test covers it." Precedent: PR #2139Float.abslatent contract. - Required fix: add
trading/analysis/weinstein/snapshot_pipeline/test/test_adjusted_basis.mlcovering, at minimum: (a)close_price = nan⇒ O/H/L unscaled,close = adjusted_close; (b)close_price = 0.0andclose_price < 0.0⇒ same; (c) idempotence on an already-adjusted bar; (d) bitwise identity whenadjusted_close = close_price(the R1 pin — assert onInt64.bits_of_floator useequal_toon the whole record, notfloat_equal). Then either extend_factorto guardadjusted_closesymmetrically (NaN / ≤ 0 ⇒ factor 1.0, close stays raw) or amend the docstring to state plainly that only raw-close corruption is handled and that a corruptadjusted_closepoisons the bar. - harness_gap: LINTER_CANDIDATE — a deterministic unit fixture with known corrupt inputs catches this with no inference.
CP1: Bar_reader.sidetable_basis → Panel_callbacks → Resistance_sketch_reader threading is untested end-to-end
- Finding:
bar_reader.mliandpanel_callbacks.mliboth document the threading as a contract, and it is listed under "What it does" in the PR body. Mutation M4 — removing~sidetable_basis:(Bar_reader.sidetable_basis bar_reader)from_full_analysis_of_survivor, the only production site that supplies aweekly_sidetable— leavestrading/weinstein/strategy/testgreen (exit 0).sidetable_basisoccurs in exactly one test repo-wide, and only at theResistance_sketch_reader.readunit boundary. An adjusted-basis warehouse could therefore anchor at rawClosein production — the exact raw/adjusted mixing this PR exists to eliminate — with no test noticing. - Location:
trading/trading/weinstein/strategy/lib/weinstein_strategy_screening.ml:144; contracts attrading/trading/weinstein/strategy/lib/bar_reader.mli:186-192andtrading/trading/weinstein/strategy/lib/panel_callbacks.mli:123-128. - Authority:
.claude/rules/qc-behavioral.mdCP1 — "Each non-trivial claim in new .mli docstrings has an identified test that pins it." - Required fix: add one test in
trading/trading/weinstein/strategy/test/test_panel_callbacks.mlthat callsstock_analysis_callbacks_of_weekly_viewswith~snapshot_cb,~weekly_sidetable, and~sidetable_basis:Adjusted, then asserts the resultingget_sketch ()hasanchor_closeequal to the stub'sAdjusted_close(and a companion at theRawdefault assertingClose). That kills M4. - harness_gap: LINTER_CANDIDATE — deterministic golden-scenario test.
CP1: resistance_sketch.mli states a precondition that is factually wrong
- Finding: the new docstring says "The rescale is a no-op for split-free data (factor 1.0), so split-free callers are bit-unchanged." Bit-level probe: with no split, dividend adjustment alone (
close_price 100.0,adjusted_close 99.87) yields high0x1.9f758e219652cp+6vs input0x1.ap+6— not bit-unchanged. The true precondition isadjusted_close = close_price. Because every fixture in the affected suites setsadjusted_close = close_price, the test surface cannot expose the discrepancy. The same wording appears in theweekly_sidetable_builder.mlheader comment and in the PR body's R1 section. The R1 conclusion itself ("existing dense/prebuilt warehouses are unchanged") still holds — but via theRawdefault and the non-recomputation of prebuilt artifacts, not via a no-op rescale. - Location:
trading/analysis/weinstein/snapshot_pipeline/lib/resistance_sketch.mli:96-99(compute_windowedBasis paragraph) and:63-70(compute);trading/analysis/weinstein/snapshot_pipeline/lib/resistance_sketch.ml:186-191; PR body §"R1 bit-identity". - Authority:
.claude/rules/qc-behavioral.mdCP1; #2139 latent-contract precedent. - Required fix: replace "split-free data" with "data where
adjusted_close = close_price(typically only the most recent unadjusted segment)" in all three places, and state explicitly thatcompute_windowed/of_daily_barsoutput does change for dividend-adjusted history — which is intended, and is the reason#2133's follow-up warehouse rebuild + golden re-pin is required. Add the bitwise-identity assertion named in the CP4 item so the corrected precondition is pinned. - harness_gap: LINTER_CANDIDATE — a fixture with
adjusted_close ≠ close_priceand no split is deterministic and would have caught the wording.
Non-blocking follow-ups (not part of the rework)
Adjusted_basis.to_adjusted_basisis described as a "New shared helper", butSvg_series._to_adjusted_basis(trading/trading/weinstein/snapshot/lib/svg_series.ml:39-47) still carries a byte-equivalent private copy and was not switched over. Two copies of the rescale is a drift risk given the.mlinames the other one as its semantic reference. Worth a small follow-up to makeSvg_seriescall the shared helper.- The odoc cross-ref
{!Weinstein_snapshot.Svg_series._to_adjusted_basis}inadjusted_basis.ml:4targets a_-prefixed private symbol and will not resolve; folding it into follow-up 1 removes the need for it.
Closes the two surviving mutations and the wrong precondition wording from
review 4795688051. Tests + doc wording only; no production logic changed.
N1 (CP4) — M3, the corrupt/NaN raw-close guard in `Adjusted_basis`, had zero
coverage: deleting it left all four affected test dirs green. New
`test_adjusted_basis.ml` pins NaN / 0.0 / negative raw close => factor 1.0, the
4:1 split rescale, idempotence, and (bitwise, via `Int64.bits_of_float`) the
factor-1.0 identity across seven magnitudes. M3 re-run after the change: exit 0
-> exit 1 (3 failures).
N2 (CP1) — M4, dropping `~sidetable_basis` at the sole production call site
(`weinstein_strategy_screening.ml:144`), left the suite green. The new test in
`test_panel_callbacks.ml` drives `Internal_for_test.on_market_close` over a
side-table-backed reader and records every `read_field` the run issues for the
scored symbol. `Resistance_sketch_reader` is the only caller of that primitive
in the tree, so the recorded field is exactly the sketch anchor column the
`Bar_reader -> screening -> Panel_callbacks -> reader` chain selected:
`Adjusted_close` under `Adjusted`, `Close` under `Raw`. M4 re-run: exit 0 ->
exit 1.
N3 (docs) — the stated precondition said the rescale is a no-op for
"split-free" data. The real condition is `adjusted_close = close_price`; a
dividend-only adjustment moves every O/H/L bit, so `compute_windowed` /
`of_daily_bars` output does change for essentially every real dividend payer
(intended). Corrected in `resistance_sketch.{ml,mli}` and
`live_resistance_sketch.mli`, with R1's conclusion restated on its actual
basis: existing warehouses are safe because the default is `Raw` and prebuilt
artifacts are not recomputed, not because the rescale is a no-op. A
dividend-only fixture (`adjusted_close <> close_price`) now pins it — every
other fixture in these suites sets them equal, which is why the suite could not
expose the gap.
`adjusted_basis.mli` also now states plainly that the guard is one-sided: a
corrupt `adjusted_close` is unguarded and degrades silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Rework iteration 1 pushed as N1 / CP4 — M3 (corrupt/NaN raw-close guard). Added
N2 / CP1 — M4 (
N3 — "split-free" wording. Corrected in all three code sites ( Local verification (foreground, exit codes read): Filed, not fixed here (reported back to the dispatcher rather than grown into this PR): (1) |
Merged this run: #2143 (H-RUNENV-WORKTREE-BLIND — the run-in-env.sh false-green that had been invalidating worktree verification repo-wide) and #2146 (Current-milestone marker, re-arming check_11_linter_expiry). #2145 reworked off a red CI, QC'd, and reworked again on two surviving mutations; awaiting re-QC. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Automated daily orchestrator run 30340958135. Full summary: `dev/daily/2026-07-28.md`. ## Merged this run - **#2143 → `916825fb`** — `H-RUNENV-WORKTREE-BLIND`. `run-in-env.sh` built the *main checkout* whenever an agent verified a branch from a worktree, returning a green that asserted nothing. Full QC loop in one run: behavioral NEEDS_REWORK (q2) → rework → re-QC APPROVED (q4) → merge. - **#2146 → `d37f94a8`** — `Current milestone: M5` marker; re-arms `check_11_linter_expiry`, which had been silently unable to evaluate *any* milestone-pinned exception. Docs-only carve-out, CI-only gate. ## Left in flight - **#2145** — adjusted-basis sketch migration. CI was red on arrival (2 nesting violations in its own new functions); fixed, then structural APPROVED (q4) and behavioral NEEDS_REWORK (q2) on **2 of 8 mutations surviving**. Rework `eaba3891` pushed; needs re-QC + merge. ## Two things worth the human's attention 1. **Measured spend on 2026-07-27 was $405.30 — 203% of the $200 cap** — while that day's summaries reported "~5% of cap, UNDER_TARGET". Step 3.75 estimates from the price table instead of reading the measured records that are on disk and correct. See §Budget / §Escalations. 2. **`.claude/agents/lead-orchestrator.md` write access** is now blocking a four-run-deep queue of spec fixes. See Question 2. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: claude-orchestrator <noreply@github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: GitHub Actions Bot <bot@github-actions>
dayfine
left a comment
There was a problem hiding this comment.
Reviewed SHA: 77e5b57
Structural QC — sketch-adjusted-basis (SCOPED DELTA RE-REVIEW)
This is a scoped delta re-review, not a fresh full structural review.
- I previously reviewed this PR at
ae52b3f1(review id 4795521335) and returned APPROVED, quality 4. - Since then: one rework commit (
eaba3891) plus a clean merge ofmain(tip77e5b57b). - What this review covers: the ~296 lines of brand-new test code in the delta, the new
test/dunestanza, and re-verification of A2/A3 against the current tip. - What this review does NOT re-derive: the production logic of
adjusted_basis.ml/resistance_sketch.ml/ the reader chain. That rests on the prior APPROVED, which transfers because I verified the production delta is comment/docstring-only — zero executable production lines changed (git diff ae52b3f16 77e5b57b1over the fivelib/files). - Files differing between the two SHAs under
dev/lib/run-in-env.sh,trading/devtools/checks/*,docs/design/*,dev/daily/*,dev/health/*arrived via the merge of main (#2143/#2146/#2147). Confirmed againstGET /pulls/2145/files(31 files) — none of those paths are PR content.
Hard gates — verified, not predicted
I did not run the gates locally (three concurrent builds on a 4-CPU box OOM into a misleading exit 137). Instead I queried and read GET /repos/dayfine/trading/commits/77e5b57b12a9a934bc18440649650b659f0f44c5/check-runs and confirmed the completed results on this exact tip. This is strictly stronger evidence than a local run — CI executes the full linter suite (fn_length, nesting, magic_numbers, file_length, mli_coverage, status_file_integrity, no_python) in a clean container:
build-and-test— completed / success (check-run 90302276692, 2026-07-28T14:27:10Z)perf-tier1-smoke— completed / success (check-run 90302276450, 2026-07-28T14:17:35Z)
To be explicit about the distinction: this is a verified pass on the reviewed SHA, not a prediction of one.
Structural Checklist
| # | Check | Status | Notes |
|---|---|---|---|
| H1 | dune build @fmt |
PASS | By CI evidence, cited above — build-and-test success on 77e5b57b. Verified via check-runs API, not predicted. |
| H2 | dune build |
PASS | Same run. |
| H3 | dune runtest |
PASS | Same run; includes the full linter suite in a clean container. |
| P1 | Functions ≤ 50 lines (linter) | PASS | fn_length_linter green in H3. Longest new fn _drive_screen_tick (~28 lines) is a single call with labelled args. |
| P2 | No magic numbers (linter) | PASS | linter_magic_numbers.sh green in H3. |
| P3 | Config completeness | NA | Delta adds no tunable production values — production delta is comment-only. |
| P4 | Public-symbol export hygiene (linter) | PASS | linter_mli_coverage.sh green in H3. New test files export nothing. |
| P5 | Internal helpers underscore-prefixed | PASS | All new helpers conform: _as_of, _bar, _equal_bar, _bits, _unscaled_with_adjusted_close, _thread_daily_bars, _recording_cb, _drive_screen_tick, _anchor_fields_read_by_screen. |
| P6 | Test-pattern conformance (.claude/rules/test-patterns.md) |
PASS | Applied all three greppable sub-rules to both new blocks — see detail below. Zero hits in new code. |
| — | New test/dune stanza correct + minimal + actually wired |
PASS | See detail below. |
| A1 | Core module modifications | PASS | Delta touches no portfolio/, orders/, position/, strategy/lib executable code. Production delta is comment-only. |
| A2 | No new analysis/ → trading/trading/ imports |
PASS | Only two dune files differ; trading/devtools/checks/dune is from the main merge (#2143), not PR content. The PR's own analysis/.../test/dune change adds no libraries — only a test name. Its pre-existing trading.data_panel.snapshot dep is the always-permitted reverse direction (analysis consuming trading). |
| A3 | No unnecessary non-feature module modifications | PASS | Computed from GET /pulls/2145/files (31 files), not a git-log ancestry walk, and re-fetched origin first. All 31 are in-scope snapshot/sketch/basis paths plus dev/status/weekly-snapshot.md (a per-track status file — permitted; dev/status/_index.md untouched). |
P6 detail — the three sub-rules, on new code only
Both new blocks open Matchers and are clean of all three patterns (an open Matchers that still contained one would be a FAIL, not a PASS):
List.exists ... equal_to true/false— zero hits in either file.let _ = ...on_market_close/let _ = ....run— zero hits. This is the sub-rule most at risk here, since_drive_screen_tickcallsInternal_for_test.on_market_close. Its result is returned, threaded out through_anchor_fields_read_by_screen, and asserted (field fst is_ok/field snd is_ok,test_panel_callbacks.ml:938-940). Correct handling, not a discard.match ... with | Error ... -> assert_failure/ bare| Ok ... ->— sixassert_failurehits exist intest_panel_callbacks.mlat lines 209, 427, 430, 472, 607, 610, but all are pre-existing, below the new block which starts at line 814.awk 'NR>=810 && /assert_failure/'returns nothing. New code has zero.
Composition is idiomatic throughout: one assert_that per value, checks composed via all_of + field + elements_are, no nested assert_that inside a matcher callback (grep for fun .* -> .*assert_that: no hits).
Two things I want to call out as genuinely good rather than merely compliant:
test_adjusted_basis.mlasserts IEEE-754 bit patterns (_bits,Int64.bits_of_float) where the contract is bit-identity, rather than afloat_equalwith an epsilon — which by construction cannot distinguish identity from a 1-ulp drift. That is the right instrument for the claim being pinned.- The integration assertion is self-guarding against vacuity.
test_sidetable_basis_threads_through_screeningassertselements_are [equal_to Adjusted_close]on the recorded field list — exactly one element. If the synthetic series failed to drive the stock past Phase 1 and the sketch reader were never reached,seenwould be empty and the test would fail, not silently pass. That is the correct defence against the "test that doesn't actually test" failure mode.
test/dune detail — is the new test genuinely wired into dune runtest?
Yes, verified. The stanza change is a one-line addition of test_adjusted_basis to the existing (tests (names ...)) list. Dune's tests stanza attaches every listed name to the runtest alias, and the file ends with let () = run_test_tt_main suite (line 158) with all seven cases registered in suite (lines 140-156). The suite is reachable and executes.
The stanza is also minimal: no libraries were added. Everything the new file needs (core, matchers, ounit2, types, weinstein.snapshot_pipeline) was already declared for its sibling tests. This is the correct shape — contrast the #2143 finding where a test was added but never wired.
Test-code quality
Positives beyond the above: helper naming is consistent and self-documenting; fixture constants are semantically meaningful OHLC values rather than arbitrary literals; derived expectations are written as derivations (104.0 *. 0.9987 at test_adjusted_basis.ml:134) instead of pre-computed literals, so the intent survives; there is no duplication between the two new blocks (one is a unit-level guard suite, the other an integration thread — disjoint concerns); and the block comments explain why each contract is unreachable from the existing suites, which is the part that usually rots.
Worth noting that the rework's production doc-comments correct a previously misleading claim — resistance_sketch.ml used to say the rescale was "a no-op for split-free data", now correctly narrowed to bit-identity exactly when adjusted_close = close_price, with the dividend-only case called out. test_dividend_only_adjustment_is_not_a_no_op pins precisely that correction. Docs and tests moved together; that is the right pattern.
FLAGs (do not block approval)
test_panel_callbacks.mlis now 973 lines.linter_file_length.shscopes to*/lib/*.mland exempts test files entirely, so this draws no linter signal — judging it manually per the review contract. The 16-case "Panel_callbacks parity" suite is cohesive and this delta contributed only 138 lines, so I am not blocking on it. But it is past the point where a reader can hold it in one pass, and.claude/rules/code-health-discipline.mdtreats size growth as a leading indicator rather than something to address on failure. Suggest splitting the parity suite (e.g. sketch/basis threading vs. volume/RS parity) before the next block lands in this file.test_adjusted_basis.ml:128-131collapses a comparison to a boolean —field (fun r -> Poly.equal (_bits r) (_bits b)) (equal_to ~msg:... false). This does not match P6 sub-rule 1 (it is notList.exists) and is mitigated on both sides: a custom~msgis supplied, and the sameall_ofimmediately pins the concretehigh_price/close_pricevalues, so a failure is diagnosable. Noting it only as a style nit for the direction of travel.module FL = Portfolio_risk.Force_liquidation(test_panel_callbacks.ml:33) is a two-letter alias used exactly once. Prefer the qualified path or a fuller alias.
Quality Score
4 — New tests are well-instrumented and non-vacuous (bit-level identity, self-guarding integration assertion), P6 fully clean and the dune wiring correct and minimal; held off 5 by the 973-line test file and two small style nits.
Verdict
APPROVED
(Delivered as event: COMMENT because the review bot cannot self-APPROVE; the verdict above is the authoritative structural result. No FAIL items — the three FLAGs above are non-blocking.)
dayfine
left a comment
There was a problem hiding this comment.
Reviewed SHA: 77e5b57
Behavioral QC — split-safe sketch basis (#2133 defect 2) — re-review after rework iteration 1
Re-reviewed by re-running the mutations myself in an isolated worktree at this tip, not by
reading the rework diff. Baseline analysis/weinstein/snapshot_pipeline/test and
trading/weinstein/strategy/test both exit 0 (test_adjusted_basis.exe: 7 tests).
Seven mutations this pass, 6 killed / 1 surviving (the survivor is non-material — FLAG below).
Mutation ledger (this pass)
| ID | Mutation | Exit | Killed by |
|---|---|---|---|
| M3 | delete the corrupt-close guard; _factor becomes bare b.adjusted_close /. b.close_price |
1 | NaN raw close admits no factor, Zero raw close…, Negative raw close… (3 failures) |
| N1 | boundary weakening: Float.( <= ) b.close_price 0.0 → Float.( < ) … |
1 | Zero raw close admits no factor (1 failure, exactly the boundary) |
| N2 | drop only the `Float.is_nan b.close_price | ` disjunct | |
| N3 | sub-epsilon drift: high_price = b.high_price *. f *. (1.0 +. 1e-15) |
1 | Equal closes is bitwise identity, Idempotent on an already-adjusted bar (+4 other suites) |
| M4 | delete ~sidetable_basis:(Bar_reader.sidetable_basis bar_reader) at weinstein_strategy_screening.ml:144 |
1 | Side-table basis threads through the screening path (1 failure; 14/15 other tests passed) |
| N4 | Panel_callbacks ?sidetable_basis default Raw→Adjusted (panel_callbacks.ml:243) |
1 | Sketch-v5 side-table threading selects v5 vs dense |
| N5 | Bar_reader.of_snapshot_views ?sidetable_basis default Raw→Adjusted (bar_reader.ml:179) |
0 — SURVIVES | none — non-blocking, see FLAG |
M3 and M4, the two survivors of the previous pass, are both dead. N1/N2 additionally show each
disjunct of the guard is independently pinned, which is stronger than the required minimum.
N3 confirms the R1 bit-identity assertion is genuinely bitwise: a 1e-15 relative drift is ~1e-13
absolute at these magnitudes and would pass a float_equal ~epsilon:1e-9; it fails
Int64.bits_of_float. The test asserts identity, not approximation.
On the author's pushback re: M4 — the author is right, and my prior review was wrong
My previous required fix said: "add one test in test_panel_callbacks.ml that calls
stock_analysis_callbacks_of_weekly_views with ~snapshot_cb, ~weekly_sidetable, and
~sidetable_basis:Adjusted … That kills M4." That last sentence is false, and I am
recording it as a defect in the review rather than in the code.
M4 deletes the argument at the screening call site. A test that itself supplies
~sidetable_basis to stock_analysis_callbacks_of_weekly_views never executes
weinstein_strategy_screening.ml:144, so it structurally cannot observe the deletion — it would
assert on a basis it passed in by hand. Empirically corroborated: under M4, 14 of 15 tests in
the suite passed, and the one pre-existing test in exactly the shape I prescribed
(test_weekly_sidetable_threading, which calls stock_analysis_callbacks_of_weekly_views
directly with ~snapshot_cb + ~weekly_sidetable) passed cleanly. Only the author's
Internal_for_test.on_market_close-driven test failed.
Driving the real screening path and recording every read_field the run issues is the only shape
that can kill this mutation, and the recorder is sound precisely because
Resistance_sketch_reader is the sole read_field caller in the tree (every bar/view read goes
through read_field_history), so the recorded fields are exactly the anchor column selected.
The author correctly rejected an under-specified prescription and substituted a stronger one.
A prescribed fix that does not kill the mutation it was written for is a review defect; this one
was.
On finding 3 (wording) — also partly mis-scoped by me
I claimed the false "no-op for split-free data" wording appeared in three places including the
weekly_sidetable_builder.ml header. It did not:
git show ae52b3f1:…/weekly_sidetable_builder.ml | grep -n "no-op\|split-free\|bit-" returns
nothing, and the file is byte-unchanged since ae52b3f1. Its header describes the adjusted basis
and makes no bit-identity claim, so there was nothing to fix there.
The two real code sites are corrected, and the author corrected four sites rather than the two
that existed: resistance_sketch.mli:63-70 (compute — now states plainly that it does not
rescale) and :96-99 (compute_windowed), resistance_sketch.ml:191, plus
adjusted_basis.mli:29-33 and live_resistance_sketch.mli:28-33. PR body §R1 is rewritten and now
states the true reason existing warehouses are unchanged (the Raw default + non-recomputation of
prebuilt artifacts), explicitly disclaiming the no-op explanation.
I verified the replacement statement is true in both directions, since this is the load-bearing
claim for every prebuilt golden: f = adjusted/close = x/x is exactly 1.0 for finite non-zero
x and h *. 1.0 == h bitwise, so adjusted_close = close_price ⇒ bit-identity; conversely if
adjusted_close ≠ close_price the bar cannot be bit-identical because close_price := adjusted_close
differs, even where the ratio rounds to 1.0 and O/H/L happen not to move. "Exactly when" is correct.
Contract Pinning Checklist
| # | Check | Status | Notes |
|---|---|---|---|
| CP1 | Each non-trivial claim in new .mli docstrings has an identified test that pins it |
PASS | Both previously-unpinned claims are now pinned. (a) bar_reader.mli:186-193 / panel_callbacks.mli threading claim → test_sidetable_basis_threads_through_screening (kills M4, verified). (b) adjusted_basis.mli "Idempotent" → test_idempotent_on_already_adjusted_bar (kills N3). (c) adjusted_basis.mli bit-identity precondition → test_equal_closes_is_bitwise_identity (7 magnitudes, kills N3). (d) the previously false resistance_sketch.mli precondition is now factually correct and pinned by test_dividend_only_adjustment_is_not_a_no_op. Claims carried over from the prior pass (hash→basis mapping, unknown-hash raise, both-hash load_gated, anchor-column selection, distinct hash literals, builder/compute_windowed/live rescale) remain pinned, each with a killing mutation on record. |
| CP2 | Each claim in PR body "Test plan"/"Test coverage" has a corresponding committed test | PASS | All five original Test-plan items still map to real, load-bearing tests. The rework added one new PR-body claim — R1's "verified across seven magnitudes (1e-8 … 1e9) and now pinned by test_adjusted_basis.ml" — and test_equal_closes_is_bitwise_identity carries exactly seven: [1e-8; 0.01; 1.0; 3.7; 123.456789; 99_999.99; 1e9]. No PR#478-style phantom claims. |
| CP3 | Pass-through / identity / invariant tests pin identity, not just size_is |
PASS | _equal_bar is whole-record equal_to ~cmp:Types.Daily_price.equal; the identity/idempotence tests compare full 4-tuples of Int64.bits_of_float (elements_are (List.map … equal_to)). No size_is-only identity assertion anywhere in the new tests. This is the strongest form CP3 asks for. |
| CP4 | Each guard named in code docstrings has a test exercising the guarded-against scenario | PASS | The corrupt-close guard is pinned three ways — M3 (whole guard), N1 (boundary), N2 (NaN disjunct) — all killed, so each disjunct is independently covered. adjusted_basis.mli "Pinned by test_adjusted_basis.ml" is accurate. The one-sided-guard asymmetry took the documented-not-fixed branch of my required fix: adjusted_basis.mli:21-27 now states plainly that only close_price corruption is handled, that a corrupt adjusted_close poisons the bar (nan→all-NaN, 0.0→zeroed, negative→negative), and names the silent-degradation mechanism (a NaN weekly high never compares above the anchor, so the week is dropped from the histogram). adjusted_basis.ml:3-8 carries the matching comment. Accepted, with the cleanup backlog item below. |
Behavioral Checklist (domain)
Per .claude/rules/qc-behavioral-authority.md §"When to skip this file entirely": the rework delta
is test-only plus doc-comments on a data-plumbing / basis-selection change. All S*/L*/C*/T* rows
are NA — no stage-classification, stop, or screener-cascade logic is touched. The S5 rationale
recorded in my prior review (this corrects the overhead-resistance input to the buy criterion so
historical supply and the breakout anchor lie on one continuous scale, per
weinstein-book-reference.md §4.3 Overhead Resistance) is unchanged and still PASS. A1 — NA,
qc-structural did not flag it.
Test-pattern conformance (.claude/rules/test-patterns.md)
Both new files conform: one assert_that per value, composition via all_of / field /
elements_are, no assert_that nested in a matcher callback, and no bare
match … | Error _ -> assert_failure in the new code.
test_sidetable_basis_threads_through_screening's two assert_that calls are on two distinct
values (the result pair, then the field pair), which is the rule, not a violation.
Nit (non-blocking, no rework required): test_dividend_only_adjustment_is_not_a_no_op asserts
field (fun r -> Poly.equal (_bits r) (_bits b)) (equal_to ~msg:… false) — a boolean folded into
equal_to false, the same smell qc-structural's P6 sub-rule 1 targets. matchers.mli:85 provides
not_, so field _bits (not_ (equal_to (_bits b))) reads better and reports better on failure.
Likewise pair (matchers.mli:241) would replace the field fst / field snd tuple pattern.
Cosmetic only — the assertion is correct and load-bearing as written.
FLAG (non-blocking) — N5: the Bar_reader Raw default is unpinned
bar_reader.ml:179's ?(sidetable_basis = Weekly_sidetable_reader.Raw) can be flipped to
Adjusted with trading/weinstein/strategy/test staying green (exit 0). The new screening test
cannot see it because it passes ~sidetable_basis explicitly, and
test_weekly_sidetable_threading builds callbacks via Panel_callbacks rather than Bar_reader
(which is why it catches N4 but not N5).
Not blocking, because the default is unobservable everywhere it applies. I checked both
production side-table consumers and both resolve the basis from the manifest hash and pass it
explicitly: snapshot_warehouse_reader.ml:76-80 and panel_runner.ml:214-222, each via
Weekly_sidetable_reader.basis_for ~manifest_format_hash. Every caller that omits
~sidetable_basis also supplies no weekly_sidetable_loader, so weekly_sidetable_for returns
None, the sketch takes the dense path, and sidetable_basis is never consulted. The substantive
part of the .mli claim — None hash ⇒ Raw — is pinned by test_basis_for_recognized_hashes.
So this is belt-and-braces coverage, not a live gap. Worth adding a Bar_reader-level default
assertion if this file is touched again; recorded here rather than filed, to avoid backlog noise.
Follow-ups from the prior review — confirmed captured, not dropped
Both non-blocking items are filed in dev/status/cleanup.md §Backlog with source attribution to
this PR, as required: :34 adjusted_basis_guard_asymmetry (the one-sided guard, incl. the
silent-drop mechanism) and :35 rescale_duplication (Svg_series._to_adjusted_basis still
carries a byte-equivalent private copy that was not switched over, folding in the unresolvable odoc
ref {!Weinstein_snapshot.Svg_series._to_adjusted_basis} to a _-private symbol). Correctly
captured as follow-ups rather than silently dropped.
Quality Score
4 — All contract-pinning checks pass on re-verified mutations; the rework exceeded the required minimum (per-disjunct guard coverage, genuinely bitwise identity assertions that survive a sub-epsilon mutation, four wording sites corrected rather than two) and correctly rejected an erroneous prescription with a sound structural argument. Held back from 5 only by a cosmetic matcher nit and the unpinned Bar_reader default.
Verdict
APPROVED
…MB out, PH in, validation clean (#2150) The 07-24 picks regenerated after #2145 (split-safe resistance sketch basis) + the weekly-review warehouse rebuild (adjusted-basis side-tables, new format hash `128e4c1e…`, 3,173/3,173 verified). ## The regrade result (#2133 defect 2, closed for live picks) - **CLMB (was rank 2, score 86, "Clean (0.12)") drops out of the candidate list entirely.** Its clean grade was a split artifact: the 4:1 split hid the real 2024-11→2025-07 supply wall at $25–34 sitting directly above the $25.46 close. Honest grading removes the pick — exactly what the user's chart-review instinct said. - **PH (score 75, Virgin_territory 0.00) enters** in the freed 20th slot. - **All 19 other candidates, scores, and grades bit-identical** — the fix is surgical. - Classes: 3 EXTENDED (suppressed) / 5 through-entry / 12 valid-stop. - **`validation.txt`: OK, no findings** — zero errors AND zero warnings (v3 carried the CLMB `split_in_window` basis-suspect warning; it leaves with its subject). README supersession chain updated (v4 ⟶ v3 ⟶ v2 ⟶ v1, all kept for audit). Record-artifact + README only → docs-only per weekly-picks precedent. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Automated daily orchestrator run — **2026-07-28 run 2** (GHA run 30380136239). Full summary: `dev/daily/2026-07-28-run2.md`. ## What happened - **PR #2145 merged (`790d23a0`)** — re-QC after rework iteration 1. Both gates APPROVED q4 at the exact tip; CI re-verified immediately before merge. `consecutive_rework_count` 2 → 0. - **31 orphaned budget records recovered** ($283.31, some since 2026-07-14). - **Three workflow defects filed** in `dev/status/orchestrator-automation.md`. ## The budget records were not a bookkeeping problem `.github/workflows/orchestrator.yml:423-453` bundles a run's budget JSON onto the `ops/daily-*` PR **only if such a PR is currently open**. Otherwise it pushes to a standalone `ops/budget-<date>-<run_id>` branch and, by design (comment line 384), **opens no PR**. Nothing ever merged them. Consequence: every daily total computed from `dev/budget/*.json` on main has been understated — including run 1's `[high]` budget escalation, which was raised *this morning* off the incomplete set. | date | on main (as reported) | orphaned | **true** | **% of $200 cap** | |---|---|---|---|---| | 2026-07-24 | $59.13 | $17.40 | **$76.53** | 38% | | 2026-07-25 | $50.54 | $14.84 | **$65.38** | 33% | | 2026-07-26 | $107.77 (54%) | $84.14 | **$191.91** | **96%** | | 2026-07-27 | $405.30 (203%) | $58.44 | **$463.74** | **232%** | | 2026-07-28 (5 runs, pre-this-run) | $46.72 | $45.21 | **$91.93** | 46% | Records are copied verbatim from their fallback branches; nothing already on main was modified. ## Three defects filed - **`A-BUDGET-ORPHAN`** — the mechanism above. Cheapest fix is one `curl -X POST /pulls` beside the existing fallback push. - **`A-SUMMARY-STALE-FALLBACK`** — two runs today (`30353290609`, `30366079322`) exited `success` having written no summary. `publish-summary` then silently selected *run 1's already-merged file*, and the **`Fail on escalations` gate graded them against it**. The gate's guarantee is void in exactly the circumstance it exists for. - **`A-GIT-SAFE-DIRECTORY`** — `git` is unusable at the start of every run (`--user 0` + `HOME: /home/opam` ⇒ `detected dubious ownership`, exit 128 on every command) until an agent fixes it by hand. Both silent runs logged `ShellError: Failed with exit code 128` ~10s after start. ## Correction I made to my own reasoning My first root cause for the silent runs was wall-clock exhaustion in Step 6.5's CI poll. The logs disagree: both reported `"is_error": false` with `num_turns` **33** and **51** against a `--max-turns 200` cap. They ended voluntarily. The summary records the wrong hypothesis alongside the corrected one. 🤖 Dispatched by GHA orchestrator run [30380136239](https://github.com/dayfine/trading/actions/runs/30380136239) --------- Co-authored-by: claude-orchestrator <noreply@github.com>
…22pp return / +6.8pp MaxDD (#2156) ## What this records The measurement owed by #2133 (task #12, `next-session-priorities-2026-07-28.md` P0): the promoted resistance-v2 bundle's record-of-record was measured on split-blind side-tables. Two arms, one pinned binary (@`9f50de924`), record-convention scenario, promoted-bundle defaults: | Arm | Return | Trades | Win rate | MaxDD | |---|---:|---:|---:|---:| | Raw control (= pinned record) | +8,689.4% | 1,172 | 38.5% | 30.3% | | **Split-safe (honest)** | **+8,366.8%** | **1,122** | **37.7%** | **37.1%** | Control reproduces the record exactly — the #2145 old-hash bit-compat claim holds at path level. Honest grading: return flattering mild (−322pp, −3.7% rel); **MaxDD was flattered by 6.8pp**. Entry churn broad (~190/~140 symbol swap at the cap-20 boundary); AXTI monster intact. ## Files - Ledger entry `2026-07-28-split-basis-blast-radius.sexp` + index row (verdict `Inconclusive` — measurement entry, not a mechanism verdict). - Memo `dev/notes/split-basis-blast-radius-2026-07-28.md` (method, migration provenance 2,894/2,908 clean, trade-level why, follow-ups). - `DEEP_RESULTS.md`: split-basis caveat block on the record row — **no record re-pin here**; that is a user decision (R3). Fold-level promotion re-cert is the named open follow-up. Clone built by `rebuild_weekly_sidetables.exe` (PR #2153). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
What & why
Fixes issue #2133 defect 2: the resistance-supply machinery measured overhead supply on raw prices, while warehouses store raw OHLC plus a separate
Adjusted_close. Any symbol with a split inside the 520-week lookback got mis-scaled overhead — a forward split hid real supply (e.g. CLMB's 4:1 graded "Clean" despite a real adjusted supply wall directly above), a reverse split fabricated phantom supply. This pins every supply measurement to the split/dividend-adjusted basis.Design (hash-gated basis migration — existing artifacts stay bit-identical)
Snapshot_pipeline.Adjusted_basis.to_adjusted_basis— factor =adjusted_close /. close_price, O/H/L scaled,close_price := adjusted_close; corrupt/NaN raw close ⇒ factor 1; idempotent. Same semantics asSvg_serieschart-side rescale.Weekly_sidetable— keeps the old hash asformat_hash_raw_basis(back-compat) and mints a distinct adjusted-basisformat_hash.Weekly_sidetable_builder— rescalesdeep_bars/barsonto the adjusted basis before the weekly fold; new side-tables carry adjusted high/mid, new manifests stamp the adjusted hash.Weekly_sidetable_reader—load_gatedaccepts both hashes (loud on unknown); newbasis : Raw | Adjusted+basis_forsurfaces the warehouse basis.resistance_sketch_reader— v5 anchors at rawClosefor Raw side-tables (bit-identical to today) and atAdjusted_closefor Adjusted ones. Basis threadedBar_reader → Panel_callbacks → reader, defaultRawso every existing caller is unchanged.Resistance_sketch.compute_windowedandLive_resistance_sketch.of_daily_barsrescale input bars (self-consistent anchor; fixes the live picks generator immediately).R1 bit-identity
The rescale (
Adjusted_basis.to_adjusted_basis) is bitwise identity exactly whenadjusted_close = close_price(factor1.0) — IEEE-754x /. xis exactly1.0andh *. 1.0is exactlyh, verified across seven magnitudes (1e-8 … 1e9) and now pinned bytest_adjusted_basis.ml. It is NOT a no-op merely for split-free data: a dividend-only adjustment (close100.0, adjusted99.87) already moves every O/H/L bit. SinceResistance_sketch.compute_windowedandLive_resistance_sketch.of_daily_barsrescale unconditionally, those two paths do change output for essentially every real dividend payer, split or not. That change is the intended fix.Existing dense/prebuilt warehouses are still unchanged — but for a different reason than "the rescale is a no-op": the
sidetable_basisdefault isRaw, so no existing caller re-anchors, and prebuilt artifacts are not recomputed by this PR. The behaviour changes are: (a) newly BUILT side-tables/sketches are adjusted-basis, (b) new-hash warehouses anchor atAdjusted_close, (c) any from-bars REBUILD of the dense columns now produces adjusted-basis values. No warehouse rebuild / golden re-pin in this PR (that's the separate #2133 follow-up, which the corrected wording above is precisely why it is required).Test plan
Adjusted_close(140) vs Raw ⇒Close(150, legacy).format_hash/format_hash_raw_basispinned to distinct literals.compute_windowedsplit fixture: pre-split overhead visible above the anchor (raw basis drops it),max_high_520wis the adjusted max.anchor_close= adjusted close.dune build && dune runtest(affected suites) +dune build @fmtgreen.🤖 Generated with Claude Code