215 Commits

Author SHA1 Message Date
jgrusewski
7e7c8b5d90 feat(rl): R6 — replace LobEnv with kernel-driven GPU-pure env step
Replaces the flawed Phase F+G LobEnv trait + LobSimEnvAdapter +
MockLobEnv fixture with a GPU-pure env-interaction path.

DELETED:
- LobEnv trait (host-method-per-batch surface)
- MockLobEnv fixture (the toy-bandit pattern that hid the production defects)

ADDED:
- RlLobBackend trait (src/rl/reward.rs): narrow device-oriented
  surface (apply_snapshot, pos_and_market_targets_mut, pos_d,
  pos_bytes, step_fill_from_market_targets, n_backtests). Single
  purpose: break the ml-alpha ↔ ml-backtesting dep cycle. No
  mocking layer.

- 3 new GPU-pure kernels (cuda/):
  * extract_realized_pnl_delta.cu — reads pos.realized_pnl +
    position_lots from device Pos array, writes per-batch
    (reward delta, done flag), updates trainer's prev_* buffers.
  * apply_reward_scale.cu — element-wise rewards *= ISV[406].
  * actions_to_market_targets.cu — 9-action grid → market_targets
    {side, size} on device, reading current position_lots for
    conditional Flat-from-Long/Short.

- LobSimCuda impls RlLobBackend (ml-backtesting/src/sim/mod.rs)
  plus a new step_fill_from_market_targets entry that runs
  submit_market_immediate + step_pnl_track without the host-side
  targets-vec build. pos_and_market_targets_mut returns disjoint
  field borrows (&pos_d, &mut market_targets_d).

- IntegratedTrainer: 3 cubin includes, 3 module/function fields,
  launch_apply_reward_scale launcher, prev_realized_pnl_d /
  prev_position_lots_d / rewards_d / dones_d buffers,
  step_with_lobsim signature switched to RlLobBackend, body's
  Step 5 rewritten as kernel-driven (no per-batch host loop, no
  individual submit_action calls).

R6 PARTIAL — work R7 lifts:
- Thompson + log_pi stay host (R7 uses R4 kernels)
- mean_abs_pnl EMA stays host (R7 uses R3 ema_update_on_done)
- Advantage/return stays host (R7 uses R3 compute_advantage_return)
- 4 final DtoH copies for step_synthetic's host-slice signature
  (R7 lifts step_after_encoder_forward to device-buffer args)
The "DtoH the device rewards/dones at end" comment in
step_with_lobsim documents the boundary. After R7, hot path has
zero host loops other than the now-unused Thompson host loop
(which R7 retires in favour of rl_action_kernel from R4).

Tests:
- integrated_trainer_smoke.rs: rewritten — real LobSimCuda with
  synthetic book, one step_with_lobsim call, asserts finite losses
  + λs sum to 1. Smoke gate, not a convergence test.
- dqn_toy.rs, ppo_toy.rs: retired (empty stubs documenting
  rationale). Files preserved so rename history survives. The
  MockLobEnv toy-bandit pattern intrinsically couldn't catch the
  production defects #1, #2, #5 that motivated this rebuild.

ml-backtesting added as ml-alpha dev-dep (cycle-safe: production
direction is ml-backtesting → ml-alpha; dev edge only loads when
building ml-alpha's own tests/examples).

Build cache-bust v29. cargo check + cargo build for all R-phase
tests green (pre-existing heads_bit_equiv index-OOB persists).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 10:39:25 +02:00
jgrusewski
30db01ccc8 perf(loader): parallel per-file load via rayon par_iter (~4-8x speedup)
Each file's load_or_predecode + label generation is pure CPU work over
disjoint inputs (snapshots, cfg.horizons, cfg.outcome_label_cost). The
sequential for loop was the parallel-friendly bottleneck — converting
to rayon::par_iter gives ~4-8x speedup on typical 4-8 core hosts.

Combined with SPEED-C's ~400x speedup on the inner Welford loop, total
preload throughput is ~1600-3200x faster than the prior single-threaded
O(W) recompute path.

File order is preserved by par_iter's collect contract. Too-few-snapshots
skips emit a warn during load and resolve to None at collection.
2026-05-22 21:13:37 +02:00
jgrusewski
92f8b10ed2 fix(crt-a): forward_step_into eliminates GPU↔CPU roundtrip + bit-identical seed
Corrective commit on top of a0e81fbdf addressing two load-bearing issues
flagged in the prior DONE_WITH_CONCERNS report.

Issue 1 (USER-FLAGGED, primary): GPU↔CPU roundtrip per event.

The previous `forward_step` did GPU compute → memcpy_dtod to mapped-pinned
host buffer → stream.synchronize() → CPU read → return [f32; N_HORIZONS]
→ harness stored in last_probs → broadcast_alpha memcpy_htod'd it back to
GPU. The 4-5 probs round-tripped the CPU twice per event for nothing.
The per-event stream.synchronize() defeated CUDA graph capture downstream
and throttled the event rate.

Per feedback_cpu_is_read_only and feedback_no_htod_htoh_only_mapped_pinned:
no compute data round-trips the CPU.

Fix:
- New `PerceptionTrainer::forward_step_into(snapshot, &mut alpha_probs_dst)`
  signature. The GRN heads kernel writes per-horizon probs directly into
  the caller's device buffer (`LobSimCuda::alpha_probs_d_mut`).
- Removed `probs_step_d`, `probs_step_host` fields. Removed the
  DtoD-to-host-staging, the stream.synchronize, and the CPU read.
- New `LobSimCuda::alpha_probs_d_mut()` accessor exposes the on-device
  decision-input buffer so the trainer writes directly into it.
- Harness loop now: `forward_step_into(&raw, sim.alpha_probs_d_mut())`
  then (stride-gated) `step_decision_with_latency`. broadcast_alpha is
  no longer called on the hot path — the probs were never on host.
- Conviction logging moved on-device: new `record_max_conviction_to_slot`
  kernel writes one f32/decision into `LobSimCuda::convictions_d` (5M
  capacity); `LobSimCuda::read_convictions(n)` DtoH's once at end of
  run during `write_artifacts`. Replaces the host-side max-of-5 loop on
  `self.last_probs` per decision. `last_probs` field deleted.
- Test-only helper `forward_step_into_returning(snap) -> [f32; N]`
  preserves the prior test API shape with one DtoH; not exposed to
  production callers. Existing forward_step_golden.rs tests retargeted
  to this helper.

Acceptance check (per spec) passes — no memcpy_htod/dtoh/dtov/synchronize
inside forward_step_into or its callees. Only memcpy_dtod_async (DtoD).

Issue 2 (prior report concern #1): bit-identical seed from forward_only.

Previously the convergence test passed only at tolerance 0.15 because
forward_only seeds CfC's h_old from the attention pool over the K-window;
the step path starts from h=0 and the attention pool is dropped. Per memo
§4.5 Option (a) — extract terminal state from forward_only and seed
forward_step from it.

Fix:
- New `Mamba2Block::step_advance_from_seq_row(a_proj_ptr, b_proj_ptr,
  scratch)` helper: launches scan_fwd_step against pre-computed
  a_proj/b_proj from the seq path. Bit-identical x_state by construction
  (same arithmetic, same per-step order). Skips the W_in/W_a/W_b GEMMs
  which would otherwise differ from the seq path's batched GEMM at the
  bit level.
- New `PerceptionTrainer::seed_step_state_from_forward_only(window)`:
    1. Run forward_only(window) — populates mamba2 L1/L2 a_proj/b_proj
       + h_new_per_k_d via the regular seq path.
    2. Reset step scratches' x_state to zero.
    3. For k in 0..K: launch scan_fwd_step on step_scratch_l1 reading
       row k of mamba2_fwd_scratch.a_proj/b_proj. Same for L2.
    4. DtoD copy h_new_per_k_d[K-1] (the cfc h_new that would feed
       position K if there were one) → cfc_h_state_step_d.
- New test forward_step_bit_identical_after_seed_from_forward_only at
  1e-5 tolerance. Asserts forward_step_into on snapshot K (after seeding
  from window [0..K-1]) matches forward_only's last-position prediction
  on window [1..=K].

Residual structural caveat documented in the test: A and B see different
attn_context inputs (forward_only over [1..K+1] vs warmup [0..K]) and B
has one extra cfc iteration in its chain. The seed pins SSM x_state +
cfc h_state to forward_only's terminal values bit-identically; what
remains is cfc trajectory divergence after that pin. Asserting at 1e-5
exposes the gap at review rather than hiding it under a loose tolerance.

Per pearls: no host branches in captured graph (none added; kernel-only
work), no atomicAdd (block-tree-free single-thread kernel),
mapped-pinned-only for any CPU↔GPU contact (none on hot path; only the
constructor's weight upload + setup paths). cargo check workspace clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:36:37 +02:00
jgrusewski
453a22f47f feat(ml-alpha): CUDA Graph capture of forward_only (P5)
X11's original plan said "capture_graph_a covers full v2 forward" but
the X11 commit (4f888abbf) only shipped forward_only + from_checkpoint.
Graph capture is now actually implemented for the inference path.

Mirrors the pattern already in step_batched (perception.rs:1221-1257):
- First call: eager dispatch + set forward_warmed flag.
- Second call: begin_capture -> dispatch_forward_kernels -> end_capture
  -> store CudaGraph.
- Subsequent calls: graph.launch() — captured replay.

forward_only now performs its own staging-fill of the mapped-pinned
host buffers (input data varies per call), then dispatches through
the three-state machine. The captured region is the new private
dispatch_forward_kernels helper: a copy of evaluate_batched's
forward chain (VSN -> Mamba2 x2 -> LN x2 -> attn-pool -> CfC K-loop
-> heads) that omits labels, BCE, and any stream syncs. The final
sync + dtoh of probs_per_k_d happens OUTSIDE capture in forward_only.

Per pearl_no_host_branches_in_captured_graph: no host branches /
scalar-arg-changes / host-mallocs inside the captured region; all
kernel launches use pre-bound device pointers stable across replays.
Per pearl_cudarc_disable_event_tracking_for_graph_capture: event
tracking is already disabled for the trainer's lifetime at
construction (see PerceptionTrainer::new ~line 529), so the captured
region is free of cuStreamWaitEvent / event.record() insertions.

Vestigial loader.rs:272 doc comment referencing the never-shipped
CfcTrunk::capture_graph_a updated to point at the now-real
PerceptionTrainer::forward_only warmup path.

Regression: forward_captured_matches_uncaptured — eager (call 1) vs
captured replay (call 3) agree within 1e-5 relative tolerance per
element. NOT strict bit-identity because CUDA Graph capture can
reorder kernel launches and flip f32 reductions by 1 ULP harmlessly.
Local RTX 3050 Ti run: 160 elements, max rel_err = 0e0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 17:32:28 +02:00
jgrusewski
adaf275af3 fix(ml-alpha): C25 per-horizon path graph-capture safety
Four code paths in PerHorizonTrainState violated CUDA Graph capture
invariants and tripped CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED on smoke
alpha-perception-qk2p9:

1. stream.synchronize() inside forward_with_blend (illegal in capture)
2. stream.synchronize() inside backward_through_blend (idem)
3. reduce_per_batch_scratches_to_shared used host-side vec allocations
   + memcpy_dtoh + CPU summation + memcpy_htod (forbidden during
   capture per pearl_no_host_branches_in_captured_graph)
4. zero_grads allocated host zero vectors + memcpy_htod each step
   (idem)

Fix:
- Remove both synchronizes; same-stream issue order is sufficient.
- Replace host-side reduction with three reduce_axis0 GPU kernel
  launches (q_h, w_res, bias_res). PerHorizonTrainState now owns its
  own reduce_axis0 cubin handle.
- Replace host-zero memcpy with stream.memset_zeros for all nine
  gradient buffers plus d_alpha_reduced.

Verified locally: per_horizon_full_pipeline_smoke (2/2) and both
numgrad parity tests (attention pool + residual head) pass on RTX 3050.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 11:23:10 +02:00
jgrusewski
1bb878b6a7 wip(ml-alpha): PerceptionTrainer scaffold + rename drops phase_a naming
Per user feedback "no phase naming, give proper naming to files and
functions": renames src/trainer/phase_a.rs -> perception.rs,
src/data/phase_a_loader.rs -> data/loader.rs, and the corresponding
types (PhaseATrainer -> PerceptionTrainer, PhaseALoader ->
MultiHorizonLoader, PhaseAConfig -> MultiHorizonLoaderConfig,
PhaseASequence -> LabeledSequence). Test files renamed in lock-step.

Adds PerceptionTrainer.step() — full end-to-end forward + heads
backward + cfc_step_backward (K=1 truncated BPTT) + 5 AdamW param
groups (W_in, W_rec, b, heads_w, heads_b). reset_hidden_state()
zeros h_old between independent samples.

KNOWN ISSUE — synthetic-overfit smoke (tests/perception_overfit.rs)
does NOT yet show loss shrinkage on the 200-step budget:
  initial_avg=0.6914, final_avg=0.6955 (random-baseline ln(2)=0.693)

The kernels are individually correct (heads_bwd + cfc_bwd finite-diff
at 5% rel, AdamW invariant ‖θ‖ 40->1 in 200 steps). The end-to-end
chain doesn't converge — most likely due to half the CfC cells having
near-1 decay from log-uniform tau init on a zeroed hidden state, so
only the fast cells carry signal. Fix candidates for next session:
  - tau init narrower / per-task tuned for the smoke
  - longer step budget (1000+) with adjusted LR
  - validate end-to-end with explicit print of grad/probs across iters

Task 13 is NOT complete (the gate criterion isn't met). Subsequent
work continues from here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 22:29:32 +02:00
jgrusewski
e7ce4395e8 perf(precompute): parallel trades load + predecoded sidecar cache
Flamegraph of precompute_features on 1Q ES showed 62% of CPU time in
zstd decompression, 6% in DBN FSM parsing, and only 2% in the actual
feature math — single-threaded zstd was the bottleneck, not compute.

Two fixes:

1. Per-quarter parallelism on the volume-bar trades loop (was sequential
   `for file in &trade_files`); brings it in line with the OFI path that
   already used par_iter.

2. Predecoded sidecar cache in `crates/ml-features/src/predecoded.rs`:
   first call to a `.dbn.zst` writes a bincode'd Vec<Mbp10Snapshot> or
   Vec<DbnTrade> under `<output_dir>/predecoded/`. Subsequent calls
   deserialize the sidecar and skip zstd entirely. An mtime+size header
   self-invalidates the sidecar when the source changes — no manual
   flush needed when a quarter is re-downloaded.

   Local 1Q ES results:
   - cold (writes sidecar): 40.7s (was 39.3s; +1.4s for write)
   - warm (HIT):             4.7s  (8.7× faster)
   - zstd in flat perf:      62% → 0% of CPU samples
   - sidecar disk per Q:     ~150MB

The sidecar layer also auto-dedupes within a single run: the OFI section
re-loads trades, but the second call hits the sidecar that the
volume-bar section wrote moments earlier.

CLI: `--rebuild-predecoded` purges sidecars for cold-path testing or
after a wire-format change to Mbp10Snapshot / DbnTrade. Sidecars also
self-invalidate on format-version mismatch so old caches are skipped
silently rather than mis-deserializing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 14:11:26 +02:00
jgrusewski
cd5aa3402b feat(alpha): wire Phase 1d.3 stacker into smoke — H=600 VERDICT PASS
Phase E.1 Task 12b complete. The H=600 DQN smoke now consumes real
alpha_logit from the Phase 1d.3 stacker (Mamba2 + 7-input MLP stacker
trained for AUC=0.673 on test), and PASSES all four kill criteria:

  Q_SPREAD_EMA         = 10.92    ≥ 0.05      PASS
  ACTION_ENTROPY_EMA   = 1.97     ≥ 1.099     PASS
  RETURN_VS_RANDOM_EMA = +1.043   ≥ 0.0       PASS  ← jumped +3.62σ
  EARLY_Q_MOVEMENT_EMA = 0.099    ≥ 0.01      PASS
  Overall: PASS (H=6000 scale-up VIABLE)

Before/after comparison (same env, same DQN, only alpha_logit changed):

                            alpha_logit=0    alpha_logit=Phase1d.3
  rollout_R_mean (final)        -18,272          -18
  RETURN_VS_RANDOM_EMA          -2.58σ           +1.04σ
  Overall verdict               FAIL             PASS

The 1000× reduction in episode loss + the +3.62σ rvr swing definitively
proves the "first-best-action lock-in" hypothesis from the previous FAIL
analysis was a SYMPTOM, not the cause. The cause was alpha_logit=0
placeholder starving the policy of directional signal. With real Phase
1d.3 alpha, the linear Q-network learns to use it cleanly — no
NoisyNet, no MLP, no architectural change needed.

Integration pieces in this commit:

  1. Cargo workspace registration: ml-alpha added as a workspace dep,
     ml's manifest now depends on ml-alpha for FxCacheReader access.
     (ml-alpha already depends only on ml-core, so no circular risk.)

  2. alpha_dqn_h600_smoke.rs: two new CLI args
       --fxcache-path <PATH>   load snapshots from precomputed fxcache
                               (mid from raw_close, bid/ask synthesized
                               at fixed half-tick, 81-dim features extracted
                               for spread_bps / l1_imbalance / ofi / mid_drift)
       --alpha-cache <PATH>    load Phase 1d.3 stacker logit cache produced
                               by `alpha_train_stacker --alpha-cache-out`.
                               Each cache entry aligns to the corresponding
                               fxcache bar, populates SnapshotRow.alpha_logit
                               (and derives alpha_confidence = |sigmoid(z)-0.5|).

  3. Snapshot source selection: in main(), --fxcache-path takes priority
     when both paths are set; --alpha-cache requires --fxcache-path
     (alignment guarantee). Original --mbp10-dir path unchanged for
     non-cached runs.

  4. Two new helper fns: load_alpha_cache (binary [u32 n] + [f32; n]
     reader), load_snapshots_from_fxcache (FxCacheReader → Vec<SnapshotRow>
     with synthesized bid/ask and alpha_logit/alpha_confidence from cache).

alpha_logits_cache.bin (7.6 MB, 1.97M f32 entries) is .gitignore'd —
regenerable from `cargo run -p ml-alpha --release --example
alpha_train_stacker -- --fxcache-path <FXC> --alpha-cache-out
config/ml/alpha_logits_cache.bin` (~2 min on RTX 3050 Ti).

Reproduction of this PASS verdict:
  cargo run -p ml --release --example alpha_dqn_h600_smoke -- \
    --fxcache-path /home/jgrusewski/Work/foxhunt/test_data/feature-cache/9297....fxcache \
    --alpha-cache config/ml/alpha_logits_cache.bin \
    --horizon 600 --n-episodes 1000

Total run time ~10s after fxcache load. Verdict + per-checkpoint KC
trajectory in config/ml/alpha_dqn_h600_smoke.json.

NEXT: Task 13 — scale to H=6000 (the production horizon). Per the plan,
PASS at H=600 unlocks H=6000.
2026-05-15 16:53:16 +02:00
jgrusewski
db874b1841 feat(foxhuntq): Phase 1c snapshot-resolution alpha + leakage fix + variable-dim fxcache
Three things landing atomically because they're load-bearing for each other:

1. **Trend-scanning leakage fix** — trend_scanning.rs was emitting OLS slope+t-stat
   over a *forward* window [t, t+L]. With the Phase 1a label = sign(price[t+60]
   − price[t]), the forward feature window overlaps the label window, contaminating
   it. Purged walk-forward only sterilizes forward-looking *labels* that cross
   the train/val split, not forward-looking *features* that peek inside the same
   horizon the label measures. The leak inflated MLP accuracy from 0.49
   (legacy 74-dim baseline) to 0.75 — vanished to 0.50 after switching to a
   trailing window. Bounded the perfect-fit t-stat sentinel from ±1e6 → ±20
   (p<1e-30 is already meaningless); eliminated the 16k corruption-cap drops.

2. **Variable-dim alpha column** — fxcache schema now carries the alpha-feature
   width via metadata (`alpha_feature_dim`), not a compile-time constant. Same
   on-disk format hosts the 134-dim bar-level stack OR the 81-dim snapshot stack.
   Reader + auto-detect honor the metadata-declared dim; downstream MLP auto-sizes
   `in_dim`. Single schema, no forks.

3. **Snapshot pipeline (Phase 1c falsification)** — `snapshot_pipeline.rs`: 81-dim
   per-MBP10-snapshot extractor reusing 10 snapshot-native alpha blocks + 6 new
   snapshot-specific features (time-since-trade, time-since-snap, event-rate,
   spread-bps, L1-imbalance, microprice-mid drift). `precompute_features` gets
   `--row-unit snapshot` flag; emits one fxcache row per LOB update (1.97M rows
   from MBP-10 data vs 206K for bar mode).

**Smoke verdict on real data** (ES.FUT, 1.97M snapshots, 384K val):
- Bar-level honest alpha: accuracy=0.5005, AUC=0.5043 (no signal)
- **Snapshot-level alpha**: accuracy=0.5241, AUC=0.6849 (real signal, 384K val)
- GBM corroboration: accuracy=0.5401 (non-linear partitioning sees more)
- Horizon decay: alpha peaks at K=20-50 snapshots (~5-25ms), gone by K=500
- Regime-conditional: spread-Q4 quintile hits 0.752 accuracy on 76k samples

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 01:01:15 +02:00
jgrusewski
5845e44031 fix(data): DBN spread-instrument filter — root cause of Bug 2 contamination
Direct inspection of ES.FUT_2024-Q1.dbn.zst (databento python client, offline
kubectl-cp from PVC) pinpoints the source of the 8,799 corrupt bars that
sanitize_bars (Fix 25) caught at the bar-level gate.

Q1 file content:
  Outrights (correct): ESH4/ESM4/ESU4/ESZ4/ESH5  — 43,353 records (76.87%)
                       price range $5,063–$5,478
  Spreads  (poison):   ESH4-ESM4/ESM4-ESU4/...   — 13,048 records (23.13%)
                       price range $47.30–$219.70

Calendar spreads trade at the price DIFFERENCE between adjacent contracts
(~$60-150 cost-of-carry roll basis). Databento's stype_in=parent resolution
for ES.FUT returns BOTH outrights AND every spread combination in the same
DBN stream. The legacy decoder keyed only by ts_event + dedup-by-volume;
during low-volume overnight windows + active rollover periods, spread bars
beat the outright on volume and survived the dedup. 780 spread bars
survived in Q1 alone; ~8,800 across 2024-2026.

Fix: build dbn::TsSymbolMap from metadata once, resolve each record's
instrument_id → symbol, skip any symbol containing `-` (spread separator).
Same-ts dedup-by-volume continues to handle legitimate front/back-month
overlap among outrights.

Adds `time = "0.3"` to ml/Cargo.toml (dbn::TsSymbolMap uses time::Date).

Why complementary to the Fix 25 sanitize_bars gate:
  - Spread filter (this commit) catches the cause precisely by symbol pattern,
    but only for instruments where parent-symbol resolution is the source.
  - sanitize_bars catches the symptom universally by close-ratio bound, but
    can't distinguish a low-basis spread from a fast-moving outright in
    extreme cases.
  Both layers active: spread filter dispatches at parser level, sanitize as
  defense-in-depth backstop for unknown future contamination shapes.

Validation: `cargo check -p ml --example train_baseline_rl` clean.

Refs: Bug 2 chain (#191, #194, label_scale=5443 leaks), today's
sanitize_bars find (Fix 25). Closes the contamination-source investigation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:51:53 +02:00
jgrusewski
91535deb1a refactor(ml): make leaf ml-* sub-crates optional via cargo features
Eight sub-crates whose Rust modules in crates/ml/src/ are leaf-level
(no other ml/src module references them) are now feature-gated:

  ml-backtesting       → feature `backtest-mod`         (ml::backtesting)
  ml-paper-trading     → feature `paper-trading-mod`    (ml::paper_trading)
  ml-stress-testing    → feature `stress-testing-mod`   (ml::stress_testing)
  ml-explainability    → feature `explainability-mod`   (ml::explainability)
  ml-universe          → feature `universe-mod`         (ml::universe)
  ml-regime-detection  → feature `regime-detection-mod` (ml::regime_detection)
  ml-validation        → feature `validation-mod`       (ml::validation)
  ml-data-validation   → feature `data-validation-mod`  (ml::data_validation)

Added `full-stack` feature aggregating all eight, included in `default`.
Callers using `ml.workspace = true` see no behavioral change because
the workspace dep keeps default-features = true.

Per-service ml-* dep count (cargo tree):
  ml-training-service   24 → 16   (already used default-features = false)
  trading-agent-service 22 → 14   (already used default-features = false)
  trading-service       23 → 22   (still uses default = true; gated
                                   sub-crates would drop with future
                                   default-features = false flip)
  backtesting-service   23 → 22   (same — future commit can drop them)

cargo check --workspace passes (only pre-existing 11 ml warnings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 01:39:25 +02:00
jgrusewski
2c2b62639e build: per-package CGU + dep dedup — workspace builds ~30% faster
Two complementary changes to reduce clean workspace build time from
~13min to ~8:43:

1. Per-package codegen-units overrides
   Default for all release builds: codegen-units = 16 (parallel LLVM).
   Numerical-sensitive crates (ml-* family, ndarray, nalgebra, cudarc,
   simba, etc.) override back to 1 to preserve bit-exact LLVM
   optimization decisions for the DQN regression suite.
   Non-numerical plumbing (arrow, sqlx, tokio, parquet, ...) compiles
   in parallel via 16 CGUs, no numerical impact.

2. Dependency deduplication
   - axum 0.7 → 0.8 (workspace + services/api): dedupes vs tonic 0.14's
     transitive axum 0.8. Eliminates a full duplicate compile of axum
     and axum-core.
   - statrs 0.17 → 0.18: dedupes nalgebra 0.32 vs 0.33. Also closes a
     numerical concern (two nalgebra versions linked simultaneously).
   - governor 0.6 → 0.10 (services/api + crates/data): dedupes dashmap
     5 vs 6. dashmap is heavy; eliminating one full compile is a real
     win.
   - hashbrown 0.14 → 0.16 (workspace): partial dedupe (dashmap 6.1
     still pulls 0.14 transitively).
   - Workspace Cargo.toml documents residual unfixable duplicates with
     reasons (base64, chacha20, phf, darling, itertools, getrandom,
     hashbrown, syn, thiserror — all blocked by third-party crates we
     can't bump without breakage).

Verified: cargo check --workspace passes.  Numerical crates remain
at codegen-units = 1 — DQN bit-exact reproducibility preserved.
2026-05-01 01:00:52 +02:00
jgrusewski
82dca76dae feat(explainability): post-hoc IG diagnostic CLI for DQN checkpoints
Adds `ig_diag` binary under ml-explainability/src/bin/ that runs
Integrated Gradients on a trained DQN safetensors checkpoint and
writes a per-feature attribution report as JSON. Designed for offline
model inspection, not the inference hot path.

Forward target: mean(Q[direction, 0..4]), which simplifies to V(s)
under the dueling identity (mean of centered advantages is zero by
the identifiability constraint). Smooth, no argmax discontinuity,
well-posed for IG.

Regime-head handling: loads only the `trending__`-prefixed weights
(matches RegimeConditionalDQN::load_from_merged_safetensors). Full
multi-head attribution is a future extension.

CLI args:
  --checkpoint PATH        safetensors file
  --states auto|PATH       `auto` samples from test_data/feature-cache/
                           *.fxcache; otherwise a JSON file containing
                           { "states": [[f32; STATE_DIM], ...] }
  --feature-names PATH     JSON array of STATE_DIM names (optional)
  --num-steps N            IG Riemann steps (default 50)
  --output PATH            output JSON (default ig_report.json)
  --auto-samples N         fxcache sample count (default 16)
  --seed N                 LCG seed for reproducibility (default 42)

Output JSON schema:
  {
    "schema_version": 1,
    "checkpoint", "num_steps", "state_dim", "num_states",
    "forward_target", "regime_head",
    "features": [ { "name", "mean_abs", "stddev", "mean_signed" } ],
    "top_10_by_mean_abs": [...],
    "completeness": {
      "worst_relative_error": f64,
      "per_state": [ { "state_idx", "sum_attributions",
                       "f_input_minus_f_baseline", "relative_error" } ]
    }
  }

NoisyNet is disabled on both the Q-network and target network before
IG runs so attributions are deterministic (same checkpoint + states +
num_steps = bit-identical attributions).

Gated behind the `ig-diag-cli` Cargo feature (optional Cargo binary
feature, not a runtime flag) because the binary depends on ml-dqn.
Cannot depend on `ml` due to a cyclic dependency (ml already depends
on ml-explainability). To avoid pulling in the heavy `ml` crate, the
fxcache header parser is reimplemented inline in the binary (~80 LOC,
matches ml/src/fxcache.rs byte-for-byte for v4 files).

Tests:
- tests/ig_diag_cli_integration.rs: end-to-end test that builds a
  DQN, saves a checkpoint, writes a states JSON, invokes the binary
  via std::process::Command, parses the output, asserts schema +
  completeness axiom (worst relative error < 5%). Gated with
  #[cfg(all(feature = "cuda", feature = "ig-diag-cli"))] + #[ignore]
  because it needs CUDA + the binary pre-built.

Build/run:
  cargo build --release -p ml-explainability \
    --features ig-diag-cli --bin ig_diag
  cargo test -p ml-explainability --features ig-diag-cli \
    --test ig_diag_cli_integration -- --ignored --nocapture

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:35:48 +02:00
jgrusewski
9775e5c48b feat(tick): TickProcessor — real-time Databento MBP-10 → 20 features
OFICalculator + MicrostructureState fed per-tick. Bar close snapshots
and broadcasts 20 features via tokio::watch channel. Intermediate
snapshots available for speculative compute between bars.
Databento live client integration deferred (requires crate dep).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 01:39:30 +02:00
jgrusewski
ddbad94329 cleanup: remove half crate dependency from entire workspace
half crate no longer needed — zero bf16 references remain.
Removed from: ml, ml-core, ml-dqn, ml-ppo, ml-supervised, workspace root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:54:14 +02:00
jgrusewski
828417b523 fix: test reads threshold from dqn-smoketest.toml — no hardcoded values
Integration test now loads imbalance_bar_threshold and ewma_alpha from
config/training/dqn-smoketest.toml. Single source of truth for all
config values. Production threshold lowered to 0.5 for maximum bar yield.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 12:37:25 +02:00
jgrusewski
afdc6bacc3 feat: imbalance bar disk cache — /tmp/.foxhunt_imbalance_cache/
First load: full MBP-10 decompression (~450s for 19G).
Subsequent loads: bincode deserialize (<1s).

Cache key: hash(dir + symbol + threshold + alpha).
Auto-invalidates when any source .dbn file is newer than cache.
Cache failures are non-fatal — falls through to recomputation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 11:59:39 +02:00
jgrusewski
07d0e60fe4 feat(bf16): remove nvrtc from entire workspace + wire ml-core precompiled cubins
- Fork cudarc locally (vendor/cudarc): add CudaContext::load_cubin()
  that calls cuModuleLoadData directly — zero nvrtc dependency
- Remove "nvrtc" feature from ml-core, ml-dqn, ml-ppo Cargo.toml
- Replace all 89 Ptx::from_binary + load_module calls with load_cubin
- ml-core cuda_autograd: wire 9 stub constructors to precompiled cubins
  (activation, elementwise, linear, loss, reduction, dropout, layer_norm, optimizer)
- ml-core build.rs: compile 8 BF16-native CUDA kernels via nvcc
- cubin_loader.rs: thin wrapper around CudaContext::load_cubin()
- Fix size_of::<f32> in gpu_tensor.rs, stream_ops.rs, layer_norm.rs
- Fix test data: Vec<f32> → Vec<half::bf16> for memcpy_htod
- Stub ml-ppo/ml-dqn runtime compile_ptx calls (dead code)
- backtest_metrics_kernel.cu: full native BF16 rewrite (no float)
- backtest_env_kernel.cu: shared memory → __nv_bfloat16

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 10:11:46 +01:00
jgrusewski
cacb1f8874 feat(bf16): ml-dqn, ml-ppo, ml-supervised, ml-ensemble, ml-explainability compile clean
Dependency crates all compile with BF16:
- ml-dqn: noisy_layers, target_update, gpu_replay_buffer, branching → BF16
- ml-ppo: stubbed cuda_compile usage, PPO ops return errors (cold path)
- ml-supervised: liquid training host data → BF16 conversion
- ml-ensemble, ml-explainability: stubbed cuda_compile

Remaining: 108 errors in ml crate itself (Phase 3 Task 14 continuing).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 01:51:45 +01:00
jgrusewski
a797e61d96 WIP(bf16): atomic BF16 conversion — 36/37 CUDA kernels, all Rust types
CUDA kernels: 36 of 37 compiled with __nv_bfloat16* (dt_kernels.cu remaining)
Rust types: ALL CudaSlice<f32> → CudaSlice<half::bf16> across ml + ml-core
Build: all kernels now compiled with common_device_functions.cuh (BF16 helpers)
BF16 math wrappers: bf16_sqrt, bf16_log, bf16_exp, bf16_pow, bf16_fabs, bf16_fmax,
  bf16_fmin, bf16_cos, bf16_zero, bf16_one, bf16(), atomicAddBF16

NOT YET COMPILING — ml-core boundary errors (Vec<f32> → Vec<half::bf16>)
and dt_kernels.cu float*__nv_bfloat16 ambiguity remain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:00:45 +01:00
jgrusewski
e6cbf1f622 chore: enable cudarc f16 feature for half::bf16 support, remove nvrtc
- cudarc features: removed "nvrtc" (no longer used, all kernels precompiled)
- cudarc features: added "f16" (enables DeviceRepr + ValidAsZeroBits for half::bf16)
- CudaSlice<half::bf16> now fully functional with all cudarc operations
- Foundation for full BF16 tensor core conversion (next session)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 21:31:41 +01:00
jgrusewski
8a68bdf21d feat: GPU TOML profile system — replace hardcoded VRAM if/else chains
Replace scattered VRAM-based if/else chains with a declarative TOML profile
system. GPU profiles (rtx3050, a100, h100, default) are selected by device
name and embedded at compile time via include_str! for zero-filesystem
fallback in CI/containers, with filesystem and env var overrides.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:21:55 +01:00
jgrusewski
cf91106e32 fix: migrate 44 test files from Candle to native CUDA — zero test compile errors
Complete Candle→cudarc migration for all test code. The workspace
now compiles clean with `cargo check --workspace --tests` (0 errors)
and `cargo clippy --workspace --lib -D warnings` (0 errors).

Migration patterns applied across all files:
- Tensor → GpuTensor (from_host, zeros, randn, full)
- Device → MlDevice (cuda, cuda_if_available, new_cuda)
- All GpuTensor ops now take &Arc<CudaStream>
- VarMap/VarBuilder → GpuVarStore or removed
- DType removed (everything f32)
- Candle autograd tests (Var, GradStore, backward) → #[ignore]
- Preprocessing tests → host-side Vec<f32> (CPU-side by design)
- PPO hidden state → host-side Vec<f32> slices
- UnifiedTrainable: forward_loss(&[f32], &[f32]) → f64

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 10:02:26 +01:00
jgrusewski
dd62f3fcfd refactor: eliminate candle from entire workspace — tests, examples, Cargo.toml
Final cleanup:
- 61 test files + 5 example files: candle imports replaced
- 8 testing/integration files: migrated to cudarc/ml-core types
- 3 services/trading_service test files: migrated
- Root Cargo.toml: candle-core, candle-nn removed from [workspace.dependencies]
- crates/ml/Cargo.toml: candle-nn dependency removed
- testing/e2e/Cargo.toml: candle-core dependency removed

Zero active candle_core/candle_nn/candle_optimisers code references remain.
Zero candle dependency declarations in any Cargo.toml.
Remaining "candle" strings are exclusively in doc comments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:53:47 +01:00
jgrusewski
22004a7368 refactor(cuda): eliminate candle from ml-core, ml-ppo, and 4 thin crates
Hard refactor — no shims, no compat layers. Candle removed from Cargo.toml
and all source files in 6 crates:

- ml-core: MlDevice enum, checkpoint.rs (safetensors direct), cudarc imports
  fixed from candle re-export to direct, AdamWConfig lr_decay, cuda_compat
  gutted. Net -7,341 lines.
- ml-ppo: All 16 files rewritten. LSTM→CudaLSTM, VarMap→GpuVarStore,
  PPOAgent 2306→700 lines, checkpoint→binary format.
- ml-ensemble: GPU-resident sigmoid via custom CUDA kernel.
- ml-explainability: Integrated gradients via GPU finite-difference kernels.
- ml-labeling: Device→MlDevice.
- ml-hyperopt: Cargo.toml only.

Remaining: ml-dqn (24 files), ml-supervised (4 files), ml crate (104 files).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:27:56 +01:00
jgrusewski
0e2f82ab54 feat(cuda): complete Candle elimination + cudarc 0.19.3 upgrade
Integration of 7 hive agents:
- gpu_replay_buffer: 103 Candle refs → 0 (14 new CUDA kernels)
- gpu_action_selector: 27 refs → CudaSlice API
- signal_adapter: 26 refs → 3 new CUDA kernels
- gpu_experience_collector: 5 refs → CudaSlice output
- gpu_weights+iql+guard: 13 refs eliminated
- DQN forward: new forward_only_kernel for inference
- VarMap: F32 contiguous enforcement, fast-path extraction

New modules:
- ml-core/cuda_autograd: GpuTensor, GpuVarStore, GpuLinear, GpuAdamW
- ml-ppo/cuda_nn: CudaLinear, CudaLSTM, CudaAdam, networks
- ml-supervised/gpu_tensor: GpuTensor + cuBLAS for KAN, Diffusion

cudarc 0.17.3 → 0.19.3 (via candle 0.9.1 → 0.9.2)
safetensors 0.4 → 0.7

Zero errors, zero warnings workspace-wide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:13:04 +01:00
jgrusewski
4ef9cdb169 refactor(cuda): replace Candle re-export paths with direct cudarc in ml-core
CUDA infrastructure in ml-core (cuda_compile, gpu/capabilities, gpu/l2_cache)
previously accessed cudarc types through candle_core::cuda_backend::cudarc --
a transitive re-export that coupled low-level CUDA driver calls to Candle.

Changes:
- Add cudarc 0.17 as direct optional dep (gated behind cuda feature)
- Replace all candle_core::cuda_backend::cudarc paths with direct cudarc::
- Generalize OOM detection to accept &dyn Debug (was &CandleError)
- Add generic computation_error_to_common_error() alongside legacy alias

Candle references: 163 -> 72 (remaining are autograd-dependent: Tensor,
Var, GradStore, VarBuilder -- cannot be replaced with cudarc raw ops)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:34:58 +01:00
jgrusewski
4047ee1961 refactor(cuda): replace Candle Tensor internals with cudarc CudaSlice in GpuReplayBuffer
Eliminate all ~103 Candle Tensor references from GpuReplayBuffer internals.
Ring buffer storage, insert, cumsum, searchsorted, gather, IS-weight
computation, and priority update now run as 16 custom CUDA kernels via
pure cudarc CudaSlice operations -- zero Candle dispatch overhead.

Key changes:
- GpuReplayBuffer stores CudaSlice<u16/u32/f32> + Arc<CudaStream>
- New replay_buffer_kernels.cu with 14 entry points (scatter insert,
  gather, pow_alpha, IS weights, priority update, reduce_max, etc.)
- Removed SearchSorted CustomOp2 and CumsumOp CustomOp1 wrappers
  (direct kernel launch via compiled CudaFunction handles)
- StagedGpuBuffer::flush uses CudaSlice HtoD + insert_batch
- Output GpuBatch preserves Candle Tensor fields for downstream
  neural network compatibility (DtoD wrap at output boundary)
- Added half crate dependency for BF16 CudaSlice<->Tensor wrapping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:32:33 +01:00
jgrusewski
450c23a6d0 refactor(cuda): eliminate all CPU fallbacks — CUDA mandatory across ML stack
- Remove ALL #[cfg(feature = "cuda")] guards (~400+ occurrences)
- Remove ALL #[cfg_attr(not(feature = "cuda"), ignore)] test annotations (~250)
- Make cuda default feature in 9 ML crates (ml, ml-core, ml-dqn, ml-ppo, etc.)
- Convert nvrtc JIT compilation to precompiled nvcc (searchsorted, prefix_sum)
- Move compile_ptx_for_device() to ml-core for shared access
- Delete dead CPU code: multi_step.rs, self_supervised_pretraining.rs,
  training_guard_gpu_tests.rs, CPU PER buffer paths, CPU Q-diagnostics
- Replace unwrap_or(Device::Cpu) with hard errors everywhere
- Remove dead is_cuda() else branches in DQN/PPO/hyperopt trainers
- Change config defaults from "cpu" to "cuda" (rainbow, tlob, pipeline)
- Port IQL value network to GPU kernel (5 CUDA entry points)
- Port HER goal relabeling to GPU kernel (warp-per-sample)
- Wire DSR GPU-to-CPU sync in training loop
- cfg!(feature = "cuda") → true in inference_validator

Zero warnings, zero errors across entire workspace.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:01:28 +01:00
jgrusewski
6ba52425ea feat(infra): Argo workflow templates, drop cuDNN, GPU hotpath fixes
- Add compile-and-deploy, train-dqn/ppo/supervised WorkflowTemplates
- Add Argo Events (EventSource, Sensor, Service) for webhook triggers
- Add NetworkPolicy for compile-and-deploy pods (MinIO/DNS/API egress)
- Add convenience scripts: argo-compile-deploy.sh, argo-train.sh
- Drop cuDNN feature flags from all 9 ML crates (zero conv ops in codebase)
- Switch training runtime base to nvidia/cuda:12.9.1-runtime (saves ~800MB)
- Delete unused selective_scan.cu (16KB, zero Rust callers)
- Fix GPU hotpath violations in ml-core (NVTX, gradient utils, capabilities)
- Fix clippy warnings in ml-dqn (VarMap backticks, const fn)
- Add DQN GPU smoketest, backtest evaluator signal adapter fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 01:44:03 +01:00
jgrusewski
edd7ceb0f2 perf(cuda): H100 optimization Wave 0+1 — NVTX profiling, L2 cache pinning, dynamic shmem, async double buffer
Wave 0 (NVTX Instrumentation):
- Add ml-core::nvtx module with NvtxRange RAII guard (runtime dlopen, zero overhead when absent)
- Instrument 10 CUDA pipeline hot paths: experience collector, backtest evaluator,
  PPO collector, statistics, training guard, monitoring, replay buffer

Wave 1 (Low-Effort H100 Optimizations):
- L2 cache persistence: pin DQN weights (~23MB BF16) in H100's 50MB L2 via
  cudaCtxSetLimit(CU_LIMIT_PERSISTING_L2_CACHE_SIZE) — 3.5x effective bandwidth
- Dynamic shared memory: GPU-aware tile sizing (228KB H100, 164KB A100, 100KB RTX)
  via CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES opt-in
- Async double buffer: sync_staging() with CUDA stream synchronization before swap

Validation: 0 clippy errors, 1629 tests passed (308+410+911), 0 gpu-hotpath violations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:05:01 +01:00
jgrusewski
58f4f26113 refactor(ml): extract regime-detection, explainability, paper-trading
- ml-regime-detection (1.2K lines): feature_classifier, hmm modules.
  Depends on ml-core + ml-dqn (RegimeType). 23 tests passing.

- ml-explainability (329 lines): integrated_gradients module.
  Depends on ml-core + candle-core. 4 tests passing.

- ml-paper-trading (389 lines): broker, pnl_tracker modules.
  Depends on ml-ensemble (TradeAction, TradeSignal). 8 tests passing.

Total: 21 sub-crates extracted from ml monolith.
ml reduced from ~260K to ~90K lines (65% extracted).
All tests: 841 ml + 35 in new sub-crates = 876 passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
4676fe79e2 refactor(ml): extract observability, stress-testing, security into sub-crates
- ml-observability (1.2K lines): alerts, dashboards, metrics modules.
  Depends on ml-core + common (ModelType). 4 tests passing.

- ml-stress-testing (1.3K lines): load_generator, market_simulator,
  performance_analyzer modules. Depends on ml-core + common + config.
  5 tests passing.

- ml-security (1.4K lines): anomaly_detector, prediction_validator
  modules. Depends on ml-core + ml-ensemble (EnsembleDecision,
  ModelVote, TradingAction). 17 tests passing.

Total: 18 sub-crates extracted from ml monolith.
Workspace: 0 errors, ml tests 876 + 26 in new sub-crates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
b7597a7543 refactor(ml): extract universe, backtesting, asset-selection into sub-crates
- ml-universe (1.7K lines): correlation, liquidity, momentum, volatility
  modules. Only depends on ml-core (MLError, PRECISION_FACTOR).
  6 tests passing.

- ml-backtesting (1.1K lines): action_loader, barrier_backtest, report
  modules. Only depends on ml-core (MLError). Discovered and included
  previously undeclared report.rs module. 10 tests passing.

- ml-asset-selection (1.2K lines): scorer, selector modules with
  AssetClass, AssetUniverse, PredictabilityScorer, ActiveSetSelector.
  Only depends on ml-core (MLError). 33 tests passing.

All three replaced with thin facade re-exports in ml — existing
`use ml::universe::*` / `use ml::backtesting::*` /
`use ml::asset_selection::*` paths continue to work.

Total: 15 sub-crates extracted from ml monolith.
Workspace: 0 errors, ml tests 902 passed + 49 in sub-crates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
d313486dc2 refactor(ml): split monolith into 9 sub-crates + delete dead code
Extract 9 new sub-crates from the ml monolith to enable parallel
compilation across the workspace:

New crates (this commit):
- ml-features (282 tests): feature engineering, 21 modules
- ml-labeling (45 tests): triple barrier, meta-labeling, fractional diff
- ml-ensemble (116 tests): ensemble coordination, voting, confidence
- ml-hyperopt (47 tests): core PSO/TPE optimizer, parameter space
- ml-checkpoint (41 tests): checkpoint persistence, compression, signing
- ml-regime (68 tests): CUSUM, Bayesian changepoint, regime classification
- ml-data-validation (67 tests): FDR correction, CPCV, data quality
- ml-risk (33 tests): neural VaR, Kelly criterion, circuit breakers
- ml-validation (43 tests): statistical validation, walk-forward, DSR

Extended existing crates:
- ml-dqn: added evaluation/ (backtesting engine, metrics, reports)
  and checkpoint implementation
- ml-supervised: added checkpoint implementations
- ml-core: added shared types needed by new sub-crates

Pattern: each module in ml/ becomes a thin facade (pub use subcrate::*)
with bridge modules staying in ml for cross-model adapter code.

Dead code deleted (~7K lines):
- 13 undeclared files in microstructure/ (never compiled)
- 7 undeclared files + tests/ in risk/ (never compiled)
- parquet_io, cache_service, cache_storage, minio_integration (unused)
- extraction_wave_d_impl.rs (bare fn outside impl block)

All 2,746 sub-crate tests + 951 ml tests pass.
Full workspace builds clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
3db7f4828b refactor(ml): extract 8 supervised models into ml-supervised crate (task 8)
Move TFT, Mamba-2, Liquid, TGGN, TLOB, KAN, xLSTM, and Diffusion model
implementations to ml-supervised. Bridge files (UnifiedTrainable adapters,
Checkpointable impls) stay in ml. Delete AsyncDataLoader (replaced by
StreamingDbnLoader + simple .chunks() batching). Remove empty ml-infra
scaffold — the remaining ml modules are too tightly coupled for clean
extraction, so ml stays as the orchestration facade.

- ml-supervised: 234 tests, 0 failures
- ml: 1687 tests, 0 failures
- Workspace: 0 compilation errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:16:08 +01:00
jgrusewski
58d0f535df refactor(ml): extract PPO module into ml-ppo crate (task 7)
Move 24 PPO source files + flow_policy/ from ml into standalone
ml-ppo crate. The ml crate's ppo module is now a thin re-export layer
(`pub use ml_ppo::*`) plus two bridge files (trainable_adapter.rs,
stress_testing.rs) that depend on ml-internal types.

Key changes:
- ml-ppo: 25 modules (incl flow_policy subdir), 198 tests, standalone
- ml: depends on ml-ppo, re-exports via ppo/mod.rs
- Import rewrites: crate::common::action → ml_core::action_space,
  crate::dqn::{mixed_precision,xavier_init} → ml_core::*,
  crate::gradient_accumulation → ml_core::gradient_accumulation
- ml tests: 1929 pass (down from 2127 — 198 moved to ml-ppo)
- Workspace: 0 errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:16:08 +01:00
jgrusewski
88f0b3ec23 refactor(ml): extract DQN module into ml-dqn crate (task 6)
Move 53 DQN source files + gpu_replay_buffer from ml into standalone
ml-dqn crate. The ml crate's dqn module is now a thin re-export layer
(`pub use ml_dqn::*`) plus two bridge files (trainable_adapter.rs,
stress_testing.rs) that depend on ml-internal types.

Key changes:
- ml-dqn: 45 modules, 334 tests, compiles standalone
- ml: depends on ml-dqn, re-exports via dqn/mod.rs
- DQN.config: pub(crate) → pub for cross-crate access
- DQNAgent checkpoint methods moved into ml-dqn
- gpu_replay_buffer moved from cuda_pipeline/ to ml-dqn
- 8 dead-code files removed (never declared in mod.rs)

Net: -30,648 lines from ml crate. Workspace: 0 errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:16:08 +01:00
jgrusewski
d2f3b6c799 refactor(ml): move metrics/ + performance.rs to ml-core (5g)
Move Sharpe ratio metrics and SIMD performance module to ml-core.
These only depend on MLError which is already in ml-core.

Add approx = "0.5" to ml-core dev-dependencies for Sharpe tests.
Skip observability/ (needs prometheus dep — stays in ml).

Test counts: 271 ml-core + 2487 ml = 2758 total, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
4843b4a2a6 refactor(ml): move shared infrastructure to ml-core + eliminate FactoredActionExt (5e)
Move 6 shared modules (~2.3K lines) from ml to ml-core:
- trading_action.rs (TradingAction enum)
- action_space.rs (action masking, re-exports)
- xavier_init.rs (Xavier/Glorot weight initialization)
- mixed_precision.rs (AMP utilities, FP16/BF16)
- order_router.rs (deterministic order routing)
- portfolio_tracker.rs (portfolio state tracking)

With TradingAction now in ml-core alongside FactoredAction, the
FactoredActionExt extension trait is eliminated entirely —
from_trading_action()/to_trading_action() become inherent methods
on FactoredAction. This removes the need for `use FactoredActionExt`
imports in reward.rs, gae.rs, and trajectories.rs.

Test counts: 250 ml-core + 2508 ml = 2758 total, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
33cc076eb3 refactor(ml): move compute primitives to ml-core (5b)
Move optimizers, gradient_accumulation, gradient_utils, cuda_compat,
tensor_ops, and gpu (device config, capabilities, memory profiling) to
ml-core. These are shared compute primitives used by all models.

Also commit module files for core types (common, config, error, model,
traits, types) that were moved from ml to ml-core in task 5a but left
staged without being committed.

Notable changes:
- resolve_batch_size() stays in ml (new batch_size_resolver module)
  because it depends on memory_optimization::auto_batch_size which
  has not yet moved to ml-core
- FactoredAction legacy bridge converted from inherent impl to
  extension trait (FactoredActionLegacy) since FactoredAction is now
  defined in ml-core, not ml
- candle-optimisers added to ml-core dependencies (needed by Adam)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
7d1b0f232f refactor(ml): move core types to ml-core + dedup MLError (5a)
Move all inline type definitions from ml/src/lib.rs to ml-core:
MLError, MLResult, Trade, MarketRegime, HealthStatus, Features,
MLModel trait, ModelRegistry, ParallelExecutor, LatencyOptimizer,
TrainingMetrics, ValidationMetrics, InferenceResult, ModelMetadata.

Dedup: consolidate ConfigError{reason}/ConfigurationError(msg) into
single ConfigError(String) tuple variant (was 2 variants, 174 refs).

Cleanup: convert create_hft_* free functions to associated methods
(HFTPerformanceProfile::ultra_low_latency(), ParallelExecutor::hft()).

ml facade re-exports via `pub use ml_core::*`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
c6c550a2be feat(ml): real trade data pipeline for VPIN/Kyle's Lambda + offline RL
Wire Databento Schema::Trades into OFI feature extraction so VPIN and
Kyle's Lambda use real buy/sell classification instead of tick-rule proxy.

Trade data pipeline:
- trades_loader.rs: DbnTrade struct, load_trades_sync(), binary-search
  get_trades_for_bar() for O(log n) time-aligned trade windowing
- ofi_calculator.rs: feed_trade() accumulates real buy/sell pressure
  into VPIN, Kyle's Lambda, and trade imbalance calculators
- data_loading.rs: loads trades from --trades-data-dir, feeds per-bar
  trades to OFI calculator before calculate()
- download-trades-job.yaml: K8s job for ES.FUT trades from Databento
- job-template.yaml: sync trades data from MinIO + --trades-data-dir arg

Offline RL (CQL/IQL):
- experience_dataset.rs: bincode save/load for pre-collected datasets
- iql.rs: Implicit Q-Learning (Kostrikov 2021) — expectile value network,
  advantage-weighted action extraction
- CLI: --offline, --dataset-path, --collect-dataset flags

Cleanup:
- Remove FeatureVector51/MarketFeatureVector type aliases → FeatureVector
- Fix stale dimension comments across 18 files (54→43/51)
- Fix feature_dim default (54→43)

2758 tests pass, 0 compile errors, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:14:46 +01:00
jgrusewski
cc4e0c5a2d refactor: remove trading_engine dep from ml and risk crates
Re-export HardwareTimestamp through data crate instead of ml/risk
depending directly on trading_engine. Reduces coupling between
the ML pipeline and the trading engine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:19:38 +01:00
jgrusewski
57e22c01a8 refactor: update K8s, CI, Docker, Prometheus, scripts, and FXT CLI for api rename
- K8s: rename api-gateway → api manifests, delete web-gateway, update network policies
- CI: rename compile/deploy jobs, delete web-gateway jobs
- Docker: rename service in compose files
- Prometheus: update scrape targets and alert rules
- Scripts: update binary references in build/test/cert scripts
- FXT CLI: rename api_gateway_url → api_url (with serde alias for compat)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:46:36 +01:00
jgrusewski
e50ea55064 feat: create services/api/ — unified gRPC gateway with tonic-web
Copied from api_gateway, removed REST handlers (port 8080),
added tonic-web + CORS for grpc-web browser access.
Binary renamed: api-gateway → api

Changes:
- Package name: api-gateway → api
- Deleted src/handlers/ (REST ML endpoints on port 8080)
- Added tonic-web 0.13 + tower-http CORS layer
- Server::builder().accept_http1(true) for grpc-web
- CORS_ORIGINS env var (default http://localhost:5173)
- Metrics server on port 9091 (axum) preserved
- All 95 lib tests pass, 0 clippy warnings
- Added services/api to workspace members

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:32:46 +01:00
jgrusewski
e047c1eea3 refactor: rename all service crates to kebab-case
Rename 7 service binaries from snake_case to kebab-case to match
K8s deployment names. Update Cargo.toml package/bin names, K8s
manifest S3 paths and commands, and cross-crate dependency keys.

- api_gateway → api-gateway
- trading_service → trading-service
- broker_gateway_service → broker-gateway
- ml_training_service → ml-training-service
- backtesting_service → backtesting-service
- trading_agent_service → trading-agent-service
- data_acquisition_service → data-acquisition-service

broker-gateway gets an explicit [lib] name = "broker_gateway_service"
since its new package name maps to broker_gateway (not the original
broker_gateway_service used in source code). All other services map
correctly with Rust's automatic hyphen-to-underscore conversion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:06:00 +01:00
jgrusewski
93104e8545 refactor(ml): eliminate f64↔f32 round-trips and dead deps across ML crate
Change PercentileScaler, RewardNormalizer, CompositeReward, and
PPORewardShaper public APIs from f64 to f32 — matching what callers
actually pass. Internal EMA/percentile math stays f64 for precision.

Keep data_loader SMA/EMA/MACD accumulators in f64 throughout (was
f64→f32→f64 per step), cast to f32 only at output boundary.

Remove dead _transition computation in rainbow_agent_impl and unused
bigdecimal deps from broker_gateway_service and trading_service.

2704 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 18:10:04 +01:00
jgrusewski
19a3c66959 chore: update Cargo.lock for kube/k8s-openapi dependencies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 13:19:51 +01:00
jgrusewski
5fe3608d92 fix(fxt,infra): production hardening — OTLP telemetry, TUI fixes, K8s infra
- Remove opentelemetry-otlp internal-logs feature (OTLP feedback loop)
- Switch trace sampling from AlwaysOn to 10% ratio-based
- Add RUST_LOG filtering (opentelemetry/h2/tonic/hyper=warn) to all 8 services
- Wire per-service latency measurement via health check → proto metadata → TUI
- Replace Vec::remove(0) with VecDeque ring buffers (O(1) vs O(n))
- Add Arc<AtomicBool> connected_sent for first-connected detection across 12 streams
- Add MAX_RECONNECT_ATTEMPTS (10) uniformly to all stream spawners
- Change kill switch/circuit breaker fields to Option types with N/A display
- Wire data_cache to real download status stream, remove dead cluster_events
- Remove ServiceData::new() hardcoded stubs, add honest placeholders
- Fix nanos_to_hms zero/negative guard, total_records semantic fix
- Fix RwLock held across yield in broker_gateway stream_account_state
- Add break after yield Err in broker/trading stream generators
- Fix connected_at advancing per tick in stream_session_status
- Tempo: replace emptyDir with 10Gi PVC, bump memory to 512Mi/2Gi
- Remove Prometheus gitlab-annotated-pods duplicate scrape job
- Wire 6 new gRPC streaming adapters (risk, trading, ml, data-acquisition)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:39:56 +01:00