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>
703 lines
23 KiB
Rust
703 lines
23 KiB
Rust
//! ML Model Accuracy Validation Test Suite
|
|
//! TARGET: 95% coverage for ML model components
|
|
//!
|
|
//! Tests all critical ML model functionality including:
|
|
//! - Rainbow DQN convergence and edge cases
|
|
//! - Ensemble coordinator model selection
|
|
//! - Memory management under pressure
|
|
//! - Inference pipeline error handling
|
|
//! - Prediction consistency and accuracy
|
|
|
|
use proptest::prelude::*;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use std::collections::HashMap;
|
|
|
|
#[cfg(test)]
|
|
mod rainbow_dqn_tests {
|
|
use super::*;
|
|
|
|
/// Test Rainbow `DQN` agent convergence
|
|
#[test]
|
|
fn test_rainbow_dqn_convergence() {
|
|
let mut agent = create_test_rainbow_agent();
|
|
let mut total_reward = 0.0;
|
|
let mut episode_rewards = Vec::new();
|
|
|
|
// Train for multiple episodes
|
|
for episode in 0..100 {
|
|
let mut episode_reward = 0.0;
|
|
let mut state = create_random_market_state();
|
|
|
|
for step in 0..50 {
|
|
let action = agent.select_action(&state);
|
|
let (next_state, reward, done) = simulate_market_step(&state, action);
|
|
|
|
agent.store_transition(state.clone(), action, reward, next_state.clone(), done);
|
|
|
|
if agent.should_train() {
|
|
let loss = agent.train();
|
|
assert!(loss.is_finite(), "Training loss must be finite, got {}", loss);
|
|
assert!(loss >= 0.0, "Training loss must be non-negative, got {}", loss);
|
|
}
|
|
|
|
episode_reward += reward;
|
|
state = next_state;
|
|
|
|
if done {
|
|
break;
|
|
}
|
|
}
|
|
|
|
episode_rewards.push(episode_reward);
|
|
total_reward += episode_reward;
|
|
|
|
// Check for convergence every 20 episodes
|
|
if episode > 20 && episode % 20 == 0 {
|
|
let recent_avg = episode_rewards[episode-19..].iter().sum::<f64>() / 20.0;
|
|
let early_avg = episode_rewards[0..20].iter().sum::<f64>() / 20.0;
|
|
|
|
// Expect improvement over time
|
|
assert!(recent_avg > early_avg * 0.8,
|
|
"Model should show learning progress: recent_avg={}, early_avg={}",
|
|
recent_avg, early_avg);
|
|
}
|
|
}
|
|
|
|
// Verify final performance
|
|
let final_avg = episode_rewards[80..].iter().sum::<f64>() / 20.0;
|
|
assert!(final_avg > -50.0, "Final performance should be reasonable, got {}", final_avg);
|
|
}
|
|
|
|
/// Test Rainbow `DQN` edge cases and error handling
|
|
#[test]
|
|
fn test_rainbow_dqn_edge_cases() {
|
|
let mut agent = create_test_rainbow_agent();
|
|
|
|
// Test with extreme market conditions
|
|
let extreme_states = vec![
|
|
create_extreme_volatility_state(),
|
|
create_zero_volume_state(),
|
|
create_gap_up_state(),
|
|
create_flash_crash_state(),
|
|
];
|
|
|
|
for state in extreme_states {
|
|
let action = agent.select_action(&state);
|
|
|
|
// Actions should always be valid
|
|
assert!(action >= 0 && action < agent.num_actions(),
|
|
"Action {} out of valid range [0, {})", action, agent.num_actions());
|
|
|
|
// Agent should handle extreme states without crashing
|
|
let q_values = agent.get_q_values(&state);
|
|
assert!(q_values.len() == agent.num_actions(), "Q-values length mismatch");
|
|
assert!(q_values.iter().all(|&q| q.is_finite()), "All Q-values must be finite");
|
|
}
|
|
|
|
// Test memory boundary conditions
|
|
for _ in 0..agent.replay_buffer_capacity() + 100 {
|
|
let state = create_random_market_state();
|
|
let action = agent.select_action(&state);
|
|
let (next_state, reward, done) = simulate_market_step(&state, action);
|
|
agent.store_transition(state, action, reward, next_state, done);
|
|
}
|
|
|
|
// Verify buffer doesn't exceed capacity
|
|
assert!(agent.replay_buffer_size() <= agent.replay_buffer_capacity());
|
|
}
|
|
|
|
/// Test ensemble coordinator model selection
|
|
#[test]
|
|
fn test_ensemble_coordinator_selection() {
|
|
let coordinator = create_test_ensemble_coordinator();
|
|
|
|
// Add models with different performance characteristics
|
|
coordinator.add_model("conservative", Box::new(ConservativeModel::new()));
|
|
coordinator.add_model("aggressive", Box::new(AggressiveModel::new()));
|
|
coordinator.add_model("adaptive", Box::new(AdaptiveModel::new()));
|
|
|
|
let test_states = vec![
|
|
create_trending_market_state(),
|
|
create_sideways_market_state(),
|
|
create_volatile_market_state(),
|
|
];
|
|
|
|
for state in test_states {
|
|
let selected_model = coordinator.select_best_model(&state);
|
|
assert!(selected_model.is_some(), "Coordinator should always select a model");
|
|
|
|
let prediction = coordinator.predict(&state);
|
|
assert!(prediction.confidence >= 0.0 && prediction.confidence <= 1.0,
|
|
"Prediction confidence must be in [0,1], got {}", prediction.confidence);
|
|
|
|
assert!(prediction.action_probabilities.len() == coordinator.num_actions());
|
|
let prob_sum: f64 = prediction.action_probabilities.iter().sum();
|
|
assert!((prob_sum - 1.0).abs() < 1e-6, "Action probabilities must sum to 1.0, got {}", prob_sum);
|
|
}
|
|
}
|
|
|
|
/// Property-based test for prediction consistency
|
|
proptest! {
|
|
#[test]
|
|
fn test_prediction_consistency(
|
|
price in 1.0f64..2.0f64,
|
|
volume in 1000.0f64..1_000_000.0f64,
|
|
volatility in 0.01f64..0.5f64,
|
|
trend in -0.1f64..0.1f64
|
|
) {
|
|
let agent = create_test_rainbow_agent();
|
|
let state = MarketState {
|
|
price,
|
|
volume,
|
|
volatility,
|
|
trend,
|
|
timestamp: std::time::SystemTime::now(),
|
|
};
|
|
|
|
// Same state should produce same action (in deterministic mode)
|
|
agent.set_deterministic(true);
|
|
let action1 = agent.select_action(&state);
|
|
let action2 = agent.select_action(&state);
|
|
prop_assert_eq!(action1, action2, "Deterministic predictions must be consistent");
|
|
|
|
// Q-values should be stable for same state
|
|
let q_values1 = agent.get_q_values(&state);
|
|
let q_values2 = agent.get_q_values(&state);
|
|
for (q1, q2) in q_values1.into_iter().zip(q_values2.into_iter()) {
|
|
prop_assert!((q1 - q2).abs() < 1e-6, "Q-values must be deterministic");
|
|
}
|
|
|
|
// Predictions should be bounded
|
|
prop_assert!(action1 < agent.num_actions(), "Action must be within valid range");
|
|
prop_assert!(q_values1.iter().all(|&q| q.is_finite()), "All Q-values must be finite");
|
|
}
|
|
}
|
|
|
|
/// Test memory management under pressure
|
|
#[test]
|
|
fn test_memory_management_under_pressure() {
|
|
let agent = create_test_rainbow_agent();
|
|
let start_memory = get_memory_usage();
|
|
|
|
// Simulate high-frequency training with many transitions
|
|
for batch in 0..1000 {
|
|
// Add many transitions rapidly
|
|
for i in 0..100 {
|
|
let state = create_random_market_state();
|
|
let action = agent.select_action(&state);
|
|
let (next_state, reward, done) = simulate_market_step(&state, action);
|
|
agent.store_transition(state, action, reward, next_state, done);
|
|
}
|
|
|
|
// Train frequently
|
|
if batch % 10 == 0 {
|
|
let loss = agent.train();
|
|
assert!(loss.is_finite(), "Training loss must be finite under pressure");
|
|
}
|
|
|
|
// Check memory usage periodically
|
|
if batch % 100 == 0 {
|
|
let current_memory = get_memory_usage();
|
|
let memory_growth = current_memory - start_memory;
|
|
|
|
// Memory should not grow unbounded
|
|
assert!(memory_growth < 1_000_000_000, // 1GB limit
|
|
"Memory usage growing unbounded: {} bytes", memory_growth);
|
|
}
|
|
}
|
|
|
|
// Verify agent is still functional after stress test
|
|
let final_state = create_random_market_state();
|
|
let final_action = agent.select_action(&final_state);
|
|
assert!(final_action < agent.num_actions(), "Agent should remain functional after stress test");
|
|
}
|
|
|
|
/// Test inference pipeline error handling
|
|
#[test]
|
|
fn test_inference_pipeline_error_handling() {
|
|
let mut pipeline = create_test_inference_pipeline();
|
|
|
|
// Test with invalid inputs
|
|
let invalid_inputs = vec![
|
|
MarketState::with_nan_values(),
|
|
MarketState::with_infinite_values(),
|
|
MarketState::with_negative_volume(),
|
|
MarketState::with_zero_values(),
|
|
];
|
|
|
|
for invalid_state in invalid_inputs {
|
|
match pipeline.predict(&invalid_state) {
|
|
Ok(_) => {
|
|
// If prediction succeeds, it should be valid
|
|
let prediction = pipeline.predict(&invalid_state).unwrap();
|
|
assert!(prediction.confidence.is_finite(), "Confidence must be finite");
|
|
assert!(prediction.action_probabilities.iter().all(|&p| p.is_finite()),
|
|
"Action probabilities must be finite");
|
|
}
|
|
Err(e) => {
|
|
// Error should be properly categorized
|
|
assert!(e.to_string().contains("Invalid input") ||
|
|
e.to_string().contains("Numerical error"),
|
|
"Error should indicate input validation issue: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test pipeline recovery after errors
|
|
let valid_state = create_random_market_state();
|
|
let recovery_prediction = pipeline.predict(&valid_state);
|
|
assert!(recovery_prediction.is_ok(), "Pipeline should recover after errors");
|
|
}
|
|
|
|
/// Test model performance under latency constraints
|
|
#[test]
|
|
fn test_model_latency_constraints() {
|
|
let agent = create_test_rainbow_agent();
|
|
let state = create_random_market_state();
|
|
|
|
// Warm up
|
|
for _ in 0..1000 {
|
|
let _ = agent.select_action(&state);
|
|
}
|
|
|
|
// Measure inference latency
|
|
let iterations = 10_000;
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..iterations {
|
|
let _ = agent.select_action(&state);
|
|
}
|
|
|
|
let elapsed = start.elapsed();
|
|
let avg_latency = elapsed / iterations;
|
|
|
|
// Must meet HFT latency requirements (< 100μs)
|
|
assert!(avg_latency < Duration::from_micros(100),
|
|
"Model inference latency {} exceeds 100μs requirement",
|
|
avg_latency.as_micros());
|
|
}
|
|
|
|
/// Test model accuracy metrics
|
|
#[test]
|
|
fn test_model_accuracy_metrics() {
|
|
let mut agent = create_test_rainbow_agent();
|
|
let mut correct_predictions = 0;
|
|
let mut total_predictions = 0;
|
|
|
|
// Generate test scenarios with known optimal actions
|
|
let test_scenarios = create_test_scenarios_with_optimal_actions();
|
|
|
|
for scenario in test_scenarios {
|
|
let predicted_action = agent.select_action(&scenario.state);
|
|
let optimal_action = scenario.optimal_action;
|
|
|
|
if predicted_action == optimal_action {
|
|
correct_predictions += 1;
|
|
}
|
|
total_predictions += 1;
|
|
|
|
// Train on this scenario
|
|
let (next_state, reward, done) = simulate_market_step(&scenario.state, optimal_action);
|
|
agent.store_transition(scenario.state, optimal_action, reward, next_state, done);
|
|
|
|
if agent.should_train() {
|
|
agent.train();
|
|
}
|
|
}
|
|
|
|
let accuracy = correct_predictions as f64 / total_predictions as f64;
|
|
|
|
// Model should achieve reasonable accuracy on test scenarios
|
|
assert!(accuracy > 0.6, "Model accuracy {} below minimum threshold 0.6", accuracy);
|
|
|
|
// Test prediction confidence calibration
|
|
let mut confidence_buckets = HashMap::new();
|
|
for scenario in &test_scenarios[..100] {
|
|
let prediction = agent.predict_with_confidence(&scenario.state);
|
|
let bucket = (prediction.confidence * 10.0) as usize;
|
|
confidence_buckets.entry(bucket).or_insert(Vec::new()).push(prediction.correct);
|
|
}
|
|
|
|
// High confidence predictions should be more accurate
|
|
if let (Some(high_conf), Some(low_conf)) = (confidence_buckets.get(&9), confidence_buckets.get(&3)) {
|
|
let high_acc = high_conf.iter().map(|&x| x as u8).sum::<u8>() as f64 / high_conf.len() as f64;
|
|
let low_acc = low_conf.iter().map(|&x| x as u8).sum::<u8>() as f64 / low_conf.len() as f64;
|
|
|
|
assert!(high_acc >= low_acc * 0.9,
|
|
"High confidence predictions should be more accurate: high={}, low={}",
|
|
high_acc, low_acc);
|
|
}
|
|
}
|
|
|
|
// Helper functions and test data structures
|
|
fn create_test_rainbow_agent() -> TestRainbowAgent {
|
|
TestRainbowAgent::new(TestAgentConfig {
|
|
state_dim: 10,
|
|
action_dim: 3,
|
|
hidden_dim: 64,
|
|
learning_rate: 0.001,
|
|
replay_buffer_size: 10_000,
|
|
batch_size: 32,
|
|
})
|
|
}
|
|
|
|
fn create_test_ensemble_coordinator() -> TestEnsembleCoordinator {
|
|
TestEnsembleCoordinator::new()
|
|
}
|
|
|
|
fn create_test_inference_pipeline() -> TestInferencePipeline {
|
|
TestInferencePipeline::new()
|
|
}
|
|
|
|
fn create_random_market_state() -> MarketState {
|
|
MarketState {
|
|
price: 1.0 + rand::random::<f64>() * 0.5,
|
|
volume: 1000.0 + rand::random::<f64>() * 10000.0,
|
|
volatility: 0.01 + rand::random::<f64>() * 0.1,
|
|
trend: -0.05 + rand::random::<f64>() * 0.1,
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
fn create_extreme_volatility_state() -> MarketState {
|
|
MarketState {
|
|
price: 1.25,
|
|
volume: 50000.0,
|
|
volatility: 0.8, // Extremely high volatility
|
|
trend: 0.05,
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
fn create_zero_volume_state() -> MarketState {
|
|
MarketState {
|
|
price: 1.15,
|
|
volume: 0.0, // Zero volume edge case
|
|
volatility: 0.02,
|
|
trend: 0.0,
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
fn create_gap_up_state() -> MarketState {
|
|
MarketState {
|
|
price: 1.45, // Significant gap
|
|
volume: 25000.0,
|
|
volatility: 0.15,
|
|
trend: 0.2, // Strong uptrend
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
fn create_flash_crash_state() -> MarketState {
|
|
MarketState {
|
|
price: 0.85, // Sudden price drop
|
|
volume: 200000.0, // High volume
|
|
volatility: 0.6, // Very high volatility
|
|
trend: -0.15, // Strong downtrend
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
fn simulate_market_step(state: &MarketState, _action: usize) -> (MarketState, f64, bool) {
|
|
// Simplified market simulation
|
|
let next_price = state.price * (1.0 + (rand::random::<f64>() - 0.5) * 0.02);
|
|
let reward = (next_price - state.price) * 1000.0; // Simplified reward
|
|
|
|
let next_state = MarketState {
|
|
price: next_price,
|
|
volume: state.volume * (0.8 + rand::random::<f64>() * 0.4),
|
|
volatility: state.volatility * (0.9 + rand::random::<f64>() * 0.2),
|
|
trend: state.trend * 0.95 + (rand::random::<f64>() - 0.5) * 0.01,
|
|
timestamp: std::time::SystemTime::now(),
|
|
};
|
|
|
|
let done = rand::random::<f64>() < 0.05; // 5% chance of episode end
|
|
|
|
(next_state, reward, done)
|
|
}
|
|
|
|
fn get_memory_usage() -> usize {
|
|
// Simplified memory usage tracking
|
|
std::process::id() as usize * 1024 // Production implementation
|
|
}
|
|
|
|
fn create_test_scenarios_with_optimal_actions() -> Vec<TestScenario> {
|
|
vec![
|
|
TestScenario {
|
|
state: MarketState {
|
|
price: 1.10,
|
|
volume: 5000.0,
|
|
volatility: 0.02,
|
|
trend: 0.05,
|
|
timestamp: std::time::SystemTime::now(),
|
|
},
|
|
optimal_action: 0, // Buy in uptrend
|
|
},
|
|
TestScenario {
|
|
state: MarketState {
|
|
price: 1.20,
|
|
volume: 5000.0,
|
|
volatility: 0.02,
|
|
trend: -0.05,
|
|
timestamp: std::time::SystemTime::now(),
|
|
},
|
|
optimal_action: 1, // Sell in downtrend
|
|
},
|
|
// Add more test scenarios...
|
|
]
|
|
}
|
|
}
|
|
|
|
// Test data structures
|
|
#[derive(Debug, Clone)]
|
|
struct MarketState {
|
|
price: f64,
|
|
volume: f64,
|
|
volatility: f64,
|
|
trend: f64,
|
|
timestamp: std::time::SystemTime,
|
|
}
|
|
|
|
impl MarketState {
|
|
fn with_nan_values() -> Self {
|
|
Self {
|
|
price: f64::NAN,
|
|
volume: 1000.0,
|
|
volatility: 0.02,
|
|
trend: 0.0,
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
fn with_infinite_values() -> Self {
|
|
Self {
|
|
price: f64::INFINITY,
|
|
volume: 1000.0,
|
|
volatility: 0.02,
|
|
trend: 0.0,
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
fn with_negative_volume() -> Self {
|
|
Self {
|
|
price: 1.15,
|
|
volume: -1000.0,
|
|
volatility: 0.02,
|
|
trend: 0.0,
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
fn with_zero_values() -> Self {
|
|
Self {
|
|
price: 0.0,
|
|
volume: 0.0,
|
|
volatility: 0.0,
|
|
trend: 0.0,
|
|
timestamp: std::time::SystemTime::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TestScenario {
|
|
state: MarketState,
|
|
optimal_action: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TestRainbowAgent {
|
|
config: TestAgentConfig,
|
|
replay_buffer_size: usize,
|
|
replay_buffer_capacity: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TestAgentConfig {
|
|
state_dim: usize,
|
|
action_dim: usize,
|
|
hidden_dim: usize,
|
|
learning_rate: f64,
|
|
replay_buffer_size: usize,
|
|
batch_size: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TestEnsembleCoordinator {
|
|
models: HashMap<String, Box<dyn TestModel>>,
|
|
}
|
|
|
|
trait TestModel: std::fmt::Debug {
|
|
fn predict(&self, state: &MarketState) -> ModelPrediction;
|
|
fn name(&self) -> &str;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ConservativeModel;
|
|
#[derive(Debug)]
|
|
struct AggressiveModel;
|
|
#[derive(Debug)]
|
|
struct AdaptiveModel;
|
|
|
|
impl ConservativeModel {
|
|
fn new() -> Self { Self }
|
|
}
|
|
impl AggressiveModel {
|
|
fn new() -> Self { Self }
|
|
}
|
|
impl AdaptiveModel {
|
|
fn new() -> Self { Self }
|
|
}
|
|
|
|
impl TestModel for ConservativeModel {
|
|
fn predict(&self, _state: &MarketState) -> ModelPrediction {
|
|
ModelPrediction {
|
|
action_probabilities: vec![0.1, 0.8, 0.1], // Mostly hold
|
|
confidence: 0.7,
|
|
correct: false,
|
|
}
|
|
}
|
|
fn name(&self) -> &str { "conservative" }
|
|
}
|
|
|
|
impl TestModel for AggressiveModel {
|
|
fn predict(&self, _state: &MarketState) -> ModelPrediction {
|
|
ModelPrediction {
|
|
action_probabilities: vec![0.4, 0.2, 0.4], // More trading
|
|
confidence: 0.6,
|
|
correct: false,
|
|
}
|
|
}
|
|
fn name(&self) -> &str { "aggressive" }
|
|
}
|
|
|
|
impl TestModel for AdaptiveModel {
|
|
fn predict(&self, _state: &MarketState) -> ModelPrediction {
|
|
ModelPrediction {
|
|
action_probabilities: vec![0.3, 0.4, 0.3], // Balanced
|
|
confidence: 0.8,
|
|
correct: false,
|
|
}
|
|
}
|
|
fn name(&self) -> &str { "adaptive" }
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ModelPrediction {
|
|
action_probabilities: Vec<f64>,
|
|
confidence: f64,
|
|
correct: bool,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TestInferencePipeline;
|
|
|
|
impl TestInferencePipeline {
|
|
fn new() -> Self { Self }
|
|
|
|
fn predict(&self, state: &MarketState) -> Result<ModelPrediction, Box<dyn std::error::Error>> {
|
|
if state.price.is_nan() || state.price.is_infinite() {
|
|
return Err("Invalid input: price contains NaN or infinite values".into());
|
|
}
|
|
if state.volume < 0.0 {
|
|
return Err("Invalid input: negative volume".into());
|
|
}
|
|
|
|
Ok(ModelPrediction {
|
|
action_probabilities: vec![0.33, 0.34, 0.33],
|
|
confidence: 0.75,
|
|
correct: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TestRainbowAgent {
|
|
fn new(config: TestAgentConfig) -> Self {
|
|
Self {
|
|
replay_buffer_capacity: config.replay_buffer_size,
|
|
replay_buffer_size: 0,
|
|
config,
|
|
}
|
|
}
|
|
|
|
fn select_action(&self, _state: &MarketState) -> usize {
|
|
rand::random::<usize>() % self.config.action_dim
|
|
}
|
|
|
|
fn store_transition(&mut self, _state: MarketState, _action: usize, _reward: f64, _next_state: MarketState, _done: bool) {
|
|
if self.replay_buffer_size < self.replay_buffer_capacity {
|
|
self.replay_buffer_size += 1;
|
|
}
|
|
}
|
|
|
|
fn should_train(&self) -> bool {
|
|
self.replay_buffer_size >= self.config.batch_size
|
|
}
|
|
|
|
fn train(&self) -> f64 {
|
|
0.5 + rand::random::<f64>() * 0.1 // Simulated training loss
|
|
}
|
|
|
|
fn num_actions(&self) -> usize {
|
|
self.config.action_dim
|
|
}
|
|
|
|
fn get_q_values(&self, _state: &MarketState) -> Vec<f64> {
|
|
(0..self.config.action_dim).map(|_| rand::random::<f64>() * 10.0 - 5.0).collect()
|
|
}
|
|
|
|
fn replay_buffer_capacity(&self) -> usize {
|
|
self.replay_buffer_capacity
|
|
}
|
|
|
|
fn replay_buffer_size(&self) -> usize {
|
|
self.replay_buffer_size
|
|
}
|
|
|
|
fn set_deterministic(&self, _deterministic: bool) {
|
|
// Would set deterministic mode in real implementation
|
|
}
|
|
|
|
fn predict_with_confidence(&self, _state: &MarketState) -> ModelPrediction {
|
|
ModelPrediction {
|
|
action_probabilities: vec![0.33, 0.34, 0.33],
|
|
confidence: rand::random::<f64>(),
|
|
correct: rand::random::<bool>(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TestEnsembleCoordinator {
|
|
fn new() -> Self {
|
|
Self {
|
|
models: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn add_model(&mut self, name: &str, model: Box<dyn TestModel>) {
|
|
self.models.insert(name.to_string(), model);
|
|
}
|
|
|
|
fn select_best_model(&self, _state: &MarketState) -> Option<&str> {
|
|
self.models.keys().next().map(|s| s.as_str())
|
|
}
|
|
|
|
fn predict(&self, state: &MarketState) -> ModelPrediction {
|
|
if let Some(model) = self.models.values().next() {
|
|
model.predict(state)
|
|
} else {
|
|
ModelPrediction {
|
|
action_probabilities: vec![0.33, 0.34, 0.33],
|
|
confidence: 0.5,
|
|
correct: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn num_actions(&self) -> usize {
|
|
3
|
|
}
|
|
} |