- 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>
102 lines
2.8 KiB
Rust
102 lines
2.8 KiB
Rust
//! TFT model integration test
|
|
//!
|
|
//! Verifies: sequence buffering -> forward pass -> quantile output
|
|
//! TFT requires sequence_length observations before producing real predictions.
|
|
//! Uses lightweight config for fast execution (<10s)
|
|
|
|
use ml::ensemble::adapters::TftInferenceAdapter;
|
|
use ml::ensemble::inference_adapter::{FeatureVector, ModelInferenceAdapter};
|
|
use ml::tft::TFTConfig;
|
|
|
|
fn small_tft_config() -> TFTConfig {
|
|
TFTConfig {
|
|
input_dim: 20,
|
|
hidden_dim: 32,
|
|
num_heads: 2,
|
|
num_layers: 1,
|
|
prediction_horizon: 5,
|
|
sequence_length: 4,
|
|
num_quantiles: 9,
|
|
num_static_features: 6,
|
|
num_known_features: 6,
|
|
num_unknown_features: 8,
|
|
dropout_rate: 0.0,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
const SEQ_LEN: usize = 4;
|
|
|
|
fn make_fv(ts: i64) -> FeatureVector {
|
|
FeatureVector {
|
|
values: vec![0.1; 51],
|
|
timestamp: ts,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_tft_adapter_buffers_before_predicting() {
|
|
let adapter = TftInferenceAdapter::new(small_tft_config(), SEQ_LEN)
|
|
.expect("TftInferenceAdapter::new should succeed");
|
|
|
|
assert_eq!(adapter.model_name(), "TFT");
|
|
assert!(
|
|
!adapter.is_ready(),
|
|
"TFT should not be ready with empty buffer"
|
|
);
|
|
|
|
// Feed SEQ_LEN - 1 feature vectors: should return neutral (0.0, 0.0)
|
|
for i in 0..(SEQ_LEN - 1) {
|
|
let pred = adapter
|
|
.predict(&make_fv(1_700_000_000 + i as i64))
|
|
.expect("predict should not error during buffering");
|
|
assert_eq!(
|
|
pred.direction, 0.0,
|
|
"Should return neutral direction while buffering"
|
|
);
|
|
assert_eq!(
|
|
pred.confidence, 0.0,
|
|
"Should return zero confidence while buffering"
|
|
);
|
|
}
|
|
|
|
assert!(!adapter.is_ready(), "TFT should still not be ready");
|
|
}
|
|
|
|
#[test]
|
|
fn test_tft_produces_valid_prediction_after_warmup() {
|
|
let adapter = TftInferenceAdapter::new(small_tft_config(), SEQ_LEN)
|
|
.expect("TftInferenceAdapter::new should succeed");
|
|
|
|
// Fill buffer
|
|
for i in 0..SEQ_LEN {
|
|
let _ = adapter.predict(&make_fv(1_700_000_000 + i as i64));
|
|
}
|
|
|
|
assert!(
|
|
adapter.is_ready(),
|
|
"TFT should be ready after filling buffer"
|
|
);
|
|
|
|
// Next prediction should produce real values
|
|
let pred = adapter
|
|
.predict(&make_fv(1_700_000_000 + SEQ_LEN as i64))
|
|
.expect("TFT predict should succeed after warmup");
|
|
|
|
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"
|
|
);
|
|
}
|