- 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>
124 lines
3.7 KiB
Rust
124 lines
3.7 KiB
Rust
//! Production Feature Extractor Adapter for Common ML Strategy
|
|
//!
|
|
//! This adapter bridges the ml::features::extraction::FeatureExtractor with
|
|
//! the common::ml_strategy::ProductionFeatureExtractor225 trait to inject
|
|
//! 225-feature extraction into SharedMLStrategy without circular dependencies.
|
|
|
|
use anyhow::Result;
|
|
use chrono::{DateTime, Utc};
|
|
use common::ml_strategy::ProductionFeatureExtractor225;
|
|
|
|
use super::extraction::{FeatureExtractor, OHLCVBar};
|
|
|
|
/// Production-grade 225-feature extractor adapter for SharedMLStrategy
|
|
///
|
|
/// This struct wraps the ml::features::extraction::FeatureExtractor and implements
|
|
/// the ProductionFeatureExtractor225 trait from common, enabling dependency injection
|
|
/// of the full 225-feature extractor into SharedMLStrategy.
|
|
///
|
|
/// # Usage
|
|
/// ```rust,ignore
|
|
/// use ml::features::ProductionFeatureExtractorAdapter;
|
|
/// use common::ml_strategy::SharedMLStrategy;
|
|
///
|
|
/// let extractor = Box::new(ProductionFeatureExtractorAdapter::new());
|
|
/// let strategy = SharedMLStrategy::new(extractor, vec![], 0.7);
|
|
/// ```
|
|
#[derive(Debug)]
|
|
pub struct ProductionFeatureExtractorAdapter {
|
|
inner: FeatureExtractor,
|
|
}
|
|
|
|
impl ProductionFeatureExtractorAdapter {
|
|
/// Create new production feature extractor adapter
|
|
pub fn new() -> Self {
|
|
Self {
|
|
inner: FeatureExtractor::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for ProductionFeatureExtractorAdapter {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl ProductionFeatureExtractor225 for ProductionFeatureExtractorAdapter {
|
|
fn update(&mut self, price: f64, volume: f64, timestamp: DateTime<Utc>) -> Result<()> {
|
|
// Convert price/volume to OHLCV bar (approximate high/low from price)
|
|
let bar = OHLCVBar {
|
|
timestamp,
|
|
open: price,
|
|
high: price * 1.001, // Approximate high (0.1% above close)
|
|
low: price * 0.999, // Approximate low (0.1% below close)
|
|
close: price,
|
|
volume,
|
|
};
|
|
|
|
// Update internal state
|
|
self.inner.update(&bar)
|
|
}
|
|
|
|
fn extract_features(&mut self) -> Result<Vec<f64>> {
|
|
// Extract 225-dimensional feature vector
|
|
let feature_array = self.inner.extract_current_features()?;
|
|
Ok(feature_array.to_vec())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use chrono::Utc;
|
|
|
|
#[test]
|
|
fn test_adapter_basic_usage() -> Result<()> {
|
|
let mut adapter = ProductionFeatureExtractorAdapter::new();
|
|
|
|
// Feed 60 bars (warmup period = 50)
|
|
for i in 0..60 {
|
|
let price = 100.0 + i as f64;
|
|
let volume = 1000.0;
|
|
let timestamp = Utc::now();
|
|
adapter.update(price, volume, timestamp)?;
|
|
}
|
|
|
|
// Extract features
|
|
let features = adapter.extract_features()?;
|
|
|
|
// Validate 51 features
|
|
assert_eq!(features.len(), 51, "Should extract exactly 51 features");
|
|
|
|
// Validate all features are finite
|
|
for (i, &val) in features.iter().enumerate() {
|
|
assert!(
|
|
val.is_finite(),
|
|
"Feature {} should be finite, found {}",
|
|
i, val
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_adapter_warmup_period() -> Result<()> {
|
|
let mut adapter = ProductionFeatureExtractorAdapter::new();
|
|
|
|
// Feed only warmup bars (50)
|
|
for i in 0..50 {
|
|
let price = 100.0 + i as f64;
|
|
let volume = 1000.0;
|
|
let timestamp = Utc::now();
|
|
adapter.update(price, volume, timestamp)?;
|
|
}
|
|
|
|
// Should be able to extract features after warmup
|
|
let features = adapter.extract_features()?;
|
|
assert_eq!(features.len(), 51);
|
|
|
|
Ok(())
|
|
}
|
|
}
|