- DQN: adapter creation, deterministic inference, varied inputs - PPO: adapter creation, deterministic inference, short input padding - TFT: sequence buffering, valid prediction after warmup - Mamba2: sequence buffering, valid prediction, deterministic SSM - Ensemble: all 4 models -> EnsembleCoordinator -> trading decision - Smoke: full pipeline with 10 sequential predictions, stability check This establishes the production baseline proving the ML pipeline works end-to-end with all 4 models contributing to ensemble decisions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
89 lines
2.5 KiB
Rust
89 lines
2.5 KiB
Rust
//! PPO model integration test
|
|
//!
|
|
//! Verifies: create model -> forward pass -> valid output with padding
|
|
//! Uses lightweight config for fast execution (<10s)
|
|
|
|
use ml::ensemble::adapters::PpoInferenceAdapter;
|
|
use ml::ensemble::inference_adapter::{FeatureVector, ModelInferenceAdapter};
|
|
use ml::ppo::ppo::PPOConfig;
|
|
|
|
fn small_ppo_config() -> PPOConfig {
|
|
PPOConfig {
|
|
state_dim: 64,
|
|
num_actions: 45,
|
|
policy_hidden_dims: vec![32, 32],
|
|
value_hidden_dims: vec![32, 32],
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_ppo_adapter_produces_valid_prediction() {
|
|
let adapter =
|
|
PpoInferenceAdapter::new(small_ppo_config()).expect("PpoInferenceAdapter::new should succeed");
|
|
|
|
assert_eq!(adapter.model_name(), "PPO");
|
|
assert!(adapter.is_ready());
|
|
|
|
// 51-dim input gets zero-padded to state_dim=64
|
|
let fv = FeatureVector {
|
|
values: vec![0.1; 51],
|
|
timestamp: 1_700_000_000_000_000,
|
|
};
|
|
|
|
let pred = adapter.predict(&fv).expect("PPO predict should succeed");
|
|
|
|
assert!(
|
|
pred.direction >= -1.0 && pred.direction <= 1.0,
|
|
"direction {} out of [-1,1]",
|
|
pred.direction
|
|
);
|
|
assert!(
|
|
pred.confidence >= 0.0 && pred.confidence <= 1.0,
|
|
"confidence {} out of [0,1]",
|
|
pred.confidence
|
|
);
|
|
assert!(pred.direction.is_finite(), "direction must not be NaN/Inf");
|
|
assert!(
|
|
pred.confidence.is_finite(),
|
|
"confidence must not be NaN/Inf"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ppo_deterministic_inference() {
|
|
let adapter =
|
|
PpoInferenceAdapter::new(small_ppo_config()).expect("PpoInferenceAdapter::new should succeed");
|
|
|
|
let fv = FeatureVector {
|
|
values: vec![0.3; 51],
|
|
timestamp: 1_700_000_000_000_000,
|
|
};
|
|
|
|
let pred1 = adapter.predict(&fv).expect("predict 1");
|
|
let pred2 = adapter.predict(&fv).expect("predict 2");
|
|
|
|
assert_eq!(
|
|
pred1.direction, pred2.direction,
|
|
"PPO inference should be deterministic"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ppo_handles_short_input() {
|
|
let adapter =
|
|
PpoInferenceAdapter::new(small_ppo_config()).expect("PpoInferenceAdapter::new should succeed");
|
|
|
|
// 10-dim input (much shorter than state_dim=64) should still work via padding
|
|
let fv = FeatureVector {
|
|
values: vec![0.5; 10],
|
|
timestamp: 1_700_000_000_000_000,
|
|
};
|
|
|
|
let pred = adapter
|
|
.predict(&fv)
|
|
.expect("PPO should handle short input via padding");
|
|
assert!(pred.direction.is_finite());
|
|
assert!(pred.confidence.is_finite());
|
|
}
|