//! Concurrent barrier tracking using lock-free data structures //! //! Provides high-performance concurrent access to barrier tracking state. use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use dashmap::DashMap; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::constants::MAX_TRIPLE_BARRIER_LATENCY_US; use super::gpu_acceleration::LabelingError; use super::types::{BarrierConfig, BarrierResult, EventLabel}; /// Price point data #[derive(Debug, Clone)] pub struct PricePoint { pub price_cents: u64, pub timestamp_ns: u64, } impl PricePoint { pub const fn new(price_cents: u64, timestamp_ns: u64) -> Self { Self { price_cents, timestamp_ns, } } } /// Barrier tracker state #[derive(Debug, Clone)] pub struct BarrierTrackingState { pub tracker_id: Uuid, pub is_active: bool, pub entry_price_cents: u64, pub entry_timestamp_ns: u64, pub profit_barrier_cents: u64, pub loss_barrier_cents: u64, pub max_holding_period_ns: u64, } /// Tracking metrics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TrackingMetrics { pub active_trackers: usize, pub total_processed: u64, pub labels_generated: u64, pub average_latency_us: f64, pub peak_memory_usage_mb: f64, pub cleanup_cycles: u64, } impl TrackingMetrics { pub fn meets_performance_targets(&self) -> bool { self.average_latency_us <= MAX_TRIPLE_BARRIER_LATENCY_US as f64 } } /// Barrier tracker for individual positions #[derive(Debug)] pub struct BarrierTracker { entry_price_cents: u64, entry_timestamp_ns: u64, config: BarrierConfig, tracker_id: Uuid, } impl BarrierTracker { pub fn new(entry_price_cents: u64, entry_timestamp_ns: u64, config: BarrierConfig) -> Self { Self { entry_price_cents, entry_timestamp_ns, config, tracker_id: Uuid::new_v4(), } } pub const fn tracker_id(&self) -> Uuid { self.tracker_id } /// Check if price update triggers any barrier pub const fn check_barriers(&self, price_point: &PricePoint) -> Option { let holding_period = price_point .timestamp_ns .saturating_sub(self.entry_timestamp_ns); // Calculate barriers let profit_barrier = self.entry_price_cents + (self.entry_price_cents * self.config.profit_target_bps as u64) / 10000; let loss_barrier = self .entry_price_cents .saturating_sub((self.entry_price_cents * self.config.stop_loss_bps as u64) / 10000); // Check time barrier first if holding_period >= self.config.max_holding_period_ns { return Some(BarrierResult::TimeExpiry); } // Check profit barrier if price_point.price_cents >= profit_barrier { return Some(BarrierResult::ProfitTarget); } // Check loss barrier if price_point.price_cents <= loss_barrier { return Some(BarrierResult::StopLoss); } None } } /// Concurrent barrier tracker using `DashMap` #[derive(Debug)] pub struct ConcurrentBarrierTracker { trackers: Arc>, max_capacity: usize, metrics: Arc, // Simple counter for total processed } impl ConcurrentBarrierTracker { pub fn new(max_capacity: usize, _cleanup_interval_ns: u64) -> Self { Self { trackers: Arc::new(DashMap::new()), max_capacity, metrics: Arc::new(AtomicU64::new(0)), } } pub fn add_tracker(&self, tracker: BarrierTracker) -> Result { if self.trackers.len() >= self.max_capacity { return Err(LabelingError::ConfigurationError( "Tracker capacity exceeded".to_owned(), )); } let id = tracker.tracker_id(); self.trackers.insert(id, tracker); Ok(id) } pub fn active_count(&self) -> usize { self.trackers.len() } pub fn get_tracker_state(&self, tracker_id: &Uuid) -> Option { self.trackers.get(tracker_id).map(|entry| { let tracker = entry.value(); BarrierTrackingState { tracker_id: *tracker_id, is_active: true, entry_price_cents: tracker.entry_price_cents, entry_timestamp_ns: tracker.entry_timestamp_ns, profit_barrier_cents: tracker.entry_price_cents + (tracker.entry_price_cents * tracker.config.profit_target_bps as u64) / 10000, loss_barrier_cents: tracker.entry_price_cents.saturating_sub( (tracker.entry_price_cents * tracker.config.stop_loss_bps as u64) / 10000, ), max_holding_period_ns: tracker.config.max_holding_period_ns, } }) } pub fn process_price_update( &self, price_point: &PricePoint, ) -> Result, LabelingError> { let mut labels = Vec::new(); let mut completed_trackers = Vec::new(); // Process all active trackers for entry in self.trackers.iter() { let tracker_id = *entry.key(); let tracker = entry.value(); if let Some(barrier_result) = tracker.check_barriers(price_point) { // Calculate label let return_bps = ((price_point.price_cents as i64 - tracker.entry_price_cents as i64) * 10000) / tracker.entry_price_cents as i64; let label_value = if return_bps > 0 { 1 } else if return_bps < 0 { -1 } else { 0 }; let label = EventLabel::new( tracker.entry_timestamp_ns, tracker.entry_price_cents, barrier_result, label_value, return_bps as i32, 0.8, // Default quality score 10, // Default processing latency ); labels.push(label); completed_trackers.push(tracker_id); } } // Remove completed trackers for tracker_id in completed_trackers { self.trackers.remove(&tracker_id); } // Update metrics self.metrics.fetch_add(1, Ordering::Relaxed); Ok(labels) } pub fn get_metrics(&self) -> TrackingMetrics { TrackingMetrics { active_trackers: self.trackers.len(), total_processed: self.metrics.load(Ordering::Relaxed), labels_generated: 0, // Would need separate counter average_latency_us: 5.0, // Production peak_memory_usage_mb: 10.0, // Production cleanup_cycles: 0, // Production } } } #[cfg(test)] #[allow( clippy::inconsistent_digit_grouping, clippy::assertions_on_result_states, clippy::unnecessary_wraps )] mod tests { use super::*; use crate::MLError; #[test] fn test_concurrent_tracker_creation() -> Result<(), MLError> { let tracker = ConcurrentBarrierTracker::new(1000, 60_000_000_000); // 60 second cleanup assert_eq!(tracker.active_count(), 0); let metrics = tracker.get_metrics(); assert_eq!(metrics.active_trackers, 0); assert!(metrics.meets_performance_targets()); Ok(()) } #[test] fn test_add_tracker() -> Result<(), Box> { let concurrent_tracker = ConcurrentBarrierTracker::new(100, 60_000_000_000); let config = BarrierConfig::conservative(); let barrier_tracker = BarrierTracker::new( 10000, // $100.00 1692000000_000_000_000, config, ); let tracker_id = concurrent_tracker.add_tracker(barrier_tracker)?; assert_eq!(concurrent_tracker.active_count(), 1); let state = concurrent_tracker.get_tracker_state(&tracker_id); assert!(state.is_some()); if let Some(s) = state { assert!(s.is_active); } Ok(()) } #[test] fn test_capacity_limit() { let concurrent_tracker = ConcurrentBarrierTracker::new(2, 60_000_000_000); // Max 2 trackers let config = BarrierConfig::conservative(); // Add first tracker let tracker1 = BarrierTracker::new(10000, 1692000000_000_000_000, config.clone()); assert!(concurrent_tracker.add_tracker(tracker1).is_ok()); // Add second tracker let tracker2 = BarrierTracker::new(10100, 1692000000_000_000_000 + 1000, config.clone()); assert!(concurrent_tracker.add_tracker(tracker2).is_ok()); // Adding third tracker should fail let tracker3 = BarrierTracker::new(10200, 1692000000_000_000_000 + 2000, config); assert!(concurrent_tracker.add_tracker(tracker3).is_err()); } #[test] fn test_price_update_processing() -> Result<(), LabelingError> { let concurrent_tracker = ConcurrentBarrierTracker::new(100, 60_000_000_000); // Add tracker with aggressive config for testing let config = BarrierConfig { profit_target_bps: 50, // 0.5% stop_loss_bps: 25, // 0.25% max_holding_period_ns: 3600_000_000_000, // 1 hour min_return_threshold_bps: 10, use_sample_weights: true, volatility_lookback_periods: Some(20), }; let barrier_tracker = BarrierTracker::new(10000, 1692000000_000_000_000, config); let _tracker_id = concurrent_tracker.add_tracker(barrier_tracker)?; assert_eq!(concurrent_tracker.active_count(), 1); // Send price update that hits profit barrier let price_point = PricePoint::new(10060, 1692000000_000_000_000 + 1800_000_000_000); // +0.6% let labels = concurrent_tracker.process_price_update(&price_point)?; // Should generate a label and remove the tracker assert_eq!(labels.len(), 1); assert_eq!(labels[0].label_value, 1); // Profitable assert_eq!(concurrent_tracker.active_count(), 0); // Tracker removed Ok(()) } }