Files
foxhunt/ml/src/bridge.rs
jgrusewski 8b9abcc3c1 fix: resolve all clippy errors across 37+ workspace crates
Eliminate ~4,260 clippy deny-level errors that blocked workspace-wide
clippy runs. Errors cascaded: upstream crate failures (ctrader-openapi,
risk-data) hid thousands of downstream errors in ml, tli, backtesting.

Key changes:
- ctrader-openapi: fix shadow_unrelated/shadow_reuse (renamed vars)
- risk-data/risk: replace non-ASCII em dashes with ASCII equivalents
- tli: allow deny lints on prost-generated proto code, fix shadows
- trading_engine: fix let_underscore_must_use, wildcard matches, shadows
- broker_gateway_service: allow dead_code on unused redis_client field
- ml (4030 errors): remove local deny overrides for unwrap/expect/indexing
  (workspace warn level sufficient), add crate-level allows for non-safety
  mass-violation lints (non_ascii_literal, shadow_*, str_to_string, etc.),
  batch-fix em dashes, unseparated literal suffixes, format_push_string,
  wildcard matches, impl_trait_in_params, mutex_atomic, and more
- backtesting: replace unwrap() on first()/last() with match destructure
- tests: simplify loop-that-never-loops, fix mutex unwrap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 12:44:10 +01:00

357 lines
12 KiB
Rust

//! 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> {
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> {
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<Price> {
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<Price> {
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> {
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<Decimal> {
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<f64> {
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<f32> {
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<Vec<Price>> {
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<f64> {
prices.iter().map(Self::price_to_f64).collect()
}
/// Batch convert f64 vector to Decimal vector
pub fn f64_vec_to_decimals(values: &[f64]) -> MLResult<Vec<Decimal>> {
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<Vec<f64>> {
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<Price>;
/// Convert to common::Decimal
fn to_decimal(&self) -> MLResult<Decimal>;
}
/// Trait for types that can be converted from financial types
pub trait FromFinancial<T> {
/// Convert from common::Price
fn from_price(price: &Price) -> Self;
/// Convert from common::Decimal
fn from_decimal(decimal: &Decimal) -> MLResult<Self>
where
Self: Sized;
}
impl ToFinancial for f64 {
fn to_price(&self) -> MLResult<Price> {
MLFinancialBridge::f64_to_price(*self)
}
fn to_decimal(&self) -> MLResult<Decimal> {
MLFinancialBridge::f64_to_decimal(*self)
}
}
impl ToFinancial for f32 {
fn to_price(&self) -> MLResult<Price> {
MLFinancialBridge::f32_to_price(*self)
}
fn to_decimal(&self) -> MLResult<Decimal> {
MLFinancialBridge::f32_to_decimal(*self)
}
}
impl FromFinancial<Price> for f64 {
fn from_price(price: &Price) -> Self {
MLFinancialBridge::price_to_f64(price)
}
fn from_decimal(decimal: &Decimal) -> MLResult<Self> {
MLFinancialBridge::decimal_to_f64(decimal)
}
}
impl FromFinancial<Price> for f32 {
fn from_price(price: &Price) -> Self {
MLFinancialBridge::price_to_f32(price)
}
fn from_decimal(decimal: &Decimal) -> MLResult<Self> {
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<Vec<(String, Decimal)>> {
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<Vec<Decimal>> {
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<f64> {
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<Vec<Vec<f64>>> {
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<f64> {
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, &current_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<Price> = 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());
}
}