Deploy-the-fund knobs: single-row fund_config (capital, target_vol, kelly)
set via 'fxhnt fund-config', read by the nightly allocation with a settings
fallback. Per-sleeve crypto allocation (promote xsfunding/unlock via
promote_on_pass, demote bybit_4edge to a research reference; generalize the
honest-inputs provider to size any crypto sleeve). Marginal LOO gate KEPT +
a predetermined falsification gate (G1 survival / G2 deployment% / G3
anti-reactive) that accepts the honest verdict either way. target_vol 0.20.
Paper scope; execution wiring a follow-on.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Upgrade of the B2a allocation engine. Tasks: 1 inverse-vol (risk-parity) base weights, 2 capacity
ceiling from an honest capacity curve, 3 compute_allocation weights inverse-vol capped (not
equal), 4 honest-reference series+ceiling provider per deploy strategy (bybit_4edge book /
positioning sleeve / equity-default), 5 record_allocation sizes from the provider + caps at
capacity, 6 policy knobs (capacity_min_sharpe, equity_default_ceiling) + ceiling shown next to the
curve. Reuses forward-gating, cross-venue, funding-decay, killswitch, vol-target, $ conversion.
Self-review flags the int-vs-str series-key conversion at the provider seam. Scope stays paper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Focused upgrade (not a new system) of allocation_engine.compute_allocation: (1) equal-weight ->
inverse-vol (risk-parity) capped at max_strategy_weight; (2) a per-deploy-strategy capacity
ceiling from the honest-reference so a growing fund never over-scales a capacity-limited edge
(xsfunding breaks ~$350k). Everything else (forward-gating via deploy+promoted, cross-venue,
funding-decay, killswitch, vol-target, $ conversion) already exists and is reused.
Gate reality investigated: deploy-tier = only bybit_4edge + positioning (both WAIT post-re-warm);
strategy_allocation is empty because compute_allocation needs 60 forward days but the re-warm
reset tracks to ~8. So sizing basis moves to the honest-reference BACKTEST (stable, capacity-
adjusted), with the forward gate as the LIVE-DEPLOY switch only ("honest weights now, live per
gate"). Granularity: bybit_4edge needs a book-level capacity; positioning uses its sleeve curve;
multistrat (IBKR equity) gets a high default ceiling in v1 (real equity-ADV model = follow-on).
Self-critical section: Sharpe-4 not forward-proven (concentration cap + gating guard), equity
capacity stub, backtest-sizing-not-live caveat. Scope stays paper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Coin-level ADV/impact honest backtest reference for the Bybit crypto sleeves + falsification
gates + risk-parity allocation + leverage ceiling + gated pump-detection candidate edge, ending
in a read-only honest-reference-report CLI. Whole-branch review + real-data smoke test caught and
fixed a ~100x cost miscalibration, an edge-4 sign flip, a book inner-join collapse, and an unwired
G4. Real-data numbers sane (xsfunding Sh 3.5@$35k capacity-capped ~$350k; positioning Sh 2.9
scales; risk-parity book Sh 4.3@$35k; pump edge honestly rejected). No live-behaviour change.
Research/validation-scope design for a coin-level capacity-, cost-, and leverage-honest
backtest reference, after quantifying (2026-07-15) that the book's numbers are tail-inflated:
top-10 days drive 58% of unlock / 29% of positioning; unlock's Sharpe collapses 1.03->0.19
under a P98 upside haircut; the "levered" backtest's -11.8% maxDD < the naive -15.4% (a
re-weighting artefact, not leverage).
Locked decisions (from brainstorming): coin-level ADV/impact model (turnover data verified:
818 coins, 2021-2026, USD); participation_cap 3%; AUM curve {35k,100k,350k,1M,10M} to locate
the ceiling; risk-parity/inverse-vol weighting; ex-ante risk layer with an anti-reactive gate
(G4) — reactive de-sizing stays out (A/B-rejected 3x); candidate edge #4 (pump/anomaly
detection) falsification-tested through the same honest-cost pipeline. No live cockpit change
(production gate-replacement is a follow-on). Self-critical section names the 5 ways this could
still fool us (recompute fidelity, slippage-is-modeled-not-measured, survivorship, AUM
relevance, still-a-backtest).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The IBKR-backed sections carried a second exec chip ("REAL PAPER", blue) on top of
the plain PAPER badge, doubling up on the fleet exec row and the IBKR account cards.
It's redundant (the section titles already say "IBKR paper account") and crowds narrow
mobile rows. Per request, collapse to just PAPER:
- fleet exec row (cockpit.html): drop the extra REAL PAPER badge — the row already
renders exec_badge = PAPER right before it.
- IBKR account cards (paper.html) + "Following the plan" block (strategy.html): swap
real_paper_badge() -> exec_badge('paper'), matching the crypto book's PAPER chip.
- remove the now-unused real_paper_badge macro (_macros.html) and .badge-exec.real CSS
(base.html); refresh the stale REAL PAPER prose in paper.html / app.py docstrings.
- tests updated to assert the PAPER badge instead of REAL PAPER (the real-trades sub-row
and behind/ahead-of-plan pill are unchanged).
Verified locally in a real browser at mobile widths: both /paper cards now read
"… PAPER", no REAL PAPER anywhere, all routes still 200. 224 web/forward tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Phase-0b venue re-home reset the crypto sleeves' forward OOS clock to 2026-07-14
(definition-hash change → auto-re-inception). Since these were faithful re-homes (same
edge + PIT data, record_mode=recompute), the pre-re-home days are still valid OOS; on
2026-07-15 they were recovered by backdating the active anchors' t0 in the DB (unlock
06-20 → recon-gate PASS +4.57%; xsfunding/positioning/bybit_4edge/levered → 8d).
That backdate lived ONLY in the DB — a full operational-Postgres rebuild would let the
anchors re-incept to `today` and silently lose the recovered window. This makes it
reproducible from git (the SSOT):
- forward_definition.inception_override(sid): returns a definition's declared
`inception_t0.date`, VERSION-PINNED (honored only when its `version` == the
definition's current version — a genuine re-tune bumps `version`, auto-ignoring a
stale pin and correctly getting a fresh today-clock). Lives OUTSIDE `params` so it is
NOT hashed — declaring it never itself re-inceptions (verified: the five sleeves'
definition_hash is byte-identical to their live DB anchors).
- run_track: at inception, t0 = inception_override(sid) if present AND <= today, else
today (never incept in the future — clock-skew/not-yet-reached pins fall back to today).
- registry: xsfunding/unlock/positioning/bybit_4edge/bybit_4edge_levered carry their
pinned pre-re-home t0 (07-07/06-20/07-06/07-07/07-07, version 2).
Tests: inception_override version match/stale/absent + not-hashed + a regression pin on
the recovered dates; run_track honors the pin, ignores a version-stale pin, and falls
back to today for a future pin. 262 forward/registry tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first pass only filtered the Overview fleet(); the Backtest tab still showed
vrp as "Options income (put spreads)" + a "Live forward — Equity VRP" section,
and /strategy/vrp still rendered. Fix at the SOURCE so every registry-driven path
inherits the hide, then patch the pages that bypass the source.
- forward_nav.ForwardNavRepo.registry(): SINGLE authoritative filter — exclude
rows whose STRATEGY_REGISTRY entry is `archived: True`. Every caller (fleet,
overview gate specs, detail()'s reg lookup) now drops vrp/vrp_exec automatically.
DB rows stay seeded (code + OPRA data kept) — only hidden from the UI.
- dashboard_service.detail(): explicit archived guard -> returns None so
/strategy/vrp 404s (belt-and-suspenders over the registry() filter).
- dashboard_service: _active_ibkr_account_sids() helper excludes archived sids;
the IBKR account per-strategy breakdown + recent-fills roll-up iterate it, so
vrp never renders a row/label on the account page.
- app.py: _SIM_BOOKS excludes archived books -> no vrp Backtest pill, and a
?book=vrp request falls back to the default book (folded "Live forward" section
can't surface vrp either). _SIM_BOOK_LABELS derives from the filtered list.
- fleet() keeps its archived-check as defense-in-depth (registry() is now the
authoritative filter).
Render paths fixed: Overview, Backtest (/paper/sim + /paper/sim/run pills,
selector, folded Live-forward), /strategy/<sid> detail, IBKR account per-strategy
breakdown. Verified end-to-end: no "vrp"/"Equity VRP"/"Options income" on any page,
detail("vrp"/"vrp_exec")=None, /strategy/vrp=404, raw DB rows still seeded.
Tests: registry()/detail()/sim-book-list/IBKR-breakdown all assert vrp ABSENT;
test_cockpit_vrp_render flipped to assert 404; test_ibkr_sim_web asserts vrp
excluded from pills + falls back; account-view/detail-breakdown tests use a
synthetic non-archived `newstrat` for the dimmed/scale rows.
uv run pytest -q: 2006 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The scale-0->1 deploy step (516499a) failed: the argo-workflow SA had
deployments[patch] (for rollout restart) but not deployments/scale (kubectl
scale) nor pods (kubectl wait --for=delete pod). Grant deployments/scale
[patch,update] + pods [get,list,watch]. No outage — it failed before touching
the running dashboard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VRP (equity-vol XSP put-credit-spreads) is FALSIFIED/shelved (co-crashes,
fails the marginal gate). Hide it from the cockpit and stop its nightly Dagster
asset, while KEEPING all VRP code and the 13yr OPRA data archived.
- registry: mark both `vrp` and `vrp_exec` entries `archived: True` (entries KEPT).
- dashboard_service.fleet(): single hide point — skip any sleeve whose registry
meta is archived, so it never renders (deploy/research grouping or exec-twin).
- definitions.py: de-wire `vrp_nav` from the import, combined_book_job selection,
and Definitions assets list — nightly no longer materializes it.
- assets.py: drop `vrp_nav` from cockpit_forward deps; KEEP the vrp_nav function.
- tests: flip test_orchestration_definitions to assert vrp_nav is de-wired
(12 assets, not a cockpit_forward dep); switch the stale-badge render test off
the now-hidden vrp; add test_fleet_omits_archived_sleeves.
uv run pytest -q: 2006 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cockpit deploy workflow rollout-restarted deploy/fxhnt-dashboard, which
briefly overlaps two tailscale sessions on the same identity -> session race ->
the external dashboard.fxhnt.ai proxy link goes stale and the cockpit reads
'down' after every deploy (observed 2026-07-15). The dashboard's tailscale
sidecar serves the public URL, so it must be scaled to 0 (clean session
teardown) before a fresh non-overlapping one comes up. git-sync re-pulls src the
same way, so code still updates. dagster keeps rollout restart (no external
proxy). See reference_fxhnt_dashboard_proxy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The IGLN gold-sleeve 'priced but no order' bug. scaled_weights sizes orders to
the paper envelope (envelope*within_weight), but _plan's entry-floor/hysteresis
threshold used entry_floor*nlv (the full $1M account NLV) — so the effective
floor was ~entry_floor*(nlv/envelope) of the envelope (~5%), silently dropping
every sleeve below it. A 3% gold sleeve at a $100k envelope on a $1M account
skipped. FIX: rebalance_weights takes sizing_capital (the envelope); the floor
is entry_floor*min(sizing_capital,nlv). Sizing + recording unchanged. Regression
test: a 3% sleeve survives WITH the envelope basis, drops without it. 2005 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Probed live 2026-07-15: IGLN (physical-gold ETC) and DBMF (Euronext-primary)
hit Error 10197 'no market data during competing live session' / 354 on the
delayed live stream, so they skipped — but reqHistoricalData returns a clean
daily close for both (and the control ICOM). market_price now falls back to the
historical last close when the live snapshot yields no price; skips only if
BOTH fail. Fine for paper sizing. Gets the UCITS book to 5/5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Probe 2026-07-15: UCITS orders resolved on LSEETF but placing DIRECT to LSEETF
tripped IBKR's precautionary direct-route restriction (Error 10311 -> order
discarded 201). Route the qualified conId via SMART instead — SMART is in every
UCITS line's valid-exchange set, and the conId pins the exact instrument so it
can't mis-route to the wrong (US grey-market) line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live probe on DU9600528 (2026-07-15): SMART+ISIN returns 0 results for all
five UCITS lines (SMART grabs the wrong US grey-market listing or nothing);
LSEETF+ISIN+USD resolves each cleanly, and SMART stays in every valid-exchange
set so orders still route. Two fixes: (1) UcitsListing.exchange default
SMART->LSEETF; (2) _resolve_contract qualifies the ISIN WITHOUT a symbol (the
map ticker is cosmetic and differs from the USD line's real symbol: IEF->IDTM,
GLD->IGLN). Map tickers corrected to the USD lines. Rebalancer envelope
1,000,000 -> 100,000. 2004 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VrpStrategy.advance replayed ~1600 trading days issuing a fresh Session +
DB round-trip per day (chain_asof + bars_for x N) — thousands of queries,
minutes-long. Add an opt-in in-memory preload: one SELECT mirrors the
covered slice, and chain_asof/bars_for/trading_days serve from memory,
falling back to the DB for any date outside the preloaded range (never
under-returns). The live single-day exec path is untouched (no preload).
Guarded by a cache==DB equivalence test across every query shape. 2004 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- single-source vrp_marking valuation: backtest + live 50%-profit-take signal
- exec twin freezes last COMPLETE OPRA session (freeze_date/T-1), today=calendar for DTE/booking
- robust step-back on degenerate published session; realized P&L keeps raw fill marks
- defined-risk [0,width] clip preserved; 2002 tests green
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-review Minors.
MINOR-1 (document intentional asymmetry): on a degenerate slice where a
rung's own put legs price (real _mark_spread actual mark) but no ATM
call/put pair prices anywhere (model_spread_value -> None), the pure-model
backtest advance DROPS the rung (its only mark IS the model value) while
the live exec plan_vrp_ladder HOLDS it (take_profit is False when the
model signal is None, so it manages on the real fill mark until DTE<=1 or
the model recovers). Correct — the twin has strictly more information.
Making them consistent would regress either the model-only backtest (a
raw-leg fallback re-introduces the mark noise this fix removed) or the
model-signal profit-take (discarding a real fill mark). Added precise
cross-referencing comments at both divergence points. No thresholds
changed.
MINOR-2 (robust not fragile): _freeze_latest_session only stepped back on
DatabentoRangeUnavailable; a PUBLISHED-but-degenerate session makes
_xsp_forward_estimate/_forward_from_marks raise RuntimeError ("cannot
estimate forward" / "no matched call/put strike" / "no ATM call/put
pair"), which was uncaught -> plan_and_record_vrp and nightly vrp_nav
hard-crashed. Now the bounded step-back loop also treats a
forward-estimation RuntimeError as a step-back trigger, matched narrowly
via _is_degenerate_forward_error so an unrelated RuntimeError still
propagates; stays bounded and still raises DatabentoRangeUnavailable
loudly when exhausted. Both live-exec and nightly paths get more robust.
Tests: degenerate-then-usable step-back; all-degenerate -> loud raise;
unrelated RuntimeError propagates (not swallowed); predicate matches only
the forward messages. uv run pytest -q -> 2002 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live execute-vrp crashed at 07:42 UTC: plan_and_record_vrp froze
_freeze_day(as_of=today), but OPRA publishes on a ~1-session lag, so
today's slice is never available intraday -> DatabentoRangeUnavailable
before the run ever reached the IBKR combo (the scheduled Monday 14:40
UTC run would crash identically).
Fix: freeze the last COMPLETE OPRA session via _freeze_latest_session
(the same robust bounded step-back the nightly vrp_nav uses) and key
every bar read to that freeze_date (T-1) -- _mark_spread,
vrp_marking.model_spread_value, _select_spread, and the new-rung price
lookup -- while keeping `today` (calendar) for all date-logic: DTE,
entry-weekday, the last_run_date idempotency key, run_track booking and
the pos-state stamp. Only the OPRA data date lags; the booking date does
not.
Test: test_freeze_uses_last_complete_session_not_today drives the real
plan_and_record_vrp composition with a dbn whose freeze for today raises
DatabentoRangeUnavailable but succeeds for T-1; asserts no crash, marks
frozen at T-1 (absent at today), exec return still recorded under today.
uv run pytest -q -> 1998 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-ups on the VRP model-mark fix.
MINOR-1 (reconcile the live exec twin): vrp_exec_record still marked the
spread as raw short-long for its 50%-profit-take signal — the same
single-leg mark noise removed from the backtest — so a noisy far-OTM
long-leg spike could spuriously trip a live close on the paper account.
Extract the model-mark valuation into a shared single-source module
vrp_marking.py (forward_and_atm_iv, model_spread_value, settlement_value);
both vrp_book.VrpStrategy and vrp_exec_record now call it. plan_vrp_ladder
takes exec_marks (actual fill basis) AND signal_marks (model); the
profit-take decision uses only the model signal, while the close order
net limit and realized P&L stay mark-to-actual.
Also fixes a latent bug: the freeze band stores near-ATM calls only for
DTE[20,45], so a spread aged below 20 DTE has no calls at its own expiry.
The prior forward required calls at the spread's expiry -> would have
prematurely closed every spread ~20 DTE before expiry. The shared forward
now falls back to the globally cleanest ATM pair across expiries (parity
forward is spot, common across expiries under r=0), markable at any age.
MINOR-2 (efficiency): memoize (F, IV) per (day, expiry) — advance threads
a per-day day_cache; plan_and_record_vrp threads a signal_cache — so
rungs sharing an expiry don't re-query the chain.
MINOR-3 (doc): the absolute defined-risk bound [-width*100, +width*100]
holds unconditionally; the tight [-(width-credit)*100, +credit*100] is the
clean-BS-surface case (entry model value == credit).
Tests: uv run pytest -q -> 1997 passed. New: noisy-actual-mark doesn't
trip the exec profit-take; backtest and shared marking are single-source.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The daily mark was `short_close - long_close` — the difference of two
individually-noisy raw OPRA close marks. On a ~$1-2 20Δ 5-wide spread,
~$1 of quote noise in either leg swung the value 50-100%, and ÷ the $500
defined-risk margin turned mark noise into ±50% "daily returns"
(prod: ann_vol 68.5%, maxDD -92.3%, Sharpe -0.02, 14 days |ret|>15%).
It was a mark-noise artifact, not real P&L.
Rebuild the daily value from a smooth model instead:
- extract a reusable per-day (forward F, ATM IV) helper: parity forward
at the clean ATM strike + implied_vol_put inverted from that ATM mark,
queried over a wide DTE window so an open spread is markable at any age;
- _spread_value = clip(bs_put(F,K_short,iv,tau) - bs_put(F,K_long,iv,tau),
0, width) — smooth (spot+vol, not a noisy quote difference) and hard-
clipped to the defined-risk bound [0, width]; carry last good IV on a
degenerate day; close (no zombie) only when the expiry chain is gone;
- realize clip(K_short - F, 0, width) settlement intrinsic at DTE<=1;
- baseline P&L at the entry-day model value so every return is a change
in the smooth model value (no raw far-OTM leg noise on day 1).
Per-spread cumulative P&L is bounded to the defined-risk envelope
[-(width-credit)*100, +credit*100]. Entry credit / strike selection
unchanged.
Synthetic BEFORE/AFTER: ann_vol 211% -> 59%, |ret|>15% 65 -> 1 day,
extremes +62%/-36% -> +13%/-17%, Sharpe (noise) 3.8 / prod -0.02 -> +1.9.
The residual vol is real defined-risk P&L (inherent per-margin exposure,
sized by the allocation engine), not mark noise.
Tests: 11 vrp_book unit cases (bounded+smooth value, defined-risk
cumulative bound, clean-path daily bound, clipped settlement, dollar
basis); full VRP unit+integration green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The EU-domiciled DU paper account can't trade US ETFs (PRIIPs/KID). Arm the
PRIIPs-legal UCITS leg (fxhnt-ucits-rebalancer, --venue ucits, ISIN-resolved
contracts + real UCITS pricing) on a weekday 09:00 UTC slot inside LSE/Euronext
hours so the UCITS ETFs actually price + fill; suspend the US leg
(fxhnt-ibkr-rebalancer) which only KID-rejects on this account. Paper only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The IBKR paper account (EU-domiciled) rejects US-listed ETFs (PRIIPs/KID). The
--venue ucits path remaps to UCITS equivalents, but two bugs blocked it (found
in prod): contracts were built SMART/USD (LSE UCITS ETFs -> Error 200), and the
US price was reused for the UCITS ticker (wrong share count).
Fix: resolve each UCITS contract by ISIN via qualifyContracts (Order carries
exchange/currency/sec_id_type; US path unchanged); size from the real UCITS USD
price fetched from IBKR (delayed); per-sleeve skip+log on any resolution/price
failure (two-layer exception isolation — one thin sleeve can't wedge the book;
no rescale; never a wrong-size/wrong-symbol order). DBMF -> iMGP DBi Managed
Futures UCITS (ISIN LU2951555585). Venue-switch books as inception.
Real-money-adjacent (paper). Modeled US track UNCHANGED. 1991 green, opus review.
NEEDS LIVE VALIDATION on DU9600528 during EU market hours (mocks can't).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review round 2 (F1 Important + two Minor):
F1 — per-sleeve exception isolation for the UCITS price fetch. A raise (not
empty return) from the IB API previously propagated out of route_ucits, past
_plan's per-order isolation, aborting the WHOLE rebalance. Now IbkrBroker.
market_price wraps the IB round-trip in try/except -> log + None, and
route_ucits wraps price_fn(listing) in try/except -> log + skip. One thin/broken
sleeve (e.g. the new DBMF LU2951555585 line) can no longer wedge the book.
F2 — a non-injective UCITS map raised inside the broker block and was
misreported as "paper-envelope refused". The CLI now validates the map with
apply_ucits_map BEFORE connecting, printing a clear "UCITS map error: collision".
F3 — first UCITS run compared US-keyed prior positions against UCITS-keyed
prices, booking a spurious multistrat_exec return. Extracted
exec_return_for_run(pos_prior, prices, venue): a venue switch (prior book-state
venue tag != current venue) is booked as INCEPTION (no return); same-venue runs
delegate to compute_exec_return as before.
Tests: +5 (price_fn raises -> skip others route; market_price IB-raise -> None;
venue switch -> inception; same-venue -> normal return; no-prior -> inception).
uv run pytest -q: 1991 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
execute-multistrat --venue ucits was unrunnable and mis-sized:
- place_order hardcoded Stock(symbol, SMART, USD) so LSE-listed UCITS ETFs
(CSPX/IBTM/SGLN/ICOM/DBMF) failed IBKR resolution (Error 200);
- apply_ucits_map reused the US ticker's Yahoo price for the UCITS ticker, so
shares = weight*envelope/price was wrong;
- any single unmapped sleeve refused the whole (PRIIPs-legal) rebalance.
Fix:
- Order carries optional exchange/currency/sec_id_type/sec_id (US path keeps
SMART/USD defaults, byte-unchanged). IbkrBroker resolves UCITS orders via
Contract(secIdType=ISIN, secId, currency=USD) + qualifyContracts; an
unresolved contract logs loudly and is SKIPPED (no crash, no wrong order).
- IbkrBroker.market_price fetches the REAL UCITS USD price via delayed market
data (reqMarketDataType(3) + reqMktData snapshot); the UCITS route sizes from
it, not the US Yahoo price. Modeled US weights unchanged.
- route_ucits routes sleeves that map+resolve+price and loudly logs+skips the
rest; survivors keep their own weight (book NOT rescaled), skipped $ reported.
- UcitsSettings.map now carries verified ISINs (CSPX IE00B5BMR087, IBTM
IE00B1FZS798, SGLN IE00B4ND3602, ICOM IE00BDFL4P12) and DBMF is mapped to the
real iMGP DBi Managed Futures UCITS ETF (LU2951555585, USD line) with a
liquidity caveat comment.
TDD mocks the IB client (no live broker). Full suite: 1986 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings the IBKR paper strategies to cockpit parity with the crypto book, in
plain language, verified end-to-end (5 tasks, whole-branch opus review READY
TO MERGE, 1976 green):
- Backtests page includes the IBKR strategies' honest-cost curves.
- Capture the REAL DU paper-account state at each execute-multistrat run —
fills -> exec_fills (strategy-tagged) + a per-run ibkr_account_nav snapshot.
BEST-EFFORT + paper-only (triple-guarded: never blocks/crashes a real
rebalance; gate basis untouched; honest 0.0% market-order cost).
- Holdings detail: what a book holds (plain asset descriptions), recent trades,
value-over-time (real vs the plan), "Following the plan" reconciliation, and a
per-strategy breakdown that scales by row.
- Overview: a plain "real trades" sub-line + "behind/ahead of plan" pill.
- Consolidated /paper into a two-book landing (crypto simulated + IBKR real);
the crypto book moved byte-identical to /paper/crypto.
- Plain language enforced (single display_names source; web tests assert no raw
ids / bps / "vs sim" / "divergence" on every new surface).
Paper-only; no live/arm; gate/promotion/allocation unchanged; crypto book
data/behavior untouched. New tables (ibkr_account_nav, sim_curve_ret) +
exec_fills.strategy_id ALTER are idempotent + Postgres-safe.
POST-DEPLOY: trigger an execute-multistrat run so the IBKR book syncs (empty
until captured); the nightly writes the IBKR sim curve.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Whole-branch-review fixes for the IBKR paper cockpit parity feature:
- strategy.html: suppress the pre-existing "Real trades vs the plan" exec-twin
card when the new "Following the plan" card (from d.recon) already covers
the same IBKR book — was rendering the same "vs plan" % twice under two
labels with two different cost-to-trade numbers. Kept for non-IBKR exec
twins (e.g. Bybit) that have no d.recon.
- cockpit.html: the new D3 "real trades" sub-row no longer renders the raw
registry venue slug (e.g. "ibkr-equity") in its exec-tag; replaced with the
plain "IBKR paper account" label.
- _sim_result.html: reworded the Bybit measured-cost caption's "avg drag X bp"
to "avg drag X%", removing the last visible `bp` jargon on that surface.
Extends the existing plain-language web tests to cover each surface.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consolidates /paper into ONE "Paper books" landing showing both the crypto 4-edge book
(simulated, PAPER) and the IBKR paper account (REAL PAPER) as clickable cards, each linking
to its own holdings detail. The crypto book's former /paper content (headline/live book/
trades/edges) moves verbatim to /paper/crypto — data/behavior unchanged, only its URL.
DashboardService gains ibkr_account_view() (account-wide NAV/positions + a per-strategy
breakdown of StratExecRow, one row per strategy trading on the shared IBKR account — a
dimmed "not started yet" row for a registered-but-not-executing strategy, proven to scale by
row via a synthetic second executing strategy) and paper_books() (wires Task 4's
IbkrAccountRollup into the landing card instead of leaving it dead code). The IBKR account
detail page (Task 3's strategy.html) now renders this per-strategy breakdown.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tighten test_web_overview_shows_real_paper_badge_and_real_trades_subrow from loose substrings
to pinned values: "+3.10%" executed return, "0.7% behind plan" (+ an "ahead of plan" sign guard),
"cost to trade 0.0%", and "3 trades · 2 holdings" (+ a swapped-count guard). The fixture now seeds
3 fills over 2 held symbols (SPY traded twice) so a trade/holdings count swap, a stray sign, or a
wrong divisor fails the test rather than passing on a loose match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DashboardService.fleet() now marks a strategy row exec_status="EXEC" + attaches the shared
exec_vs_sim recon signal (Task 3) whenever a REAL IBKR paper-account book (multistrat/vrp) has
captured exec data; overview() rolls the executing rows up into an IbkrAccountRollup (reusing
each row's already-computed recon, no duplicate math) — carried on the model, not rendered as its
own card (Task 5's consolidated Paper-books page is the account home).
cockpit.html's "All forward tracks" table gets a REAL PAPER badge and a nested "real trades"
sub-row (reusing the existing .exec-tag/tr.exrow exec-twin style) for a genuinely-executing row:
real return, a "N trades · M holdings →" click-through to its holdings detail, the plan_pill
("X% behind/ahead of plan"), and honest cost-to-trade — plain language only, mutually exclusive
with the crypto book's generic exec-twin row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review fixes (2 Major + 2 Minor):
- MAJOR: reword the pre-existing "Executed reality" card (same detail page) off the forbidden
"divergence"/"vs sim"/"decay"/"bp" jargon -> "Real trades vs the plan", "N% behind/ahead of plan",
"cost to trade %". Extend the web forbidden-strings test to assert (on VISIBLE text, stripping
CSS/markup) no divergence/decay/vs-sim/bare-sim/bps anywhere on the rendered page.
- MAJOR: %-of-book uses GROSS exposure (sum of abs) as the denominator, so a mixed-sign defined-risk
book (vrp put-credit-spread: short put + long wing) no longer renders >100%/negative/non-summing
shares. Each row keeps its signed value. New mixed-sign holdings test.
- MINOR: holdings value is marked at each holding's last trade price (no live mark) — surface a per-row
"as of <date>" and a column caveat so a stale mark isn't over-trusted.
- MINOR: an unmapped asset symbol (future vrp option contracts) falls back to a plain generic "other"
instead of echoing a raw contract string.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- display_names.py: the SINGLE internal-id -> human-name map every view uses (absorbs app.py's
interim _SIM_BOOK_LABELS local map + a per-asset "what it is" description for the holdings table).
- exec_reconcile.py: exec_vs_sim() — THE shared "how closely real trades follow the plan" signal
(executed vs modeled cumulative forward return + median slippage + fees), reused by later D3/D4 work.
- dashboard_service.detail() attaches holdings/recent_trades/account_nav/recon for the IBKR real-account
books (multistrat/vrp), sourced from IbkrAccountRepo (Task 2) + ForwardNavRepo's new cum_return().
- strategy.html: "Following the plan" card + plan_pill, a real-vs-plan value-over-time curve (new
charts.overlay_curve), a "what it's holding right now" table, and a "recent trades" table with honest
cost-to-trade as a % (never a fabricated non-zero) — all plain-language, no raw ids/bps/"vs sim".
- Fixed a pre-existing raw-id leak (exec_twin_sid rendered as literal link text) via a new
`display_name` Jinja global, used project-wide.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>