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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
_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>
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>
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>
- 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>
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>
_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>
- 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>
- 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>
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>
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>
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>
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>
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>
Foundation for a 2nd, uncorrelated execution venue alongside the Bybit crypto book:
- AlpacaBroker (httpx REST, Broker port): account_state + place_order, FRACTIONAL
market+day orders, is_paper from base_url, supports_fractional=True. httpx test
seam (MockTransport) — no network.
- ExecutionService fractional-aware: sizes float shares when broker.supports_fractional
(Alpaca) — precise weights at small capital — else whole shares (IBKR, unchanged).
- AlpacaSettings (paper base_url default; live gated by allow_live). CLI
.
- Suspended CronJob + credentials scaffold (arm after paper keys, like the Bybit
testnet leg). Runs the survivor book end-to-end.
The multi-strat ETF book on Alpaca is a documented follow-up (its position logic
isn't on the clean port yet). Full suite green (1827); 6 new tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two gains-only, adaptive haircuts in _book_breakdown (SSOT for the positioning
forward track + gate ref + 4-edge book), both OFF by default (byte-identical):
- CAPACITY: cap each coin's GAIN by min(1, participation*med_turnover/(capital*|w|))
— you cannot bank more of an extreme move than you can exit. Barely binds at
$100k (positioning IS capturable there), caps thin names as capital scales.
- TAIL-CONCENTRATION: clip each coin's positive contribution at K*sigma_book (robust
MAD scale of the book's own daily returns, K=4), so no single microcap-squeeze day
dominates the book. Cuts the fat right tail (28% of return was 10 days).
Threaded capacity_capital/tail_cap_k through positioning_returns_from_store ->
_positioning_series -> sleeve_returns_from_store -> BybitFourEdgeStrategy ->
bybit_4edge_backtest_summary + the nav builders; wired s.paper_capital +
POSITIONING_TAIL_CAP_K=4.0 at the asset/persist layer. Validated in-cluster on real
data: off=byte-identical, tail-cap trims CAGR 154->120% (K=4), capacity curve
degrades monotonically (00k Sh2.8 -> 00M Sh-2.3). Full suite green (1821).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complements Fix A: the individual forward tracks now get a reconciliation-gate
backtest ref built from their OWN forward strategy (correct basis), so the gate
reconciles instead of withholding PASS.
- backtest_summary_from_rows(): shared helper; ref built from a strategy's
advance(None,{}) inception series (extracted from bybit_4edge_backtest_summary).
- _run_paper_tracker(persist_backtest_ref=True): crypto_tstrend / unlock /
stablecoin persist a ref from their own strategy — NOT the raw sleeve series
(crypto_tstrend's forward is 15%-book-vol-targeted, a DIFFERENT basis; a
sleeve-series ref would understate expected return -> false-PASS). xsfunding
excluded (live-snapshot, no full-history series).
- positioning ref via bybit_4edge_backtest_summary(sleeves=[positioning]) in the
persist Job (its forward IS BybitFourEdgeStrategy(sleeves=[positioning]) — same
basis), reusing the precomputed sleeve series.
Full suite green (1818).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reconciliation gate degraded to a bare min-window + clean-exec timer when no
backtest_summary row existed, yet still returned PASS labelled 'reconciles with
backtest' — a hollow/false-PASS. For a real-capital gate the safe error is a
false-WAIT, never a false-PASS. Now backtest=None -> WAIT ('no backtest reference
to reconcile against — PASS withheld'). Reverses the prior degrade-to-PASS test.
Surfaced by positioning showing PASS +1.82% (one +15.3% anomaly day) with no
backtest_summary row; crypto_tstrend + stablecoin_rotation had the same hollow PASS.
Fix B (persist per-strategy backtest refs) makes their gates reconcile for real.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
xvenue_carry (cross-venue Bybit<->Deribit static carry) is VALIDATED-BUT-NOT-LIVE
with ~0 forward contribution (measured: +0.004%/day, dilutes book CAGR -18pp at
equal weight). Retire the forward-tracked strategy: registry entry, xvenue_carry_nav
asset + build_xvenue_carry_forward_nav, definitions wiring, cockpit backtest-ref
mapping, include_xvenue_carry persist path. KEEP the cross_venue_carry_eval research
lib + deribit_funding_bars ingestion (data stays warm for a possible cross-venue
execution revival). Asset count 21 -> 20; tests updated to assert the retire.
Full suite green (1816).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-tune _LEVERED_SLEEVE_SHAPE from the Sharpe-max fixed shape (carry ~53%) to a
tail-budgeted one (carry ~40%, non-carry inverse-vol): {tstrend 1.0, unlock 2.2,
xsfunding 3.0, positioning 1.3}. Caps a -20% carry dislocation to ~-8% of the book
(vs -10.5%) while keeping Sharpe ~2.7 (>> naive 2.26). One constant re-tunes the
whole levered chain. A new invariant test pins carry at 40% of gross. 1824 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The persist Job recomputed the heavy multi-symbol sleeve walk-forward three
times: once to write bybit_sleeve_ret, once for the naive backtest reference,
once for the levered one. Thread an optional precomputed `sleeve_returns`
through persist_bybit_sleeve_returns, BybitFourEdgeStrategy, and
bybit_4edge_backtest_summary (all default to the prior recompute path, so
existing callers/tests are unchanged); the CLI computes the 4 deploy sleeves
ONCE and reuses them for the row persist + both references. Behavior-neutral
(equivalence tests assert byte-identical summaries + identical persisted rows).
~3x faster; the in-cluster Job now avoids the tunnel entirely. 1818 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>