Commit Graph

435 Commits

Author SHA1 Message Date
jgrusewski
9278561ec5 perf(ci): replace sccache with persistent target dir for incremental compilation
sccache forces CARGO_INCREMENTAL=0, causing all 37 workspace crates to
recompile from scratch every CI run (~20 min). Only upstream deps were
cached (635 hits); 109 workspace rlib crates were non-cacheable.

Changes:
- Add cargo-target-cpu and cargo-target-cuda PVCs (30Gi each)
- Mount persistent target dir at /cargo-target via CARGO_TARGET_DIR
- Drop RUSTC_WRAPPER=sccache and SCCACHE_DIR from both compile steps
- Drop hardcoded CARGO_BUILD_JOBS=14 (let cargo auto-detect from nproc)
- Add 25GB cleanup guard to prevent unbounded PVC growth
- Update binary copy paths to use $CARGO_TARGET_DIR/release/

First build (cold PVC) is same speed. Subsequent builds with typical
3-5 file changes should drop from ~20 min to ~2-3 min via incremental.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 21:22:17 +01:00
jgrusewski
3a2923b800 feat(ci): per-binary selective compilation in Argo pipeline
Replace monolithic compile-all with granular per-binary change detection.
detect-changes now outputs space-separated package/example lists based on
a dependency map from source directories to binary targets:

  - Shared crates (common, config, Cargo.toml) → all binaries
  - Service-specific dirs → only that service binary
  - Domain crates (trading_engine, risk) → dependent service subset
  - ML crates → ml-training-service + trading-service + all training
  - ML subdirs (trainers/, hyperopt/, evaluation/) → specific training binaries

compile-services and compile-training accept package lists and build only
affected binaries, saving ~20-30s link time per skipped binary.

deploy-services restarts only affected deployments (trading-service
excluded from auto-deploy for safety).

Fix: 'latest' package update now replaces individual files instead of
deleting the entire package, preventing corruption during partial builds.

compile-and-train-template: derive needed training binaries from model
parameter (3 instead of 7), drop unused training_uploader build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 16:32:07 +01:00
jgrusewski
645e251fbb fix(docker): add passwd package for useradd on ubuntu:24.04
Ubuntu 24.04 minimal doesn't include passwd (useradd/groupadd).
Debian bookworm-slim had it by default. Required for foxhunt user.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:53:27 +01:00
jgrusewski
76bf7d9df1 infra(ci): set CARGO_BUILD_JOBS=14 to match cgroup CPU request
Cargo defaults to /proc/cpuinfo CPU count (32 host cores) but each
compile pod is cgroup-limited to 14 CPU request. Without this, both
pods spawn 32 threads each (64 total) causing heavy throttling on
the 32-core POP2 node. Setting CARGO_BUILD_JOBS=14 per pod avoids
context-switch overhead and uses cgroup-aware parallelism.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:43:50 +01:00
jgrusewski
63d8619379 infra(docker): upgrade all images to Ubuntu 24.04 + CUDA 12.9
Align all 4 Docker images with local dev environment:
- ci-builder: CUDA 12.4.1/Ubuntu 22.04 → 12.9.1/Ubuntu 24.04
- ci-builder-cpu: Debian bookworm → Ubuntu 24.04 (+ explicit rustup)
- foxhunt-runtime: Debian bookworm → Ubuntu 24.04
- foxhunt-training-runtime: CUDA 12.6.3 → 12.9.1, nvrtc 12-6 → 12-9

All images now have glibc 2.39, matching the local build machine.
Binaries compiled locally or in CI will run in any of these containers
without GLIBC_2.3x version mismatch errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:34:47 +01:00
jgrusewski
bcde1c2a95 infra(ci): replace MinIO sccache with local RWO PVCs
Switch sccache backend from S3 (MinIO) to persistent local storage.
Two 20Gi PVCs (sccache-cpu, sccache-cuda) on scw-bssd-retain eliminate
network I/O for cache reads/writes. Both compile steps always run on
the same node so RWO is sufficient.

Removes 13 S3 env vars per compile step, replaces with SCCACHE_DIR=/sccache.
Updates ci-pipeline-template, compile-and-train-template, kustomization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:25:44 +01:00
jgrusewski
6a8f8d7b50 fix(train): correct CLI args for hyperopt vs training binaries
hyperopt_baseline_rl uses --output (JSON file path), while
train_baseline_rl uses --output-dir (checkpoint directory).
Split arg generation by preset.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:39:02 +01:00
jgrusewski
88c83c17ce fix(train): use correct Argo Events label selector for CI workflow lookup
Argo Events labels workflows with events.argoproj.io/trigger, not
workflows.argoproj.io/workflow-template. Fixed find_ci_workflow() so
ci-train preset can actually find CI pipeline runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:35:16 +01:00
jgrusewski
d67f696971 feat(train): add ci-train preset for CI-gated auto-training
New `ci-train` preset monitors the Argo CI workflow for a commit,
polls until completion, then auto-submits the training job (defaults
to hyperopt). Prints Prometheus/log links during monitoring.

Usage: ./infra/scripts/train.sh ci-train --model dqn [--commit SHA]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:30:59 +01:00
jgrusewski
356f1d295f fix(train): remove duplicate annotations block from rebase artifact
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:08:02 +01:00
jgrusewski
e89fbc2b4d fix(metrics): wire training pod scraping to Grafana dashboard
Three root causes for "no metrics" on the training dashboard:

1. Dashboard template variables ($model, $fold) sourced from
   foxhunt_training_current_epoch which isn't emitted until the first
   epoch completes. Switch to foxhunt_training_step which fires from
   step 500 onward.

2. train.sh pod template missing Prometheus annotations
   (prometheus.io/scrape, port, path). Also add the
   app.kubernetes.io/component label to the eval manifest so
   evaluation pods are discoverable too.

3. DQN and PPO trainers only called set_epoch() at the END of each
   epoch. Move the call to the TOP of the epoch loop so the gauge
   exists from the first training iteration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:02:29 +01:00
jgrusewski
9d6a04ab4a fix(grafana): add 20 missing metric panels to training dashboard
Remove phantom foxhunt_training_total_epochs reference (never registered)
and replace with foxhunt_training_step from the recent metrics commit.
Add full coverage for all 52 Tier-1 Prometheus metrics across 5 new rows:
RL diagnostics (Q-overestimation, PPO entropy/KL/advantage), training
health (NaN/grad explosion/feature errors, checkpoint ops, data load
latency), epoch returns, supervised eval (accuracy/precision/recall/F1),
and hyperopt details (mode, trial epoch, failed trials).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:02:29 +01:00
jgrusewski
47d4959588 fix(train): correct --output to --output-dir arg, add Prometheus annotations
The train_baseline_rl binary expects --output-dir, not --output.
Also adds prometheus.io scrape annotations to job pod templates
so training metrics appear in Grafana.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 20:53:36 +01:00
jgrusewski
7751f7615a fix(ci): unblock CPU service builds and fix H100 BF16 regime classification
Three fixes validated by 20/20 hyperopt trials on H100 (zero OOM):

1. Workspace default-features: ml-core, ml-dqn, ml-ppo, ml-supervised
   workspace deps now have default-features=false. Prevents cudarc
   (which requires nvcc) from leaking into CPU service builds via
   Cargo feature unification. CI compile-services was failing with
   "Failed to execute nvcc: No such file or directory" (exit 101).

2. BF16 comparison fix: Candle's gt()/le() don't support BF16 operands.
   Cast ADX/CUSUM features to F32 before threshold comparison in
   regime classification. Previous approach (cast threshold to BF16)
   failed due to Candle broadcast_as reverting dtype.

3. CI pipeline: expand ML change detection to all 14 sub-crates,
   add component:compile labels for sccache network policy matching,
   bump training runtime to CUDA 12.6 + Ubuntu 24.04 (glibc 2.39).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 18:11:04 +01:00
jgrusewski
89c3fb89d9 feat(dqn): Branching DQN with full GPU Rainbow parity (7 fixes)
Bring Branching Dueling Q-Network (Tavakoli 2018) to full Rainbow parity
with the existing GPU hotpath. 3 independent advantage heads (exposure=5,
order=3, urgency=3) decompose the 45-action space into learnable branches.

H1 - CUDA fallback: gate GpuExperienceCollector when use_branching=true
     (fused kernel hardcodes NUM_ACTIONS=5, incompatible with 45 factored)
H2 - Per-branch C51 distributional: each branch outputs [batch, n_d, atoms]
     log-softmax, loss = avg of D cross-entropies vs projected Bellman target
M1 - NoisyNet: MaybeNoisyLinear enum in branch heads, reset_noise/disable_noise
     wired through select_action, compute_loss, and set_eval_mode
M2 - Regime-conditional IS weights: Trending=1.2, Ranging=0.8, Volatile=0.6
     applied to branching loss via ADX/CUSUM features at state[40:41]
M3 - State dim alignment: align_dim_for_tensor_cores() in from_dqn_params()
     for H100 HMMA dispatch (8-byte alignment)
L1 - Fill simulator: splitmix64 replaces golden ratio hash (chi-squared tested)
L2 - Hyperopt 29D: branch_hidden_dim [64,256] added to PSO search space

Config plumbing: branch_hidden_dim, v_min/v_max/num_atoms, use_distributional,
use_noisy, noisy_sigma_init all flow from DQNConfig → BranchingConfig.

10 files, +3207/-125 lines, 33 branching tests + 387 ml-dqn + 284 ml-core pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:17:06 +01:00
jgrusewski
31d6d63d3f feat(dashboard): add Source selector (Live/CI History/All) for training metrics
Adds a `source` template variable to the training dashboard that controls
which Prometheus job labels are queried:
- Live: only `training-pods` (running pods scraped directly)
- CI History: only `.*_baseline.*` (completed CI runs via pushgateway)
- All: both sources combined

All 37 panel queries now use `job=~"$source"` for consistent filtering.
Active Workers panel stays hardcoded to `job="training-pods"` since it
only makes sense for live pods. Template variable queries (model/fold
dropdowns) also respect the source selector.

Default is "Live" — dashboard shows only the currently running training
session with no pushgateway stale data contamination.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:28:38 +01:00
jgrusewski
9a36dfc0ae fix(dashboard): filter training metrics to live pods, exclude stale pushgateway data
Stat panels (Active Workers, Current Epoch, Trial Progress, Elapsed, Best
Objective) showed conflicting values from pushgateway stale gauges overlapping
with live training pod metrics. Scoped all stat/gauge panels and template
variable queries to job="training-pods" so the dashboard reflects only the
currently running training session. Time-series panels remain unfiltered via
$model template variable scoping — dropdown only lists live models, so
historical pushgateway data is naturally excluded without explicit filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:22:43 +01:00
jgrusewski
3d68e9feaf fix(ci): make CUDA non-default to unblock CPU service builds
- Remove cuda from default features in ml-core, ml-dqn, ml-ppo, ml
- Propagate cuda feature from ml → ml-core/ml-dqn/ml-ppo
- CI compile-training already uses --features ml/cuda explicitly
- Fix MaxDD log format: {:.1}% → {:.3}% (was rounding 0.033% to 0.0%)
- Suppress unused_labels/unused_variables warnings for cfg(cuda) code
- Add CALLBACK_ENDPOINT env to ml-training-service deployment
- Fix Grafana active_workers query to use sum() with fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:12:03 +01:00
jgrusewski
c384ab4b43 fix(infra): replace gitlab.com/prometheus annotations with prometheus.io
Prometheus was configured to scrape pods via prometheus.io/scrape
annotations, but all services and training jobs used gitlab.com/
prefix from legacy GitLab-managed Prometheus — causing Prometheus
to never discover any foxhunt pods. This resulted in stale/missing
metrics on the Grafana training dashboard.

12 files updated across services/, gpu-overlays/, training/, and
monitoring/ (node-exporter, dcgm-exporter cleanup).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:39:24 +01:00
jgrusewski
8c0bed4d2d fix(infra): update training job template to use ci-training-h100 pool
The nodeSelector pointed to non-existent 'ci-training' pool. The actual
Scaleway pool is 'ci-training-h100', matching the CI pipeline template.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 17:20:15 +01:00
jgrusewski
809295dc5f fix(ml): correct GPU experience path — aligned state_dim, reward tracking, Prometheus label
Three bugs in the GPU experience collection hot path:

1. gpu_batch_to_experiences() had hardcoded state_dim=43 but the CUDA kernel
   outputs states at the ALIGNED dimension (56 with OFI, 48 without). After
   sample 0, every replay buffer entry had corrupted state vectors — the
   network was learning from garbage data.

2. GPU path never called monitor.track_reward(), so mean_reward was always
   reported as 0.0 in epoch logs despite the agent generating real rewards.

3. Action tracking was double-counted (direct array write + track_action_by_exposure),
   inflating diversity metrics by 2x. Consolidated into single bounded call.

Also adds missing app.kubernetes.io/component label to job-template.yaml
so Prometheus training-pods scrape job discovers training pods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:11:01 +01:00
jgrusewski
8926c6e1cf perf(ml): eliminate CPU from DQN training hot path — GPU-resident ops only
GPU experience collector was falling back to CPU because it required a
curiosity VarMap, but curiosity_weight=0.0 means the module is never
created. Fix: make curiosity optional (CuriosityWeightSet::zeros() for
GPU buffers, curiosity_scale=0.0 in kernel config). Also require
explicit binary-tag SHA in Argo training workflow (no "latest" fallback).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 09:15:52 +01:00
jgrusewski
7382ffd1e2 feat(infra): QuestDB metrics sink + monitoring network policies
Add QuestDB ILP sink for training metrics, update Prometheus scrape
configs, and fix network policies for monitoring stack connectivity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 02:42:53 +01:00
jgrusewski
3d43c80264 feat(infra): compile-and-train unified workflow + exclude trading-service from CI deploy
- New compile-and-train-template.yaml: compile + GPU warmup in parallel,
  then fetch-binary → hyperopt → train-best → evaluate → upload-results
- Compile step outputs SHA tag via Argo output parameter, fetch-binary
  uses it directly (no 'latest' package indirection)
- Remove trading-service from CI deploy step — must be explicitly enabled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:13:43 +01:00
jgrusewski
4976e2e1d7 fix(ml): OFI preload uses adapter's load_ofi_features, not trainer's empty field
Root cause: preload_data() called loader.ofi_features.take() on the internal
DQN trainer, but load_training_data() only loads OHLCV bars — it never
populates ofi_features. The OFI loading is done by the hyperopt adapter's
own load_ofi_features() method.

Fixes:
- preload_data() now calls self.load_ofi_features() directly
- load_ofi_features() uses self.mbp10_data_dir when set (was hardcoded ../mbp10)
- input_dim pre-computation uses OFI-aware size (51 when enabled, 43 otherwise)
- CI 'latest' package: delete-then-upload to avoid GitLab duplicate file issue
- Warn when mbp10_data_dir is set but no OFI features loaded

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:29:22 +01:00
jgrusewski
0491387d27 fix(infra): node-bootstrap DaemonSet + GitLab package auth + latest tag
- Rename node-dns-fix → node-bootstrap, fix nvidia gate race condition
  (always create conf.d/ and write 50-registry.toml, don't gate on
  99-nvidia.toml which doesn't exist on fresh autoscaled GPU nodes)
- Update busybox 1.36 → 1.37
- Fix fetch-binary: use PRIVATE-TOKEN/gitlab-pat (not DEPLOY-TOKEN)
- Fix upload-results: use gitlab-pat (gitlab-ci-token didn't exist)
- Add 'latest' rolling package version in both compile-services and
  compile-training (re-upload after CalVer upload)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:53:31 +01:00
jgrusewski
ebe8b41055 fix(infra): add network policy for GitLab package registry access
Service pods' initContainers need egress to gitlab-webservice-default:8181
to fetch release binaries from the Generic Package Registry. Without this,
curl hangs for 2+ minutes then times out (default-deny-all blocks it).

Covers all app.kubernetes.io/part-of=foxhunt pods in one policy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:15:19 +01:00
jgrusewski
72bc133dba fix(infra): use inputs.parameters for Argo template tag passing
Argo Workflows can't resolve {{tasks.X.outputs}} inside template
bodies — only in DAG task arguments. Pass tag via arguments →
inputs.parameters in compile-services, compile-training,
upload-release, and deploy-services templates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:44:52 +01:00
jgrusewski
7a9683d5fb feat(infra): GitLab releases with CalVer auto-versioning
Replace MinIO binary distribution with GitLab Generic Package Registry.
Every code push to main auto-creates a CalVer tag (vYYYY.MM.N),
compiles, uploads binaries to GitLab packages, creates a Release
with auto-generated notes, and deploys via deployment patching.

- New CI templates: create-tag, upload-release
- Modified: compile-services/training upload to GitLab packages
- Modified: deploy-services patches FOXHUNT_RELEASE on deployments
- All 7 service initContainers fetch from GitLab (curl, deploy token)
- Training job-template binary fetch from GitLab (data stays MinIO)
- MinIO retains: sccache, training data, model checkpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:38:41 +01:00
jgrusewski
9a74fda3ab fix(infra): mount training-data-pvc instead of downloading from MinIO
The training-data PVC already has all data (OHLCV, MBP-10, trades).
Mount it read-only at /data in hyperopt/train/evaluate steps instead
of rcloning ~50GB from MinIO every step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:01:55 +01:00
jgrusewski
042c361690 feat(infra): GPU warmup in CI pipeline + OFI data dirs in training workflow
- CI pipeline: add gpu-warmup step parallel to compile-training,
  triggers GPU node autoscale so it's ready when training starts
- Training workflow: add mbp10-data-dir and trades-data-dir parameters,
  download MBP-10 + trades data in all steps (hyperopt, train, evaluate)
- Use parameterized paths consistently (no hardcoded /tmp paths)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:47:55 +01:00
jgrusewski
3f4c39e035 feat(ml): wire OFI data dirs into hyperopt DQN adapter
Add --mbp10-data-dir and --trades-data-dir CLI args to
hyperopt_baseline_rl binary so hyperopt trials can use real
order book and trade data for VPIN/Kyle's Lambda features.

- DQNTrainer: add mbp10_data_dir/trades_data_dir fields + with_ofi_data_dirs() builder
- DQNHyperparameters: pipe through from trainer instead of hardcoded None
- download-trades-job: fix nodeSelector to ci-compile-cpu (platform pool full)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:36:42 +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
1ad79493dc feat(infra): training data cache architecture — MinIO → PVC sync
- Expand training-data-pvc from 10Gi → 200Gi (OHLCV + MBP-10 + headroom)
- Add data-sync-job: rclone sync from MinIO → PVC (delta-only, fast repeats)
- Add sync-training-data init container to training job template
  (auto-syncs data from MinIO before each training run)
- Add training-job + data-sync-job NetworkPolicies (MinIO, Tempo, Pushgateway)
- Add app.kubernetes.io/part-of: foxhunt to training pod template
- download_baseline: add --parallel N flag for concurrent Databento downloads
- download_baseline: delete local files after MinIO upload (saves scratch space)
- Bump download job scratch volume to 100Gi

Architecture: Databento → MinIO (source of truth) → PVC (local cache).
First sync pulls everything; subsequent runs only sync deltas (seconds).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:45:30 +01:00
jgrusewski
0d8e59caf5 fix(infra): add NetworkPolicy for data-download jobs, fix pod labels
The download job pod was missing app.kubernetes.io/part-of: foxhunt,
so the default-deny-all egress policy blocked it and MinIO's ingress
policy rejected it. Also removed hostname pinning (was a misdiagnosis).

Added data-download-job NetworkPolicy allowing egress to MinIO:9000
and external HTTPS:443 (Databento API).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:22:48 +01:00
jgrusewski
f1c1faa306 fix(infra): use platform node pool for MBP-10 download job
The cluster only has a 'platform' pool, not 'default'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:11:39 +01:00
jgrusewski
5829d6378e feat(data): schema-configurable Databento download with MinIO upload
- download_baseline now reads schema from universe TOML config
  (was hardcoded to ohlcv-1m, now supports mbp-10 and other schemas)
- Add --rclone-dest flag for direct upload to MinIO after each download
- Add config/universe-es-mbp10.toml: ES.FUT MBP-10, same date range
- Add infra/k8s/training/download-mbp10-job.yaml: K8s Job to download
  ES.FUT MBP-10 data in-cluster and upload to MinIO

Usage:
  kubectl apply -f infra/k8s/training/download-mbp10-job.yaml

Prereqs: databento-credentials secret, download_baseline binary in MinIO

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:19:46 +01:00
jgrusewski
c63611fb03 fix(dashboard): overhaul training Grafana dashboard layout and visuals
- Remove all hardcoded Y-axis min/max bounds (41 removals) — dynamic scaling
- Eliminate Hyperopt section: merge into Training Status + Run Summary
- Deduplicate trial progress (3 panels → 1 gauge + 1 stat)
- Combine ML Jobs + Errors into single panel
- Add Current Trial and Hyperopt Elapsed to status bar
- Add Best Objective Δ (deriv) trend indicator
- Smooth line interpolation + gradient fills on all timeseries
- Stacked area for Action Distribution, gradient fill for Replay Buffer
- Shared crosshair tooltips, consistent threshold colors
- Status bar: stat panels with sparklines, trial before epoch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:14:59 +01:00
jgrusewski
fbb674f9e2 fix(infra): make gpu-warmup tolerant of missing nvidia-smi
nvidia-smi is driver-mounted by the GPU operator, which may not be
ready when the warmup pod starts on a fresh autoscaled node. The
warmup's purpose is just triggering autoscale, not GPU validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:56:35 +01:00
jgrusewski
f356ce157c fix(infra): restore rclone line continuations before --transfers=8
Previous fix removed ALL trailing backslashes from --s3-no-check-bucket,
but 3 of 6 occurrences need the continuation for --transfers=8 on the
next line. Restores \ on lines where --transfers follows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:45:33 +01:00
jgrusewski
1d87fa41bc fix(infra): remove trailing backslash in training workflow rclone commands
Same bug as service manifests — \\ after --s3-no-check-bucket caused
chmod to be parsed as rclone args. Fixed in 6 places.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:35:27 +01:00
jgrusewski
5dbe529ee6 fix(ci): rebuild CI builder images with mold linker
Both Dockerfiles already install mold v2.35.1 but the registry images
are stale builds without it. This change triggers rebuild-ci-builder
and rebuild-ci-builder-cpu pipeline steps via detect-changes.

.cargo/config.toml uses -fuse-ld=mold — without mold in the image,
linking falls back to the system default (slower).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:20:09 +01:00
jgrusewski
b31329931f fix(infra): remove MinIO TLS, fix sccache 0% cache hits, update pool selectors
- Remove all HTTPS/TLS from MinIO (plain HTTP for internal cluster traffic)
- Fix sccache 0% cache hit rate (rustls rejected self-signed MinIO cert)
- Remove hardcoded URLs from k8s_dispatcher.rs (S3_ENDPOINT, TRAINING_RUNTIME_IMAGE,
  CALLBACK_ENDPOINT now required env vars)
- Update GitLab registry S3 credentials to HTTP endpoint
- Fix PVC manifest (20Gi → 100Gi to match cluster)
- Fix nodeSelector: infra/foxhunt → platform (match actual node pool)
- Fix rclone trailing backslash causing chmod to be parsed as rclone args
- Remove minio-ca-cert ConfigMap references from all manifests
- Update trading-service GPU overlay to l40s pool

20 files changed, -118 lines net

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:53:05 +01:00
jgrusewski
4e0d1fcbe6 fix(ci): add imagePullPolicy: IfNotPresent to training workflow
Kubernetes defaults to Always for :latest tags, forcing registry
round-trips that fail on fresh GPU nodes where containerd HTTP-only
registry config has a race condition with HTTPS fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:49:34 +01:00
Administrator
4658d7e914 Merge branch 'feature/action-diversity-fix' into 'main'
feat(ml): multi-window backtest + top-K ensemble training

See merge request root/foxhunt!2
2026-03-06 07:48:06 +00:00
jgrusewski
84366d8dd8 fix(infra): pin CI sensor pod to DEV1-L platform nodes
The Argo Events sensor had no nodeSelector and randomly landed on the
H100 GPU node, preventing autoscale-down. Pin it to DEV1-L to avoid
wasting expensive GPU node hours on a lightweight event listener.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 08:45:59 +01:00
jgrusewski
5a9fa1534b feat(ml): multi-window backtest objective + top-K ensemble training
Multi-window backtest: splits validation data into 3 non-overlapping
windows and aggregates with mean(Sharpe) - 0.5*std(Sharpe), penalizing
inconsistency and reducing overfit to a single data segment.

Top-K ensemble: hyperopt now emits top_k_params (top 5 trials) in JSON
output. train_baseline_rl gains --ensemble-top-k flag to train multiple
models per fold from different hyperopt configs, saving checkpoints as
dqn_ensemble_{k}_fold_{n}.safetensors.

Workflow template: adds ensemble-top-k parameter (default 5) and passes
--ensemble-top-k to the train-best step.

2720 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 08:42:03 +01:00
jgrusewski
2b77a7fbe1 fix(dashboard): clamp degenerate outliers in training metrics panels
Add clamp_max() to PromQL queries and hard Y-axis limits to prevent
early-epoch degenerate values from blowing up panel scaling (Sharpe
showing 12 instead of 1.3, Profit Factor at 30k).

Run Summary: clamp_max on Best Val Loss (5), Sharpe (5), Profit Factor (20)
Training Quality: clamp_max on Epoch Sharpe (5), Sortino (10), PF (20)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 02:22:21 +01:00
jgrusewski
5323cf11e7 fix(dashboard): convert Run Summary to smooth timeseries, 3x2 layout
Replace stat panels with timeseries using smooth line interpolation,
gradient fill, and multi-tooltip. Layout changed from 6x1 to 3x2 grid.
Legends show model/fold only when multiple series exist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:37:31 +01:00
jgrusewski
9067402284 feat(dashboard): add Run Summary row to training dashboard
Add 6 stat panels showing overall training run metrics: Best Val Loss,
Best Sharpe, Best Win Rate, Min Max Drawdown, Total Epochs, and Best
Profit Factor. Placed between Training Status and Training Curves rows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:28:29 +01:00