Commit Graph

43 Commits

Author SHA1 Message Date
jgrusewski
6cdfbff8d6 plan5(task2): A.4 regression-detection hard-stop on 2N consecutive error-band
Adds the convergence guardrail: every per-epoch HEALTH_DIAG metric is
checked against the bands in config/metric-bands.toml; N consecutive
warn-band epochs emit a tracing::warn; 2N consecutive error-band epochs
return Err(CommonError::RegressionDetected{...}) cleanly from the
training loop, which propagates to the train_baseline_rl subprocess
exit code (no libc::raise — clean Rust error path).

Wire-points:
- New module: crates/ml/src/trainers/dqn/trainer/monitoring.rs
  - MetricBands {warn_low, warn_high, error_low, error_high}
  - BandSettings {consecutive_epochs_for_warn, consecutive_epochs_for_error}
  - MetricBandsRegistry: load_from_toml + update_and_check
  - TerminationReason {RegressionWarn, RegressionError}
  - NaN treated as out-of-band (consecutive++; never resets streak)
  - Unknown metrics return None (silent OK per Invariant 7 audit)
- crates/common/src/error.rs: new CommonError::RegressionDetected variant
  carrying {metric, value, band, consecutive}
- crates/ml/src/trainers/dqn/trainer/constructor.rs: load
  config/metric-bands.toml at trainer init; warn-only on missing file
  (backward compat for environments without the config)
- crates/ml/src/trainers/dqn/trainer/training_loop.rs: harvest per-epoch
  metrics (parallel emit alongside HEALTH_DIAG), feed each through
  registry.update_and_check; on Some(TerminationReason::RegressionError)
  emit final HEALTH_DIAG[N]: TERMINATED_BY_REGRESSION line and return Err
- services/trading_service/src/error.rs: minimal handler for the new
  CommonError variant (existing pattern)

Validation:
- 8 unit tests in monitoring::tests pass (band logic, NaN, warn-only
  behaviour, error-streak threshold, unknown-metric, invalid TOML)
- regression_detection GPU smoke (3.19s): trainer with intentionally
  narrow train_loss error band [0, 1e-9] self-terminates at epoch 5
  after 6 consecutive error-band epochs; final HEALTH_DIAG line emits
  TERMINATED_BY_REGRESSION with metric/value/consecutive/band fields
- multi_fold_convergence smoke (650s, --release): all 3 folds train
  to completion, all 3 checkpoints saved, no false-positive
  termination on the populated metric bands. Per-fold best train
  Sharpe: F0=-9.7831 (bit-baseline), F1=25.8272, F2=39.2687. F1/F2
  on the lower end of observed noise distribution
  ({74.56, 61.10, 71.53, 25.83} for F1; {88.20, 61.57, 65.96, 39.27}
  for F2) but training healthy throughout: aux clauses fire every
  epoch, sharpe_ema recovers from F0 collapse (-9.78 → +14.8 by start
  of F2), no regression detection trips.

config/metric-bands.toml populated for the metrics emitted by
HEALTH_DIAG today (avg_q_value, train_loss, val_sharpe, train_sharpe,
aux_next_bar_mse, aux_regime_ce, isv_* slot EMAs, sharpe_ema, etc.).
Bands derived from current cleanroom smoke + permissive defaults
where only one sample exists; populate-metric-bands-from-runs.py will
tighten them after Plan 5 Task 5's multi-seed pass produces real
distributions.

Constraints honoured: GPU-only in hot path (band check is CPU-side
post-HEALTH_DIAG, off the captured graph); no atomicAdd; no stubs;
no // ok: band-aids; no tuned constants beyond the toml-loaded bands;
no .unwrap() introduced; cargo check clean at 11 warnings (workspace
baseline preserved, plus ml-dqn pre-existing 1 warning).

Audit doc: new row added documenting monitoring.rs module, the
CommonError variant, the training_loop wire-point, and the design
choice that band-checks run AFTER HEALTH_DIAG emit (not before) so
the diag log already reflects the metric values that triggered any
termination.

Plan 5 T1 (multi-seed harness) landed at c6634254e+47c8b783c; T2
(this) gives the regression hard-stop that the multi-seed final
pass (T5) consumes to bail out early on bad seeds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:49:14 +02:00
jgrusewski
b69de781b9 cleanup: delete sqlx_test placeholder and restore BrokerAdapter alias
- common/sqlx_test.rs: the entire module was a single /* ... */ block
  behind a TODO because the identifier types (`OrderId`, `TradeId`,
  `Symbol`, `AccountId`, `OrderSide`) do not derive `sqlx::Type`. The
  file has been a dead placeholder behind `#[cfg(all(test,
  feature=\"database\"))]` for a long time; delete it and drop the
  `mod sqlx_test;` declaration.
- data/brokers/mod.rs: re-enable the `pub type BrokerAdapter = Box<dyn
  BrokerClient>;` alias — the trait is already implemented by
  `InteractiveBrokersAdapter` and re-exported from the module. Delete
  the commented-out `BrokerFactory::create_client` block: it
  referenced `ICMarketsClient`, which does not exist in this crate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:37:44 +02:00
jgrusewski
3e0435bce5 fix: switch tracing to stderr (unbuffered) — stdbuf has no effect on Rust
Rust's std::io::Stdout uses internal BufWriter that bypasses libc,
making stdbuf -oL useless. stderr is unbuffered by default in Rust,
so tracing output appears immediately in container logs.

Also reverts n_episodes debug cap back to 16384.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:29:55 +02:00
jgrusewski
be8f3310d5 feat: gradient stability + 9-agent audit bug fixes
Per-component gradient clipping:
- 2 new CUDA kernels (dqn_clipped_saxpy, dqn_clip_grad)
- CQL gradient isolated into separate scratch buffer
- Budget allocation: C51=70%, CQL=15%, IQN=10%, Ens=5%
- Dynamic budget — inactive components' share goes to C51

Spectral norm extended to all 10 weight matrices:
- Was trunk-only (W_s1, W_s2), now covers all heads
- 16 u/v power iteration buffers, batched GOFF sync-back
- Uniform sigma_max, end-to-end Lipschitz bounded

Bug fixes from 9-agent audit:
- Backtest episode reset: all 8 fields (was 5), max_equity updated before floor check
- gradient_clip_norm unified: 10.0 everywhere (was 10.0 vs 1.0)
- entropy_coefficient: Option<f64> → f64, single default 0.001
- Close price: unwrap_or(1.0) → direct indexing (no silent wrong rewards)
- step_count: only increments during training (was incrementing on inference)
- Quantile loss: single CUDA context (was 3×), silent fallbacks removed
- MaybeNoisyLinear: single-variant enum removed → direct NoisyLinear
- Budget fractions: exported as pub(crate) constants, referenced not hardcoded

Monitoring:
- Per-component gradient Prometheus gauges (C51 raw, combined)
- Diagnostic logging every 1000 steps

Tests: 7 new gradient budget tests, 1 gradient bounds smoke test
All 488 tests pass (359 ml-dqn + 129 ml)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:52:44 +01:00
jgrusewski
1da1b52bcd feat: centralize bars_per_day — no more hardcoded 390.0 scattered everywhere
- Added BARS_PER_DAY, BARS_PER_YEAR, ANNUALIZATION_FACTOR to common::thresholds::time
- Added bars_per_day field to DQNHyperparameters (default 390.0, configurable)
- compute_epoch_financials() now takes bars_per_day parameter from hyperparams
- coordinator_extended.rs + ab_testing.rs use centralized ANNUALIZATION_FACTOR
- When switching to tick data, change one constant in common/thresholds.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:17:20 +01:00
jgrusewski
ca0a6b9c60 Merge branch 'worktree-branching-dqn-plan' 2026-03-11 08:46:58 +01:00
jgrusewski
6ddcb52825 perf(ci): fix full workspace rebuild — move version from compile-time to runtime
Root cause: `option_env!("FOXHUNT_BUILD_VERSION")` in common/build_info.rs
was a compile-time macro tracked by cargo fingerprints (Rust 1.80+). Every
pipeline run with a new tag invalidated `common` → cascading rebuild of all
38 dependent workspace crates, even when zero source files changed.

Fix: replace `option_env!()` with `std::env::var()` (runtime LazyLock). Cargo
no longer tracks the version env var, so `common` only recompiles when its
source actually changes.

Also: skip git checkout when HEAD already matches target SHA (zero mtime
changes), and drop `-x` from git clean to preserve gitignored files.

Expected: ~3.7min → <1min for unchanged-crate rebuilds on warm PVC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 02:10:20 +01:00
jgrusewski
a5ea8713b6 feat(dqn): Branching DQN + KernelWeightPack + metrics propagation
Branching DQN (Tavakoli et al., 2018):
- 3 independent advantage heads (exposure=5, order=3, urgency=3) in CUDA kernel
- `use_branching` as hyperopt-tunable parameter (index 11 in 29D space)
- Branch weight pointers packed into KernelWeightPack struct

KernelWeightPack refactor:
- 87→38 CUDA kernel params by packing 48 weight pointers into 384-byte #[repr(C)] struct
- UNPACK_WEIGHT_PTRS macro in CUDA header for clean kernel-side access
- Single .arg(&weight_pack) replaces 48 individual .arg() calls

Hyperopt metrics in JSON output:
- Added `metrics: Option<serde_json::Value>` to TrialResult<P>
- `extract_metrics()` trait method with default None (DQN overrides)
- JSON output now includes per-trial backtest metrics (Sharpe, Sortino,
  Calmar, Omega, drawdown, win_rate, trades) + top-level best_metrics

Prometheus backtest gauges (Rust-side, no CUDA):
- 8 new gauges: foxhunt_hyperopt_best_{sharpe,sortino,calmar,omega,
  max_drawdown_pct,win_rate,total_trades,total_return_pct}

Dynamic episode scaling:
- Removed hardcoded 256 episode cap on small GPUs
- VRAM budget calculation in optimal_n_episodes() handles scaling naturally
- Removed stale .min(256) in PPO trainer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 00:46:53 +01:00
jgrusewski
7ef92983f9 fix(clippy): apply cargo clippy --fix across workspace
Mechanical auto-fixes: redundant borrows, clone on Copy, or_insert_with,
single-char push_str, get(0) → first(), needless borrow, let_and_return.
150 files, no behavior changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:17:51 +01:00
jgrusewski
0355fb17bc perf(metrics): expose step-level DQN metrics to Prometheus gauges
Move Q-value stats, gradient norm, and training step from tracing::info!
(stdout-only, block-buffered, invisible mid-epoch) to Prometheus gauges
(atomic set, scrapable every 15s). Downgrade formatted step logs to
debug! level to eliminate allocation/serialization overhead in the hot path.

New gauge: foxhunt_training_step (current step within epoch)
Updated: foxhunt_training_q_value_mean/max, foxhunt_training_gradient_norm
now update every 500 steps instead of only at epoch boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 18:44:29 +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
c318ffa30a fix(ml): 3 hyperopt objective bugs — sliding windows, tanh normalization, adaptive tau
1. Walk-forward windows: replaced 3 non-overlapping with sliding (50% overlap, ~5 windows).
   Aggregation changed from mean-0.5*std to median-0.5*IQR for outlier robustness.

2. Composite score: tanh normalization prevents Calmar ratio scale dominance
   (0.02% drawdowns → values in thousands drowning out Sharpe/Sortino).

3. Q-value overestimation: new Prometheus gauge foxhunt_training_q_overestimation_ratio,
   warning log when ratio>10 or q_mean>5, adaptive tau doubles when Q-mean growth>0.5/epoch
   (capped at 0.01), decays back when stable.

2742 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:14:31 +01:00
jgrusewski
06a875e6fc fix(metrics): push training metrics to pushgateway before pod exit
Ephemeral Argo workflow pods terminate after training completes, causing
Prometheus to lose all scraped metrics. Add push_to_gateway() to POST
final metrics to the existing pushgateway service so they persist on the
Grafana training dashboard after pod completion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:32:09 +01:00
jgrusewski
08d070bb95 refactor: rename JWT issuer foxhunt-api-gateway → foxhunt-api, drop compat aliases
- JWT issuer now foxhunt-api across all 16 files (services, tests, config, docker-compose)
- Remove serde alias api_gateway_url from FxtConfig (no backwards compat)
- Remove api_gateway CLI alias from e2e orchestrator
- All services must deploy simultaneously for JWT validation to match

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:22:04 +01:00
jgrusewski
075f715fa1 refactor: replace all api_gateway/web-gateway references across codebase
- Rename test functions, variables, bench names (api_gateway → api)
- Update e2e orchestrator executable path (target/debug/api)
- Clean Grafana dashboards: remove dead web-gateway panels, fix trailing
  pipes/commas, update pod selectors
- Update metrics_validation test metric prefixes (api_gateway_ → api_)
- Fix .gitlab-ci.yml remaining path reference
- Fix FXT CLI test flag and function names
- Keep JWT issuer foxhunt-api-gateway (cross-service auth compat)
- Keep serde alias api_gateway_url (config backwards compat)

cargo check --workspace passes cleanly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:18:58 +01:00
jgrusewski
9d88898722 feat(common): add build_info module with CalVer version function
CI sets FOXHUNT_BUILD_VERSION (e.g. 2026.03.408); local dev falls
back to CARGO_PKG_VERSION.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:54:57 +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
jgrusewski
8a77a2d700 style(common): add clippy allow for too_many_arguments on epoch metrics
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:47:22 +01:00
jgrusewski
6e8cb318d7 feat(common): add 11 Prometheus gauges for epoch financial metrics
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:42:35 +01:00
jgrusewski
864cf8690d fix(dqn,ppo): prevent 45-action collapse with entropy, exploration, and monitoring fixes
DQN: wire SAC-style entropy regularizer into TD loss, fix diversity
penalty normalization from ln(3) to ln(45), add epsilon floor (0.05)
for noisy nets, enable sigma scheduling (0.8→0.4 with 0.3 floor),
add UCB count-based exploration bonus, expand hyperopt search space
from 43D to 45D.

PPO: replace fabricated entropy metric (value_loss×0.5) with real
Shannon entropy from action distribution, fix EntropyRegularizer
normalization from ln(3) to ln(45), implement Monte Carlo entropy
estimate for flow policy (was returning zeros), fix hyperopt VRAM
bound from 3 to 45 actions.

Monitoring: add normalized action entropy and diversity Prometheus
gauges, add exploration diagnostics logging per epoch.

2503 ml tests pass, 277 common tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 02:04:22 +01:00
jgrusewski
b08b67a5cd feat(metrics): register 17 new Prometheus metrics with verbose gating
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:56:51 +01:00
jgrusewski
354cd5c116 feat(metrics): add 6 hyperopt Prometheus metrics to training_metrics
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:18:50 +01:00
jgrusewski
64ca8f97ce fix(observability): dedicated OTLP runtime for sync training binaries
The batch span processor needs a tokio runtime for gRPC transport and
periodic flush. Async services already have one via #[tokio::main], but
sync training binaries (hyperopt, train, evaluate) don't.

Previous approach (making binaries async with #[tokio::main]) caused
"Cannot start a runtime from within a runtime" panics because the ML
crate's internal code creates its own tokio runtimes for block_on().

New approach: build_otel_tracer() detects runtime context via
Handle::try_current(). If absent, it creates a dedicated 1-worker
multi-thread runtime stored in a process-lifetime OnceLock. The worker
thread actively polls the OTLP batch export task.

Reverts training binaries to sync fn main() so internal runtime creation
(hyperopt adapters, DQN/PPO trainers) continues working as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 18:54:10 +01:00
jgrusewski
0ac8aeee52 refactor(common): make init_observability sync with optional OTLP endpoint
Remove unnecessary async from init_observability -- body was fully sync.
Change otlp_endpoint from &str to Option<&str> -- when None, OTLP layer
is skipped (fmt-only mode). Update all 8 service callers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:03:13 +01:00
jgrusewski
c2118b3073 refactor: split common/types.rs (4909 LOC) into 10 focused modules
Split god file into: aliases.rs, domain.rs, events.rs, identifiers.rs,
market_data.rs, market.rs, service.rs, trading_enums.rs, type_error.rs.
Re-exports via mod.rs maintain backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:32:52 +01:00
jgrusewski
7a602274cd refactor: consolidate RetryConfig to common::resilience
The storage crate had its own RetryConfig struct (max_attempts,
initial_delay, max_delay, backoff_multiplier) duplicating
common::resilience::retry::RetryConfig.

Added backoff_multiplier field to common's RetryConfig and updated
storage to re-export and use common's version with its field names
(max_retries, base_delay). Updated all storage tests accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:45:59 +01:00
jgrusewski
61950e4c05 refactor: consolidate ErrorSeverity to common crate
Three duplicate ErrorSeverity enums (data/error.rs, data/validation.rs,
database/error.rs) with variants Low/Medium/High/Critical now use the
canonical definition in common::error::ErrorSeverity.

Extended common's ErrorSeverity with Low, Medium, High variants (alongside
existing Debug, Info, Warn, Error, Critical) and added PartialOrd/Ord derives
so both severity models coexist in a single type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:39:54 +01:00
jgrusewski
4eb710785f feat(ml): add EnsembleModelAdapter + build_production_strategy(), harden metrics server
- Create EnsembleModelAdapter in ml::ensemble wrapping model IDs
- Add build_production_strategy() factory: 10-model ensemble with
  ProductionFeatureExtractorAdapter + graceful degradation (zero confidence
  when no checkpoints loaded)
- Wire backtesting_service to use the factory function
- Harden metrics HTTP server: 5s read timeout, 8KB request limit,
  correct Content-Type charset=utf-8

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:55:56 +01:00
jgrusewski
5401723118 refactor(common): delete MLFeatureExtractor + SimpleDQNAdapter, refactor SharedMLStrategy
- Delete MLFeatureExtractor (1,294 lines) and SimpleDQNAdapter (235 lines)
- Delete 830 lines of inline tests for deleted types
- Remove legacy_feature_extractor field from SharedMLStrategy
- Replace new() and new_with_production_extractor() with new(extractor, models, threshold)
- Single constructor accepts injected models via Vec<Box<dyn MLModelAdapter>>
- Update all callers: backtesting_service, 2 integration tests, 2 trading_service tests
- Fix doc comments referencing MLFeatureExtractor
- Fix feature count test: real extractor produces 51 features, not 225

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:47:32 +01:00
jgrusewski
3da361b5b8 fix(common): resolve all clippy errors in test targets
- Fix 36 manual_range_contains: x >= lo && x <= hi → (lo..=hi).contains(&x)
- Fix 10 useless_vec: vec![...] → [...] where array suffices
- Fix 10 unused variables: for i in → for _ in
- Fix 6 missing .unwrap() on Result-returning constructors
- Fix 4 float_cmp: wrap in .abs() for epsilon comparisons
- Fix 2 assertions_on_constants: remove assert!(true)
- Fix 2 collapsible_if: merge nested ifs with &&
- Fix 1 clamp pattern: .max().min() → .clamp()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:02:50 +01:00
jgrusewski
7a01618e86 fix(infra): upgrade OpenTelemetry 0.27→0.31 to align tonic versions, fix DCGM scheduling
OTLP fix: opentelemetry-otlp 0.27 bundled tonic 0.12 while the workspace
uses tonic 0.14, making with_channel() impossible (type mismatch). Upgrading
to otel-otlp 0.31 aligns both on tonic 0.14, enabling explicit Channel
construction that respects http:// scheme (no spurious TLS negotiation).

API migrations (otel 0.27→0.31):
- TracerProvider → SdkTracerProvider
- with_batch_exporter(exporter, runtime) → with_batch_exporter(exporter)
- Resource::new(vec![...]) → Resource::builder().with_service_name().build()
- global::shutdown_tracer_provider() removed (Drop-based shutdown)
- opentelemetry-otlp feature "tonic" → "grpc-tonic"

DCGM fix: remove runtimeClassName: nvidia from DaemonSet — Scaleway Kapsule
GPU pools use nvidia runtime as default containerd handler. The RuntimeClass
CRD is only created by the full GPU Operator, not the device plugin alone.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 16:31:22 +01:00
jgrusewski
7744becdd5 feat(training): move metrics to common::metrics, fix all clippy errors
Move Prometheus training metrics from example-local baseline_common/
to common::metrics::{server,training_metrics} following the existing
grpc_metrics.rs pattern. Fix 29 let_underscore_must_use clippy errors
in push_metrics.rs, 3 shadow lint errors in training binaries, and
demote gradient clipping log from warn to debug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:56:43 +01:00
jgrusewski
a958582eaa feat(common): add gRPC metrics Tower layer for automatic instrumentation
Add a reusable GrpcMetricsLayer that can be applied to any tonic server
via Server::builder().layer() to automatically emit three Prometheus
metric families for every gRPC handler: started_total, handled_total
(with status code), and handling_seconds histogram.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 02:23:36 +01:00
jgrusewski
0c7f6362d9 fix(observability): combine fmt + OTLP layers in single subscriber
Previously init_observability() called init_json_logger() (which set the
global subscriber with a fmt layer) and then init_tracing() (which only
set the global tracer provider). Because no tracing_opentelemetry::layer()
was ever added to the subscriber, #[instrument] spans never reached Tempo.

Now init_observability() builds a single Registry subscriber that
composes an EnvFilter, a JSON fmt layer, and an optional OTLP layer
(via tracing_opentelemetry). The OTLP layer gracefully degrades to None
when Tempo is unavailable, so logging always works.

Also adds build_otel_tracer() to tracing_config.rs which returns
Option<Tracer> for the caller to wrap in an OpenTelemetryLayer inside
their subscriber stack, letting the S type parameter be inferred
correctly by the compiler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 00:32:35 +01:00
jgrusewski
1aef51f99b fix(stubs): implement 15 production stubs, fix routing, delete placeholders
Web-gateway routing:
- Point TRADING_SERVICE_URL at api-gateway (proto mismatch fix)
  Web-gateway uses foxhunt.tli.TradingService proto but was connecting
  directly to trading-service which implements trading.TradingService.
  api-gateway already proxies Subscribe* → Stream* correctly.

GitLab KAS:
- Disable gitlab_kas in appConfig to stop sidekiq NotifyGitPushWorker
  errors (KAS pod was already disabled but Rails still tried to connect)

Trading service monitoring (3 stubs → real):
- AcknowledgeAlert: real alert lookup + state mutation in shared store
- GetActiveAlerts: returns actual active alerts from in-memory store
- StreamAlerts: now persists generated alerts (capped at 1000 entries)

Trading service ML streams (2 stubs → real):
- StreamModelMetrics: emits real inference_count, error_count, latency
  per model every N seconds from the RuntimeModelInfo registry
- StreamSignalStrength: emits per-symbol signal aggregation from model
  ensemble weights and latency confidence

Backtesting service:
- stop_backtest: real CancellationToken cancellation (was no-op)
  Tokens stored per-backtest, execute_backtest wraps strategy call
  in tokio::select! for immediate cancellation

Deleted 7 empty placeholder files:
- 4 Wave D regime stubs (dynamic_stops, ensemble, performance_tracker,
  position_sizer) — comment-only files, never wired
- 2 Wave 3 feature stubs (microstructure, statistical)
- 1 PPO stub (unified_ppo.rs — empty struct definitions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:47:31 +01:00
jgrusewski
6b94045bcd fix(observability): switch to containerd stdout logging, fix stream retry
- Disable file logging in init_observability — use console-only (stdout).
  Containerd captures stdout, Promtail ships to Loki. Eliminates
  "Failed to create log directory: Permission denied" errors on 3 services.
- Remove LOG_DIR env vars and /app/logs emptyDir volumes from 5 deployments.
- Add OTEL_EXPORTER_OTLP_ENDPOINT=tempo to 5 services that were missing it
  (api-gateway, web-gateway, data-acquisition, trading-agent, broker-gateway).
- Make OTLP tracing init non-fatal — services degrade gracefully if Tempo is
  unavailable instead of failing entire observability stack.
- Stop web-gateway gRPC stream retry on Unimplemented status. When trading-service
  doesn't implement streaming endpoints, log once and stop instead of retrying
  every 60s indefinitely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:49:57 +01:00
jgrusewski
ada33cf181 fix(observability): use LOG_DIR env for file logging, add RBAC for K8s job dispatch
- init_observability now reads LOG_DIR env var for log directory (falls back
  to relative logs/ path). Gracefully disables file logging if directory
  cannot be created instead of failing the entire observability init.
- Add ServiceAccount, Role, and RoleBinding for ml-training-service to
  create/get/list/watch/delete batch/v1 Jobs (K8s training dispatch).
- Add LOG_DIR=/app/logs env to ml-training-service, trading-service, and
  backtesting-service deployment YAMLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 21:21:13 +01:00
jgrusewski
acf77461cb feat(common): re-export financial_types as FinancialPrice/Quantity/Money/Ratio
Aliased re-exports avoid collision with existing types::Price/Quantity/Money
during migration. Will rename to canonical names in Phase 6 cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 09:49:00 +01:00
jgrusewski
c745693420 fix(common): seal Ratio finite invariant and i128→i64 saturation
- Ratio arithmetic (Add, Sub, Mul, Div) now clamps overflow to
  f64::MAX/f64::MIN instead of producing Inf from finite inputs
- Cross-type Price*Quantity→Money and Money/Quantity→Price use
  saturating i128→i64 conversion via clamp instead of silent truncation
- Added clamp_finite() and saturating_i128_to_i64() helper functions
- Added 9 edge-case tests: 89/89 passing, clippy clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 09:47:52 +01:00
jgrusewski
0d68bfdd11 fix(common): add serde derives, preserve Ratio finite invariant
- Add Serialize/Deserialize to all four financial types
- Ratio::ln returns 0.0 for non-positive inputs (prevents NaN leak)
- Ratio::exp clamps to f64::MAX on overflow (prevents Inf leak)
- Ratio::powi clamps to f64::MAX/MIN on overflow
- Add 4 invariant-preservation tests (80 total)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 09:39:22 +01:00
jgrusewski
85eab3eca2 feat(common): add financial_types module (Price/Quantity/Money/Ratio i64/6dp)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 09:33:13 +01:00
jgrusewski
265bd2441c fix(ml,ci): zero-dim guards on all 10 models, eliminate warnings, unblock CI parallelism
- Add dimension validation in DQN, PPO, Mamba2, TGGN, TLOB, Liquid,
  KAN, xLSTM, Diffusion constructors (fail-fast on zero-dim inputs
  that would cause CUDA_ERROR_INVALID_VALUE at runtime)
- Add num_unknown_features > 0 guard to TFT (temporal input required)
- Fix 12 dead-code/unused warnings in test compilation
- Remove opt-level=3 and codegen-units=1 from target rustflags
  (was forcing O3 + single-thread codegen on dev/test builds)
- Remove hardcoded jobs=16 cap (cargo now auto-detects CPU count)
- Switch linker to clang+lld (2-5x faster linking)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:38:43 +01:00
jgrusewski
9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00