5.8 KiB
ML Inference Production Cleanup Design
Date: 2026-03-01
Branch: feature/training-prometheus-metrics
Scope: Remove legacy ML code, refactor SharedMLStrategy to use real model inference, complete training metrics instrumentation
Problem Statement
The ML inference layer has accumulated legacy scaffolding that was useful during development but is now dead code in production:
- SimpleDQNAdapter — a fake linear model with hardcoded weights, never used by any production path (only tests)
- MLFeatureExtractor — a legacy 66-feature extractor superseded by ProductionFeatureExtractorAdapter (225 features)
- TrainingMetricsPusher — a Pushgateway client with zero consumers
- SharedMLStrategy hardcodes
SimpleDQNAdapterin both constructors, making it impossible to inject real models - Hyperopt binaries emit only 4/18 Prometheus metrics (lifecycle signals only, zero training loop metrics)
Both the backtesting service and trading service already use SharedMLStrategy with ProductionFeatureExtractorAdapter, but predictions come from a hardcoded linear model instead of real trained checkpoints.
Design
Section 1: Dead Code Removal (~1,500 lines)
Delete from crates/common/src/ml_strategy.rs:
SimpleDQNAdapterstruct + all impls (lines 1310-1543)MLFeatureExtractorstruct + all impls (lines 1-1293 approximately)- All inline
#[cfg(test)]tests for both (lines 2494-2671)
Delete test/bench files:
crates/common/tests/volume_indicators_test.rscrates/common/tests/volume_indicators_integration_test.rscrates/common/tests/macd_tests.rscrates/common/benches/ml_strategy_bench.rsservices/backtesting_service/tests/ml_strategy_backtest_test.rs
Delete:
crates/ml/src/training/push_metrics.rs- Remove
pub mod push_metrics;fromcrates/ml/src/training.rs
Fix remaining test files:
crates/common/tests/ml_strategy_integration_tests.rs— rewrite to test SharedMLStrategy with mock adaptersservices/trading_service/tests/ml_order_service_tests.rs— remove SimpleDQNAdapter usage
Fix re-exports:
crates/common/src/lib.rs— remove MLFeatureExtractor + SimpleDQNAdapter re-exports- Doc comments in
crates/ml/src/features/config.rsreferencing MLFeatureExtractor
Section 2: Refactor SharedMLStrategy
Keep: MLModelAdapter trait (correct abstraction).
Change constructor:
impl SharedMLStrategy {
/// Create strategy with injected models and production feature extractor
pub fn new_with_models(
extractor: Box<dyn ProductionFeatureExtractor225>,
models: Vec<Box<dyn MLModelAdapter>>,
min_confidence_threshold: f64,
) -> Result<Self, CommonError>
}
Delete: new() constructor (legacy 66-feature path) and new_with_production_extractor() (hardcodes SimpleDQNAdapter).
Section 3: Real Model Adapter in ml/
New file: crates/ml/src/ensemble/model_adapter.rs
Create EnsembleModelAdapter that implements common::ml_strategy::MLModelAdapter:
- Wraps the model registry (
get_global_registry()) - Calls real model inference via loaded checkpoints
- Returns graceful empty predictions if no models are loaded
Factory function:
pub fn build_production_strategy(
min_confidence_threshold: f64,
) -> Result<SharedMLStrategy> {
let extractor = Box::new(ProductionFeatureExtractorAdapter::new());
let registry = get_global_registry();
let models: Vec<Box<dyn MLModelAdapter>> = registry.available_models()
.into_iter()
.map(|id| Box::new(EnsembleModelAdapter::new(id, registry.clone())) as _)
.collect();
SharedMLStrategy::new_with_models(extractor, models, min_confidence_threshold)
}
Section 4: Update Callers
services/backtesting_service/src/ml_strategy_engine.rs— useml::ensemble::build_production_strategy()crates/backtesting/src/strategy_runner.rs— sameservices/trading_agent_service/src/service.rs— replaceMLFeatureExtractor::new()withProductionFeatureExtractorAdapterservices/trading_agent_service/src/assets.rs— remove unused_feature_extractorparameter
Section 5: Complete Hyperopt Instrumentation
Add training_metrics:: calls inside optimization loops:
hyperopt_baseline_supervised.rs— 8 model hyperopt functions (TFT, Mamba2, Liquid, TGGN, TLOB, KAN, xLSTM, Diffusion)hyperopt_baseline_rl.rs— DQN and PPO hyperopt functions
Fill missing metrics in training binaries:
set_batches_per_second()in supervised trainerrecord_gradient_explosion()detection in all 4 binaries (check for inf/very large loss)record_feature_error()in data loading paths
Section 6: Metrics Server Hardening
In crates/common/src/metrics/server.rs:
- Set 5-second read timeout:
stream.set_read_timeout(Some(Duration::from_secs(5))) - Limit request line to 8KB:
reader.take(8192).read_line(...) - Fix Content-Type:
text/plain; version=0.0.4; charset=utf-8
Verification
SQLX_OFFLINE=true cargo check --workspace— zero errorsSQLX_OFFLINE=true cargo clippy -p common -p ml --all-targets -- -D warnings— zero warningsSQLX_OFFLINE=true cargo test -p common --lib— all passSQLX_OFFLINE=true cargo test -p ml --lib— all pass- All 4 training examples compile
curl localhost:9094/metricsshows all 18foxhunt_training_*metrics
Risk Assessment
| Change | Risk | Mitigation |
|---|---|---|
| Delete MLFeatureExtractor | LOW | Only test/trading_agent consumers, both legacy |
| Delete SimpleDQNAdapter | LOW | Zero production consumers |
| Refactor SharedMLStrategy constructor | MEDIUM | Update all callers (4 files) |
| EnsembleModelAdapter + model registry | MEDIUM | Graceful fallback if no checkpoints loaded |
| Hyperopt instrumentation | LOW | Additive changes only |
| Metrics server hardening | LOW | Additive safety checks |