Commit Graph

828 Commits

Author SHA1 Message Date
jgrusewski
84c9fcd146 fix(alloc): per-strategy cap now binds (concentration limit, holds cash) instead of a renormalise no-op (B2a)
The equal-weight cap+renormalise was algebraically a no-op — n=1 got 100% weight despite a 50% cap.
Cap without renormalising: when 1/n > max_strategy_weight the book holds cash rather than over-
concentrating; common case (1/n <= cap) is plain equal weight. + tests that exercise the binding cap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:17:52 +02:00
jgrusewski
497d4022db feat(alloc): compute_allocation — ex-ante vol-target on full-history vol, marginal gate, killswitch (B2a)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:10:54 +02:00
jgrusewski
5893604158 fix(alloc): deterministic tie-break in equal_weight_marginal_prune + accurate module docstring (B2a)
sorted(kept) makes the leave-one-out bench choice stable across process runs (a set's iteration order
is PYTHONHASHSEED-dependent) — a reproducibility fix for a capital-allocation function. Docstring no
longer claims compute_allocation/target_capital, which land in later tasks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:07:23 +02:00
jgrusewski
7b886f0a05 feat(alloc): pure allocation helpers — full-history vol, equal-weight marginal prune, killswitch latch (B2a)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:02:12 +02:00
jgrusewski
9ec8eaab9c feat(alloc): versioned AllocationPolicy + content-derived hash + capital_ceiling (B2a)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:58:21 +02:00
jgrusewski
3b43856621 docs: plan — declarative strategy-level allocation policy + capital ceiling (B2a), 6 tasks
AllocationPolicy + hash (T1) → pure helpers full-history-vol/equal-weight-marginal-prune/killswitch
(T2) → compute_allocation ex-ante composition + anti-reactive invariant (T3) → target_capital hard
ceiling (T4) → strategy_allocation table+repo (T5) → nightly records the allocation from A's records
+ B1 deploy set (T6). TDD, ex-ante only (full-history vol), no capital moved (that is C).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:54:56 +02:00
jgrusewski
3a91373ac4 docs: spec — declarative strategy-level allocation policy + capital ceiling (B2a)
Core of the allocation plane (layer 4, plane 2) on the data-driven-pipeline north-star.
Versioned in-code AllocationPolicy + a pure compute_allocation: equal-weight the marginal-Sharpe-
gate survivors, ex-ante vol-target leverage from the FULL-history vol (never trailing — the
anti-reactive invariant that honours the 3x-rejected reactive de-sizing conclusion: ex-ante K +
rare killswitch), a rare latching aggregate killswitch, and target_capital that caps total
deployed gross at a HARD human capital_ceiling (the one human gate, in the versioned policy).
Recorded + policy-version-stamped. No capital moved (that is C). Builds on A + B1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:27:53 +02:00
jgrusewski
a1190dddca docs(policy): note promotion_min_overlap_days in the B1 spec field list (review minor)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 15:26:02 +02:00
jgrusewski
178f0388d9 fix(policy): promotion backstop gates emptiness on deploy MEMBERSHIP not aggregated data (B1)
book_empty was `not book` (the aggregated returns dict), so deploy members present but with no
overlapping forward_nav data (e.g. a just-re-inceptioned deploy track) read as "first edge" and
AUTO-PROMOTED with no correlation check — a one-way false-promote on a capital-adjacent gate.
Now `not book_sids` (membership): no deploy members → promote (true first edge); members present
but no data → indeterminate → WITHHOLD, per spec. + regression test.

Also neutralizes the real registry's deploy-tier tracks in two pre-existing promotion tests that
were unknowingly relying on the old inversion (they didn't seed nav data for the real deploy book).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 15:24:32 +02:00
jgrusewski
a8f1fa99f4 perf(policy): evaluate_promotions reads each deploy-book series once (B1 minor)
Memoize nav_history per sid and precompute the deploy-member series once outside the candidate
loop, instead of re-reading every deploy member's history per candidate — restores the "LIGHT,
no heavy recompute" contract when more than one candidate reaches PASS. Behavior unchanged
(candidate still excluded from its own book; backstop semantics identical).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 15:11:30 +02:00
jgrusewski
0817067dcf feat(policy): promotion correlation backstop — human flag + data-confirmed diversification (B1)
evaluate_promotions now promotes a promote_on_pass candidate at gate==PASS only when its daily-return
correlation to the equal-weight deploy book (registry deploy-tier ∪ promoted, from A's records, minus
the candidate) is < POLICY.promotion_max_corr. Empty book (first edge) passes; short overlap or corr
above threshold withholds (logged). Catches a human mis-flag of a correlated edge with data. One-way
promotion unchanged; no capital.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:55:56 +02:00
jgrusewski
5eef3068b4 feat(policy): ingest stamps POLICY_VERSION + policy_hash on every gate verdict (B1)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:41:42 +02:00
jgrusewski
1adcd40ee0 feat(policy): stamp forward_summary with policy_version + policy_hash (B1 audit)
ForwardSummaryRow gains nullable policy_version + policy_hash; upsert_summary stamps them
(keyword args, default None). migrate() additively ensures the two columns on an EXISTING
forward_summary table on BOTH dialects (Postgres ADD COLUMN IF NOT EXISTS; sqlite PRAGMA-guarded
ADD COLUMN) — create_all does not add columns to an existing table, so a persistent DB (prod
Postgres rows, or a lagging test sqlite) would otherwise be missing them (fixed a regression
where the ORM SELECT referenced the absent column).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:38:59 +02:00
jgrusewski
f821929ddf refactor(policy): gate functions take ResolvedPolicy; remove scattered gate constants (B1)
evaluate_reconciliation_gate + evaluate_gate now take a ResolvedPolicy (resolve_policy(POLICY,
gate_spec)); the module constants (MIN_FORWARD_DAYS / RECON_* ) + gate.py inline defaults are
removed — thresholds now come from the single GatePolicy. Verdict logic + reason strings
unchanged → byte-identical verdicts (full suite 1873 green proves it). _gate_verdict resolves once.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:16:18 +02:00
jgrusewski
cba9f6531b feat(policy): resolve_policy — per-track overrides on GatePolicy defaults, one resolution point (B1)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 13:37:00 +02:00
jgrusewski
5a99bb6329 feat(policy): versioned GatePolicy + content-derived policy_hash (B1)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 13:33:34 +02:00
jgrusewski
889ba74852 docs: plan — declarative gate + promotion policy (sub-project B1), 6 bite-sized tasks
GatePolicy + policy_hash (T1) → resolve_policy (T2) → wire gates to ResolvedPolicy, remove
scattered constants (T3) → forward_summary policy stamp columns (T4) → ingest stamps every
verdict (T5) → promotion correlation backstop (T6). TDD, byte-identical verdicts under current
thresholds, per-verdict audit stamp, no capital.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 13:31:33 +02:00
jgrusewski
7e9f8520b9 docs: spec — declarative gate + promotion policy (sub-project B1)
First plane of the policy layer (layer 4) on the data-driven-pipeline north-star.
Consolidates the scattered gate defaults (reconciliation_gate module constants + gate.py
inline literals + registry gate_spec) into one in-code, versioned GatePolicy; resolves the
effective per-track policy at one point; stamps each verdict with policy_version + a
content-derived policy_hash (auditability + forced version-bump); and formalizes promotion
as gate==PASS AND human promote_on_pass flag AND an automatic correlation backstop (< threshold
vs the equal-weight deploy book from A's records). Byte-identical verdicts under current
thresholds. No capital (deploy-tier is tracking; capital is B2). Builds on sub-project A.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 13:25:11 +02:00
jgrusewski
7137d62bfa fix(forward): resolve whole-branch review findings (combined migration crash + hash/PIT accuracy)
- CRITICAL: add combined to migration_builders so its anchor is seeded — the pre-branch forward_summary
  row would otherwise trip the lost-anchor guard and crash combined_forward_nav every nightly
- hash accuracy: bybit cost_bps mirrors Settings default (10.0); multistrat instruments derive from
  FUND_INSTRUMENTS (no duplicate) so a return-affecting change actually re-inceptions
- sixtyforty documented as a benchmark exempt from strict PIT (spec §4)
- observed mode: xsfunding consumes the engine's UTC _today; re-inception resets carried book-state

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:51:00 +02:00
jgrusewski
5c7a5c5c49 fix(forward): restore per-tracker fault isolation in the gate ETL + test the DB sleeve series
ForwardIngestService.ingest now wraps each tracker's DB read/gate-compute/write in try/except+continue
so a failure in an earlier (research) track can't abort the batch and silently freeze the deploy-tier
real-capital gate. Adds a fault-isolation test + a direct test for the DB-backed _sleeve_return_series
(and dropped its now-unused data_dir param).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:23:38 +02:00
jgrusewski
1745249cf4 refactor(forward): gate ETL + allocator read the DB record; retire state_reader JSON path
forward_ingest now evaluates the gate from the DB record the engine writes (all_summaries +
nav_history) instead of reading *_state.json via ForwardStateReader; _sleeve_return_series reads
nav_history too. ingest_forward_state drops state_dir. state_reader.py + its test deleted — the
DB anchor + record are the single source of truth (spec sub-project A). Gate verdict logic unchanged.
Docstrings updated to reflect the engine path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:02:28 +02:00
jgrusewski
a7533980da feat(forward): wire combined through the engine (recompute); drop track-level killswitch
combined_forward_nav now runs CombinedBookStrategy through forward_engine.run_track (DB anchor +
recompute) — the last track off the JSON ForwardTracker. The former ForwardTracker-level drawdown
killswitch overlay is intentionally dropped (a track throttle, not part of the edge; vestigial
research track, not capital). record_mode observed->recompute (version 2) -> clean re-inception.
Removes now-unused build_combined_forward_nav / CombinedBookForwardTracker registration in
migration_builders.py + the test_orchestration_assets.py test (its only exerciser). Also drops the
"combined" entry from the one-time migrate-forward-anchors builder map (record_mode is now
recompute, so the observed lambda:None placeholder would crash it; combined re-inceptions cleanly
through run_track with no JSON left to migrate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 09:38:24 +02:00
jgrusewski
b1c736b35f test(forward): deterministic seed in _seeded_paper_repo (fix pre-existing flaky sleeve test)
_seeded_paper_repo seeded its synthetic sleeve returns from random.Random(hash(sleeve)), but
hash() is PYTHONHASHSEED-salted, so the vol-targeted-vs-raw assertion tripped for a small
fraction of process salts under the full suite (passed in isolation). Seed from zlib.crc32
instead — stable across processes → the test is now deterministic. Pre-existing flake, unrelated
to sub-project A; fixed to keep the suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 09:17:09 +02:00
jgrusewski
9a2859ea88 fix(forward): engine fails loud on lost anchor instead of silently re-inceptioning
Restores the safety invariant the legacy ForwardTracker enforced (raise on state loss rather
than reset the OOS clock to day 0 + discard the record — 3 prod resets "burned real capital").
run_track now raises when there is no active anchor BUT prior forward history exists (lost
anchor row), while a genuine first run still inceptions. Protects the deploy-tier tracks Task 8b
routed onto the engine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 09:17:09 +02:00
jgrusewski
a957821e55 feat(forward): wire the 3 Bybit/positioning tracks through the deterministic engine
build_{bybit_4edge,bybit_4edge_levered,positioning}_forward_nav now run their (recompute)
strategy through forward_engine.run_track (DB anchor + recompute) instead of the JSON
ForwardTracker, so the deploy-tier Bybit book + positioning sleeve record to the DB SSOT
like the other tracks. Asset callers thread repo/today/at; tests re-pointed to the DB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 08:54:14 +02:00
jgrusewski
965f286ca2 fix(forward): migration reconcile surfaces missing-coverage divergence + bounds window by today
- an accumulated date absent from the recompute now counts as divergence (status=diverged,
  missing_dates) instead of silently staying "match" — fixes the false-match when PIT history
  is incomplete (the multistrat/YahooPitClient shape the reconcile exists to catch)
- recompute window bounded to [T0, today] (today param was unused -> window was [T0, inf))

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 01:47:51 +02:00
jgrusewski
4bae18b4b7 feat(forward): one-time migration — seed anchors, reconcile recompute, import observed
migrate_track seeds each track's anchor from its *_state.json inception (preserve-T0),
reconciles recompute tracks vs the accumulated nav (divergence surfaced, never silently
overwritten), and imports observed tracks' booked log verbatim. migration_builders maps
each registry id to its (state_file, build_strategy); `fxhnt migrate-forward-anchors` CLI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 01:32:35 +02:00
jgrusewski
07bc9a2da8 docs(forward): restore Alpaca-execution + gate-reconciliation context in multistrat_nav docstring
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 01:06:32 +02:00
jgrusewski
04723ba997 style(forward): sort forward_nav.py import block (ruff I001) after Task 1/3/7 additions
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 01:03:45 +02:00
jgrusewski
33c356a8df feat(forward): PIT Yahoo close snapshots + PIT-reading client (stable recompute)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 01:01:10 +02:00
jgrusewski
5af0668b78 feat(forward): wire _run_paper_tracker to the DB-backed deterministic engine
_run_paper_tracker now runs each track through forward_engine.run_track (anchor +
recompute/observed dispatch, DB-backed) instead of the JSON ForwardTracker, keeping
the transient-error tolerance. Returns forward_days/last_date/reinception.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:50:31 +02:00
jgrusewski
a224127ab2 fix(forward): engine writes forward_nav as a true derived cache + validates record_mode early
- replace_nav purges stale pre-t0 rows on re-inception (nav cache == current record; fixes
  polluted nav_history / wrong dashboard headline_t0 after a definition change)
- record_mode validated before any anchor mutation (a bad mode no longer persists a broken anchor)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:37:22 +02:00
jgrusewski
d07e70b5a5 feat(forward): forward engine — anchor lifecycle + recompute/observed dispatch
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:21:19 +02:00
jgrusewski
1603114260 fix(forward): reuse ForwardStrategy Protocol in forward_recompute (single source of truth)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:07:23 +02:00
jgrusewski
423aad7911 feat(forward): pure recompute_series + derive_nav (matches ForwardTracker formula)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:02:20 +02:00
jgrusewski
c3451560f5 fix(forward): resolve Task 1-3 review minors (enforce single-active anchor, validate dates, tighten tests)
- set_anchor self-guards the single-active-anchor invariant (archive prior active on re-inception)
- append_record/record_series validate + normalize ISO-8601 dates (fail loud on malformed)
- ordering test now inserts out-of-order; removed dead test imports/vars; clean Session import

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:57:05 +02:00
jgrusewski
b16e8b87fa feat(forward): append-only forward_record log + carried book-state (observed mode)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:40:35 +02:00
jgrusewski
15572f0e60 feat(forward): declared definition + content hash + per-track definition entries
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:27:36 +02:00
jgrusewski
c88138aa1a feat(forward): forward_anchor table + repo (immutable per-track anchor, lineage)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:13:15 +02:00
jgrusewski
4a50c88ea5 docs: plan — deterministic forward state (sub-project A), 9 bite-sized tasks
Recompute-from-anchor implementation plan: forward_anchor table + repo (T1),
declared definition + hash (T2), append-only forward_record + book-state (T3),
pure recompute/derive_nav (T4), the anchor-lifecycle engine (T5), wire
_run_paper_tracker (T6), PIT Yahoo snapshots (T7), one-time migration (T8),
retire the state_reader JSON path (T9). TDD, WAIT-safe, idempotent, per-task commits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:00:41 +02:00
jgrusewski
55b26f8d32 docs: spec update — two record modes (recompute + observed) for sub-project A
Planning surfaced that not all active tracks are recomputable: xsfunding books a
live funding snapshot with no reproducible full-history series (and combined carries
latching kill-state). Add a per-track record_mode: `recompute` (anchor + PIT data,
the majority) vs `observed` (anchor + append-only immutable booked log + minimal
carried book-state in DB). Observed removes the hand-editable PVC JSON for those
tracks and is the same machinery sub-project C reuses for execution fills.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:54:25 +02:00
jgrusewski
e20eae988c docs: spec — deterministic forward state (sub-project A), recompute-from-anchor
Foundation of the unified data-driven pipeline: replace mutable, hand-editable
tracker JSON state with an immutable (T0, definition_hash) anchor in Postgres;
recompute the forward record over [T0, today] from PIT warehouse data each run;
auto-re-inception on declared-definition change. Migration preserves existing T0s
and reconciles (divergence = drift, surfaced not silenced). Yahoo closes get PIT
snapshots (recompute stability); execution fills scoped out to sub-project C.
Data-driven mechanics, human-gated capital — A does not touch capital.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:46:34 +02:00
jgrusewski
6b590ccf5e fix(cockpit): drop "+ BTC" from the multistrat display name (book is now 5 ETFs)
The multistrat fund book dropped BTC-USD (FUND_INSTRUMENTS = 5 pure-equity ETFs),
but the registry display_name still read "SPY/IEF/GLD/PDBC/DBMF + BTC" — the cockpit
showed the new numbers (Sh 1.63, 0/20) under a stale name that contradicted them.
Name now matches the book.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:12:32 +02:00
jgrusewski
42c9234c81 feat(multistrat): drop BTC from the fund book — purer diversifier, higher combined Sharpe
The multistrat book had BTC-USD as 1 of 6 streams — redundant (crypto exposure
already comes from the Bybit book) AND the main source of correlation between the
equity and crypto legs. Measured (437d overlap): dropping BTC cuts multistrat<->crypto
daily-return correlation +0.284 -> +0.175, lifts multistrat's own Sharpe 1.23 -> 1.46,
and the combined portfolio Sharpe ~1.95 -> 2.14. Nothing lost (crypto exposure stays
via the Bybit book); the Alpaca leg is now pure equities (no crypto order).

- multistrat: FUND_INSTRUMENTS = the 5 pure-equity ETFs (SPY/IEF/GLD/PDBC/DBMF);
  book_series/target_weights/_build parameterized by instrument list (default = the
  original 6, faithful port intact). MultiStratStrategy takes an instruments arg.
- multistrat_nav + execute-multistrat use FUND_INSTRUMENTS (BTC symbol map dropped).

Full suite green (1838). Re-inceptions the multistrat track (return series changed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:52:51 +02:00
jgrusewski
a1f9b27822 feat(gate): auto-promote research→deploy on gate PASS (multistrat)
When a track flagged `promote_on_pass` clears its reconciliation gate, the nightly
promotes it research→deploy — one-way + persistent, so it becomes fund-tier
deliberately once and never flickers with the daily gate.

- registry: multistrat gets promote_on_pass=True. Its diversification vs the crypto
  book was validated out-of-band (measured +0.284 daily-return corr over 437d → a
  real diversifier; combined Sharpe ~2.06 > 1.8). Correlation = the tested/deliberate
  judgment; gate PASS = the automated trigger.
- persistence: strategy_promotion table + ForwardNavRepo.promote / promoted_strategy_ids.
- DashboardService.fleet(): effective tier = deploy if promoted (overrides registry).
- forward_ingest.evaluate_promotions(dsn): light (reads gate_status + the flag),
  called by cockpit_forward after ingest. One-way; never reverts on a later dip.

Tested: promotes flagged+PASS, not unflagged, not while WAIT, one-way persistent,
fleet tier flips. Full suite green (1837).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:31:53 +02:00
jgrusewski
ec9f660f97 feat(cockpit): "ref" badge — mark reference-only (non-active-venue) tracks
Binance is no longer an active execution venue (deploy = Bybit, equities = Alpaca).
FleetRow.reference_only = venue not in the active set {bybit-perp, alpaca-paper},
computed in DashboardService. The All-forward-tracks table shows a subtle bordered
"ref" tag next to the venue for reference-only tracks — the Binance discovery tracks
(crypto_tstrend/unlock/xsfunding/stablecoin/combined) + the glbx 60/40 benchmark —
so it is clear at a glance which tracks are observed-only vs traded live.

Verified in a real browser against the prod DB: ref on the 6 Binance/glbx tracks,
none on the 4 Bybit/Alpaca tracks. Full suite green (1833).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:49:32 +02:00
jgrusewski
c9d4bdda1d feat(cockpit): "All forward tracks" table — complete undimmed overview
Adds a flat, undimmed section listing EVERY registered forward track (deploy +
research, incl. the headline book) in one table: name/venue/PAPER badge, tier,
basis-matched backtest ref (Sh/CAGR or — when none), and the live forward gate
(days/min + PASS/WAIT/GO). Complements the deploy/research grouped sections (the
research group stays dimmed) — the research tracks were at 62% opacity and read as
"missing"; this gives the complete picture at a glance.

Also passes `fleet` into the cockpit template context (it was computed but not
passed — the new loop rendered empty until fixed; caught by local browser test).
Verified locally in a real browser against the prod DB: all 10 tracks render.
Full suite green (1833).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:14:24 +02:00
jgrusewski
f1916404b4 fix(gate): tolerate weekday-only gaps (equity tracks) via max_gap_days
The reconciliation gate's _has_gaps assumed calendar-continuous booking (crypto is
24/7). A weekday-only US-equity NAV (multistrat) has Fri->Mon 3-day steps + holiday
long weekends (<=4d) — NORMAL, but the gate would flag them as an execution gap at
day 20 -> perpetual false-WAIT (never reconciles). Now _has_gaps takes max_gap_days
from gate_spec (default 1 = crypto, unchanged); multistrat sets 4. A step of
0/negative (duplicate/out-of-order) is still always a hole.

Surfaced verifying the just-added multistrat gate track (14/20 building, real ref
CAGR 9.9% / Sharpe 1.48). Full suite green (1833).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:49:33 +02:00
jgrusewski
f30b9c1871 feat(gate): track the multi-strat book on the cockpit reconciliation gate
Re-adds multistrat_nav (retired in the 2026-06-20 cockpit cleanup) as a forward-
tracked strategy so the TradFi book runs the SAME honest gate as the crypto edges
— the diversification-Sharpe path (uncorrelated equity leg beside crypto).

- multistrat_nav asset: _run_paper_tracker(MultiStratStrategy(YahooDailyClient()),
  persist_backtest_ref=True). Yahoo covers all 6 instruments incl BTC-USD natively.
  persist_backtest_ref publishes the basis-matched ref (full book_series via
  advance(None,{})) so the gate reconciles for real (Fix A/B), not a hollow PASS.
- registry: multistrat entry, reconciliation gate, min_forward_days=20.
- wired into the nightly combined_book_forward_job + cockpit_forward deps.

Full suite green (1832); asset count 20 -> 21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:29:54 +02:00
jgrusewski
fd9534e978 feat(exec): multistrat book on Alpaca — target-weight bridge + crypto BTC leg
Puts the adaptive multi-strat ETF book on the clean execution port:
- multistrat.target_weights(closes): exposes the final-day per-instrument weights
  (L*tw*volnorm_scale) that book_series applies but only collapses into a return.
  Refactored _volnorm via a shared _volnorm_scale so book_series stays byte-identical.
  Tested: sum(w*R[last]) reconstructs book_series's last booked return exactly.
- ExecutionService.rebalance_weights(weights, prices): a direct pre-computed-weights
  entry beside rebalance(book), sharing one _plan engine (gates + fractional sizing).
  Caller owns data<->broker symbol mapping.
- AlpacaBroker crypto-aware: a slash symbol (BTC/USD) -> time_in_force gtc; equities
  stay day. So the book's BTC-USD leg trades on Alpaca crypto alongside the 5 ETFs.
- CLI fxhnt execute-multistrat (maps BTC-USD -> BTC/USD); the alpaca-rebalancer
  CronJob now runs it (suspended until paper keys).

Full suite green (1832); +5 tests. Alpaca-crypto position-symbol form validated on
first paper run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 00:33:22 +02:00