//! Type System Bridge for ML-Financial Integration //! //! This module provides seamless conversion between ML computational types (f64/f32) //! and canonical financial types (common::Price, common::Decimal). It ensures type //! consistency across the ML-financial system boundary while maintaining computational //! efficiency for pure ML operations. use crate::{MLError, MLResult}; use common::Price; use rust_decimal::prelude::FromPrimitive; use rust_decimal::Decimal; // Note: Using common::Price directly now, no alias needed /// Conversion utilities for ML numeric types to financial types #[derive(Debug)] pub struct MLFinancialBridge; impl MLFinancialBridge { /// Convert f64 ML value to common::Price with validation pub fn f64_to_price(value: f64) -> MLResult { Price::from_f64(value).map_err(|e| { MLError::InvalidInput(format!( "Price conversion failed for value {}: {}", value, e )) }) } /// Convert f64 ML value to common::Price (fixed-point) with validation pub fn f64_to_common_price(value: f64) -> MLResult { Price::from_f64(value).map_err(|e| { MLError::InvalidInput(format!( "Common price conversion failed for value {}: {}", value, e )) }) } /// Convert f32 ML value to common::Price with validation pub fn f32_to_price(value: f32) -> MLResult { Self::f64_to_price(value as f64) } /// Convert f32 ML value to common::Price (fixed-point) with validation pub fn f32_to_common_price(value: f32) -> MLResult { Self::f64_to_common_price(value as f64) } /// Convert f64 ML value to common::Decimal with validation pub fn f64_to_decimal(value: f64) -> MLResult { Decimal::from_f64(value).ok_or_else(|| { MLError::InvalidInput(format!( "Decimal conversion failed for f64 value: {}", value )) }) } /// Convert f32 ML value to common::Decimal with validation pub fn f32_to_decimal(value: f32) -> MLResult { Self::f64_to_decimal(value as f64) } /// Convert common::Price to f64 for ML computations pub fn price_to_f64(price: &Price) -> f64 { price.to_f64() } /// Convert common::Price (fixed-point) to f64 for ML computations pub fn common_price_to_f64(price: &Price) -> f64 { price.to_f64() } /// Convert common::Price to f32 for ML computations pub fn price_to_f32(price: &Price) -> f32 { price.to_f64() as f32 } /// Convert common::Price (fixed-point) to f32 for ML computations pub fn common_price_to_f32(price: &Price) -> f32 { price.to_f64() as f32 } /// Convert common::Decimal to f64 for ML computations pub fn decimal_to_f64(decimal: &Decimal) -> MLResult { use rust_decimal::prelude::ToPrimitive; decimal.to_f64().ok_or_else(|| { MLError::InvalidInput(format!("Failed to convert Decimal {} to f64", decimal)) }) } /// Convert common::Decimal to f32 for ML computations pub fn decimal_to_f32(decimal: &Decimal) -> MLResult { use rust_decimal::prelude::ToPrimitive; decimal.to_f32().ok_or_else(|| { MLError::InvalidInput(format!("Failed to convert Decimal {} to f32", decimal)) }) } /// Batch convert f64 vector to Price vector pub fn f64_vec_to_prices(values: &[f64]) -> MLResult> { values.iter().map(|&v| Self::f64_to_price(v)).collect() } /// Batch convert Price vector to f64 vector pub fn prices_to_f64_vec(prices: &[Price]) -> Vec { prices.iter().map(Self::price_to_f64).collect() } /// Batch convert f64 vector to Decimal vector pub fn f64_vec_to_decimals(values: &[f64]) -> MLResult> { values.iter().map(|&v| Self::f64_to_decimal(v)).collect() } /// Batch convert Decimal vector to f64 vector pub fn decimals_to_f64_vec(decimals: &[Decimal]) -> MLResult> { decimals.iter().map(Self::decimal_to_f64).collect() } } /// Trait for types that can be converted to financial types pub trait ToFinancial { /// Convert to common::Price fn to_price(&self) -> MLResult; /// Convert to common::Decimal fn to_decimal(&self) -> MLResult; } /// Trait for types that can be converted from financial types pub trait FromFinancial { /// Convert from common::Price fn from_price(price: &Price) -> Self; /// Convert from common::Decimal fn from_decimal(decimal: &Decimal) -> MLResult where Self: Sized; } impl ToFinancial for f64 { fn to_price(&self) -> MLResult { MLFinancialBridge::f64_to_price(*self) } fn to_decimal(&self) -> MLResult { MLFinancialBridge::f64_to_decimal(*self) } } impl ToFinancial for f32 { fn to_price(&self) -> MLResult { MLFinancialBridge::f32_to_price(*self) } fn to_decimal(&self) -> MLResult { MLFinancialBridge::f32_to_decimal(*self) } } impl FromFinancial for f64 { fn from_price(price: &Price) -> Self { MLFinancialBridge::price_to_f64(price) } fn from_decimal(decimal: &Decimal) -> MLResult { MLFinancialBridge::decimal_to_f64(decimal) } } impl FromFinancial for f32 { fn from_price(price: &Price) -> Self { MLFinancialBridge::price_to_f32(price) } fn from_decimal(decimal: &Decimal) -> MLResult { MLFinancialBridge::decimal_to_f32(decimal) } } /// Specialized converters for common ML use cases pub mod converters { use super::*; /// Convert ML prediction results to financial format #[derive(Debug)] pub struct PredictionConverter; impl PredictionConverter { /// Convert ML model output (f64) to trading signal with Price pub fn prediction_to_signal( prediction: f64, confidence: f64, current_price: &Price, ) -> MLResult<(Price, Decimal)> { // Convert prediction to price change let price_change = prediction * MLFinancialBridge::price_to_f64(current_price); let new_price = MLFinancialBridge::f64_to_price( MLFinancialBridge::price_to_f64(current_price) + price_change, )?; let confidence_decimal = MLFinancialBridge::f64_to_decimal(confidence)?; Ok((new_price, confidence_decimal)) } /// Convert order book features (f32 array) to Price/Decimal format pub fn features_to_financial( features: &[f32], feature_names: &[&str], ) -> MLResult> { features .iter() .zip(feature_names.iter()) .map(|(&value, &name)| { let decimal = MLFinancialBridge::f32_to_decimal(value)?; Ok((name.to_string(), decimal)) }) .collect() } /// Convert portfolio weights (f64 array) to Decimal format pub fn weights_to_decimals(weights: &[f64]) -> MLResult> { MLFinancialBridge::f64_vec_to_decimals(weights) } } /// Convert financial data to ML format #[derive(Debug)] pub struct FinancialConverter; impl FinancialConverter { /// Convert price history to ML features (f64 array) pub fn prices_to_features(prices: &[Price]) -> Vec { MLFinancialBridge::prices_to_f64_vec(prices) } /// Convert price and volume data to ML input matrix pub fn market_data_to_matrix( prices: &[Price], volumes: &[Decimal], ) -> MLResult>> { let price_features = Self::prices_to_features(prices); let volume_features = MLFinancialBridge::decimals_to_f64_vec(volumes)?; Ok(price_features .into_iter() .zip(volume_features.into_iter()) .map(|(p, v)| vec![p, v]) .collect()) } /// Normalize prices for ML input (log returns) pub fn prices_to_log_returns(prices: &[Price]) -> Vec { let price_values = Self::prices_to_features(prices); price_values .windows(2) .filter_map(|window| { let p0 = window.get(0)?; let p1 = window.get(1)?; (*p0 > 0.0).then(|| (p1 / p0).ln()) }) .collect() } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_f64_to_price_conversion() { let value = 123.45; let price = MLFinancialBridge::f64_to_price(value).unwrap(); assert!((MLFinancialBridge::price_to_f64(&price) - value).abs() < 1e-8); } #[test] fn test_f64_to_decimal_conversion() { let value = 123.45; let decimal = MLFinancialBridge::f64_to_decimal(value).unwrap(); let back_to_f64 = MLFinancialBridge::decimal_to_f64(&decimal).unwrap(); assert!((back_to_f64 - value).abs() < 1e-10); } #[test] fn test_batch_conversions() { let values = vec![10.0, 20.0, 30.0]; let prices = MLFinancialBridge::f64_vec_to_prices(&values).unwrap(); let back_to_f64 = MLFinancialBridge::prices_to_f64_vec(&prices); for (original, converted) in values.into_iter().zip(back_to_f64.into_iter()) { assert!((original - converted).abs() < 1e-8); } } #[test] fn test_trait_implementations() { let value = 42.0_f64; let price = value.to_price().unwrap(); let back_to_f64 = f64::from_price(&price); assert!((value - back_to_f64).abs() < 1e-8); } #[test] fn test_prediction_converter() { use converters::PredictionConverter; let current_price_decimal = Decimal::from_f64(100.0).unwrap(); let current_price = Price::from(current_price_decimal); let prediction = 0.05; // 5% increase let confidence = 0.85; let (new_price, conf_decimal) = PredictionConverter::prediction_to_signal(prediction, confidence, ¤t_price) .unwrap(); assert!((MLFinancialBridge::price_to_f64(&new_price) - 105.0).abs() < 1e-6); assert!((MLFinancialBridge::decimal_to_f64(&conf_decimal).unwrap() - 0.85).abs() < 1e-10); } #[test] fn test_financial_converter() { use converters::FinancialConverter; let prices: Vec = vec![ Decimal::from_f64(100.0).unwrap(), Decimal::from_f64(105.0).unwrap(), Decimal::from_f64(110.0).unwrap(), ] .into_iter() .map(Price::from) .collect(); let log_returns = FinancialConverter::prices_to_log_returns(&prices); assert_eq!(log_returns.len(), 2); let r0 = log_returns.get(0).copied().unwrap(); let r1 = log_returns.get(1).copied().unwrap(); assert!((r0 - (105.0_f64 / 100.0_f64).ln()).abs() < 1e-10); assert!((r1 - (110.0_f64 / 105.0_f64).ln()).abs() < 1e-10); } #[test] fn test_invalid_conversions() { // Test negative price conversion assert!(MLFinancialBridge::f64_to_price(-10.0).is_err()); // Test NaN conversion assert!(MLFinancialBridge::f64_to_price(f64::NAN).is_err()); // Test infinity conversion assert!(MLFinancialBridge::f64_to_price(f64::INFINITY).is_err()); } }