refactor(ml): consolidate 13 duplicate OHLCVBar definitions into single canonical type

Created ml/src/types/ohlcv.rs as the single source of truth for OHLCVBar
(DateTime<Utc> timestamp, f64 OHLCV fields). Replaced all 13 duplicate
definitions across features/, regime/, real_data_loader, and evaluation/
with imports from crate::types::OHLCVBar.

Key changes:
- New: ml/src/types/mod.rs + ohlcv.rs with canonical OHLCVBar
  (derives: Debug, Clone, Copy, PartialEq, Serialize, Deserialize + Default)
- Renamed: evaluation::metrics::OHLCVBar → OHLCVBarF32 (genuinely
  different type: f32 fields, i64 timestamp for compact backtesting)
- Eliminated all import aliases (ExtractionOHLCVBar, RegimeOHLCVBar,
  PriceOHLCVBar, VolumeOHLCVBar) in dbn_sequence_loader.rs and pipeline.rs
- Renamed regime::orchestrator::Bar → OHLCVBar (same fields, just aliased)
- Updated 39 files total (13 definitions removed, imports normalized)

1883 lib tests passing, compilation clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-02-20 18:14:42 +01:00
parent 4ad9d45d7e
commit 1934367bfa
39 changed files with 171 additions and 275 deletions

View File

@@ -304,8 +304,8 @@ fn run_backtest(
_ => ml::evaluation::engine::Action::Hold,
};
// Convert OHLCVBar to evaluation OHLCVBar
let eval_bar = ml::evaluation::metrics::OHLCVBar {
// Convert OHLCVBar to evaluation OHLCVBarF32
let eval_bar = ml::evaluation::metrics::OHLCVBarF32 {
timestamp: bar.timestamp.timestamp(),
open: bar.open as f32,
high: bar.high as f32,
@@ -321,7 +321,7 @@ fn run_backtest(
// Close any open position at end
if engine.current_position.is_some() {
let last_bar = &bars[bars.len() - 1];
let eval_bar = ml::evaluation::metrics::OHLCVBar {
let eval_bar = ml::evaluation::metrics::OHLCVBarF32 {
timestamp: last_bar.timestamp.timestamp(),
open: last_bar.open as f32,
high: last_bar.high as f32,
@@ -336,9 +336,9 @@ fn run_backtest(
info!("✅ Backtest complete ({:.2}s)", elapsed.as_secs_f64());
// Calculate performance metrics
let eval_bars: Vec<ml::evaluation::metrics::OHLCVBar> = bars
let eval_bars: Vec<ml::evaluation::metrics::OHLCVBarF32> = bars
.iter()
.map(|b| ml::evaluation::metrics::OHLCVBar {
.map(|b| ml::evaluation::metrics::OHLCVBarF32 {
timestamp: b.timestamp.timestamp(),
open: b.open as f32,
high: b.high as f32,

View File

@@ -8,7 +8,7 @@ use candle_core::{Device, Tensor};
use ml::data_loaders::load_parquet_data;
use ml::dqn::dqn::WorkingDQN;
use ml::evaluation::engine::{Action, EvaluationEngine};
use ml::evaluation::metrics::{OHLCVBar, PerformanceMetrics};
use ml::evaluation::metrics::{OHLCVBarF32, PerformanceMetrics};
use std::path::Path;
use tracing::info;
@@ -105,7 +105,7 @@ async fn main() -> Result<()> {
let action = Action::from(action_idx);
// Create OHLCV bar
let bar = OHLCVBar {
let bar = OHLCVBarF32 {
timestamp: bar_idx as i64,
open: close_price,
high: close_price,

View File

@@ -10,7 +10,8 @@
//! ```
use anyhow::{Context, Result};
use ml::real_data_loader::{OHLCVBar, RealDataLoader};
use ml::real_data_loader::RealDataLoader;
use ml::types::OHLCVBar;
use std::collections::HashMap;
use tracing::{info, warn};
use tracing_subscriber::FmtSubscriber;
@@ -31,7 +32,7 @@ async fn main() -> Result<()> {
info!("Analyzing 36 features vs baseline 16 features");
// Load real market data
let data_loader = RealDataLoader::new();
let data_loader = RealDataLoader::new_from_workspace()?;
let mut file_mapping = HashMap::new();
file_mapping.insert(
"6E.FUT".to_string(),

View File

@@ -16,7 +16,7 @@ use clap::Parser;
use ml::data_loaders::load_parquet_data;
use ml::dqn::dqn::{WorkingDQN, WorkingDQNConfig};
use ml::evaluation::engine::{Action, EvaluationEngine};
use ml::evaluation::metrics::{OHLCVBar, PerformanceMetrics};
use ml::evaluation::metrics::{OHLCVBarF32, PerformanceMetrics};
use ml::features::extraction::compute_dqn_features;
use std::path::PathBuf;
use tracing::{info, warn};
@@ -122,7 +122,7 @@ fn main() -> Result<()> {
let action = Action::from(action_idx);
// Process action through evaluation engine
let ohlcv_bar = OHLCVBar {
let ohlcv_bar = OHLCVBarF32 {
timestamp: bar.timestamp,
open: bar.open,
high: bar.high,
@@ -141,7 +141,7 @@ fn main() -> Result<()> {
// Close any remaining position
if let Some(last_bar) = bars.last() {
let ohlcv_bar = OHLCVBar {
let ohlcv_bar = OHLCVBarF32 {
timestamp: last_bar.timestamp,
open: last_bar.open,
high: last_bar.high,

View File

@@ -50,7 +50,7 @@ use ml::ppo::continuous_ppo::{
use ml::ppo::flow_policy::FlowPolicyConfig;
use ml::ppo::gae::GAEConfig;
use ml::evaluation::engine::{Action, EvaluationEngine};
use ml::evaluation::metrics::{PerformanceMetrics, OHLCVBar as MetricsOHLCVBar};
use ml::evaluation::metrics::{PerformanceMetrics, OHLCVBarF32 as MetricsOHLCVBar};
/// Results from dual-phase backtesting (exploration vs exploitation)
#[derive(Debug, Clone)]

View File

@@ -1016,8 +1016,8 @@ async fn main() -> Result<()> {
};
// Create OHLCV bar
use ml::evaluation::metrics::OHLCVBar;
let bar = OHLCVBar {
use ml::evaluation::metrics::OHLCVBarF32;
let bar = OHLCVBarF32 {
timestamp: bar_idx as i64,
open: close_price as f32,
high: close_price as f32,

View File

@@ -40,16 +40,17 @@ use tracing::{debug, info, warn};
use crate::data_loaders::dbn_tick_adapter::Tick;
use crate::features::alternative_bars::{
DollarBarSampler, ImbalanceBarSampler, OHLCVBar, RunBarSampler, TickBarSampler,
DollarBarSampler, ImbalanceBarSampler, RunBarSampler, TickBarSampler,
VolumeBarSampler,
};
use crate::features::normalization::FeatureNormalizer;
use crate::types::OHLCVBar;
// Wave D regime detection feature extractors
use crate::ensemble::MarketRegime;
use crate::features::extraction::{extract_ml_features, OHLCVBar as ExtractionOHLCVBar};
use crate::features::extraction::extract_ml_features;
use crate::features::regime_adaptive::RegimeAdaptiveFeatures;
use crate::features::regime_adx::{OHLCVBar as RegimeOHLCVBar, RegimeADXFeatures};
use crate::features::regime_adx::RegimeADXFeatures;
use crate::features::regime_cusum::RegimeCUSUMFeatures;
use crate::features::regime_transition::RegimeTransitionFeatures;
@@ -112,10 +113,10 @@ pub struct DbnSequenceLoader {
current_regime: MarketRegime,
/// OHLCV bar buffer for ADX (requires historical bars with i64 timestamp)
bar_buffer_adx: Vec<RegimeOHLCVBar>,
bar_buffer_adx: Vec<OHLCVBar>,
/// OHLCV bar buffer for adaptive features (requires historical bars with DateTime timestamp)
bar_buffer_adaptive: Vec<ExtractionOHLCVBar>,
bar_buffer_adaptive: Vec<OHLCVBar>,
}
impl std::fmt::Debug for DbnSequenceLoader {
@@ -995,7 +996,7 @@ impl DbnSequenceLoader {
// This eliminates 43 zero-padded features (19.1% junk data)
// Step 1: Convert ProcessedMessage to OHLCVBar format for production pipeline
let bars: Vec<ExtractionOHLCVBar> = messages
let bars: Vec<OHLCVBar> = messages
.iter()
.filter_map(|msg| self.convert_message_to_bar(msg))
.collect();
@@ -1120,7 +1121,7 @@ impl DbnSequenceLoader {
/// Convert ProcessedMessage to OHLCVBar format for production feature extraction
///
/// REFACTOR (Wave 5 Agent 26): Helper method to bridge DbnSequenceLoader with production pipeline
fn convert_message_to_bar(&self, msg: &ProcessedMessage) -> Option<ExtractionOHLCVBar> {
fn convert_message_to_bar(&self, msg: &ProcessedMessage) -> Option<OHLCVBar> {
match msg {
ProcessedMessage::Ohlcv {
timestamp,
@@ -1139,7 +1140,7 @@ impl DbnSequenceLoader {
let timestamp_dt =
chrono::DateTime::from_timestamp(secs, nsec).unwrap_or_else(chrono::Utc::now);
Some(ExtractionOHLCVBar {
Some(OHLCVBar {
timestamp: timestamp_dt,
open: open.to_f64(),
high: high.to_f64(),
@@ -1342,9 +1343,9 @@ impl DbnSequenceLoader {
}
// ADX & Directional Indicators (indices 211-215, 5 features)
// Create OHLCVBar for ADX calculation (requires historical bars with i64 timestamp)
let current_bar_adx = RegimeOHLCVBar {
timestamp: 0, // Not used in feature calculation
// Create OHLCVBar for ADX calculation
let current_bar_adx = OHLCVBar {
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0).unwrap(), // Not used in feature calculation
open: open.to_f64(),
high: high.to_f64(),
low: low.to_f64(),
@@ -1372,7 +1373,7 @@ impl DbnSequenceLoader {
// Adaptive Strategy Metrics (indices 221-224, 4 features)
// Create OHLCVBar for adaptive features (requires DateTime timestamp)
let current_bar_adaptive = ExtractionOHLCVBar {
let current_bar_adaptive = OHLCVBar {
timestamp: chrono::Utc::now(), // Use current time
open: open.to_f64(),
high: high.to_f64(),

View File

@@ -10,7 +10,7 @@
//! All corrections are conservative and preserve data integrity.
//! Original data is never modified in-place.
use crate::real_data_loader::OHLCVBar;
use crate::types::OHLCVBar;
use anyhow::Result;
/// Data corrector for automatic fixes

View File

@@ -4,7 +4,8 @@
//! Each rule implements the `ValidationRule` trait and can be composed
//! into a comprehensive validation pipeline.
use crate::real_data_loader::{Indicators, OHLCVBar};
use crate::real_data_loader::Indicators;
use crate::types::OHLCVBar;
use anyhow::Result;
/// Validation error information

View File

@@ -3,7 +3,8 @@
//! Orchestrates multiple validation rules and generates comprehensive reports.
use super::rules::{Severity, ValidationError, ValidationRule};
use crate::real_data_loader::{Indicators, OHLCVBar};
use crate::real_data_loader::Indicators;
use crate::types::OHLCVBar;
use anyhow::Result;
use std::sync::atomic::{AtomicUsize, Ordering};

View File

@@ -2,7 +2,7 @@
//!
//! Tracks positions, executes trades based on DQN actions, and records trade history.
use super::metrics::OHLCVBar;
use super::metrics::OHLCVBarF32;
use serde::{Deserialize, Serialize};
/// Trading action from DQN model
@@ -87,7 +87,7 @@ impl EvaluationEngine {
/// * `bar_idx` - Index of current bar in the dataset
/// * `bar` - Current OHLCV bar
/// * `action` - Action selected by DQN model
pub fn process_bar(&mut self, bar_idx: usize, bar: &OHLCVBar, action: Action) {
pub fn process_bar(&mut self, bar_idx: usize, bar: &OHLCVBarF32, action: Action) {
// Update action counts
self.action_counts[action as usize] += 1;
@@ -137,7 +137,7 @@ impl EvaluationEngine {
}
/// Close current position and record trade
pub fn close_position(&mut self, exit_bar_idx: usize, exit_bar: &OHLCVBar) {
pub fn close_position(&mut self, exit_bar_idx: usize, exit_bar: &OHLCVBarF32) {
if let Some(pos) = self.current_position.take() {
// Calculate base PnL (for 1 contract)
let base_pnl = match pos.direction {

View File

@@ -50,7 +50,7 @@ impl PerformanceMetrics {
///
/// # Returns
/// Comprehensive performance metrics
pub fn from_trades(trades: &[Trade], initial_capital: f32, _bars: &[OHLCVBar]) -> Self {
pub fn from_trades(trades: &[Trade], initial_capital: f32, _bars: &[OHLCVBarF32]) -> Self {
if trades.is_empty() {
return Self {
total_return_pct: 0.0,
@@ -317,9 +317,9 @@ fn calculate_benchmark_metrics(_returns: &[f64], _annualized_return: f64) -> (f6
(0.0, 0.0, 0.0)
}
/// OHLCV bar structure (placeholder - should match actual implementation)
/// Compact f32 OHLCV bar for backtesting evaluation
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub struct OHLCVBarF32 {
pub timestamp: i64,
pub open: f32,
pub high: f32,

View File

@@ -45,16 +45,7 @@
use std::collections::VecDeque;
/// OHLCV bar structure (compatible with other Wave D features)
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
pub use crate::types::OHLCVBar;
/// ADX feature extractor using Wilder's 14-period algorithm
///

View File

@@ -11,16 +11,7 @@
use chrono::{DateTime, Utc};
/// OHLCV Bar representation
#[derive(Debug, Clone, PartialEq)]
pub struct OHLCVBar {
pub timestamp: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
pub use crate::types::OHLCVBar;
/// Tick Bar Sampler - Aggregates every N ticks (PRIMARY IMPLEMENTATION - Agent B3)
///

View File

@@ -21,6 +21,7 @@
//! ```
use crate::features::microstructure::{AmihudIlliquidity, CorwinSchultzSpread, RollMeasure};
pub use crate::types::OHLCVBar;
use anyhow::{Context, Result};
use chrono::{Datelike, Timelike};
use common::features::{BollingerBands, ATR, EMA, MACD, RSI};
@@ -46,17 +47,6 @@ use data::providers::databento::mbp10::Mbp10Snapshot;
/// - 1.0.0: Initial 51-feature implementation (43 base + 8 OFI)
pub const FEATURE_EXTRACTION_VERSION: &str = "1.0.0";
/// OHLCV bar data structure (compatible with real_data_loader)
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
/// Feature extraction result: 51-dimensional feature vector per bar
pub type FeatureVector = [f64; 51];

View File

@@ -4,20 +4,10 @@
//! Phase 1: Implements 15 core features (5 OHLCV + 10 technical indicators)
//! Phase 2-4: Will add 241 additional engineered features
pub use crate::types::OHLCVBar;
use crate::MLError;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// OHLCV bar structure (from real_data_loader)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OHLCVBar {
pub timestamp: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
#[cfg(test)]
use chrono::Utc;
/// Feature extractor with technical indicators
#[derive(Debug)]

View File

@@ -37,7 +37,8 @@ pub mod volume_features; // Wave C: Volume-based features (10 features)
// Feature configuration (Wave C)
pub use config::{FeatureConfig, FeatureGroup, FeatureIndices, FeaturePhase};
pub use extraction::{extract_ml_features, FeatureVector, OHLCVBar};
pub use extraction::{extract_ml_features, FeatureVector};
pub use crate::types::OHLCVBar;
pub use minio_integration::{
cache_exists, compute_data_hash, download_cache_metadata, download_features_from_minio,
list_cached_features, upload_cache_metadata, upload_features_to_minio, CacheMetadata,
@@ -54,7 +55,7 @@ pub use unified::{
// Alternative bar sampling (tick, volume, dollar, imbalance, run bars)
pub use alternative_bars::{
DollarBarSampler, ImbalanceBarSampler, OHLCVBar as AltBar, RunBarSampler, TickBarSampler,
DollarBarSampler, ImbalanceBarSampler, RunBarSampler, TickBarSampler,
VolumeBarSampler,
};

View File

@@ -52,14 +52,14 @@
use anyhow::{Context, Result};
use std::collections::VecDeque;
use crate::features::extraction::OHLCVBar;
use crate::features::microstructure_features::{
BuySellImbalance, HighLowSpread, InterArrivalTime, KyleLambda, PriceImpact, TickCount,
VarianceRatio, VolumeWeightedSpread,
};
use crate::features::price_features::{OHLCVBar as PriceOHLCVBar, PriceFeatureExtractor};
use crate::features::price_features::PriceFeatureExtractor;
use crate::features::time_features::TimeFeatureExtractor;
use crate::features::volume_features::{OHLCVBar as VolumeOHLCVBar, VolumeFeatureExtractor};
use crate::features::volume_features::VolumeFeatureExtractor;
use crate::types::OHLCVBar;
/// Wrapper around VecDeque that provides lazy allocation for bars
///
@@ -228,8 +228,8 @@ impl FeatureExtractionPipeline {
// Note: BarsBuffer (RingBuffer) automatically maintains window size
// via circular overwriting, no need for manual pop_front
// Convert to VolumeOHLCVBar for volume extractor
let volume_bar = VolumeOHLCVBar {
// Convert to OHLCVBar for volume extractor
let volume_bar = OHLCVBar {
timestamp: bar.timestamp,
open: bar.open,
high: bar.high,
@@ -345,10 +345,10 @@ impl FeatureExtractionPipeline {
// Price features (15)
if self.config.enable_price {
// Convert extraction::OHLCVBar to price_features::OHLCVBar
let price_bars: VecDeque<PriceOHLCVBar> = self
let price_bars: VecDeque<OHLCVBar> = self
.bars
.iter()
.map(|b| PriceOHLCVBar {
.map(|b| OHLCVBar {
timestamp: b.timestamp,
open: b.open,
high: b.high,

View File

@@ -18,16 +18,7 @@
use std::collections::VecDeque;
/// OHLCV bar structure (compatible with extraction.rs)
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
pub use crate::types::OHLCVBar;
/// Price-based feature extractor for all 15 features
#[derive(Debug)]

View File

@@ -30,16 +30,7 @@
//! - Memory footprint: <200 bytes per symbol
//! - Cache-friendly: Sequential access patterns
/// OHLCV bar structure for ADX feature extraction
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub timestamp: i64,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
pub use crate::types::OHLCVBar;
/// ADX Feature Extractor with Wilder's Smoothing
///
@@ -355,7 +346,7 @@ mod tests {
/// Helper to create a test bar
fn create_test_bar(open: f64, high: f64, low: f64, close: f64, volume: f64) -> OHLCVBar {
OHLCVBar {
timestamp: 0,
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0).unwrap(),
open,
high,
low,
@@ -402,7 +393,7 @@ mod tests {
// Second bar with NaN high (simulates corrupted DBN data)
let bar2_nan = OHLCVBar {
timestamp: 0,
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0).unwrap(),
open: 101.0,
high: f64::NAN, // NaN input - triggers the fix
low: 99.0,
@@ -450,7 +441,7 @@ mod tests {
// Second bar with Inf low (simulates price anomaly)
let bar2_inf = OHLCVBar {
timestamp: 0,
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0).unwrap(),
open: 101.0,
high: 103.0,
low: f64::INFINITY, // Inf input - triggers the fix
@@ -473,9 +464,10 @@ mod tests {
let mut adx = RegimeADXFeatures::new(14);
// Feed 10 bars with various NaN values
let epoch = chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0).unwrap();
for i in 0..10 {
let bar = OHLCVBar {
timestamp: i,
timestamp: epoch,
open: if i % 2 == 0 { 100.0 } else { f64::NAN },
high: if i % 3 == 0 { 102.0 } else { f64::NAN },
low: if i % 4 == 0 { 98.0 } else { f64::NAN },

View File

@@ -24,16 +24,7 @@
use std::collections::VecDeque;
/// OHLCV bar structure (compatible with price_features.rs)
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
pub use crate::types::OHLCVBar;
/// Statistical feature extractor with efficient rolling window algorithms
#[derive(Debug)]

View File

@@ -30,29 +30,7 @@
use anyhow::Result;
use std::collections::VecDeque;
/// OHLCV bar data structure (matches extraction.rs)
#[derive(Debug, Clone, Copy)]
pub struct OHLCVBar {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
impl Default for OHLCVBar {
fn default() -> Self {
Self {
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0).unwrap(),
open: 0.0,
high: 0.0,
low: 0.0,
close: 0.0,
volume: 0.0,
}
}
}
pub use crate::types::OHLCVBar;
/// Volume feature extractor with stateful rolling windows (Wave G17 optimized)
///

View File

@@ -51,7 +51,7 @@ use tracing::info;
use chrono::Utc;
use crate::evaluation::engine::{Action, EvaluationEngine};
use crate::evaluation::metrics::{OHLCVBar, PerformanceMetrics};
use crate::evaluation::metrics::{OHLCVBarF32, PerformanceMetrics};
use crate::hyperopt::paths::TrainingPaths;
use crate::hyperopt::traits::{HyperparameterOptimizable, ParameterSpace};
use crate::trainers::dqn::{DQNHyperparameters, DQNTrainer as InternalDQNTrainer};
@@ -2452,7 +2452,7 @@ impl HyperparameterOptimizable for DQNTrainer {
// Create OHLCV bar (we only have close price from validation data)
// For backtest purposes, we set open=high=low=close
let bar = OHLCVBar {
let bar = OHLCVBarF32 {
timestamp: bar_idx as i64, // Use bar index as timestamp
open: close_price as f32,
high: close_price as f32,

View File

@@ -948,6 +948,7 @@ pub use tft::{
QuantizedTemporalFusionTransformer, QuantizedVariableSelectionNetwork,
};
pub mod trainers; // ML model trainers with gRPC integration
pub mod types; // Canonical shared types (OHLCVBar, etc.)
pub mod transformers;
pub mod universe;

View File

@@ -29,24 +29,14 @@
//! ```
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use chrono::DateTime;
use dbn::decode::{DbnDecoder, DecodeRecordRef};
use dbn::OhlcvMsg;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tracing::{debug, info};
/// OHLCV bar - standard candlestick data structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OHLCVBar {
pub timestamp: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
use crate::types::OHLCVBar;
/// Feature matrix for ML model input
///

View File

@@ -40,6 +40,7 @@ use crate::regime::{
trending::{TrendingClassifier, TrendingSignal},
volatile::{VolatileClassifier, VolatileSignal},
};
use crate::types::OHLCVBar;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
@@ -66,17 +67,6 @@ pub enum OrchestratorError {
DetectionFailed(String),
}
/// OHLCV bar structure for regime detection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bar {
pub timestamp: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
/// Regime state output
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegimeState {
@@ -246,7 +236,7 @@ impl RegimeOrchestrator {
pub async fn detect_and_persist(
&mut self,
symbol: &str,
bars: &[Bar],
bars: &[OHLCVBar],
) -> Result<RegimeState, OrchestratorError> {
// Validate input
if bars.len() < self.min_bars {
@@ -276,22 +266,9 @@ impl RegimeOrchestrator {
// Step 2: If break detected OR forced detection, classify regime
let regime = if break_detected || prev_regime.is_none() {
// Convert bars to classifier format
let ohlcv_bars: Vec<crate::regime::trending::OHLCVBar> = bars
.iter()
.map(|b| crate::regime::trending::OHLCVBar {
timestamp: b.timestamp,
open: b.open,
high: b.high,
low: b.low,
close: b.close,
volume: b.volume,
})
.collect();
// Query regime classifiers
let trending_signal = if let Some(last_bar) = ohlcv_bars.last() {
self.trending_classifier.classify(last_bar.clone())
// Query regime classifiers (all use canonical OHLCVBar)
let trending_signal = if let Some(&last_bar) = bars.last() {
self.trending_classifier.classify(last_bar)
} else {
TrendingSignal::Ranging {
adx: 0.0,
@@ -299,30 +276,14 @@ impl RegimeOrchestrator {
}
};
let ranging_signal = if let Some(last_bar) = ohlcv_bars.last() {
let ranging_bar = crate::regime::ranging::OHLCVBar {
timestamp: last_bar.timestamp,
open: last_bar.open,
high: last_bar.high,
low: last_bar.low,
close: last_bar.close,
volume: last_bar.volume,
};
self.ranging_classifier.classify(ranging_bar)
let ranging_signal = if let Some(&last_bar) = bars.last() {
self.ranging_classifier.classify(last_bar)
} else {
crate::regime::ranging::RangingSignal::NotRanging
};
let volatile_signal = if let Some(last_bar) = ohlcv_bars.last() {
let volatile_bar = crate::regime::volatile::OHLCVBar {
timestamp: last_bar.timestamp,
open: last_bar.open,
high: last_bar.high,
low: last_bar.low,
close: last_bar.close,
volume: last_bar.volume,
};
self.volatile_classifier.classify(volatile_bar)
let volatile_signal = if let Some(&last_bar) = bars.last() {
self.volatile_classifier.classify(last_bar)
} else {
VolatileSignal::Low
};
@@ -471,12 +432,12 @@ impl RegimeOrchestrator {
mod tests {
use super::*;
fn create_test_bars(count: usize, base_price: f64) -> Vec<Bar> {
fn create_test_bars(count: usize, base_price: f64) -> Vec<OHLCVBar> {
let base_time = Utc::now();
(0..count)
.map(|i| {
let price = base_price + (i as f64 * 0.1);
Bar {
OHLCVBar {
timestamp: base_time + chrono::Duration::seconds(i as i64 * 60),
open: price,
high: price + 0.5,

View File

@@ -11,16 +11,7 @@
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// OHLCV bar structure (from feature_extraction)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OHLCVBar {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
use crate::types::OHLCVBar;
/// Ranging regime signal
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]

View File

@@ -17,16 +17,7 @@
use std::collections::VecDeque;
/// OHLCV bar structure for trending analysis
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
use crate::types::OHLCVBar;
/// Trending signal output
#[derive(Debug, Clone, PartialEq)]

View File

@@ -20,19 +20,9 @@
//! - Reuses `compute_garman_klass_volatility()` from `ml/src/features/price_features.rs`
//! - Reuses ATR calculation logic from `ml/src/features/feature_extraction.rs`
use chrono::{DateTime, Utc};
use std::collections::VecDeque;
/// OHLCV bar structure (compatible with price_features.rs)
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub timestamp: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
use crate::types::OHLCVBar;
/// Volatile signal output
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -322,6 +312,7 @@ fn safe_clip(value: f64, min: f64, max: f64) -> f64 {
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
// Test helper functions
fn create_bar(open: f64, high: f64, low: f64, close: f64, volume: f64) -> OHLCVBar {

8
ml/src/types/mod.rs Normal file
View File

@@ -0,0 +1,8 @@
//! Core types shared across the ML crate.
//!
//! This module provides canonical type definitions to eliminate duplication.
//! All modules should import shared types from here rather than defining their own.
pub mod ohlcv;
pub use ohlcv::OHLCVBar;

41
ml/src/types/ohlcv.rs Normal file
View File

@@ -0,0 +1,41 @@
//! Canonical OHLCV bar type for the ML crate.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// Canonical OHLCV bar representation.
///
/// This is the **single source of truth** for OHLCV bar data across the ML crate.
/// All feature extractors, regime detectors, data loaders, and evaluation code
/// should use this type rather than defining local copies.
///
/// # Fields
/// - `timestamp`: UTC timestamp of the bar
/// - `open`, `high`, `low`, `close`: Price fields (f64)
/// - `volume`: Trade volume (f64)
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct OHLCVBar {
pub timestamp: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
impl Default for OHLCVBar {
fn default() -> Self {
Self {
// UNIX epoch — from_timestamp(0, 0) is infallible for these inputs
timestamp: match DateTime::<Utc>::from_timestamp(0, 0) {
Some(ts) => ts,
None => Utc::now(),
},
open: 0.0,
high: 0.0,
low: 0.0,
close: 0.0,
volume: 0.0,
}
}
}

View File

@@ -25,7 +25,8 @@ use ml::data_validation::rules::{
CompletenessRule, ContinuityRule, IndicatorRule, IntegrityRule, TimestampRule,
};
use ml::data_validation::validator::{DataValidator, ValidationResult};
use ml::real_data_loader::{Indicators, OHLCVBar};
use ml::real_data_loader::Indicators;
use ml::types::OHLCVBar;
// ============================================================================
// Test Configuration Helpers

View File

@@ -22,7 +22,8 @@ use ml::data_validation::rules::{
CompletenessRule, ContinuityRule, IndicatorRule, IntegrityRule, TimestampRule,
};
use ml::data_validation::validator::{DataValidator, ValidationReport, ValidationResult};
use ml::real_data_loader::{Indicators, OHLCVBar, RealDataLoader};
use ml::real_data_loader::{Indicators, RealDataLoader};
use ml::types::OHLCVBar;
/// Test 1: OHLCV integrity validation (high≥low, volume≥0, price relationships)
#[tokio::test]

View File

@@ -20,20 +20,20 @@
use anyhow::Result;
use chrono::Utc;
use ml::evaluation::engine::{Action, EvaluationEngine, Trade};
use ml::evaluation::metrics::{OHLCVBar, PerformanceMetrics};
use ml::evaluation::metrics::{OHLCVBarF32, PerformanceMetrics};
// ================================================================================================
// TEST UTILITIES
// ================================================================================================
/// Generate synthetic OHLCV bars for testing
fn create_synthetic_bars(count: usize) -> Vec<OHLCVBar> {
fn create_synthetic_bars(count: usize) -> Vec<OHLCVBarF32> {
let mut bars = Vec::with_capacity(count);
let base_price = 100.0;
for i in 0..count {
let price = base_price + (i as f32) * 0.5;
bars.push(OHLCVBar {
bars.push(OHLCVBarF32 {
timestamp: Utc::now().timestamp(),
open: price - 0.1,
high: price + 0.2,

View File

@@ -4,7 +4,7 @@
//! not GROSS P&L. Ensures all metrics (Sharpe, win rate, returns) are accurate.
use ml::evaluation::engine::{Action, EvaluationEngine, PositionDirection};
use ml::evaluation::metrics::{OHLCVBar, PerformanceMetrics};
use ml::evaluation::metrics::{OHLCVBarF32, PerformanceMetrics};
/// Test that Trade struct includes transaction cost fields
#[test]
@@ -57,7 +57,7 @@ fn test_trade_struct_has_cost_fields() {
#[test]
fn test_losing_trade_after_costs() {
let bars = vec![
OHLCVBar {
OHLCVBarF32 {
timestamp: 1000,
open: 100.0,
high: 100.0,
@@ -65,7 +65,7 @@ fn test_losing_trade_after_costs() {
close: 100.0,
volume: 1000.0,
},
OHLCVBar {
OHLCVBarF32 {
timestamp: 2000,
open: 100.2,
high: 100.2,
@@ -147,13 +147,13 @@ fn test_metrics_use_net_pnl() {
fn test_win_rate_uses_net_pnl() {
let bars = vec![
// Trade 1: Large profit (0.8) - still profitable after costs
OHLCVBar { timestamp: 1000, open: 100.0, high: 100.0, low: 100.0, close: 100.0, volume: 1000.0 },
OHLCVBar { timestamp: 2000, open: 100.8, high: 100.8, low: 100.8, close: 100.8, volume: 1000.0 },
OHLCVBarF32 { timestamp: 1000, open: 100.0, high: 100.0, low: 100.0, close: 100.0, volume: 1000.0 },
OHLCVBarF32 { timestamp: 2000, open: 100.8, high: 100.8, low: 100.8, close: 100.8, volume: 1000.0 },
// Hold to avoid opening new position
OHLCVBar { timestamp: 3000, open: 100.8, high: 100.8, low: 100.8, close: 100.8, volume: 1000.0 },
OHLCVBarF32 { timestamp: 3000, open: 100.8, high: 100.8, low: 100.8, close: 100.8, volume: 1000.0 },
// Trade 2: Small profit (0.2) - becomes loss after costs
OHLCVBar { timestamp: 4000, open: 100.8, high: 100.8, low: 100.8, close: 100.8, volume: 1000.0 },
OHLCVBar { timestamp: 5000, open: 101.0, high: 101.0, low: 101.0, close: 101.0, volume: 1000.0 },
OHLCVBarF32 { timestamp: 4000, open: 100.8, high: 100.8, low: 100.8, close: 100.8, volume: 1000.0 },
OHLCVBarF32 { timestamp: 5000, open: 101.0, high: 101.0, low: 101.0, close: 101.0, volume: 1000.0 },
];
let mut engine = EvaluationEngine::new(10000.0);
@@ -199,7 +199,7 @@ fn test_win_rate_uses_net_pnl() {
#[test]
fn test_short_position_transaction_costs() {
let bars = vec![
OHLCVBar {
OHLCVBarF32 {
timestamp: 1000,
open: 100.0,
high: 100.0,
@@ -207,7 +207,7 @@ fn test_short_position_transaction_costs() {
close: 100.0,
volume: 1000.0,
},
OHLCVBar {
OHLCVBarF32 {
timestamp: 2000,
open: 99.5,
high: 99.5,
@@ -295,7 +295,7 @@ fn test_zero_profit_after_costs() {
// Set up a trade that exactly breaks even after costs
// Gross profit = transaction costs
let bars = vec![
OHLCVBar {
OHLCVBarF32 {
timestamp: 1000,
open: 100.0,
high: 100.0,
@@ -303,7 +303,7 @@ fn test_zero_profit_after_costs() {
close: 100.0,
volume: 1000.0,
},
OHLCVBar {
OHLCVBarF32 {
timestamp: 2000,
open: 100.3,
high: 100.3,
@@ -329,9 +329,9 @@ fn test_zero_profit_after_costs() {
}
// Helper function to create standard test bars
fn create_test_bars() -> Vec<OHLCVBar> {
fn create_test_bars() -> Vec<OHLCVBarF32> {
vec![
OHLCVBar {
OHLCVBarF32 {
timestamp: 1000,
open: 100.0,
high: 100.0,
@@ -339,7 +339,7 @@ fn create_test_bars() -> Vec<OHLCVBar> {
close: 100.0,
volume: 1000.0,
},
OHLCVBar {
OHLCVBarF32 {
timestamp: 2000,
open: 100.5,
high: 100.5,

View File

@@ -13,7 +13,8 @@
use anyhow::Result;
use chrono::Utc;
use ml::real_data_loader::{OHLCVBar, RealDataLoader};
use ml::real_data_loader::RealDataLoader;
use ml::types::OHLCVBar;
use std::path::PathBuf;
use tempfile::TempDir;

View File

@@ -4,7 +4,7 @@
// in the backtesting pipeline (PortfolioTracker → EvaluationEngine → Hyperopt).
use ml::evaluation::engine::{Action, EvaluationEngine};
use ml::evaluation::metrics::OHLCVBar;
use ml::evaluation::metrics::OHLCVBarF32;
#[test]
fn test_kelly_scales_pnl_correctly() {
@@ -12,7 +12,7 @@ fn test_kelly_scales_pnl_correctly() {
let mut engine_full = EvaluationEngine::new_with_kelly(10_000.0, 1.0);
// Open long position at $100
let bar1 = OHLCVBar {
let bar1 = OHLCVBarF32 {
timestamp: 0,
open: 100.0,
high: 100.0,
@@ -23,7 +23,7 @@ fn test_kelly_scales_pnl_correctly() {
engine_full.process_bar(0, &bar1, Action::Buy);
// Close at $110 (10% gain)
let bar2 = OHLCVBar {
let bar2 = OHLCVBarF32 {
timestamp: 1,
open: 110.0,
high: 110.0,
@@ -63,7 +63,7 @@ fn test_kelly_scales_losses_correctly() {
// Full Kelly (1.0)
let mut engine_full = EvaluationEngine::new_with_kelly(10_000.0, 1.0);
let bar1 = OHLCVBar {
let bar1 = OHLCVBarF32 {
timestamp: 0,
open: 100.0,
high: 100.0,
@@ -74,7 +74,7 @@ fn test_kelly_scales_losses_correctly() {
engine_full.process_bar(0, &bar1, Action::Buy);
// Price drops to $90 (-10% loss)
let bar2 = OHLCVBar {
let bar2 = OHLCVBarF32 {
timestamp: 1,
open: 90.0,
high: 90.0,
@@ -106,7 +106,7 @@ fn test_kelly_with_short_positions() {
let mut engine_full = EvaluationEngine::new_with_kelly(10_000.0, 1.0);
// Short at $100
let bar1 = OHLCVBar {
let bar1 = OHLCVBarF32 {
timestamp: 0,
open: 100.0,
high: 100.0,
@@ -117,7 +117,7 @@ fn test_kelly_with_short_positions() {
engine_full.process_bar(0, &bar1, Action::Sell);
// Cover at $90 (10% profit for short)
let bar2 = OHLCVBar {
let bar2 = OHLCVBarF32 {
timestamp: 1,
open: 90.0,
high: 90.0,
@@ -146,7 +146,7 @@ fn test_default_new_uses_full_kelly() {
// Verify that EvaluationEngine::new() defaults to Kelly=1.0
let mut engine_default = EvaluationEngine::new(10_000.0);
let bar1 = OHLCVBar {
let bar1 = OHLCVBarF32 {
timestamp: 0,
open: 100.0,
high: 100.0,
@@ -156,7 +156,7 @@ fn test_default_new_uses_full_kelly() {
};
engine_default.process_bar(0, &bar1, Action::Buy);
let bar2 = OHLCVBar {
let bar2 = OHLCVBarF32 {
timestamp: 1,
open: 110.0,
high: 110.0,

View File

@@ -9,7 +9,7 @@
//! 3. Run tests and verify all pass
use ml::evaluation::engine::{Action, EvaluationEngine};
use ml::evaluation::metrics::{PerformanceMetrics, OHLCVBar};
use ml::evaluation::metrics::{PerformanceMetrics, OHLCVBarF32};
/// Results from dual-phase backtesting (exploration vs exploitation)
#[derive(Debug, Clone)]
@@ -76,8 +76,8 @@ impl DualPhaseBacktestResults {
}
// Helper function to create a dummy OHLCVBar for testing
fn create_test_bar(price: f32) -> OHLCVBar {
OHLCVBar {
fn create_test_bar(price: f32) -> OHLCVBarF32 {
OHLCVBarF32 {
timestamp: 0,
open: price,
high: price + 1.0,

View File

@@ -24,7 +24,7 @@ use ml::validation::{
/// 8-10: MACD (line, signal, histogram = line - signal)
/// 11-13: Bollinger Bands (upper, middle, lower)
/// 14: ATR(14)
fn build_features_from_loader(loader: &RealDataLoader, bars: &[ml::real_data_loader::OHLCVBar]) -> Vec<Vec<f32>> {
fn build_features_from_loader(loader: &RealDataLoader, bars: &[ml::types::OHLCVBar]) -> Vec<Vec<f32>> {
let feat_matrix = loader
.extract_features(bars)
.expect("extract_features failed");