Fix 1: wire record_cycle in factory-cycle --execute so the funnel table is populated.
LoopResult gains deployed_before/deployed_after/forward_count; cli.py calls
fs.record_cycle(proposed=forward_count, ...) and record_allocation after the loop.
Fix 2+4: replace equal-weight book proxy with HRP-weighted blend of deployed sleeves;
rewrite admission as sequential — each candidate is re-gated against the running mean
of already-admitted-this-cycle candidates, so two candidates uncorrelated to the book
but correlated to each other cannot both be admitted in one cycle.
Fix 3: HRP inclusion now requires >= min_forward_days (was >= 2). Folded into the
final rets comprehension in allocate_and_promote.
Fix 5: StrategyStoreLoopAdapter.set_status now asserts status == "DEPLOYED" so a future
demotion path cannot silently promote via the loop adapter.
Fix 6: persisted kill-switch — FactoryFlagRow ORM model added to cockpit_models.py;
FactoryStore.set_kill(on)/is_killed() read/write a key="kill" row; factory-cycle
folds db kill with env kill; factory-flatten renamed to factory-halt; factory-resume
and factory-status commands added.
Fix 7: sleeve returns fetched once per sleeve in the final HRP comprehension via a
shared dict, avoiding double store I/O.
Fix 8: three new integration tests covering min_forward_days filter, empty-book
founding-admit-then-gate, and mutual-correlation blocking within a cycle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix 1 (HRP, IMPORTANT): both the main bisection path and the except-branch fallback in
hrp_weights now guard the weight vector after computation — if any weight is non-finite,
negative, or the sum is ≤ 0 (triggered by a zero-variance asset causing 1/0→inf in _ivp),
the result is replaced with a uniform distribution (1/n each), which is always a valid
allocation. Prevents NaN weights from silently propagating to capital allocation.
Fix 2 (correlation, MINOR): replace exact `std() == 0` zero-vol guards in full_correlation
and left_tail_correlation with `std() < 1e-12`, so arrays with near-constant float-noise
(std ~1e-17) return NaN instead of a garbage corrcoef value.
Fix 3 (gate, MINOR): split the formerly combined `not isfinite(mg) or mg < min_marginal`
branch into two: a non-finite marginal now reports "marginal Sharpe unestimable — fail-safe
reject" (distinct from the "< threshold" message), making log/alert triage unambiguous.
Admit/reject outcome is unchanged.
Fix 4 (correlation, MINOR): _aligned now returns two empty arrays immediately when either
input has length 0, guarding against the a[-0:] == a[:] footgun that would otherwise return
the full array. Downstream len < 2 guards then produce NaN as designed.
New tests: test_hrp_zero_vol_asset_falls_back_to_valid, test_near_constant_returns_nan,
test_empty_input_is_nan_not_raise, test_unestimable_marginal_reason_is_distinct.
All 203 tests pass; ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix 1: derive rebalance_id from last panel day (reb<day_int>) instead of
wall-clock strftime so crash+re-run in a different minute reuses the same
deterministic clientOrderIds — Binance dedupes on clientOrderId → idempotent.
Fix 2: add rate_limited: bool field to RebalancePlan; set True whenever any
placed Fill has status == "REJECTED" (budget-exhausted leg); surface in CLI echo
so operators see exhaustion explicitly rather than learning via a drift halt.
Fix 3: test_reduce_only_at_exact_cover_boundary locks the boundary invariant
that an order sized to exactly cover an open position (qty == |current_qty|) is
always flagged reduce_only for both SELL-covers-long and BUY-covers-short cases.
Fix 4: test_reconcile_flags_unexpected_drift_on_untraded_symbol proves
reconciliation catches drift on holdings the orchestrator never touched this
cycle (the actual-union path in diff_positions covers untraded symbols too).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
C2 (over-fill): after cancel, fetch `order_status` for the authoritative post-cancel
fill qty instead of the stale place-response; maker may keep filling during the sleep
so the stale qty would size the IOC too large.
C1 (false-halt): accumulate BOTH the maker AND the IOC fills into `intended` so
reconciliation sees the full executed quantity and does not false-halt on the IOC leg.
I3 (dry-run observability): `block_reason` is now populated and returned even when
`execute=False`, so monitoring can see gate trips on dry-runs.
I4 (arrival mid): capture the true decision-time `book.mid` per symbol while building
route inputs; pass that to `_persist` / TCA instead of the stale maker/abort blend.
I5 (θ=0 hold): `gross_scaler == 0` (EMA/Kelly warm-up or NaN-recovery) now returns
early with `block_reason="zero_gross_scale_hold"` rather than flattening the book.
Deliberate exits belong to the kill-switch / crypto-flatten path.
M7 (rate-budget window): `RateBudget.try_order` now resets the order counter after a
60-second window so a long-running process never permanently exhausts the per-minute budget.
Tests: add FakeExchangePartialMaker + FakeExchangeMakerFillsDuringSleep variants and
three new integration tests exercising the full maker→cancel→order_status→IOC path
(previously zero coverage); add SELL-above-mid savings case to test_tca; add short
position drift + expected-position-fully-closed cases to test_reconciliation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Any non-finite input to gross_scaler now returns 0.0 (flat) instead of silently
propagating through min()/max() to the non-NaN argument and potentially inverting
to maximum exposure. A negative/misconfigured gross_cap is clamped to 0.0 (not
negative). pretrade_block blocks on NaN session_pnl_pct ("session_pnl_unknown")
and on a misconfigured session_dd that is non-finite or non-negative
("bad_session_dd_config"). cap_weights zeros all outputs on non-finite NLV, zeros
individual symbols on non-finite weights, and skips the ADV cap when ADV is
non-finite (rather than weakening the per_symbol_cap). GateConfig gains kelly_cap
field for cohesion. Locked by 13 new NaN/edge-case unit tests (166 total, all pass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- crypto_momentum_research.py: add strict=True to zip(day, close) in load_crypto_panel
so mismatched-length arrays raise immediately instead of silently truncating the panel
- crypto_momentum_research.py: extract turnover variable to fix E501 (line 64, 126→≤120 chars)
- test_binance_ccxt_helpers.py: add test_rate_budget_production_weight_margin that exercises
RateBudget(max_orders=int(1200*0.9), max_weight=int(2400*0.9)) — the exact args used in
BinanceUsdmExchange.__init__ — and asserts the 2160 effective ceiling (boundary 2159/2161)
- smart_router.py: add clarifying comment on qty=usd/mid explaining that MID is correct
(both target_usd and current notional are mark-to-mid; changing to limit price breaks
mark-to-mid consistency)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend `_plan_one` to treat zero depth on the relevant side as `no_book`
(computed before the depth-usd calc so it never reaches the dust branch);
add a comment that `round_qty` floors make qty-overshooting impossible.
Tests: replace non-falsifiable qty assertion with exact `abs(p.qty-4.995)<1e-9`;
rename old dust test to `test_min_notional_skip` (matches its skip_reason);
add `test_dust_skip`, `test_no_book_when_zero_depth`,
`test_reduce_only_false_when_increasing`, `test_reduce_only_false_when_flipping`.
9 tests total, all pass; full suite 123 passed; ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_backoff now reads last_response_headers for a Retry-After value and
prefers it (capped at 60 s) over the blind 2^attempt fallback when a
rate-limit error is caught.
positions() now explicitly checks side ∈ {"long","short"} and skips
any position with an unrecognized side, preventing silent sign errors
from None/unknown values being coerced to -1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Forward-tracking only needs recent bars (>250d signal lookback); re-pulling all-expiry history weekly cost
~$18.70/run. Rolling FXHNT_DATABENTO_FETCH_YEARS window (default 2) cuts it ~90% with identical forward signals.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pod CIDR 100.64.0.0/15 overlaps Tailscale CGNAT (100.64.0.0/10), so a kernel-mode tailscale proxy hijacks
cross-node pod routes and can't reach the Service (the old separate proxy failed identically to the cluster's
web-dashboard). Fix: tailscale userspace sidecar INSIDE the cockpit pod, serving tailnet -> 127.0.0.1:8080
(no kernel routes, localhost target). Reachable at https://fxhnt-dashboard*.tailff20e.ts.net with MagicDNS TLS.
Netpol adds public 443/UDP egress for the sidecar's coordination/DERP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Argo WorkflowTemplate fxhnt-cockpit mirrors foxhunt's build-ci-image (Kaniko) + compile-and-deploy (kubectl):
clones root/fxhnt over in-cluster gitlab-shell, builds infra/docker/fxhnt.Dockerfile to the internal registry
(native registry+token access, no laptop push, no private-CA trust), applies manifests + rolls the dashboard.
Manifests corrected to the cluster's real secrets: DSN via PGPASSWORD (db-credentials), DATABENTO via
databento-credentials, TS via tailscale-auth; image -> internal gitlab registry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Postgres-backed in-cluster cockpit (FastAPI+HTMX) over the in-cluster TimescaleDB: forward-track fleet view,
period-aware raw rolling means (daily/weekly/monthly, no normalization), factory population. Combined-book
edge pipeline ported into fxhnt (crypto + futures fetchers) so the daily loop runs fully in-cluster via a
CronJob (fetch -> forward-track -> ingest); dashboard Deployment behind a Tailscale proxy. 28 commits,
103 tests, reviewed per milestone. Live deploy (image push + kubectl apply + dashboard.fxhnt.ai re-route)
deferred to an explicit deploy step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix A: `fxhnt fetch --futures` with no DATABENTO_API_KEY now exits 1
with a clear message instead of surfacing a RuntimeError traceback;
guard runs before any network call so crypto still proceeds normally.
Fix B: `fetch_crypto_pit` writes `funding` as a per-day array aligned
to `kd` (same length as `day`/`close`), dropping the separate
`funding_day` key that was unaligned with the price axis.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>