#![allow(unsafe_code)] // Intentional unsafe for AVX2 vectorized backtesting performance

//! Adaptive Strategy Runner for Backtesting
//!
//! This module provides the bridge between the backtesting engine and the adaptive strategy
//! system, allowing historical data to flow through real ML models for validation.

use anyhow::Result;
use async_trait::async_trait;
use common::ml_strategy::ProductionFeatureExtractor225;
use common::Order;
use common::{OrderSide, OrderStatus, Position, Price, Quantity, Symbol};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use trading_engine::types::events::MarketEvent;
// Use canonical types from ML module and real ML registry
use chrono::{DateTime, Utc};
use dashmap::DashMap;
use ml::features::ProductionFeatureExtractorAdapter;
use ml::{get_global_registry, Features, ModelPrediction};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::{debug, info, warn};

// SIMD optimizations for HFT performance
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;

use crate::strategy_tester::{
    PerformanceSnapshot, SignalType, Strategy, StrategyConfig, StrategyContext, StrategyResult,
    TradeRecord, TradingSignal,
};

/// Adaptive strategy runner that integrates ML models with backtesting
pub struct AdaptiveStrategyRunner {
    /// Strategy configuration
    config: AdaptiveStrategyConfig,
    /// Current market state
    market_state: Arc<RwLock<MarketState>>,
    /// Model predictions cache (lock-free for HFT performance)
    predictions_cache: Arc<DashMap<String, ModelPrediction>>,
    /// Performance tracking
    performance_tracker: Arc<RwLock<PerformanceTracker>>,
    /// Feature extractor (legacy 3-5 dim fallback)
    feature_extractor: Arc<FeatureExtractor>,
    /// Production 51-dimension feature extractor from ml::features
    production_extractor: Arc<RwLock<ProductionFeatureExtractorAdapter>>,
    /// Risk manager
    risk_manager: Arc<RiskManager>,
}

/// Configuration for adaptive strategy
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdaptiveStrategyConfig {
    /// Models to use for predictions
    pub active_models: Vec<String>,
    /// Minimum confidence threshold for trading
    pub min_confidence: f64,
    /// Maximum position size as fraction of portfolio
    pub max_position_size: f64,
    /// Lookback period for feature extraction
    pub lookback_period: usize,
    /// Model update frequency (in ticks)
    pub model_update_frequency: u64,
    /// Risk management settings
    pub risk_settings: RiskSettings,
    /// Feature extraction settings
    pub feature_settings: FeatureSettings,
}

impl Default for AdaptiveStrategyConfig {
    fn default() -> Self {
        Self {
            active_models: vec![
                "TLOB".to_string(),
                "MAMBA".to_string(),
                "TFT".to_string(),
                "DQN".to_string(),
                "PPO".to_string(),
            ],
            min_confidence: 0.65,
            max_position_size: 0.05, // 5% max position
            lookback_period: 100,
            model_update_frequency: 1000,
            risk_settings: RiskSettings::default(),
            feature_settings: FeatureSettings::default(),
        }
    }
}

/// Risk management settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskSettings {
    /// Maximum drawdown before stopping
    pub max_drawdown: f64,
    /// Stop loss percentage
    pub stop_loss: f64,
    /// Take profit percentage
    pub take_profit: f64,
    /// Kelly fraction multiplier
    pub kelly_fraction: f64,
}

impl Default for RiskSettings {
    fn default() -> Self {
        Self {
            max_drawdown: 0.10,   // 10% max drawdown
            stop_loss: 0.02,      // 2% stop loss
            take_profit: 0.04,    // 4% take profit
            kelly_fraction: 0.25, // Conservative 25% of Kelly
        }
    }
}

/// Feature extraction settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureSettings {
    /// Price features to extract
    pub price_features: Vec<String>,
    /// Volume features to extract
    pub volume_features: Vec<String>,
    /// Technical indicators to compute
    pub technical_indicators: Vec<String>,
    /// Microstructure features
    pub microstructure_features: Vec<String>,
}

impl Default for FeatureSettings {
    fn default() -> Self {
        Self {
            price_features: vec![
                "returns".to_string(),
                "log_returns".to_string(),
                "volatility".to_string(),
                "price_momentum".to_string(),
            ],
            volume_features: vec![
                "volume".to_string(),
                "volume_momentum".to_string(),
                "vwap".to_string(),
            ],
            technical_indicators: vec![
                "rsi".to_string(),
                "macd".to_string(),
                "bollinger_bands".to_string(),
            ],
            microstructure_features: vec![
                "bid_ask_spread".to_string(),
                "order_flow_imbalance".to_string(),
                "market_impact".to_string(),
            ],
        }
    }
}

/// Current market state
#[derive(Debug, Clone)]
struct MarketState {
    /// Current timestamp
    current_time: DateTime<Utc>,
    /// Price history
    price_history: Vec<(DateTime<Utc>, Decimal)>,
    /// Volume history
    volume_history: Vec<(DateTime<Utc>, Decimal)>,
    /// Current position
    current_position: Option<Position>,
}

impl Default for MarketState {
    fn default() -> Self {
        Self {
            current_time: Utc::now(),
            price_history: Vec::new(),
            volume_history: Vec::new(),
            current_position: None,
        }
    }
}

/// Tracks an open (or partially open) entry for PnL accounting.
#[derive(Debug, Clone)]
struct OpenEntry {
    /// Side of the original entry
    side: OrderSide,
    /// Average entry price
    entry_price: Price,
    /// Remaining quantity in the entry
    quantity: Decimal,
    /// Timestamp when the position was opened
    entry_time: DateTime<Utc>,
}

/// Performance tracking for the strategy
#[derive(Debug, Clone)]
struct PerformanceTracker {
    /// Total trades executed
    total_trades: u64,
    /// Winning trades
    winning_trades: u64,
    /// Total PnL
    total_pnl: Decimal,
    /// Maximum drawdown
    max_drawdown: Decimal,
    /// Current drawdown
    current_drawdown: Decimal,
    /// Peak portfolio value
    peak_value: Decimal,
    /// Initial capital for equity curve baseline
    initial_capital: Decimal,
    /// Completed round-trip trade records
    trade_records: Vec<TradeRecord>,
    /// Equity snapshots: (timestamp, portfolio_value)
    equity_snapshots: Vec<(DateTime<Utc>, Decimal)>,
    /// Open position entries keyed by symbol for PnL accounting
    open_entries: HashMap<String, OpenEntry>,
}

impl Default for PerformanceTracker {
    fn default() -> Self {
        Self {
            total_trades: 0,
            winning_trades: 0,
            total_pnl: Decimal::ZERO,
            max_drawdown: Decimal::ZERO,
            current_drawdown: Decimal::ZERO,
            peak_value: Decimal::ZERO,
            initial_capital: Decimal::ZERO,
            trade_records: Vec::new(),
            equity_snapshots: Vec::new(),
            open_entries: HashMap::new(),
        }
    }
}

/// Feature extractor for ML models with object pooling for performance
struct FeatureExtractor {
    config: FeatureSettings,
}

impl FeatureExtractor {
    /// Create new feature extractor with configuration
    ///
    /// # Arguments
    /// * `config` - Feature extraction configuration
    ///
    /// # Returns
    /// * `Self` - New feature extractor instance
    fn new(config: FeatureSettings) -> Self {
        Self {
            config,
        }
    }

    /// Extract features from market data
    ///
    /// # Arguments
    /// * `market_state` - Current market state containing price and volume history
    ///
    /// # Returns
    /// * `Result<Features>` - Extracted feature vector ready for ML model input
    ///
    /// # Errors
    ///
    /// Returns error if feature extraction fails or insufficient data
    async fn extract_features(&self, market_state: &MarketState) -> Result<Features> {
        let mut feature_values = Vec::new();
        let mut feature_names = Vec::new();

        // Extract price features
        if let Some(features) = self.extract_price_features(market_state).await? {
            feature_values.extend(features.0);
            feature_names.extend(features.1);
        }

        // Extract volume features
        if let Some(features) = self.extract_volume_features(market_state).await? {
            feature_values.extend(features.0);
            feature_names.extend(features.1);
        }

        // Extract technical indicators
        if let Some(features) = self.extract_technical_features(market_state).await? {
            feature_values.extend(features.0);
            feature_names.extend(features.1);
        }

        Ok(Features {
            values: feature_values,
            names: feature_names,
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_micros() as u64,
            symbol: None, // Symbol will be set by the calling context when available
        })
    }

    /// Extract price-based features from market data
    ///
    /// # Arguments
    /// * `market_state` - Market state with price history
    ///
    /// # Returns
    /// * `Result<Option<(Vec<f64>, Vec<String>)>>` - Feature values and names, or None if insufficient data
    async fn extract_price_features(
        &self,
        market_state: &MarketState,
    ) -> Result<Option<(Vec<f64>, Vec<String>)>> {
        if market_state.price_history.len() < 2 {
            return Ok(None);
        }

        let mut values = Vec::new();
        let mut names = Vec::new();

        let prices: Vec<f64> = market_state
            .price_history
            .iter()
            .map(|(_, price)| price.to_f64().unwrap_or(0.0))
            .collect();

        // Calculate returns
        if self.config.price_features.contains(&"returns".to_string()) {
            let returns = self.calculate_returns(&prices);
            values.extend(returns);
            names.push("returns".to_string());
        }

        // Calculate volatility
        if self
            .config
            .price_features
            .contains(&"volatility".to_string())
        {
            let volatility = self.calculate_volatility(&prices);
            values.push(volatility);
            names.push("volatility".to_string());
        }

        Ok(Some((values, names)))
    }

    /// Extract volume-based features from market data
    ///
    /// # Arguments
    /// * `market_state` - Market state with volume history
    ///
    /// # Returns
    /// * `Result<Option<(Vec<f64>, Vec<String>)>>` - Feature values and names, or None if insufficient data
    async fn extract_volume_features(
        &self,
        market_state: &MarketState,
    ) -> Result<Option<(Vec<f64>, Vec<String>)>> {
        if market_state.volume_history.len() < 2 {
            return Ok(None);
        }

        let mut values = Vec::new();
        let mut names = Vec::new();

        let volumes: Vec<f64> = market_state
            .volume_history
            .iter()
            .map(|(_, volume)| volume.to_f64().unwrap_or(0.0))
            .collect();

        // Average volume
        if self.config.volume_features.contains(&"volume".to_string()) {
            let avg_volume = volumes.iter().sum::<f64>() / volumes.len() as f64;
            values.push(avg_volume);
            names.push("avg_volume".to_string());
        }

        Ok(Some((values, names)))
    }

    /// Extract technical indicator features from market data
    ///
    /// # Arguments
    /// * `market_state` - Market state with sufficient price history for indicators
    ///
    /// # Returns
    /// * `Result<Option<(Vec<f64>, Vec<String>)>>` - Technical indicator values and names, or None if insufficient data
    async fn extract_technical_features(
        &self,
        market_state: &MarketState,
    ) -> Result<Option<(Vec<f64>, Vec<String>)>> {
        if market_state.price_history.len() < 14 {
            // Need minimum data for indicators
            return Ok(None);
        }

        let mut values = Vec::new();
        let mut names = Vec::new();

        let prices: Vec<f64> = market_state
            .price_history
            .iter()
            .map(|(_, price)| price.to_f64().unwrap_or(0.0))
            .collect();

        // RSI
        if self
            .config
            .technical_indicators
            .contains(&"rsi".to_string())
        {
            let rsi = self.calculate_rsi(&prices, 14);
            values.push(rsi);
            names.push("rsi_14".to_string());
        }

        Ok(Some((values, names)))
    }

    /// Calculate returns from price series with SIMD optimization
    ///
    /// # Arguments
    /// * `prices` - Array of price values
    ///
    /// # Returns
    /// * `Vec<f64>` - Vector of return percentages
    ///
    /// # Note
    ///
    /// Uses SIMD instructions on x86_64 for performance when available
    fn calculate_returns(&self, prices: &[f64]) -> Vec<f64> {
        // OPTIMIZATION: Use SIMD for vectorized return calculations
        if prices.len() < 2 {
            return Vec::new();
        }

        #[cfg(target_arch = "x86_64")]
        {
            if is_x86_feature_detected!("avx2") {
                return self.calculate_returns_simd(prices);
            }
        }

        // Fallback to scalar implementation
        self.calculate_returns_scalar(prices)
    }

    /// Calculate returns using scalar operations (fallback)
    ///
    /// # Arguments
    /// * `prices` - Array of price values
    ///
    /// # Returns
    /// * `Vec<f64>` - Vector of return percentages
    fn calculate_returns_scalar(&self, prices: &[f64]) -> Vec<f64> {
        prices
            .windows(2)
            .map(|window| {
                if window[0] != 0.0 {
                    (window[1] - window[0]) / window[0]
                } else {
                    0.0
                }
            })
            .collect()
    }

    /// Calculate returns using SIMD AVX2 instructions (x86_64 only)
    ///
    /// # Arguments
    /// * `prices` - Array of price values
    ///
    /// # Returns
    /// * `Vec<f64>` - Vector of return percentages
    ///
    /// # Safety
    ///
    /// Uses unsafe AVX2 intrinsics for vectorized computation
    #[cfg(target_arch = "x86_64")]
    fn calculate_returns_simd(&self, prices: &[f64]) -> Vec<f64> {
        let mut returns = Vec::with_capacity(prices.len() - 1);
        let len = prices.len() - 1;

        // SAFETY: SIMD intrinsics validated with feature detection and proper data alignment
        unsafe {
            // Process 4 elements at a time with AVX2
            let mut i = 0;
            while i + 4 <= len {
                let prev = _mm256_loadu_pd(prices.as_ptr().add(i));
                let curr = _mm256_loadu_pd(prices.as_ptr().add(i + 1));

                // Calculate (curr - prev) / prev
                let diff = _mm256_sub_pd(curr, prev);
                let result = _mm256_div_pd(diff, prev);

                // Store results
                let mut temp = [0.0; 4];
                _mm256_storeu_pd(temp.as_mut_ptr(), result);

                for j in 0..4 {
                    returns.push(if prices[i + j] != 0.0 { temp[j] } else { 0.0 });
                }

                i += 4;
            }

            // Handle remaining elements
            for j in i..len {
                let ret = if prices[j] != 0.0 {
                    (prices[j + 1] - prices[j]) / prices[j]
                } else {
                    0.0
                };
                returns.push(ret);
            }
        }

        returns
    }

    /// Calculate price volatility (standard deviation of returns)
    ///
    /// # Arguments
    /// * `prices` - Array of price values
    ///
    /// # Returns
    /// * `f64` - Volatility as standard deviation of returns
    fn calculate_volatility(&self, prices: &[f64]) -> f64 {
        let returns = self.calculate_returns(prices);
        if returns.is_empty() {
            return 0.0;
        }

        let mean = returns.iter().sum::<f64>() / returns.len() as f64;
        let variance =
            returns.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / returns.len() as f64;

        variance.sqrt()
    }

    /// Calculate Relative Strength Index (RSI)
    ///
    /// # Arguments
    /// * `prices` - Array of price values
    ///
    /// * `period` - Period for RSI calculation (typically 14)
    ///
    /// # Returns
    /// * `f64` - RSI value between 0 and 100
    fn calculate_rsi(&self, prices: &[f64], period: usize) -> f64 {
        if prices.len() < period + 1 {
            return 50.0; // Neutral RSI
        }

        let mut gains = Vec::new();
        let mut losses = Vec::new();

        for window in prices.windows(2) {
            let change = window[1] - window[0];
            if change > 0.0 {
                gains.push(change);
                losses.push(0.0);
            } else {
                gains.push(0.0);
                losses.push(-change);
            }
        }

        if gains.len() < period {
            return 50.0;
        }

        let avg_gain = gains.iter().rev().take(period).sum::<f64>() / period as f64;
        let avg_loss = losses.iter().rev().take(period).sum::<f64>() / period as f64;

        if avg_loss == 0.0 {
            return 100.0;
        }

        let rs = avg_gain / avg_loss;
        100.0 - (100.0 / (1.0 + rs))
    }
}

/// Risk manager for position sizing and risk controls
struct RiskManager {
    config: RiskSettings,
}

impl RiskManager {
    /// Create new risk manager with configuration
    ///
    /// # Arguments
    /// * `config` - Risk management settings
    ///
    /// # Returns
    /// * `Self` - New risk manager instance
    fn new(config: RiskSettings) -> Self {
        Self { config }
    }

    /// Calculate position size using Kelly criterion
    ///
    /// # Arguments
    /// * `prediction` - Model prediction with confidence score
    ///
    /// * `account_value` - Current account value
    /// * `current_price` - Current market price
    ///
    /// # Returns
    /// * `Result<Decimal>` - Position size in shares/units
    ///
    /// # Note
    ///
    /// Uses conservative Kelly fraction scaling for risk management
    fn calculate_position_size(
        &self,
        prediction: &ModelPrediction,
        account_value: Decimal,
        current_price: Decimal,
    ) -> Result<Decimal> {
        // Simple Kelly-based position sizing
        let confidence = prediction.confidence;
        let edge = (confidence - 0.5) * 2.0; // Convert to [-1, 1] range

        if edge <= 0.0 {
            return Ok(Decimal::ZERO);
        }

        // Kelly fraction with conservative scaling
        let kelly_size =
            Decimal::try_from(edge * self.config.kelly_fraction).unwrap_or(Decimal::ZERO);

        let max_size = account_value
            * Decimal::try_from(self.config.max_drawdown).unwrap_or(Decimal::new(5, 2)); // 5% fallback

        let position_value = kelly_size * account_value;
        let position_size = if current_price > Decimal::ZERO {
            position_value / current_price
        } else {
            Decimal::ZERO
        };

        Ok(position_size.min(max_size / current_price))
    }

    /// Check if trade passes risk checks
    ///
    /// # Arguments
    /// * `signal` - Trading signal to validate
    ///
    /// * `current_position` - Current position if any
    /// * `account_value` - Current account value
    ///
    /// # Returns
    /// * `Result<bool>` - True if trade passes all risk checks
    fn validate_trade(
        &self,
        signal: &TradingSignal,
        _current_position: Option<&Position>,
        account_value: Decimal,
    ) -> Result<bool> {
        // Check position size limits
        if let Some(price) = signal.target_price {
            let trade_value = signal.quantity.to_decimal()? * price.to_decimal()?;
            let position_fraction = trade_value / account_value;

            if position_fraction
                > Decimal::try_from(self.config.max_drawdown).unwrap_or(Decimal::new(10, 2))
            {
                debug!(
                    "Trade rejected: position size too large ({:.2}%)",
                    position_fraction * Decimal::from(100)
                );
                return Ok(false);
            }
        }

        // Additional risk checks can be added here
        Ok(true)
    }
}

impl AdaptiveStrategyRunner {
    /// Create new adaptive strategy runner
    ///
    /// # Arguments
    /// * `config` - Configuration for adaptive strategy
    ///
    /// # Returns
    /// * `Self` - New adaptive strategy runner with initialized components
    pub fn new(config: AdaptiveStrategyConfig) -> Self {
        let feature_extractor = Arc::new(FeatureExtractor::new(config.feature_settings.clone()));
        let risk_manager = Arc::new(RiskManager::new(config.risk_settings.clone()));
        let production_extractor = Arc::new(RwLock::new(ProductionFeatureExtractorAdapter::new()));

        Self {
            config,
            market_state: Arc::new(RwLock::new(MarketState::default())),
            predictions_cache: Arc::new(DashMap::new()),
            performance_tracker: Arc::new(RwLock::new(PerformanceTracker::default())),
            feature_extractor,
            production_extractor,
            risk_manager,
        }
    }

    /// Get ensemble prediction from all active models (optimized for HFT performance)
    ///
    /// # Arguments
    /// * `features` - Feature vector for prediction
    ///
    /// # Returns
    /// * `Result<ModelPrediction>` - Ensemble prediction with confidence-weighted averaging
    ///
    /// # Note
    ///
    /// Uses lock-free caching and parallel model execution for low-latency performance
    async fn get_ensemble_prediction(&self, features: &Features) -> Result<ModelPrediction> {
        let registry = get_global_registry();

        // OPTIMIZATION: Use predict_selected for parallel model execution
        let predictions = registry
            .predict_selected(&self.config.active_models, features)
            .await;

        // Filter successful predictions
        let valid_predictions: Vec<ModelPrediction> = predictions
            .into_iter()
            .filter_map(|result| result.ok())
            .collect();

        if valid_predictions.is_empty() {
            return Err(anyhow::anyhow!("No valid predictions from any model"));
        }

        // Ensemble using confidence-weighted average
        let total_confidence: f64 = valid_predictions.iter().map(|p| p.confidence).sum();

        if total_confidence == 0.0 {
            return Err(anyhow::anyhow!("Zero total confidence in predictions"));
        }

        let weighted_value = valid_predictions
            .iter()
            .map(|p| p.value * p.confidence)
            .sum::<f64>()
            / total_confidence;

        let ensemble_confidence = valid_predictions.iter().map(|p| p.confidence).sum::<f64>()
            / valid_predictions.len() as f64;

        // OPTIMIZATION: Use lock-free DashMap instead of async RwLock
        for prediction in &valid_predictions {
            self.predictions_cache
                .insert(prediction.model_id.clone(), prediction.clone());
        }

        Ok(ModelPrediction::new(
            "ensemble".to_string(),
            weighted_value,
            ensemble_confidence,
        ))
    }

    /// Generate trading signal from prediction
    ///
    /// # Arguments
    /// * `prediction` - Model prediction with confidence and direction
    ///
    /// * `symbol` - Symbol to trade
    /// * `current_price` - Current market price
    ///
    /// * `account_value` - Current account value for position sizing
    ///
    /// # Returns
    /// * `Result<Option<TradingSignal>>` - Trading signal if confidence threshold is met
    fn generate_signal(
        &self,
        prediction: &ModelPrediction,
        symbol: Symbol,
        current_price: Decimal,
        account_value: Decimal,
    ) -> Result<Option<TradingSignal>> {
        // Check confidence threshold
        if prediction.confidence < self.config.min_confidence {
            debug!(
                "Prediction confidence {:.3} below threshold {:.3}",
                prediction.confidence, self.config.min_confidence
            );
            return Ok(None);
        }

        // Determine trade direction
        let side = if prediction.value > 0.5 {
            OrderSide::Buy
        } else if prediction.value < -0.5 {
            OrderSide::Sell
        } else {
            return Ok(None); // Neutral signal
        };

        // Calculate position size
        let quantity =
            self.risk_manager
                .calculate_position_size(prediction, account_value, current_price)?;

        if quantity <= Decimal::ZERO {
            return Ok(None);
        }

        let signal_type = match side {
            OrderSide::Buy => SignalType::Buy,
            OrderSide::Sell => SignalType::Sell,
        };

        let quantity_as_quantity = Quantity::from_f64(quantity.to_f64().unwrap_or(0.0))?;

        let mut metadata = HashMap::new();
        metadata.insert("strategy".to_string(), serde_json::json!("adaptive_ml"));
        metadata.insert(
            "ensemble_confidence".to_string(),
            serde_json::json!(prediction.confidence),
        );
        metadata.insert(
            "prediction_value".to_string(),
            serde_json::json!(prediction.value),
        );
        metadata.insert(
            "model_count".to_string(),
            serde_json::json!(self.config.active_models.len()),
        );

        let signal = TradingSignal {
            symbol,
            signal_type,
            quantity: quantity_as_quantity,
            target_price: Some(Price::from_f64(current_price.to_f64().unwrap_or(0.0))?),
            stop_loss: None,
            take_profit: None,
            confidence: Decimal::try_from(prediction.confidence).unwrap_or(Decimal::ZERO),
            metadata,
        };

        Ok(Some(signal))
    }
}

#[async_trait(?Send)]
impl Strategy for AdaptiveStrategyRunner {
    fn name(&self) -> &str {
        "adaptive_ml_strategy"
    }

    async fn initialize(
        &mut self,
        initial_capital: Decimal,
        _config: StrategyConfig,
    ) -> Result<()> {
        info!(
            "Initializing Adaptive ML Strategy with capital: {}",
            initial_capital
        );

        {
            let mut tracker = self.performance_tracker.write();
            tracker.peak_value = initial_capital;
            tracker.initial_capital = initial_capital;
        }

        // Verify models are available
        let registry = get_global_registry();
        let available_models = registry.get_model_names();

        for model_name in &self.config.active_models {
            if !available_models.contains(model_name) {
                warn!("Model {} not found in registry", model_name);
            }
        }

        info!("Adaptive ML Strategy initialized successfully");
        Ok(())
    }

    async fn on_market_event(
        &mut self,
        event: &MarketEvent,
        context: &StrategyContext,
    ) -> Result<Vec<TradingSignal>> {
        let mut signals = Vec::new();

        match event {
            MarketEvent::Trade {
                symbol,
                price,
                timestamp,
                ..
            } => {
                // Update market state
                {
                    let mut state = self.market_state.write();
                    state.current_time = *timestamp;
                    state.price_history.push((*timestamp, price.to_decimal()?));
                    // Note: volume not available in MarketEvent::Trade, using placeholder
                    // state.volume_history.push((*timestamp, Volume::ZERO));

                    // Keep only recent history
                    let max_history = self.config.lookback_period;
                    if state.price_history.len() > max_history {
                        let excess = state.price_history.len() - max_history;
                        state.price_history.drain(0..excess);
                    }
                    if state.volume_history.len() > max_history {
                        let excess = state.volume_history.len() - max_history;
                        state.volume_history.drain(0..excess);
                    }
                }

                // Update production extractor with new price data
                let price_f64 = price.to_decimal()?.to_f64().unwrap_or(0.0);
                // Volume not available in MarketEvent::Trade; use a default
                let volume_f64 = 1000.0;
                {
                    let mut prod_ext = self.production_extractor.write();
                    if let Err(e) = prod_ext.update(price_f64, volume_f64, *timestamp) {
                        debug!("Production extractor update failed: {}", e);
                    }
                }

                // Try production 51-dim feature extraction first
                let production_features = {
                    let mut prod_ext = self.production_extractor.write();
                    prod_ext.extract_features()
                };

                match production_features {
                    Ok(feat_values) if feat_values.len() == 51 => {
                        // Build feature names for the 51-dim vector
                        let feature_names: Vec<String> = (0..51)
                            .map(|i| format!("prod_feature_{}", i))
                            .collect();

                        let ts_micros = timestamp
                            .timestamp_micros();
                        let features = Features {
                            values: feat_values,
                            names: feature_names,
                            timestamp: ts_micros as u64,
                            symbol: Some(symbol.to_string()),
                        };

                        match self.get_ensemble_prediction(&features).await {
                            Ok(prediction) => {
                                if let Some(signal) = self.generate_signal(
                                    &prediction,
                                    symbol.clone(),
                                    price.to_decimal()?,
                                    context.account_balance,
                                )? {
                                    let current_position = context.positions.get(symbol);
                                    if self.risk_manager.validate_trade(
                                        &signal,
                                        current_position,
                                        context.account_balance,
                                    )? {
                                        signals.push(signal);
                                    }
                                }
                            }
                            Err(e) => {
                                debug!("Prediction failed: {}", e);
                            }
                        }
                    }
                    Ok(_) | Err(_) => {
                        // Production extractor not warmed up yet, fall back to legacy extractor
                        let market_state = self.market_state.read().clone();

                        if market_state.price_history.len() >= 10 {
                            match self
                                .feature_extractor
                                .extract_features(&market_state)
                                .await
                            {
                                Ok(features) => {
                                    match self.get_ensemble_prediction(&features).await {
                                        Ok(prediction) => {
                                            if let Some(signal) = self.generate_signal(
                                                &prediction,
                                                symbol.clone(),
                                                price.to_decimal()?,
                                                context.account_balance,
                                            )? {
                                                let current_position =
                                                    context.positions.get(symbol);
                                                if self.risk_manager.validate_trade(
                                                    &signal,
                                                    current_position,
                                                    context.account_balance,
                                                )? {
                                                    signals.push(signal);
                                                }
                                            }
                                        }
                                        Err(e) => {
                                            debug!("Prediction failed: {}", e);
                                        }
                                    }
                                }
                                Err(e) => {
                                    debug!("Feature extraction failed: {}", e);
                                }
                            }
                        }
                    }
                }
            },
            _ => {
                // Handle other event types if needed
            },
        }

        Ok(signals)
    }

    async fn on_order_update(&mut self, order: &Order, context: &StrategyContext) -> Result<()> {
        if order.status == OrderStatus::Filled {
            let fill_price = order
                .average_price
                .unwrap_or_else(|| order.price.unwrap_or(Price::ZERO));
            let fill_qty = order.quantity.to_decimal().unwrap_or(Decimal::ZERO);
            let symbol_key = order.symbol.to_string();

            let mut tracker = self.performance_tracker.write();

            // Check if this fill closes (or reduces) an existing open entry
            let mut closed = false;
            if let Some(entry) = tracker.open_entries.get(&symbol_key).cloned() {
                // A Buy closes a short entry; a Sell closes a long entry
                let is_closing = matches!(
                    (&entry.side, &order.side),
                    (OrderSide::Buy, OrderSide::Sell) | (OrderSide::Sell, OrderSide::Buy)
                );

                if is_closing {
                    let closed_qty = entry.quantity.min(fill_qty);
                    let entry_dec =
                        entry.entry_price.to_decimal().unwrap_or(Decimal::ZERO);
                    let exit_dec = fill_price.to_decimal().unwrap_or(Decimal::ZERO);

                    // PnL: long close = (exit - entry) * qty
                    //       short close = (entry - exit) * qty
                    let pnl = if entry.side == OrderSide::Buy {
                        (exit_dec - entry_dec) * closed_qty
                    } else {
                        (entry_dec - exit_dec) * closed_qty
                    };

                    let return_pct = if entry_dec != Decimal::ZERO {
                        pnl / (entry_dec * closed_qty) * Decimal::from(100)
                    } else {
                        Decimal::ZERO
                    };

                    let trade = TradeRecord {
                        trade_id: uuid::Uuid::new_v4().to_string(),
                        symbol: order.symbol.clone(),
                        side: entry.side.clone(),
                        entry_price: entry.entry_price,
                        exit_price: fill_price,
                        quantity: order.quantity,
                        entry_time: entry.entry_time,
                        exit_time: context.current_time,
                        pnl,
                        return_pct,
                        commission: Decimal::ZERO,
                    };

                    tracker.total_trades += 1;
                    tracker.total_pnl += pnl;
                    if pnl > Decimal::ZERO {
                        tracker.winning_trades += 1;
                    }
                    tracker.trade_records.push(trade);

                    // Update or remove the open entry
                    let remaining = entry.quantity - closed_qty;
                    if remaining <= Decimal::ZERO {
                        tracker.open_entries.remove(&symbol_key);
                    } else {
                        if let Some(e) = tracker.open_entries.get_mut(&symbol_key) {
                            e.quantity = remaining;
                        }
                    }

                    // If the fill is larger than the old entry, open a new entry
                    // for the remainder (position flip)
                    let excess = fill_qty - closed_qty;
                    if excess > Decimal::ZERO {
                        tracker.open_entries.insert(
                            symbol_key.clone(),
                            OpenEntry {
                                side: order.side.clone(),
                                entry_price: fill_price,
                                quantity: excess,
                                entry_time: context.current_time,
                            },
                        );
                    }

                    closed = true;
                }
            }

            if !closed {
                // Opening a new position or adding to existing
                let entry = tracker
                    .open_entries
                    .entry(symbol_key)
                    .or_insert_with(|| OpenEntry {
                        side: order.side.clone(),
                        entry_price: fill_price,
                        quantity: Decimal::ZERO,
                        entry_time: context.current_time,
                    });
                entry.quantity += fill_qty;
                // Update average entry price (weighted average)
                // This simple version just keeps the latest; for proper
                // accounting the PositionTracker in strategy_tester handles it
            }

            // Take an equity snapshot
            tracker
                .equity_snapshots
                .push((context.current_time, context.account_balance));

            info!(
                "Order filled: {} {} @ {} | total_pnl={} trades={} wins={}",
                order.side,
                order.quantity,
                fill_price,
                tracker.total_pnl,
                tracker.total_trades,
                tracker.winning_trades,
            );
        }

        Ok(())
    }

    async fn on_position_update(
        &mut self,
        position: &Position,
        context: &StrategyContext,
    ) -> Result<()> {
        // Update market state with current position
        {
            let mut state = self.market_state.write();
            state.current_position = Some(position.clone());
        }

        // Update performance tracking
        {
            let mut tracker = self.performance_tracker.write();

            if context.account_balance > tracker.peak_value {
                tracker.peak_value = context.account_balance;
                tracker.current_drawdown = Decimal::ZERO;
            } else if tracker.peak_value > Decimal::ZERO {
                tracker.current_drawdown =
                    (tracker.peak_value - context.account_balance) / tracker.peak_value;
                if tracker.current_drawdown > tracker.max_drawdown {
                    tracker.max_drawdown = tracker.current_drawdown;
                }
            }

            // Record equity snapshot on every position update
            tracker
                .equity_snapshots
                .push((context.current_time, context.account_balance));
        }

        Ok(())
    }

    async fn finalize(&mut self, context: &StrategyContext) -> Result<StrategyResult> {
        let tracker = self.performance_tracker.read();

        let baseline = if tracker.initial_capital > Decimal::ZERO {
            tracker.initial_capital
        } else {
            tracker.peak_value
        };

        let total_return = if baseline > Decimal::ZERO {
            (context.account_balance - baseline) / baseline
        } else {
            Decimal::ZERO
        };

        let win_rate = if tracker.total_trades > 0 {
            Decimal::from(tracker.winning_trades) / Decimal::from(tracker.total_trades)
        } else {
            Decimal::ZERO
        };

        // Calculate Sharpe ratio (simplified)
        let sharpe_ratio = if tracker.max_drawdown > Decimal::ZERO {
            total_return / tracker.max_drawdown
        } else {
            Decimal::ZERO
        };

        // Build performance timeline from equity snapshots
        let performance_timeline: Vec<PerformanceSnapshot> = tracker
            .equity_snapshots
            .iter()
            .map(|(ts, value)| {
                let realized = tracker.total_pnl;
                let dd = if tracker.peak_value > Decimal::ZERO {
                    (tracker.peak_value - *value) / tracker.peak_value
                } else {
                    Decimal::ZERO
                };
                PerformanceSnapshot {
                    timestamp: *ts,
                    portfolio_value: *value,
                    cash_balance: *value,
                    unrealized_pnl: Decimal::ZERO,
                    realized_pnl: realized,
                    open_positions: 0,
                    drawdown: if dd > Decimal::ZERO { dd } else { Decimal::ZERO },
                }
            })
            .collect();

        let trades = tracker.trade_records.clone();

        Ok(StrategyResult {
            strategy_name: "adaptive_ml_strategy".to_string(),
            total_return,
            annualized_return: total_return, // Simplified
            max_drawdown: tracker.max_drawdown,
            sharpe_ratio,
            total_trades: tracker.total_trades,
            win_rate,
            avg_trade_return: if tracker.total_trades > 0 {
                tracker.total_pnl / Decimal::from(tracker.total_trades)
            } else {
                Decimal::ZERO
            },
            final_value: context.account_balance,
            trades,
            performance_timeline,
        })
    }

    async fn get_state(&self) -> Result<serde_json::Value> {
        let market_state = self.market_state.read();
        let tracker = self.performance_tracker.read();
        let cache = &self.predictions_cache;

        Ok(serde_json::json!({
            "strategy_name": "adaptive_ml_strategy",
            "config": self.config,
            "current_time": market_state.current_time,
            "price_history_length": market_state.price_history.len(),
            "volume_history_length": market_state.volume_history.len(),
            "current_position": market_state.current_position,
            "total_trades": tracker.total_trades,
            "max_drawdown": tracker.max_drawdown,
            "cached_predictions": cache.len(),
            "active_models": self.config.active_models,
        }))
    }
}

/// Create a configured adaptive strategy runner
///
/// # Returns
/// * `AdaptiveStrategyRunner` - Strategy runner with default configuration
pub fn create_adaptive_strategy() -> AdaptiveStrategyRunner {
    AdaptiveStrategyRunner::new(AdaptiveStrategyConfig::default())
}

/// Create adaptive strategy with custom configuration
///
/// # Arguments
/// * `config` - Custom adaptive strategy configuration
///
/// # Returns
/// * `AdaptiveStrategyRunner` - Strategy runner with specified configuration
pub fn create_adaptive_strategy_with_config(
    config: AdaptiveStrategyConfig,
) -> AdaptiveStrategyRunner {
    AdaptiveStrategyRunner::new(config)
}

#[cfg(test)]
#[allow(
    clippy::float_cmp,
    clippy::field_reassign_with_default,
    clippy::cast_lossless,
)]
mod tests {
    use super::*;

    #[test]
    fn test_adaptive_strategy_config_default() {
        let config = AdaptiveStrategyConfig::default();
        assert_eq!(config.active_models.len(), 5);
        assert_eq!(config.min_confidence, 0.65);
        assert!(config.max_position_size > 0.0);
    }

    #[test]
    fn test_risk_settings_default() {
        let risk = RiskSettings::default();
        assert_eq!(risk.max_drawdown, 0.10);
        assert!(risk.kelly_fraction > 0.0);
    }

    #[tokio::test]
    async fn test_adaptive_strategy_creation() {
        let strategy = create_adaptive_strategy();
        assert_eq!(strategy.name(), "adaptive_ml_strategy");
    }

    #[tokio::test]
    async fn test_feature_extractor() {
        let config = FeatureSettings::default();
        let extractor = FeatureExtractor::new(config);

        let mut market_state = MarketState::default();
        market_state.price_history = vec![
            (Utc::now(), Decimal::from(100)),
            (Utc::now(), Decimal::from(101)),
            (Utc::now(), Decimal::from(102)),
        ];

        let features = extractor.extract_features(&market_state).await;
        assert!(features.is_ok());
    }

    #[test]
    fn test_production_features_have_42_dimensions() -> Result<(), Box<dyn std::error::Error>> {
        let mut extractor = ProductionFeatureExtractorAdapter::new();
        // Feed 55 price updates (past the warmup period of 50)
        for i in 0..55 {
            let price = 100.0 + i as f64 * 0.1;
            let volume = 1000.0;
            let timestamp = Utc::now();
            extractor.update(price, volume, timestamp)?;
        }
        let features = extractor.extract_features()?;
        assert_eq!(
            features.len(),
            42,
            "Production extractor should produce exactly 42 features, got {}",
            features.len()
        );
        // 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_production_extractor_warmup_skips_gracefully() {
        let mut extractor = ProductionFeatureExtractorAdapter::new();
        // Feed only 5 price updates (below warmup threshold)
        for i in 0..5 {
            let price = 100.0 + i as f64 * 0.1;
            let _ = extractor.update(price, 1000.0, Utc::now());
        }
        // Should either return an error or return a short vector
        // (during warmup, extraction may fail)
        let result = extractor.extract_features();
        // We accept either an error (warmup not ready) or a valid 42-dim result
        match result {
            Ok(features) => {
                assert_eq!(features.len(), 42);
            }
            Err(_) => {
                // Expected during warmup - this is fine
            }
        }
    }

    #[test]
    fn test_adaptive_strategy_has_production_extractor() {
        let strategy = create_adaptive_strategy();
        // Verify the production extractor is initialized
        let ext = strategy.production_extractor.read();
        // Should be able to access the extractor without issues
        drop(ext);
    }

    #[test]
    fn test_performance_tracker_records_trade() {
        use crate::strategy_tester::TradeRecord;

        let mut tracker = PerformanceTracker::default();
        tracker.initial_capital = Decimal::from(100_000);
        tracker.peak_value = Decimal::from(100_000);

        // Simulate opening a long entry
        tracker.open_entries.insert(
            "AAPL".to_string(),
            OpenEntry {
                side: OrderSide::Buy,
                entry_price: Price::from_f64(100.0).unwrap(),
                quantity: Decimal::from(10),
                entry_time: Utc::now(),
            },
        );

        // Simulate a winning close: sold at 110
        let pnl = (Decimal::from(110) - Decimal::from(100)) * Decimal::from(10); // +100
        tracker.total_trades += 1;
        tracker.total_pnl += pnl;
        if pnl > Decimal::ZERO {
            tracker.winning_trades += 1;
        }
        tracker.trade_records.push(TradeRecord {
            trade_id: "test-1".to_string(),
            symbol: Symbol::from("AAPL"),
            side: OrderSide::Buy,
            entry_price: Price::from_f64(100.0).unwrap(),
            exit_price: Price::from_f64(110.0).unwrap(),
            quantity: Quantity::from_f64(10.0).unwrap(),
            entry_time: Utc::now(),
            exit_time: Utc::now(),
            pnl,
            return_pct: Decimal::from(10),
            commission: Decimal::ZERO,
        });
        tracker.open_entries.remove("AAPL");

        assert_eq!(tracker.total_trades, 1);
        assert_eq!(tracker.winning_trades, 1);
        assert_eq!(tracker.total_pnl, Decimal::from(100));
        assert_eq!(tracker.trade_records.len(), 1);
        assert_eq!(tracker.trade_records.first().map(|t| &t.symbol), Some(&Symbol::from("AAPL")));
    }

    #[test]
    fn test_performance_tracker_losing_trade() {
        let mut tracker = PerformanceTracker::default();
        tracker.initial_capital = Decimal::from(100_000);
        tracker.peak_value = Decimal::from(100_000);

        // Simulate opening a long entry
        tracker.open_entries.insert(
            "TSLA".to_string(),
            OpenEntry {
                side: OrderSide::Buy,
                entry_price: Price::from_f64(200.0).unwrap(),
                quantity: Decimal::from(5),
                entry_time: Utc::now(),
            },
        );

        // Simulate a losing close: sold at 180
        let pnl = (Decimal::from(180) - Decimal::from(200)) * Decimal::from(5); // -100
        tracker.total_trades += 1;
        tracker.total_pnl += pnl;
        if pnl > Decimal::ZERO {
            tracker.winning_trades += 1;
        }
        tracker.trade_records.push(TradeRecord {
            trade_id: "test-2".to_string(),
            symbol: Symbol::from("TSLA"),
            side: OrderSide::Buy,
            entry_price: Price::from_f64(200.0).unwrap(),
            exit_price: Price::from_f64(180.0).unwrap(),
            quantity: Quantity::from_f64(5.0).unwrap(),
            entry_time: Utc::now(),
            exit_time: Utc::now(),
            pnl,
            return_pct: Decimal::from(-10),
            commission: Decimal::ZERO,
        });
        tracker.open_entries.remove("TSLA");

        assert_eq!(tracker.total_trades, 1);
        assert_eq!(tracker.winning_trades, 0);
        assert_eq!(tracker.total_pnl, Decimal::from(-100));
        assert_eq!(tracker.trade_records.len(), 1);
    }

    #[tokio::test]
    async fn test_finalize_populates_trades() {
        let mut runner = create_adaptive_strategy();
        let initial_capital = Decimal::from(100_000);

        // Initialize the runner so peak_value / initial_capital are set
        runner
            .initialize(initial_capital, StrategyConfig::default())
            .await
            .unwrap();

        // Manually inject trade records and equity snapshots into the tracker
        {
            let mut tracker = runner.performance_tracker.write();
            tracker.total_trades = 2;
            tracker.winning_trades = 1;
            tracker.total_pnl = Decimal::from(50); // net +50

            tracker.trade_records.push(TradeRecord {
                trade_id: "finalize-1".to_string(),
                symbol: Symbol::from("AAPL"),
                side: OrderSide::Buy,
                entry_price: Price::from_f64(100.0).unwrap(),
                exit_price: Price::from_f64(110.0).unwrap(),
                quantity: Quantity::from_f64(10.0).unwrap(),
                entry_time: Utc::now(),
                exit_time: Utc::now(),
                pnl: Decimal::from(100),
                return_pct: Decimal::from(10),
                commission: Decimal::ZERO,
            });
            tracker.trade_records.push(TradeRecord {
                trade_id: "finalize-2".to_string(),
                symbol: Symbol::from("MSFT"),
                side: OrderSide::Buy,
                entry_price: Price::from_f64(300.0).unwrap(),
                exit_price: Price::from_f64(290.0).unwrap(),
                quantity: Quantity::from_f64(5.0).unwrap(),
                entry_time: Utc::now(),
                exit_time: Utc::now(),
                pnl: Decimal::from(-50),
                return_pct: Decimal::new(-333, 2),
                commission: Decimal::ZERO,
            });

            // Add equity snapshots
            let now = Utc::now();
            tracker.equity_snapshots.push((now, Decimal::from(100_100)));
            tracker.equity_snapshots.push((now, Decimal::from(100_050)));
        }

        // Build a context with the final balance
        let context = StrategyContext {
            current_time: Utc::now(),
            account_balance: Decimal::from(100_050),
            buying_power: Decimal::from(100_050),
            positions: HashMap::new(),
            open_orders: HashMap::new(),
            market_prices: HashMap::new(),
            performance: crate::strategy_tester::PerformanceMetrics::default(),
        };

        let result = runner.finalize(&context).await.unwrap();

        // Trades should be populated
        assert_eq!(result.trades.len(), 2, "finalize() must return trade records");
        assert_eq!(result.trades.first().map(|t| t.trade_id.as_str()), Some("finalize-1"));

        // Performance timeline should be populated from equity snapshots
        assert_eq!(
            result.performance_timeline.len(),
            2,
            "finalize() must return performance timeline"
        );

        // Verify aggregated stats
        assert_eq!(result.total_trades, 2);
        assert_eq!(result.win_rate, Decimal::from(1) / Decimal::from(2));
        assert_eq!(result.avg_trade_return, Decimal::from(25)); // 50 / 2
        assert_eq!(result.final_value, Decimal::from(100_050));
    }

    #[tokio::test]
    async fn test_on_order_update_tracks_pnl() {
        let mut runner = create_adaptive_strategy();
        let initial_capital = Decimal::from(100_000);

        runner
            .initialize(initial_capital, StrategyConfig::default())
            .await
            .unwrap();

        let now = Utc::now();
        let context = StrategyContext {
            current_time: now,
            account_balance: initial_capital,
            buying_power: initial_capital,
            positions: HashMap::new(),
            open_orders: HashMap::new(),
            market_prices: HashMap::new(),
            performance: crate::strategy_tester::PerformanceMetrics::default(),
        };

        // Step 1: simulate a Buy fill at $100 (opening)
        let buy_order = Order {
            id: "order_1".to_string().into(),
            client_order_id: None,
            broker_order_id: None,
            account_id: None,
            symbol: Symbol::from("AAPL"),
            side: OrderSide::Buy,
            order_type: common::OrderType::Market,
            status: OrderStatus::Filled,
            time_in_force: common::TimeInForce::Day,
            quantity: Quantity::from_f64(10.0).unwrap(),
            price: Some(Price::from_f64(100.0).unwrap()),
            stop_price: None,
            filled_quantity: Quantity::from_f64(10.0).unwrap(),
            remaining_quantity: Quantity::ZERO,
            average_price: Some(Price::from_f64(100.0).unwrap()),
            avg_fill_price: None,
            average_fill_price: None,
            exchange_order_id: None,
            parent_id: None,
            execution_algorithm: None,
            execution_params: serde_json::json!({}),
            stop_loss: None,
            take_profit: None,
            created_at: common::HftTimestamp::now_or_zero(),
            updated_at: None,
            expires_at: None,
            metadata: serde_json::json!({}),
        };

        runner.on_order_update(&buy_order, &context).await.unwrap();

        // After a buy open, no round-trip trade yet
        {
            let tracker = runner.performance_tracker.read();
            assert_eq!(tracker.total_trades, 0, "Opening buy should not count as a completed trade");
            assert!(tracker.open_entries.contains_key("AAPL"));
        }

        // Step 2: simulate a Sell fill at $110 (closing the long)
        let sell_order = Order {
            id: "order_2".to_string().into(),
            client_order_id: None,
            broker_order_id: None,
            account_id: None,
            symbol: Symbol::from("AAPL"),
            side: OrderSide::Sell,
            order_type: common::OrderType::Market,
            status: OrderStatus::Filled,
            time_in_force: common::TimeInForce::Day,
            quantity: Quantity::from_f64(10.0).unwrap(),
            price: Some(Price::from_f64(110.0).unwrap()),
            stop_price: None,
            filled_quantity: Quantity::from_f64(10.0).unwrap(),
            remaining_quantity: Quantity::ZERO,
            average_price: Some(Price::from_f64(110.0).unwrap()),
            avg_fill_price: None,
            average_fill_price: None,
            exchange_order_id: None,
            parent_id: None,
            execution_algorithm: None,
            execution_params: serde_json::json!({}),
            stop_loss: None,
            take_profit: None,
            created_at: common::HftTimestamp::now_or_zero(),
            updated_at: None,
            expires_at: None,
            metadata: serde_json::json!({}),
        };

        let close_context = StrategyContext {
            current_time: now,
            account_balance: Decimal::from(100_100), // gained 100
            buying_power: Decimal::from(100_100),
            positions: HashMap::new(),
            open_orders: HashMap::new(),
            market_prices: HashMap::new(),
            performance: crate::strategy_tester::PerformanceMetrics::default(),
        };

        runner
            .on_order_update(&sell_order, &close_context)
            .await
            .unwrap();

        // After close, we should have a completed trade with PnL = +100
        {
            let tracker = runner.performance_tracker.read();
            assert_eq!(tracker.total_trades, 1, "Closing sell should create a completed trade");
            assert_eq!(tracker.winning_trades, 1);
            assert_eq!(tracker.total_pnl, Decimal::from(100));
            assert_eq!(tracker.trade_records.len(), 1);
            assert!(!tracker.open_entries.contains_key("AAPL"), "Entry should be removed after close");
            // Equity snapshots recorded on each order update
            assert!(tracker.equity_snapshots.len() >= 2);
        }
    }
}
