Commit Graph

161 Commits

Author SHA1 Message Date
jgrusewski
f4cfec0f73 feat(orchestration): Definitions + daily combined-book schedule
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>
2026-06-15 11:51:55 +02:00
jgrusewski
c0a4932de9 feat(orchestration): B0 Dagster assets (crypto/futures bars -> nav -> cockpit) + forward_ingest extract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 11:40:39 +02:00
jgrusewski
2d7d7470fd build(orchestration): add dagster optional-dependency extra
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>
2026-06-15 11:30:55 +02:00
jgrusewski
5a75effd8d feat(combined-book): FXHNT_COMBINED_BOOK_DATA_SOURCE flag + source factory; wire CLI
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:27:24 +02:00
jgrusewski
c3c90881e0 refactor(combined-book): read via injected CombinedBookSource (default RawFileSource)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 11:24:19 +02:00
jgrusewski
c66bffabef feat(combined-book): WarehouseSource reads SSOT; equivalence test proves A->B faithful
- 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>
2026-06-15 11:16:25 +02:00
jgrusewski
9ecb24f82c feat(combined-book): CombinedBookSource port + RawFileSource (existing path) 2026-06-15 11:13:21 +02:00
jgrusewski
7a33082020 feat(warehouse): read_panel — per-symbol epoch-day matrix for one feature
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 11:11:57 +02:00
jgrusewski
461b89eba6 refactor(crypto): extract _panel builder (shared by raw + warehouse sources)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:10:13 +02:00
jgrusewski
83a6ce57dd plan(lifecycle): bite-sized B0 plan — Dagster foundation + combined-book via warehouse
10 tasks: _panel extract, warehouse read_panel, CombinedBookSource port (Raw/Warehouse,
equivalence test = A->B guarantee), CombinedBook source-injection, config flag+factory,
dagster extra, 4 assets, Definitions+schedule, cluster deploy (webserver+daemon+pg+tailnet)
+ suspend the fxhnt-forward cron. Reversible cutover via FXHNT_COMBINED_BOOK_DATA_SOURCE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:57:18 +02:00
jgrusewski
9878207ecd docs(lifecycle): design spec — B0 Dagster foundation + combined-book via warehouse
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>
2026-06-15 10:40:42 +02:00
jgrusewski
ddaf755594 merge: unified data layer increment 1 (warehouse-SSOT)
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>
2026-06-15 00:49:11 +02:00
jgrusewski
58d73960cd docs(cli): document warehouse as a --data-source option
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 00:47:45 +02:00
jgrusewski
f1e58c3d31 fix(cli): validate warehouse-ingest args before opening store; try/finally close (no DuckDB leak)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:44:21 +02:00
jgrusewski
c76bae9d05 feat(cli): warehouse-ingest + warehouse-catalog commands; warehouse data source
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>
2026-06-15 00:38:17 +02:00
jgrusewski
6089139857 fix(warehouse): type read_features params list[str|int] (strict mypy)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 00:35:43 +02:00
jgrusewski
6f9fb9a9f6 feat(warehouse): catalog/freshness inventory (store.catalog + format_catalog)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:34:55 +02:00
jgrusewski
c203df8d09 fix(warehouse): ingest_bars rejects symbol mismatch (prevent mislabeled bars)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:32:42 +02:00
jgrusewski
ecb9fba115 feat(warehouse): WarehousePriceProvider reads bars as PriceSeries (DataProvider)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:28:40 +02:00
jgrusewski
11fda08fc8 feat(warehouse): WarehouseIngest gold-write service (idempotent) 2026-06-15 00:26:44 +02:00
jgrusewski
fec4a78a83 feat(warehouse): silver_crypto normalizer (close-only daily bars) 2026-06-15 00:25:36 +02:00
jgrusewski
47603755a1 feat(warehouse): silver_futures normalizer (reuses continuous_front_adjust)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:24:41 +02:00
jgrusewski
bd0f6d78fb refactor(dbn_local): extract continuous_front_adjust + read_front_arrays for reuse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:23:05 +02:00
jgrusewski
ec8b05bfaa feat(warehouse): BarRecord canonical daily-bar value object 2026-06-15 00:20:48 +02:00
jgrusewski
fd1310fc46 feat(config): add warehouse_path setting 2026-06-15 00:19:29 +02:00
jgrusewski
7c5b1bd3e5 plan(data): bite-sized implementation plan — unified data layer / warehouse-SSOT
9 TDD tasks: config warehouse_path, BarRecord, factor dbn_local (shared adjust),
silver_futures/silver_crypto normalizers, WarehouseIngest, WarehousePriceProvider,
catalog, CLI (warehouse-ingest/catalog + warehouse data source). Increment-1 bars {close,high,low}.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 00:15:32 +02:00
jgrusewski
30c82f2a01 docs(data): design spec — unified data layer / warehouse-SSOT (platform sub-project A)
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>
2026-06-15 00:09:12 +02:00
jgrusewski
c857fbfd95 merge: single-market kinds (seasonality/breakout/vol_managed_momentum) + OHLC contract
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>
2026-06-14 23:51:37 +02:00
jgrusewski
d0e9772dd7 feat(duckdb): migrate + round-trip high/low (COALESCE fallback to close)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:40:56 +02:00
jgrusewski
fd81f1b6c6 feat(dbn_local): ratio-adjusted synthetic high/low via pure _front_to_series helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:35:59 +02:00
jgrusewski
5651a503aa feat(yahoo): adjustment-scaled high/low via pure _build_series helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:28:33 +02:00
jgrusewski
cd9dc4a504 refactor(databento): TYPE_CHECKING pandas annotation + column-absent fallback test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:26:47 +02:00
jgrusewski
63b78da87f feat(databento): fill NaN-guarded high/low via pure _df_to_series helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:23:53 +02:00
jgrusewski
c174c74ad7 feat(strategies): register breakout/seasonality/vol_managed_momentum (3->6 kinds)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:21:08 +02:00
jgrusewski
1b83390e26 feat(strategies): vol_managed_momentum kind (Moreira-Muir, de-risk only)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:18:09 +02:00
jgrusewski
45fab8c1a9 feat(strategies): seasonality kind; move seasonal_signal to domain (DRY, no shift)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:14:32 +02:00
jgrusewski
10f63192b2 feat(strategies): breakout kind (true Donchian channel)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:09:15 +02:00
jgrusewski
543e4c9f43 feat(strategies): causal rolling_max/rolling_min helpers 2026-06-14 23:06:33 +02:00
jgrusewski
90c25d70b1 fix(models): PriceSeries high/low — copy-on-default (immutability) + strict-mypy field type + test gaps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:05:03 +02:00
jgrusewski
7262238193 feat(models): PriceSeries gains high/low fields defaulting to close 2026-06-14 22:59:51 +02:00
jgrusewski
97808688d3 plan(strategies): bite-sized implementation plan — single-market kinds + OHLC contract
10 TDD tasks: PriceSeries high/low contract, rolling_max/min, 3 kinds
(breakout/seasonality/vol_managed_momentum), registration, 4 adapter high/low
fills (databento/yahoo/dbn_local/duckdb). Pure helpers for network-free adapter tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:55:46 +02:00
jgrusewski
76284d00ce docs(strategies): critical-review fixes to single-market kinds spec
- 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>
2026-06-14 22:43:12 +02:00
jgrusewski
0e232d1a1b docs(strategies): design spec — single-market kinds + OHLC contract (sub-project A)
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>
2026-06-14 22:37:28 +02:00
jgrusewski
7cafea7353 merge: constrain LlmAnalyst to backtestable kinds (verified on Ollama/qwen2.5:3b)
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>
2026-06-14 21:46:01 +02:00
jgrusewski
0689884f8b fix(factory): constrain LlmAnalyst to backtestable kinds (no hallucinated kinds)
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>
2026-06-14 21:44:16 +02:00
jgrusewski
5db5eb6fde merge: ongoing diversification-aware edge-factory (paper-safe, autonomous loop closed)
Diversification core (left-tail correlation, HRP via scipy, marginal Sharpe, fail-safe gate), FST-scaled DSR
immune system (global trial count threaded into the gauntlet bar), diversification-gated autonomous promotion
+ HRP allocation loop with governance (caps/kill-switch), domain-analyst fleet (single trial-count authority),
cockpit funnel view, daily in-cluster CronJob. Full generate->judge->promote->allocate->size loop closed:
analyst fleet -> hunt (gauntlet) -> forward -> diversification gate -> HRP -> executor sizing. ~30 commits,
246 tests, money-grade reviewed (caught + fixed: fail-open NaN, mutual-correlation admits, trial-count drift,
zero-sum HRP crash, gate-bypass). Capital-safe for paper; nothing deploys until the forward gate passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:28:33 +02:00
jgrusewski
8a63f69130 fix(factory): closure hardening — zero-sum HRP guard, read-only dry-run, single trial-count authority, funnel + E501
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>
2026-06-14 21:27:23 +02:00
jgrusewski
1ea5149ea1 feat(factory): close autonomous loop — generate→judge→promote→allocate + HRP sizing
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>
2026-06-14 21:11:52 +02:00
jgrusewski
420d74c142 fix(factory): scale DSR bar by global N + remove gate-bypass promote + silence HRP warning
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>
2026-06-14 20:54:16 +02:00
jgrusewski
3795b12318 deploy(factory): daily in-cluster factory-cycle CronJob
- 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>
2026-06-14 20:42:39 +02:00