1222 Commits

Author SHA1 Message Date
0d8ce554a3 fix(dashboard): allow tailscale sidecar egress to kube-apiserver
The dashboard NetworkPolicy blocked egress to the kube-apiserver (kube-proxy
DNATs kubernetes.default.svc 10.32.0.1:443 -> 172.16.0.11:6443, and both
10.32/16 and 100.64/15 were excepted). On a FRESH sidecar start (e.g. after
the CI deploy's scale-0->1) tailscale must read its ts-state Secret via the
apiserver -> without the allow it crashlooped 'context deadline exceeded' ->
bad gateway on dashboard.fxhnt.ai. Adds the 6443->172.16.0.11/32 egress rule,
mirroring the dagster NP fix (06d12fd). Pre-existing gap, surfaced by the
first pipeline deploy that recreated the dashboard pod.
2026-07-21 12:27:00 +00:00
7da51863a1 Merge branch 'ci/fxhnt-pipeline': fxhnt CI/CD pipeline
Argo Events sensor (gitadmin/fxhnt master push) -> fxhnt-cockpit
WorkflowTemplate (foxhunt): git-diff picks build-vs-deploy, Kaniko builds
only on dep/Dockerfile changes, deploy rolls dashboard (scale 0->1) +
dagster. Both modes validated end-to-end live. Sensor lives in the bizworx
GitOps repo (bizworx-ops/infrastructure); RBAC + workflow + build/deploy
pods are foxhunt-owned (project boundary). Review-clean: injection-hardened
(env+hex-validate), least-privilege RBAC (no rolebinding-create), dagster
on :latest so dep-bumps propagate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 11:40:34 +00:00
05f6d624c0 fix(ci): M1 split tailscale RBAC out of app manifests, drop deployer rolebinding grant
The dashboard/dagster manifests bundled the tailscale-sidecar SA+Role+RoleBinding,
which forced the webhook-triggered fxhnt-ci-deployer SA to hold create/update/patch
on roles/rolebindings in its own namespace — a privilege-escalation primitive. Move
those 6 objects to infra/k8s/rbac/tailscale-sidecars.yaml, applied once out-of-band
(not by CI); trim the deployer Role to drop rbac.authorization.k8s.io entirely, and
drop serviceaccounts too since neither CI-applied manifest creates one anymore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 11:32:51 +00:00
40c0b187a7 fix(ci): C1 dagster to :latest+Always-pull; I1 trap restores dashboard replicas
C1 (Critical, CI review): dagster webserver/daemon + K8sRunLauncher job_image
were pinned to a hardcoded tag (dagster-k8s-25e3d7c), so a mode=build dep bump
updated the dashboard (:latest) but left dagster + its run Jobs on stale deps
-> version skew in the nightly ETL. Now :latest with image_pull_policy Always.

I1 (Important): a trap scales the dashboard back to replicas=1 on ANY exit
path, so a failure between scale-0 and scale-1 can't strand the cockpit at 0.
2026-07-21 11:30:40 +00:00
ee077a2cc1 docs(ci): fix stale header comments (gitea-ssh host, submit usage) 2026-07-21 11:23:35 +00:00
e00bac067f ci(fxhnt): retire old cockpit-build-deploy + argo-deploy-cockpit.sh
Superseded by fxhnt-cockpit-workflowtemplate.yaml + fxhnt-build-sensor.yaml
+ fxhnt-ci-rbac.yaml (all validated end-to-end, both modes). The old files
targeted decommissioned infra (ci-compile-cpu pool, gitea-sshd.foxhunt,
MinIO artifacts) and no longer worked.
2026-07-21 11:23:01 +00:00
9d35df6bfc fix(ci): use plain param substitution for image tag (this Argo doesn't eval {{=...}})
The {{=sprig.trunc(...)}} expression wasn't evaluated by this Argo (v4.0.6) —
Kaniko received it literally and rejected it as an invalid tag. Use the plain
{{workflow.parameters.commit-sha}} substitution (which works everywhere else
in this template); the full 40-char sha is a valid Docker tag, no trunc needed.
2026-07-21 11:14:34 +00:00
da28c049f7 fix(ci): kaniko as args-list not sh -c (busybox choked on sprig expansion)
/busybox/sh -c parsed the {{=sprig.trunc(...)}} destination and errored on
the '(' -> syntax error, exit 2. Switched to Kaniko's entrypoint + a plain
YAML args list (the proven msp build-image pattern): Argo evaluates the
expression and passes it straight to Kaniko, no shell. Also pin executor
v1.23.2 (matches msp; no busybox needed).
2026-07-21 11:11:19 +00:00
6677f73a64 fix(ci): lower Kaniko resource requests to fit the large pool
Requested cpu:2/mem:4Gi was sized for the removed dedicated ci-compile-cpu
pool; the shared large nodes (3800m, ~1.3-1.6 CPU free) couldn't schedule it
(Insufficient cpu, autoscaler didn't add nodes). cpu:1/mem:3Gi request
(limit 3/10Gi) fits and still builds fine (Kaniko is I/O-bound here).
2026-07-21 11:07:11 +00:00
330bb56b52 fix(ci): deployer RBAC covers configmaps/serviceaccounts/roles/rolebindings
The dashboard+dagster manifests bundle a ConfigMap, ServiceAccount, and the
tailscale-sidecar Role/RoleBinding alongside the Deployment, so kubectl apply
of the whole file needs those types. RBAC-management is scoped to foxhunt
only (documented as a deliberate, namespace-local trade-off).
2026-07-21 10:51:03 +00:00
9f2b47a789 fix(ci): disable archiveLogs (no artifact repository in this Argo)
archiveLogs=true was inherited from the old MinIO-backed pipeline; this
Argo has no artifact repository configured, so log archival failed the
step. We don't need archived logs -> false.
2026-07-21 10:48:23 +00:00
80bf243023 fix(ci): use GIT_SSH_COMMAND absolute key path (Argo emissary has no HOME=/root)
The cp-key-to-~/.ssh idiom failed under Argo's executor because emissary
doesn't set HOME=/root, so git couldn't find the key -> clone exit 128.
GIT_SSH_COMMAND with the absolute key path is HOME-independent (verified
cloning under HOME=/nonexistent). Applied to all three git-clone steps.
2026-07-21 10:46:56 +00:00
6cf19ec379 fix(ci): correct gitea SSH host (gitea-sshd.foxhunt -> gitea-ssh.gitea)
The reanimated template used the OLD foxhunt-cluster gitea SSH service
(gitea-sshd.foxhunt), which doesn't exist on bizworx -> git clone failed
with exit 128. The real service is gitea-ssh.gitea.svc:22 (verified: the
argo-git-ssh-key deploy key resolves gitadmin/fxhnt HEAD there).
2026-07-21 10:41:12 +00:00
7d708b03b1 fix(ci): write decide-mode output to /workspace emptyDir not /tmp
Argo's executor reads a valueFrom.path output parameter from a mounted
volume, not the container rootfs -> /tmp/mode gave 'no such file or
directory'. /workspace is the shared emptyDir the step already mounts.
2026-07-21 10:39:15 +00:00
7318f77054 fix(ci): drop stale fr-par-2 zone pin from nodeSelectors
The reanimated template inherited topology.kubernetes.io/zone=fr-par-2 from
the old ci-compile-cpu pool, but all  nodes (and every fxhnt workload)
are in fr-par-1 -> pods were Unschedulable. pool-name=large alone is correct.
2026-07-21 10:38:05 +00:00
6d8bcc3ae7 ci(fxhnt): RBAC for the pipeline in foxhunt (SA + deploy role + cross-ns submit)
First real workflow run surfaced two gaps: (1) the WorkflowTemplate's SA
argo-workflow didn't exist in foxhunt (only in argo where msp builds);
(2) argo-events-sa couldn't submit workflows cross-ns into foxhunt. Adds
the SA, a least-privilege deployer Role (apply/scale/annotate/rollout the
dashboard+dagster deployments, exactly what kubectl-deploy runs), the Argo
workflowtaskresults role, and a foxhunt-scoped workflow-submitter binding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 10:31:24 +00:00
0069b75d0e fix(ci): harden WorkflowTemplate against template injection
commit-sha (from the untrusted Gitea webhook body.after) was substituted
INLINE into shell scripts via {{workflow.parameters.commit-sha}} in all
three git-clone/decide-mode steps -> a malicious payload could inject shell
on a cluster-privileged pod. Now passed via ENV + hex-validated before use
(the build-image template's pattern), and mode is allowlist-checked. Closes
the background security review's Orchestrator Template Injection finding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 10:20:26 +00:00
603aaaabff ci(fxhnt): single-trigger sensor + in-workflow git-diff mode decision 2026-07-21 10:14:48 +00:00
9b2b25433e ci(fxhnt): deploy step — keep scale 0->1 dance + deployed-sha annotation
Verified live: fxhnt-dashboard has its own dedicated tailscale sidecar
(TS_HOSTNAME=fxhnt-dashboard) and there is no Ingress/Gateway/VirtualService
in ns foxhunt routing dashboard.fxhnt.ai anywhere. fxhnt-ingress is an
unrelated nginx+tailscale pod (its own Deployment/Service, own tailscale
identity) for git/ssh routes, not the dashboard. The tailscale-session race
on `rollout restart` is real, so keep scale 0->1. Added the
fxhnt.io/deployed-sha annotation on both fxhnt-dashboard and dagster before
the rollouts, per the CI/CD plan Task 2.
2026-07-21 10:12:33 +00:00
6e9dfec067 ci(fxhnt): reanimate cockpit WorkflowTemplate (node pool large, sha7 tag) 2026-07-21 10:09:15 +00:00
c98b8d4b49 docs(ci): fxhnt CI/CD implementation plan (5 tasks, in-workflow mode decision)
Verified the Argo Events CRD has an open schema -> trigger-level path-filter
negation can't be validated. Redesigned the mode-split to live in the
workflow (git diff --name-only) with a single msp-style sensor (lowest risk).
5 tasks: WorkflowTemplate build side, deploy step (verify tailscale route),
sensor + decide-mode, apply+webhook, e2e verify both modes + retire old file.
Every cluster apply gated behind server dry-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 10:07:03 +00:00
fc8b1b7849 docs(ci): sharpen CI/CD spec to shared-engine/separate-projects principle
Operator's principle: fxhnt and bizworx are different projects — use the
shared bizworx platform infra (Gitea, Argo engine, registry, node-pools)
but keep fxhnt-owned objects (WorkflowTemplate, build/deploy pods, secrets,
RBAC) in the foxhunt ns. Only the Sensor lives in argo-events (unavoidable,
kept minimal). No cross-ns RBAC, no fxhnt secrets in platform ns. Adds a
deployed-sha annotation for git-sync reproducibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 10:03:28 +00:00
5d15b0f801 docs(ci): fxhnt CI/CD pipeline design spec (git-sync-first, image-on-demand)
Reanimate the retired cockpit-build-deploy WorkflowTemplate with the mode
split (build vs deploy) driven by a path-filter sensor: dep/Dockerfile
changes -> Kaniko rebuild, code-only changes -> fast rollout (git-sync
re-pulls src). Reuses the existing bizworx Argo Events/Workflows/Kaniko
stack (one Gitea, verified). No ArgoCD app, no lock file. Deploy-step
route (tailscale scale-0->1 vs rollout-restart) flagged to verify.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 09:47:43 +00:00
bf99ce2bd1 docs(vrp): CronJob verified absent on cluster — removal fully complete
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 07:33:33 +00:00
2f957bb6a6 Merge branch 'chore/remove-vrp-code': full VRP strategy code removal
Removes all VRP (volatility-risk-premium) code — 7 modules, DVOL pipeline,
vrp_nav Dagster asset + OPRA-freeze helpers, XspOptionBarsRepo class, CLI
commands, CronJob, registry entries, 13 test files. KEEPS the OPRA data
(xsp_option_bars table + XspOptionBarRow model + 13yr .dbn) per constraint.
Full suite green (2019 passed, 2 skipped); whole-branch review clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 07:19:04 +00:00
aa85ffd3a3 docs(vrp): mark VRP removal complete + record outstanding CronJob deploy action
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 00:56:15 +00:00
baf6ddbe9e test(vrp): port archived-hide coverage to synthetic entry + honest test narratives (review fixes) 2026-07-21 00:53:51 +00:00
c7a40b70ec docs(vrp): scrub stale VRP comment/docstring references across cockpit + shared infra 2026-07-21 00:10:27 +00:00
92b0871050 chore(vrp): remove fxhnt-opra-backfill CronJob (backfill-xsp-opra command deleted)
The CronJob invoked `fxhnt backfill-xsp-opra` (removed in Task 3); with the
command gone it would crash-loop. Its only purpose was freezing OPRA marks
for VRP recompute. OPRA data already frozen in xsp_option_bars (KEPT).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 00:01:30 +00:00
adf3edb054 refactor(vrp): remove vrp/vrp_exec registry entries + _REF_ALIAS + _IBKR_ACCOUNT_SIDS/SIM_BOOKS literals
Task 6+7 (shipped together per plan so the key is never gone while a literal
still references it). Registry: 12->10 keys (vrp/vrp_exec removed). Literals:
_IBKR_ACCOUNT_SIDS + SIM_BOOKS + _REF_ALIAS drop vrp. Tests that asserted
vrp-present updated to assert vrp-ABSENT. Covering tests (forward_health,
dashboard_service, forward_definition, sim_curves, display_names) 39 green:
surviving strategies still WAIT-by-default (Constraint 2 upheld).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 00:01:02 +00:00
fc663215ef refactor(vrp): delete XspOptionBarsRepo class + _build_vrp migration builder (keep table + XspOptionBarRow) 2026-07-20 23:31:52 +00:00
5b5fbb0e0b refactor(vrp): delete vrp_nav Dagster asset + OPRA-freeze/backfill helper chain 2026-07-20 23:24:06 +00:00
eb6ecf8272 refactor(vrp): remove execute-vrp/ingest-dvol/vrp-eval/vrp-defined-risk-eval/backfill-xsp-opra CLI commands 2026-07-20 23:20:00 +00:00
b71cd6b3b0 refactor(vrp): delete DVOL pipeline (deribit_dvol, dvol_ingest, VolIndexClient port) + tests 2026-07-20 23:12:49 +00:00
59cc06e594 refactor(vrp): delete VRP application modules + black_scholes + their tests 2026-07-20 23:08:46 +00:00
a263b676de docs(vrp): final VRP code-removal plan (discovery-workflow derived)
10-task subagent-driven removal: delete leaves first (7 vrp modules +
black_scholes + DVOL pipeline + 12 tests), then callers (vrp_nav asset,
XspOptionBarsRepo class, registry keys, CLI cmds, migration builder),
then comment scrubs. KEEP xsp_option_bars table/model/data + OPRA .dbn
(Constraint 1). Verified against tree: test_xsp_option_bars_repo.py does
not exist; test_vrp_rebalancer_yaml already red (dangling manifest ref).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 23:05:21 +00:00
fb14ddc75d docs(fund): tax/fund structure brief + EUR fund-ledger design spec
Research brief (6-agent workflow): NL tax/fund/accounting landscape for
going live. Resolved structure given operator's existing tech-BV + holding
+ DGA-salary: arbeid taxed in the BV, dividend (cleanly taxed) to private,
passive fund capital in Box 3 -> removes the Box-1 reclassification risk.

EUR-functional fund-ledger design spec: append-only double-entry ledger in
the existing Postgres warehouse, IBKR Flex ingestion, FIFO, fund-only scope.
No conversion pipeline (EUR end-to-end). NOT tax advice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 23:00:13 +00:00
8df02c2198 docs: VRP full-removal TODO for a fresh subagent-driven session (keep OPRA data) 2026-07-20 22:10:56 +00:00
39b313800d Merge: CBU0/CSBGU0 NLV fix + ib-gateway daily-restart fix
- fix(ucits): key the NLV stray-check on IBKR's reported symbol — the IEF UCITS
  line (ticker CBU0) reports holdings as canonical CSBGU0, so the legitimate
  Treasury book position was flagged stray + dropped from NLV. Adds
  UcitsListing.ibkr_symbol (probe-verified: only CBU0 diverges) + probe-ucits-symbols CLI.
- fix(ib-gateway): zero-pad AUTO_RESTART_TIME (8:00->08:00 AM) — IBC rejected the
  malformed time, leaving the daily auto-restart unconfigured -> ~16 restarts/day.
  Fixed + verified live (pod RESTARTS=0). Liveness failureThreshold 5->6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 22:10:01 +00:00
a16b341d90 fix(ib-gateway): zero-pad AUTO_RESTART_TIME (8:00->08:00 AM) — stops ~16 restarts/day
IBC's parser rejected '8:00 AM' ('Auto restart time setting must be hh:mm AM/PM'),
leaving the daily auto-restart UNCONFIGURED, so the gateway churned ~35 restarts over
2 days instead of one clean daily cycle. Zero-padded to '08:00 AM' — IBC now logs
'Auto restart time set to 08:00 AM' (verified live 2026-07-20, fresh pod RESTARTS=0).
08:00 UTC is before the 09:00 UTC UCITS rebalance window so the daily restart won't
collide with execution. Also bumped liveness failureThreshold 5->6 (180s) for the
reauth window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 21:30:01 +00:00
9baf1f8115 fix(ucits): key NLV stray-check on IBKR-reported symbol (CBU0->CSBGU0)
Probe (probe-ucits-symbols, live 2026-07-20) confirmed only the IEF line diverges:
config ticker CBU0 resolves to IBKR canonical symbol CSBGU0, which is what
positions() reports the holding under. _expected_book_symbols keyed on the display
ticker, so the legitimate 55-share IEF/Treasury book position (reported CSBGU0) was
flagged 'outside the book' and dropped from NLV.

Add UcitsListing.ibkr_symbol (defaults to ticker; set CSBGU0 for IEF) and include it
in _expected_book_symbols so the stray-check matches what IBKR actually reports. A
genuine stray (IBIT) is still flagged. Also fixed 4 lint items on the touched lines.

11 dashboard tests green (new CSBGU0-not-stray test + IBIT-still-stray + no regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 21:26:18 +00:00
420cffa93c feat(ucits): read-only probe-ucits-symbols CLI to detect ticker vs IBKR-reported symbol divergence
The IEF UCITS line's config ticker CBU0 resolves to IBKR canonical symbol CSBGU0
(the Error-478 root). After Monday's conId fix, CBU0 fills and IBKR reports the
holding as CSBGU0 — but _expected_book_symbols keys on the display ticker CBU0, so
the legitimate IEF/Treasury book position is flagged 'stray' and dropped from NLV
(live 2026-07-20: positions_json shows CSBGU0:55).

This adds IbkrBroker.resolve_symbol_info (read-only qualify-by-ISIN -> symbol/
localSymbol/conId) + the probe-ucits-symbols CLI to enumerate every line's real
reported symbol, so the config can carry an ibkr_symbol field and the NLV
stray-check keys on the REPORTED symbol. Probe first (this commit), fix config next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 21:17:34 +00:00
90fcbfde5c Merge: positioning per-coin gross cap (0.07) — cap every live money-path
Forensics found the positioning sleeve's 2026-07 -15% drawdown was ~entirely one
coin (LABUSDT, 10.3% long leg, -59%/-79.5% over 2 days). A 1295-day sweep validated
an ex-ante per-coin gross cap of 0.07: Sharpe 2.60->2.69, maxDD -32.3%->-29.9%,
per-year Sharpe non-decreasing; cap=0.04 falsified (2.43). Single-pass clip+renormalize
on the forming weights (causal), validated ~= iterative.

Threaded through EVERY live sink via opt-in (default None = uncapped = fail-safe):
modeled return, reconciliation ref, forward NAV, paper-book positions, testnet exec
sizing, live backfill, capital allocation. Research/eval paths left uncapped by design
(honest falsification). A source-level coverage-audit test (mutation-verified) guards
against a future uncapped live path.

positioning / bybit_4edge / bybit_4edge_levered re-inception v3, inception_t0 backdated
to 2026-07-06 so the forward gate recomputes the window (not reset).

10 tasks, whole-branch review: MERGE. 1126 tests green (the 1 skip-worthy failure is a
pre-existing torch/poc-extra env artifact, identical on master).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:44:38 +00:00
9080dffed5 test(positioning): harden cap-coverage audit to require FORWARDING, not mere presence
Final whole-branch review Minor: the audit checked the token appeared anywhere in
the function body, so a partial regression (param kept in signature but dropped
from the inner seam call) passed silently. Now require  as a
forwarded kwarg. Mutation-verified: dropping the forwarding from run_bybit_testnet's
call (while keeping the import) now FAILS the audit, where the substring check passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:32:31 +00:00
3a26b87f15 fix(positioning): cap the last 2 live paths (paper-backfill + allocation) + coverage-audit regression test
An exhaustive sweep of every caller in the positioning-cap dependency chain found two more live paths
still uncapped: the Bybit paper-book BACKFILL (bybit_paper_backfill.py, sizes/persists live paper
positions across history) and the honest-reference allocation inputs (allocation_honest_inputs.py,
feeds record_allocation, the LIVE strategy allocation engine's ex-ante capital sizing).

Threads positioning_coin_gross_cap (opt-in, default None) through both, matching the existing pattern:
- bybit_paper_backfill.py: _extract_per_sleeve + backfill_bybit_paper_book now accept and forward the
  cap; cli.py's bybit-paper-backfill command passes POSITIONING_COIN_GROSS_CAP.
- allocation_honest_inputs.py: _sleeve_inputs + _book_curve + _deploy_curve + honest_allocation_inputs
  now accept and forward the cap; allocation_ingest.py's record_allocation (called from the
  cockpit_forward Dagster asset) passes POSITIONING_COIN_GROSS_CAP into honest_allocation_inputs.

Adds tests/unit/test_positioning_cap_coverage_audit.py — a source-level regression guard asserting every
LIVE-book/position/allocation callsite passes positioning_coin_gross_cap. Fixed a boundary bug in the
brief's own _func_body helper regex (bare ^\S with re.MULTILINE always matches position 0 of the sliced
remainder, truncating every function body to its signature line) so the audit actually inspects each
function's body instead of false-failing on already-capped callsites.

Research/eval callers (verify_positioning_edge, walk_forward_positioning, look_ahead_audit,
_variant_weights, _drop_top_n, _liquidity_sweep, positioning_metalabel, bybit_overlay_ab, vrp_eval,
onchain_fundamental_eval, spread_overlay, honest_report) are untouched — they stay uncapped by design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:17:20 +00:00
f325a37070 fix(positioning): cap testnet exec-sizing path (4th uncapped money-path found by exhaustive sweep)
An exhaustive grep of every build_bybit_paper_book / combined_symbol_weights* /
latest_raw_sleeve_weights caller found bybit_testnet_run.py sized the executed
testnet positions UNCAPPED (latest_raw_sleeve_weights + combined_symbol_weights
called without positioning_coin_gross_cap). The cron is suspended (no creds) but
it is a real order-placing path — the moment it arms it would size concentrated
positions. Thread POSITIONING_COIN_GROSS_CAP the same as every other exec path.

35 testnet tests green, import + lint clean (no circular import).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:00:07 +00:00
dd527e6658 fix(positioning): thread coin_gross_cap into bybit-paper-book CLI seed path
The review of fb4cb49 found one more silently-uncapped call into
build_bybit_paper_book: the `bybit-paper-book` CLI command (cli.py) used
to manually seed/re-seed the SAME live Bybit paper book the nightly
Dagster asset populates. It mirrored the nightly asset's pre-fix call
exactly and was missed because it wasn't in that task's file list.

Pass positioning_coin_gross_cap=POSITIONING_COIN_GROSS_CAP, matching the
nightly bybit_paper_book asset call site. Default-None behavior elsewhere
is unaffected.
2026-07-20 19:53:08 +00:00
fb4cb49b18 fix(positioning): cap live paper-book positions + report-ref path (coin_gross_cap; Task-7-review Critical+Important)
Threads positioning_coin_gross_cap through the two remaining uncapped
paths found by the Task-7 whole-branch review:

- Critical: combined_symbol_weights / combined_symbol_weights_and_returns /
  latest_raw_sleeve_weights / derive_and_persist_bybit_paper_book /
  build_bybit_paper_book now accept positioning_coin_gross_cap and forward
  it into sleeve_weights_and_contributions, so the live Bybit paper-book's
  ACTUAL persisted positions honor the same per-coin cap as the reconciled
  return. The nightly bybit_paper_book asset now passes
  POSITIONING_COIN_GROSS_CAP.
- Important: bybit_4edge_backtest_summary gained a positioning_coin_gross_cap
  sibling next to positioning_tail_cap_k, forwarded into
  BybitFourEdgeStrategy; report_backtest_summary now passes
  POSITIONING_COIN_GROSS_CAP alongside POSITIONING_TAIL_CAP_K.

Scope: coin_gross_cap only (the EX-ANTE weight cap) — capacity_capital and
tail_cap_k are gains-only return haircuts and do not belong on the weight
path. Default None everywhere (OFF = byte-identical).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 19:46:21 +00:00
5d4ddcfb39 fix(positioning): thread coin_gross_cap into the LIVE forward-nav path + attribution (final-review Critical)
The per-coin gross cap (0.07) reached the reconciliation REF but not the live
forward NAV (BybitFourEdgeStrategy), so the gate would compare a capped ref
against an uncapped forward and misread a healthy book as diverging. Threads
positioning_coin_gross_cap through BybitFourEdgeStrategy, the four assets.py
builders + three nightly call sites, and the three migration_builders.py
builders, exactly parallel to the existing positioning_tail_cap_k. Also fixes
positioning_weights_and_contributions + sleeve_weights_and_contributions so
the paper-book per-symbol attribution reconciles against the capped sleeve
return (Sigma weight*return == sleeve_returns_from_store), preserving the SSOT
invariant. Default None everywhere (off = byte-identical).
2026-07-20 19:24:44 +00:00
f2730d195d fix(types): clear 4 pre-existing mypy errors in the positioning path
Every minor is a latent bug, pre-existing is no exemption:
- bybit_book_eval __getattr__ missing -> Any return annotation
- two unused type: ignore[assignment] comments (mypy: unused) removed
- positioning_returns_from_store: bind the dict[int,float] 'net' to a typed local
  instead of returning Any from _book_breakdown(...)['net']

mypy clean on both files; 26 positioning tests still green (behavior-neutral).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:56:20 +00:00