Commit Graph

1869 Commits

Author SHA1 Message Date
jgrusewski
f2938b19e8 fix(ml): improve TPE exploitation with Scott bandwidth, best-trial injection
- Replace Silverman's bandwidth (h = 1.06σn^(-1/5)) with Scott's rule
  (h = 0.7σn^(-1/(d+4))) for tighter kernels in high-D parameter spaces
- Add best-trial injection: always evaluate EI at best known point plus
  5 small perturbations (±5%), preventing optimizer from forgetting peaks
- Scale n_candidates dynamically: max(256, 8*n_dims) instead of fixed 100
- Reduce gamma from 0.25 to 0.15 when trials < 50 for tighter exploitation
- Wire model_name through PSO/TPE paths for per-trial Prometheus metrics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:54:30 +01:00
jgrusewski
08a927639e fix(ci): POSIX-compatible kubectl detection in deploy step
The foxhunt-runtime image runs as non-root user 'foxhunt' and uses
/bin/sh (dash), not bash. Two issues:
1. &>/dev/null is bash-only — use >/dev/null 2>&1 for POSIX sh
2. Fallback download to /tmp (writable), not /usr/local/bin (root-only)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:26:35 +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
7b60fd5f86 fix(ci): Docker build uses init container + emptyDir instead of PVC
The build-ci-image template used volumeClaimTemplates (PVC) which
weren't inherited when called via templateRef from ci-pipeline.

Restructured to single pod: init container (alpine/git) clones repo
into emptyDir, main container (kaniko) builds and pushes image from
the same volume. No PVC needed, works correctly via templateRef.

Tested: foxhunt-runtime image rebuilt successfully with kubectl.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:11:55 +01:00
jgrusewski
9cd4da9acf fix(ci): restructure Docker build to single pod with emptyDir
build-ci-image used volumeClaimTemplates (PVC) shared between a
git-clone pod and a kaniko-build pod in a DAG. When called via
templateRef from ci-pipeline, the VCT wasn't inherited, causing
"volume 'workspace' not found" errors.

Fixed by merging into a single pod: init container (git clone) +
main container (kaniko build) sharing an emptyDir volume. No PVC
needed, works correctly when called via templateRef.

Also removed stale compile-training-template.yaml reference from
kustomization (file doesn't exist, template is inline in ci-pipeline).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:58:42 +01:00
jgrusewski
171fe86194 fix(ci): bake kubectl into runtime image for faster deployments
Add kubectl v1.31.4 to foxhunt-runtime Dockerfile so deploy step
doesn't need to download it each run. Deploy step falls back to
curl download if kubectl not found (for current image version).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:49:51 +01:00
jgrusewski
4e71cbf549 fix(ci): use runtime image for deploy step, add podGC to sensor
- Deploy step: use foxhunt-runtime image (cached on platform node) +
  curl kubectl, replacing bitnami/kubectl:1.31 which doesn't exist.
- Sensor: add podGC:OnPodCompletion and ttlStrategy to workflow spec
  (not inherited from WorkflowTemplate via workflowTemplateRef).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:46:11 +01:00
jgrusewski
e9481c6107 fix(ml): disable val-loss early stopping in hyperopt, fix penalty metrics
Two issues causing all 20 hyperopt trials to have identical f64::MAX
objective (TPE optimizer blind):

1. Val-loss plateau early stopping fired at epoch 5-6 of every 8-epoch
   trial (plateau_window=5 too aggressive for short runs). Disabled
   early_stopping_enabled for hyperopt; gradient-collapse patience
   still active as safety net.

2. Penalty metrics used f64::MAX for gradient_norm/q_value_std which
   produced ~3.6e+308 objective. Changed to 100.0 so TPE can still
   differentiate between early-stopped trials by other metric fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:45:03 +01:00
jgrusewski
a77c873da4 fix(ci): add deploy step, podGC, move compile-services to ci-compile-cpu
Three issues prevented services from being deployed after push:

1. No deploy step: pipeline compiled + uploaded binaries to MinIO but
   never restarted service deployments. Added deploy-services step
   (bitnami/kubectl) that runs rollout restart after compile-services.

2. compile-services couldn't schedule: targeted platform node (4 CPU,
   6Gi allocatable) but requested 6Gi memory. Moved to ci-compile-cpu
   (POP2-32C-128G) alongside compile-training — both fit simultaneously
   (28/32 CPU, 48/128Gi).

3. No podGC: completed Argo pods held resources indefinitely, blocking
   subsequent pipeline steps. Added podGC:OnPodCompletion.

Also: detect-changes now triggers service rebuild when CI pipeline
template or k8s service manifests change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:34:44 +01:00
jgrusewski
5d5cce72d2 fix(infra): auto-restart containerd on GPU nodes for registry config
DaemonSet init container now:
- Detects GPU nodes (99-nvidia.toml in conf.d)
- Creates v3-compatible registry config drop-in
- Restarts containerd via chroot if config files changed
- Idempotent: skips restart if files already exist

Requires hostPID + privileged for chroot /proc/1/root.
On new nodes, first run creates files + restarts containerd
(kills pod), second run finds files unchanged (no restart).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:12:17 +01:00
jgrusewski
248b2fec70 fix(ml,infra): handle early stopping in hyperopt, GPU containerd registry
- Hyperopt: catch early stopping errors and return penalty metrics
  instead of aborting entire run. Trials that stop early are scored
  as poor (objective=-500) so optimizer avoids those configs.
- DaemonSet: detect GPU nodes (NVIDIA conf.d overlay) and create
  v3-compatible registry config drop-in for containerd v2.1+.
  Old grpc.v1.cri.registry path is silently ignored by containerd v2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:07:09 +01:00
jgrusewski
168b552d1a fix(infra): node DNS + containerd registry for cluster-internal images
Kapsule nodes use nameserver 127.0.53.53 which resolves external DNS
but returns NXDOMAIN for .svc.cluster.local. Containerd uses node DNS
for image pulls, causing ErrImagePull for cluster-internal registry refs.

Deploy node-dns-fix DaemonSet (kube-system) that:
1. Points /etc/resolv.conf to kube-dns ClusterIP (10.32.0.10)
2. Creates containerd certs.d config for HTTP GitLab registry
3. Watches for Kapsule node reconciliation resets (every 30s)
4. Restores original resolv.conf on SIGTERM

Also bump compile-services memory: 6Gi→10Gi limit (was OOM-killed at 5.7GB).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:47:41 +01:00
jgrusewski
0070a0117e feat(api): add api_uptime_seconds metric, replace web-gateway panels in cockpit
- Add api_uptime_seconds gauge to API service (5s update interval),
  matching the pattern used by trading/backtesting/ml-training services
- Remove obsolete Web Gateway Uptime and Active WebSocket Connections
  panels from cockpit (web-gateway is not deployed)
- Add API Gateway Uptime (api_uptime_seconds) and API Auth Rate
  (api_auth_requests_total) panels in their place

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:27:07 +01:00
jgrusewski
7d9808ecf0 fix(ml): smooth CVaR penalty, fix clip leakage, align noisy sigma, fix eval_supervised
Fixes from deep investigation audit (LOW/MEDIUM priority):

1. CVaR penalty: hard cliff (0 or 10) → smooth ramp with gradient signal
   for PSO. Formula: min(10, max(0, -cvar-0.05)*200).

2. Clip outliers leakage: data_loading.rs now computes clip bounds from
   training portion only (first 80%), then applies to full series.
   Log returns and windowed normalize are causal (no leakage).

3. Noisy sigma scheduler: hyperopt now matches conservative() defaults
   (enabled, initial=0.8, final=0.4) so hyperopt-found params
   generalize to train_best without scheduler mismatch.

4. evaluate_supervised.rs: NormStats fallback from test data (leakage)
   replaced with bail! matching evaluate_baseline.rs behavior.

5. Doc comments: stale 27D references updated to 31D (4 locations).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:14:31 +01:00
jgrusewski
c033a34fec fix(ml): wire minimum_profit_factor, fix eval capital, HFT score, Sortino
4 bugs found by deep investigation agents:

1. HIGH: minimum_profit_factor (search dim 30) was never forwarded from
   DQNHyperparameters to DQNConfig — trainer hardcoded 1.5, making the
   entire dimension wasted. Added field to DQNHyperparameters, wired
   through trainer.rs.

2. HIGH: Backtest EvaluationEngine used hardcoded $10K initial capital
   while training used $35K (self.initial_capital). Returns/Sharpe were
   3.5x distorted. Now uses self.initial_capital.

3. MEDIUM: calculate_hft_activity_score_wave10 multiplied already-100x
   buy_pct/sell_pct by 100 again, making the diversity penalty threshold
   (15%) unreachable (values were ~2700). Removed double multiplication.

4. MEDIUM: Sortino ratio returned 0.0 for all-positive returns (no
   downside deviation), penalizing perfect strategies in the 40%-weighted
   composite score. Now returns 100.0 (capped) when mean return > 0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:07:07 +01:00
jgrusewski
34b881fa14 fix(dashboards): show node names instead of IPs in infrastructure panels
Join node metrics with node_uname_info to resolve instance IPs to
hostnames (e.g. scw-foxhunt-platform-694db...) in all 10 node-level
panels: CPU, Memory, Disk I/O, Network I/O, Load Average, Disk Usage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:00:52 +01:00
jgrusewski
f39219be9e feat(ml): expand DQN hyperopt search space 27D→31D + GPU-batched eval inference
Root cause: catastrophic OOS eval (Sharpe -280) traced to 11 issues
including hardcoded training dynamics and per-bar CPU inference.

Search space (dqn.rs):
- Add warmup_ratio [0.0, 0.15] — was hardcoded to 0
- Add lr_decay_type [Constant/Linear/Cosine] — was hardcoded Constant
- Add min_epochs_before_stopping [2, 6] — was 1000 (disabled)
- Add minimum_profit_factor [1.1, 2.0] — was hardcoded 1.5
- Widen entropy_coefficient [0.01, 0.2] for 45-action factored space

GPU-batched eval (evaluate_baseline.rs):
- 1024-bar chunked inference for both DQN and PPO
- DQN: batch_greedy_actions per chunk (was per-bar select_action)
- PPO: action_probabilities + GPU argmax per chunk
- ~1000x fewer GPU kernel launches
- Add trade_sharpe_ratio for hyperopt-comparable metric

Preprocessing (preprocessing.rs):
- Add compute_clip_bounds/clip_outliers_with_bounds for leakage-free
  clipping across train/val/test splits

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 20:45:13 +01:00
jgrusewski
d678f7d28a fix(monitoring): filter completed Argo pods from Prometheus scrape targets
Add __meta_kubernetes_pod_phase=Running filter to training-pods job.
Completed/failed Argo workflow step pods were showing as DOWN targets
because their containers are no longer running to serve /metrics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:10:20 +01:00
jgrusewski
2b77df7dda fix(dashboards): correct metric prefixes, gRPC code filters, and service labels
- Replace api_gateway_ with api_ prefix to match actual metric exports (cockpit, observability)
- Change grpc_code!="OK" to grpc_code!="0" — gRPC uses numeric status codes (cockpit, trading)
- Update gRPC service filter to actual exported_service labels: trading-agent-service,
  broker-gateway, backtesting-service, ml-training-service, data-acquisition-service (trading)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:02:32 +01:00
jgrusewski
9dc4d2585f fix(infra): rewrite dashboards with verified metrics, fix KSM + internal DNS
Rewrite all 5 Grafana dashboards using only confirmed-existing Prometheus
metric names (1023 metrics inventoried). Dashboards now use hardcoded
datasource UIDs instead of unresolvable ${DS_*} template variables.

- Cockpit: 38 panels (service health, gRPC, Argo CI/CD, trading, metrics)
- Training: 33 panels (epochs, loss, Sharpe, Q-values, GPU, hyperopt)
- Trading: 5 rows (overview, latency, gRPC pipeline, data acq, backtesting)
- Infrastructure: 25 panels (cluster, nodes, workloads, storage, GPU, Prometheus)
- Observability: 22 panels (API gateway auth/security/routing, Loki, Tempo)

Fix kube-state-metrics: add part-of label for netpol, fix nodeSelector
(infra→platform), increase memory limit (128→256Mi for OOM), add K8s API
egress rules covering both 10.32.0.0/16 and 172.16.0.0/16 CIDR ranges.

Add training-pods pod-based scrape job for ephemeral Argo workflow pods
exposing metrics on port 9094.

Migrate all image references from localhost:30500 to internal DNS
(gitlab-registry.foxhunt.svc.cluster.local:5000) across 14 YAML files.

Fix import.sh ConfigMap name (grafana-dashboards-infra → infrastructure).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 18:55:06 +01:00
jgrusewski
b3c85d4f7c fix(infra): resolve dashboard datasource UIDs, fix Grafana→Prometheus netpol
- Remove duplicate annotated-pods and gitlab-annotated-pods scrape jobs
  (all targets already covered by dedicated jobs)
- Drop Argo controller from foxhunt-services job (dedicated HTTPS job exists)
- Resolve datasource variable placeholders to actual UIDs in dashboard JSON
- Add Ingress rule to Prometheus netpol allowing Grafana on port 9090

Targets: 49 → 18 (zero duplicates)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:43:25 +01:00
jgrusewski
b0ac0afe63 test(ml): smoke tests for hierarchical softmax and diversity short-circuit
5 new tests:
- batch_hierarchical_softmax_actions_smoke: valid outputs, diversity, exposure coverage
- batch_hierarchical_softmax_low_temp_valid: edge case at near-zero temperature
- batch_hierarchical_vs_flat_softmax_diversity: hierarchical >= flat exposure levels
- eval_softmax_temp_floor_is_half: bounds [0.5, 2.0] enforced with clamp
- diversity_short_circuit_blocks_phantom_sharpe: 2/45 actions -> positive penalty

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:30:24 +01:00
jgrusewski
4c03beb514 chore: track logo, hyperopt design docs, gitignore playwright-mcp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:06:53 +01:00
jgrusewski
53317d9af6 fix(ml): diversity short-circuit in objective, prevent phantom Sharpe from TPE
Degenerate trials with <10/45 unique actions now short-circuit the objective
to a graduated penalty (8.0 for 1 action → 0 for 10+), ignoring composite
score entirely. Previously, phantom Sharpe=2317 drove objective to -369k,
trapping TPE in the low-temp region.

Combined with temp floor raise (0.1→0.5 from prior commit), this eliminates
both the supply (no low temps) and demand (no reward) for degenerate trials.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:54:53 +01:00
jgrusewski
26d2ac7a5c feat(infra): redesign Grafana dashboards, consolidate to single Prometheus
Replace 11 overlapping legacy dashboards with 5 focused dashboards:
- Operations Cockpit (services, Argo CI/CD, resources, auth)
- Trading & ML Intelligence (signals, execution, risk, gRPC)
- Training & CI/CD (training curves, GPU, hyperopt, Argo pipelines)
- Infrastructure & Cluster (nodes, pods, storage, network)
- Observability & API Gateway (logs/Loki, traces/Tempo, API metrics)

Remove gitlab-prometheus-server dependency — single Prometheus instance
with all scrape configs including Argo Workflows (HTTPS + skip verify).
Update Grafana datasource and import.sh for new dashboard structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:50:52 +01:00
jgrusewski
d3a3403053 fix(ml): raise eval_softmax_temp floor to 0.5, add action diversity penalty
Hyperopt TPE was stuck chasing phantom Sharpe=2317 from degenerate trials
with 2/45 unique actions (temp 0.15-0.20). Two fixes:

1. Raise eval_softmax_temp bounds from [0.1, 2.0] to [0.5, 2.0] — temps
   below 0.3 consistently produce 1-3/45 actions regardless of model quality
2. Add diversity_penalty to objective: 50*(1 - unique/10) for <10/45 actions,
   so degenerate trials score badly even if they accidentally reach the TPE

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:48:03 +01:00
jgrusewski
aff2b3445d fix(dashboard): remove direct trading OrderForm from trading page
Direct trading from the dashboard is not an option in our system.
Trading page is now read-only monitoring (positions, orders, order book).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:04:53 +01:00
jgrusewski
b6b3fbd463 fix(ml,infra): GPU-resident hierarchical softmax, imagePullPolicy Always
Rewrite batch_hierarchical_softmax_actions to keep all sampling on GPU:
reshape [N,45]→[N,5,9], max-pool→[N,5] exposure Q, Gumbel-max over
exposures, parallel Gumbel-max over all sub-actions, gather chosen
exposure's sub-action. Only CPU transfer: N flat indices.

Add imagePullPolicy: Always to compile-services and compile-training
containers so rebuilt builder images are never stale-cached.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:31:25 +01:00
jgrusewski
34a78def71 fix(ml): hierarchical factored softmax for hyperopt eval
Flat Gumbel-max over 45 actions can collapse to a single exposure
bucket (e.g., 2/45 unique actions all in Long100) even with adequate
temperature, because order/urgency diversity doesn't produce trades.

Two-stage sampling: first Gumbel-max over 5 exposure levels (max Q
per level), then Gumbel-max over 9 order×urgency combos within the
chosen exposure. Guarantees exposure-level diversity proportional to
actual Q-value differences.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:20:06 +01:00
jgrusewski
240c144dcb feat(trading-service): implement ConfigService gRPC for dashboard config page
The dashboard's Config page was returning [unimplemented] because
trading-service had no ConfigService implementation. The API gateway's
ConfigServiceProxy forwards config RPCs to trading-service which now
handles them via direct SQL against the `configuration` table.

Implements GetConfiguration, UpdateConfiguration, DeleteConfiguration,
and ListCategories. Remaining 8 RPCs return UNIMPLEMENTED until needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:53:55 +01:00
jgrusewski
b14c60d4cc fix(ml,infra): raise eval_softmax_temp floor to 0.1, fix CI sccache TLS
- Raise eval_softmax_temp lower bound from 0.01 to 0.1 (log scale) to
  prevent near-greedy collapse in hyperopt walk-forward eval. Temps below
  0.05 produced degenerate 1-trade trials even with softmax sampling.

- Mount MinIO CA cert in CI compile pods and append to system trust store
  so sccache S3 backend can verify MinIO's self-signed TLS certificate.

- Add openssh-client to ci-builder-cpu Dockerfile (missing, broke git
  clone over SSH).

- Bump MinIO memory limits from 512Mi to 2Gi (OOMKilled under load).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:41:21 +01:00
jgrusewski
5215366b22 fix(infra): use proxy_pass for API gateway to support gRPC-web
grpc_pass only handles native gRPC (HTTP/2 frames). The web dashboard
uses gRPC-web via Connect protocol (HTTP/1.1 POST requests). Switch
api.fxhnt.ai nginx block to proxy_pass so tonic_web::GrpcWebLayer on
the API service can handle the protocol conversion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:39:20 +01:00
jgrusewski
e5bf30c494 fix(web-dashboard): switch protobuf codegen to js+dts for TS 5.9 compat
TypeScript 5.9 erasableSyntaxOnly rejects enum declarations in .ts
files (they emit runtime code). Switch protoc-gen-es from target=ts
to target=js+dts which generates .js runtime + .d.ts type declarations.

Also fix type mismatches in useApi.ts (OrderSide/OrderType enum types,
StartBacktestRequest field mapping) and MLDashboard signal defaulting.
Add web-dashboard network policy for MinIO egress + proxy ingress.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:32:41 +01:00
jgrusewski
6d731b860d fix(infra): use NodePort localhost:30500 for all container image refs
containerd on Kapsule nodes uses host DNS which can't resolve
.svc.cluster.local names. Switch all image references from
gitlab-registry.foxhunt.svc.cluster.local:5000 to localhost:30500
(NodePort on the GitLab registry). Also make training runtime image
configurable via TRAINING_RUNTIME_IMAGE env var.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:31:54 +01:00
jgrusewski
889ab4588b feat(infra): add web-dashboard k8s deployment on platform pool
- nginx pod serves Vite-built static assets from MinIO S3
- initContainer fetches dist/ from s3://foxhunt-binaries/web-dashboard/
- SPA routing via try_files, aggressive caching for hashed assets
- dashboard.fxhnt.ai proxied to web-dashboard:80 via tailscale nginx
- CI pipeline: build-web-dashboard task (npm ci + build + rclone upload)
- detect-changes: needs-dashboard output for web-dashboard/ paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:56:59 +01:00
jgrusewski
dc66780465 fix(infra): route dashboard.fxhnt.ai to web-gateway instead of Grafana
Split the combined grafana+dashboard nginx block so dashboard.fxhnt.ai
proxies to web-gateway:3000 (the actual trading dashboard) instead of
Grafana.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:53:34 +01:00
jgrusewski
68b6aa8313 feat(infra): migrate container registry from SCW to internal GitLab
Full migration off Scaleway Container Registry to internal GitLab
registry backed by MinIO S3. All 4 images (ci-builder, ci-builder-cpu,
foxhunt-runtime, foxhunt-training-runtime) rebuilt in internal registry.

Registry & images:
- All image refs → gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/
- imagePullSecrets: scw-registry → gitlab-registry
- Kaniko build template: two-step DAG (git-clone → kaniko-build) with shared PVC
- Kaniko layer cache enabled at root/foxhunt/cache
- AWS_ACCESS_KEY_ID: $SCW_ACCESS_KEY → $MINIO_ACCESS_KEY in .gitlab-ci.yml

Network policies:
- ci-pipeline: add HTTP/80, registry/5000, webservice/8181 egress rules

DNS & Tailscale proxy cleanup:
- Remove ci, prometheus, monitor DNS records (no longer exposed)
- Rename s3 → minio DNS record
- Remove Argo UI, Prometheus, monitor nginx server blocks
- Remove argo-htpasswd volume mount
- Tailscale proxy nodeSelector: infra → platform

Terraform cleanup:
- Delete infra/modules/registry/ (SCW CR namespace)
- Delete infra/modules/object-storage/ (SCW S3 buckets)
- Delete infra/modules/secrets/ (SCW secrets)
- Delete corresponding live configs
- TF state backend: S3 → GitLab HTTP

Argo workflows:
- Add events/ (GitLab push eventsource + ci-pipeline sensor)
- ci-pipeline + training templates: SCW → internal registry
- Delete obsolete compile-training-template.yaml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:52:24 +01:00
jgrusewski
32e7ba2507 feat(infra): replace PVC training data with MinIO S3 download
Training data PVCs (ReadWriteOnce) don't work with ephemeral GPU nodes
that autoscale across availability zones. Replace with rclone download
from MinIO to /tmp at the start of each GPU step.

- Remove training-data-pvc parameter and volume mount
- Add rclone download from s3:foxhunt-training-data/futures-baseline/
  to /tmp/futures-baseline/ in hyperopt, train-best, and evaluate steps
- Add MINIO_ACCESS_KEY/SECRET_KEY env vars to all GPU steps
- Add cuda-compute-cap parameter (default "90")
- Change data-dir default to /tmp/futures-baseline
- 36 .dbn.zst files (53MB total) uploaded to MinIO bucket

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:14:32 +01:00
jgrusewski
a31512643b feat(ml): multi-GPU trial parallelism for hyperopt
Add device_pool to DQN/PPO hyperopt trainers for round-robin GPU assignment
per trial. Binary detects all CUDA devices, scales VRAM budget by GPU count.
Single-GPU: no behavior change (pool of 1).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:37:58 +01:00
jgrusewski
e9d6c7825d feat(infra): sync Terraform GPU pools with actual SCW state
Rename ci-training → ci-training-l40s, add H100/H100x2/H100-SXM/H100-SXM-8
pool resources (all autoscaling 0→1). Remove dev pool (DevPod on platform).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:37:31 +01:00
jgrusewski
d7f5529ef3 fix(ci): add registry-auth volume for kaniko Docker builds
build-ci-image templateRef needs registry-auth volume defined in parent
workflow. Secret was also named wrong (scw-registry-credentials →
scw-registry) and needed key projection (.dockerconfigjson → config.json).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:02:41 +01:00
jgrusewski
e42c3d50d5 fix(ci): replace jq with grep for change detection (jq not in runtime image)
detect-changes was falling back to Cargo.toml (rebuild-all) because jq
is not installed in foxhunt-runtime:latest. Replaced with grep+sed
extraction that needs no external dependencies. Also added jq to the
Dockerfile for future use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:55:47 +01:00
jgrusewski
e99b258610 feat(ml): add eval_softmax_temp to DQN hyperopt search space (27D)
T=0.1 was too conservative for degenerate configs — trials with uniform
Q-values still collapsed to 1 unique action. Making temperature a
hyperopt parameter lets TPE learn the optimal exploration-exploitation
balance per config. Range [0.01, 2.0] log-scale.

Also sets training-workflow default gpu-pool to ci-training-h100.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:52:29 +01:00
jgrusewski
08aecb4973 feat(infra): add GitLab Agent for Kubernetes config
Registers foxhunt-production agent with CI and user access
for in-cluster kubectl from GitLab pipelines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:17:34 +01:00
jgrusewski
98c4eb1a2c fix(ci): per-component change detection to skip unnecessary compiles
Two bugs fixed:
1. jq parsed webhook as plain array but payload is {body:{commits:[...]}} —
   fell back to Cargo.toml catch-all, triggering full recompile every push.
2. Single needs-compile flag gave no granularity — now split into
   needs-services and needs-training with path-based detection.

crates/ml/ only → training + services (ml-training-service depends on ml)
services/ only → services only, skip training (saves ~5.5min CUDA compile)
infra/ only → skip both compiles entirely

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:07:51 +01:00
jgrusewski
c10340708f feat(ml): replace greedy argmax with Gumbel-max softmax for hyperopt eval
DQN hyperopt walk-forward eval collapsed to 1 trade when Q-values were
nearly uniform (greedy argmax always picked the same action). Replace
batch_greedy_actions with batch_softmax_actions using the Gumbel-max
trick (argmax(Q/T + Gumbel(0,1))) — fully GPU-resident, no CPU
softmax/sampling. Temperature 0.1: nearly greedy when Q-values are
separated, diverse when uniform. Logs unique_actions/45 per backtest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 09:57:48 +01:00
jgrusewski
32b52332c6 fix(ci): pass commits-json as env var to avoid shell escaping
The sensor passes GitLab commit data containing single quotes in
commit messages (e.g. Merge branch 'feature-name'). When embedded
directly in the shell script via Argo template substitution, this
breaks the shell syntax. Pass as a container env var instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:52:02 +01:00
jgrusewski
3c03e48829 fix(ci): add volumes to ci-pipeline for compile-training templateRef
When using templateRef, Argo only borrows the template definition — not
spec-level volumes or volumeClaimTemplates from the source WorkflowTemplate.
Add workspace VCT, sccache PVC, and git-ssh-key secret at the ci-pipeline
spec level so compile-training's sub-templates can access them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:49:37 +01:00
jgrusewski
504732b68f feat(ml): wire factored eval into hyperopt backtest loop
Replace legacy Buy/Sell/Hold collapse with exposure-aware evaluation.
Long100→Long50 now generates a partial-close trade instead of being
a no-op. Combined with graduated trade penalty, PSO now gets gradient
signal across the entire 26D search space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:43:42 +01:00
jgrusewski
997b2d7026 feat(ml): add graduated trade insufficiency penalty to hyperopt objective
Add calculate_trade_insufficiency_penalty() that produces monotonically
decreasing penalties for low trade counts (10.0 at 0 trades, 0.0 at 100+),
giving PSO gradient signal across the degenerate plateau where all trials
produce the same 1.25 objective.

Wire the penalty into extract_objective() with short-circuit for <10 trades
(skip composite score since it's all zeros anyway).

Include 4 unit tests verifying zero/one/graduated/no-plateau properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:43:42 +01:00