Wire the 4 B0 assets into combined_book_forward_job and a
ScheduleDefinition (cron 30 23 * * *, UTC) via a Dagster Definitions
object; dagster definitions validate passes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pins adjusted from >=1.8,<1.9 to >=1.12,<1.13 (dagster-webserver matching)
and >=0.28,<0.29 (dagster-postgres) — the 1.8.x/1.9.x/1.10.x lines cap at
Python <3.13 and the 1.11.x line caps at <=3.13.3; 1.12.x is the nearest line
that supports Python 3.13.6 (the venv runtime).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extend FeatureStore protocol with catalog() and read_panel() so WarehouseSource
can be typed against the port (mypy strict clean)
- Add silver_ingest_helpers.ingest_crypto_pit — shared bronze→silver→warehouse
write path for assets and tests
- Implement WarehouseSource in combined_book_source.py; delegates crypto panel
to read_panel+_panel and futures to WarehousePriceProvider
- Add equivalence test: warehouse-fed panel == raw-file-fed panel (A→B gate)
- 291 tests pass, 0 regressions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sub-project B increment 0: stand up Dagster on the cluster + migrate the combined-book
paper-forward track end-to-end as an asset graph reading the warehouse SSOT (proves A->B),
replacing the fxhnt-forward cron. CombinedBookSource port (Warehouse/Raw), 4 assets,
cockpit ingest, reversible cutover. B1-B4 (other tracks, gauntlet, promote/live) deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Daily bars (futures continuous-front + crypto close-only) flow bronze->silver->gold
into the point-in-time warehouse; WarehousePriceProvider reads them back via the
DataProvider port (--data-source warehouse, reversible); catalog/freshness view.
285 tests, mypy-strict clean on all new files. Polygon (incr 2) + news (incr 3) deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the `warehouse` branch to `_data_provider` (the cutover seam for
hunt/run --data-source warehouse), plus two new commands: `warehouse-ingest`
(bronze→silver→gold for futures and crypto) and `warehouse-catalog`
(per-symbol inventory/freshness view of the SSOT).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Increment 1: daily bars (futures+crypto) bronze->silver->gold into the existing
point-in-time FeatureStore, consumed via the DataProvider port (WarehousePriceProvider),
plus a catalog/freshness view. Funding + retiring the other data homes are later increments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sub-project A: grows available() 3->6 with three structurally-distinct single-market
kinds, and extends PriceSeries with high/low filled by all 4 adapters (databento/
yahoo/dbn_local/duckdb migration) for a true Donchian. 269 tests, mypy-strict clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- HIGH: seasonality must not be shifted (signal is calendar+prior-years, pre-known)
- MED: yahoo None-policy defaults missing high/low to close, not row-drop
- LOW-MED: NaN high/low guard in all adapters (rolling_max/min propagation)
- DOC: vol_managed is de-risk-only (target_vol ceiling), not full vol-targeting
- LOW: add registration + end-to-end run_backtest test
- verified: DuckDB ADD COLUMN IF NOT EXISTS, breakout lookahead, dbn_local roll factor
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds seasonality / breakout (true Donchian) / vol_managed_momentum kinds (3->6),
extends PriceSeries with high/low filled by all 4 adapters (+ DuckDB migration).
Sub-project B (cross-sectional carry/VRP harness) deferred to its own spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real Ollama test: with the kinds-constrained prompt, qwen2.5:3b produces 100% valid backtestable proposals
(was hallucinating invalid kinds before the fix). LLM generate step proven end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real Ollama test showed qwen2.5:3b inventing invalid strategy kinds (FUTURES_CONTRACTS, MARKET_RETURN, ...)
because the prompt never listed the engine's available() kinds. Now the analyst is given the allowed kinds,
the prompt forbids inventing others, and proposals with an unknown/empty kind are dropped — so the fleet only
emits backtestable hypotheses. The gauntlet still filters the rest.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
I1 (crash): zero-sum HRP ZeroDivisionError — `hrp_sum = sum(...) or 0.0` was a no-op since
`0.0 or 0.0 == 0.0`. Guard BOTH divide sites in `_size_active`: all-allocated branch and
mixed branch now fall back to equal-weight when `hrp_sum <= 0`.
I2 (honest dry-run): `factory-cycle --dry-run` previously ran the full discovery chain
(gather+hunt+forward) and wrote to the store (mark_seen, upsert DISCOVERED, set_status
RETIRED). Gate the entire GENERATE→JUDGE→PROMOTE→PERSIST block behind `if execute:`.
In dry-run, only the FactoryLoop runs in kill-gated mode (promotes nothing) and reports
the current pool state without any store writes.
M1 (double-count + dedup namespace split): analyst fleet AND hunt each called `add_trials`,
and used different key formats (`market:kind:k=v` vs `kind|market|params`). Fix:
- `candidate_key(market, kind, params)` added to `fxhnt.domain.factory` as the single
canonical key helper (format: `market:kind:k1=v1,k2=v2` — params sorted, 10g floats).
- `Proposal.key()` delegates to `candidate_key` (unified namespace).
- `FleetOrchestrator.hunt` uses `candidate_key` for its `cand_key` (was `kind|sym|params`).
- `AnalystFleet.gather` is now read-only: no `add_trials`, no `mark_seen`. The hunt is the
single counting authority. gather only filters proposals already in `store.is_seen`.
M2 (funnel double-count): `forward` was sampled BEFORE `loop.allocate_and_promote()` so
sleeves promoted this cycle appeared in both `forward` and `deployed`. Now `forward` is
computed as `fwd_report.forward_tracking - len(res.promoted)` after promotion.
M3 (E501): wrap `assemble` signature in `regime_execution.py` (was 123 chars).
Pre-existing lint (UP042, E501) in `domain/factory/models.py` also fixed:
`StrategyStatus(str, Enum)` → `StrategyStatus(StrEnum)`;
`forward_returns` comment moved above the field to fit in 120 chars.
Tests: 246 pass (was 241). New tests in test_regime_execution.py:
test_hrp_zero_weights_all_allocated_no_crash
test_hrp_zero_weight_single_leg_no_crash
test_hrp_zero_weights_mixed_branch_no_crash
Updated test_analyst_fleet.py: dedup-only contract assertions (trials==0, seen==0 after gather;
fresh proposals not pre-emptively marked seen; canonical key format check).
Updated test_factory_cycle_wiring.py: aligned with M1 — gather is read-only, hunt is the
single counting authority.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part A: factory-cycle now runs the full chain. Analyst fleet (LlmAnalyst
per specialty, graceful LLM-absent fallback) gathers proposals; a
proposal→Territory bridge groups them by kind and merges with the standing
fixed-grid hunters; FleetOrchestrator.hunt() + ForwardValidationService.run()
execute the judge step; FactoryLoop.allocate_and_promote() does the
diversification-gated promote+HRP step. Funnel counts (proposed/tested/
survived/forward/deployed/retired/global_trials) are persisted via
FactoryStore.record_cycle() in --execute mode with real values from each
layer's report object. Dry-run safe; kill-switch respected.
Part B: RegimeConditionalExecutor.assemble() sizes deployed legs by HRP
weights from the latest factory cycle allocation (injected via hrp_weights
kwarg). Legs not in the allocation (newly promoted) get an equal share of the
unallocated gross budget. No allocation recorded (hrp_weights=None) → previous
equal-weight behaviour preserved. Gross budget is always renormalised.
FactoryStore.latest_weights() added to retrieve the most-recent cycle's HRP
dict (None = no cycle, {} = cycle with no sleeves).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix 1 (CRITICAL): FleetOrchestrator.hunt now passes n_trials =
store.total_trials() + local_eff to evaluate_by_regime so the deflated-
Sharpe bar scales with the full cumulative search across all prior hunts,
not just the current territory's candidate count. The immune system is
no longer decorative. add_trials() was already called once after the loop,
so no double-counting is introduced.
Fix 2 (IMPORTANT): ForwardValidationService._review no longer auto-
promotes a FORWARD sleeve to DEPLOYED when forward_mean() > 0. That path
bypassed the factory_loop's diversification gate entirely and could route
capital around governance. _review now only retires (CUSUM decay or
negative forward mean after min_days); promotion to DEPLOYED is the
factory_loop's sole responsibility. test_forward.py updated accordingly.
Fix 3 (MINOR): hrp._ivp wraps both 1.0/diag(cov) and iv/iv.sum() inside
np.errstate(divide="ignore", invalid="ignore") so a zero-variance asset
no longer emits a RuntimeWarning before the uniform fallback catches it.
New test: tests/integration/test_global_n_flows.py asserts the n_trials
expression (store.total + local_eff) and that hunt() records
n_trials_at_discovery >= 5000 after seeding 5000 prior trials.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add infra/k8s/jobs/fxhnt-factory-cronjob.yaml: CronJob fxhnt-factory
runs `fxhnt migrate-cockpit && fxhnt factory-cycle --execute` daily at
01:00 UTC (after the 23:30 forward ingest). Mirrors fxhnt-forward exactly:
same image, imagePullSecrets, nodeSelector (platform pool), FXHNT_OPERATIONAL_DSN
literal + PGPASSWORD secretKeyRef, restartPolicy Never, concurrencyPolicy
Forbid, history limits 3/3. No PVC — factory reads/writes DB only.
- Add NetworkPolicy fxhnt-factory to infra/k8s/network-policies/fxhnt-cockpit.yaml:
egress to postgres:5432 + DNS only (no public internet). Mirrors fxhnt-dashboard.
- infra/docker/fxhnt.Dockerfile: pip install now includes factory extra
(`.[web,pg,data,factory]`) so scipy is present in the image.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>