use debot_db::{CandlePattern, PricePoint, SampleTerm, TradingStrategy, TrendType};
use debot_ml::RandomForest;
use debot_utils::random_bool;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use smartcore_proba::linalg::basic::arrays::Array2;
use smartcore_proba::linalg::basic::matrix::DenseMatrix;

use crate::candle::Candle;
use crate::{trade_action::TradeDetail, trend_metrics::TrendMetrics, TradeAction};
use std::collections::VecDeque;
use std::env;
use std::str::FromStr;

#[derive(Debug, Clone)]
struct MarketDataConfig {
    short_period: usize,
    long_period: usize,
    trading_period: usize,
    flash_crash_period: usize,
    max_size: usize,
    only_read_price: bool,
    min_expected_score: f64,
}

#[derive(Debug, Clone)]
struct MarketDataMetrics {
    price: TrendMetrics,
    rsi: TrendMetrics,
    atr: TrendMetrics,
    adx: TrendMetrics,
    stochastic: TrendMetrics,
}

impl MarketDataMetrics {
    fn new(short_period: usize, long_period: usize, macd_period: usize) -> Self {
        Self {
            price: TrendMetrics::new("price", short_period, long_period, macd_period),
            rsi: TrendMetrics::new("rsi", short_period, long_period, macd_period),
            atr: TrendMetrics::new("atr", short_period, long_period, macd_period),
            adx: TrendMetrics::new("adx", short_period, long_period, macd_period),
            stochastic: TrendMetrics::new("stochastic", short_period, long_period, macd_period),
        }
    }
}

pub struct MarketData {
    name: String,
    prices: VecDeque<PricePoint>,
    config: MarketDataConfig,
    metrics: MarketDataMetrics,
    random_forest: Option<RandomForest>,
    candles_5m: Vec<Candle>,
    candles_1h: Vec<Candle>,
    candles_4h: Vec<Candle>,
    current_candle_5m: Option<Candle>,
    current_candle_1h: Option<Candle>,
    current_candle_4h: Option<Candle>,
    score_history: VecDeque<f64>,
}

impl MarketData {
    pub fn new(
        name: String,
        short_period: usize,
        long_period: usize,
        trading_period: usize,
        flash_crash_period: usize,
        max_size: usize,
        random_forest: Option<RandomForest>,
        only_read_price: bool,
        min_expected_score: f64,
    ) -> MarketData {
        let config = MarketDataConfig {
            short_period,
            long_period,
            trading_period,
            flash_crash_period,
            max_size: if only_read_price {
                long_period * 3
            } else {
                max_size
            },
            only_read_price,
            min_expected_score,
        };
        let metrics = MarketDataMetrics::new(short_period, long_period, trading_period);
        MarketData {
            name,
            prices: VecDeque::with_capacity(max_size),
            config,
            metrics,
            random_forest,
            candles_5m: Vec::new(),
            candles_1h: Vec::new(),
            candles_4h: Vec::new(),
            current_candle_5m: None,
            current_candle_1h: None,
            current_candle_4h: None,
            score_history: VecDeque::with_capacity(trading_period),
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn last_price(&self) -> Decimal {
        self.metrics.price.val.current_value().unwrap_or_default()
    }

    pub fn last_volume(&self) -> Option<Decimal> {
        self.prices.iter().last().and_then(|p| p.volume)
    }

    pub fn last_num_trades(&self) -> Option<u64> {
        self.prices.iter().last().and_then(|p| p.num_trades)
    }

    pub fn last_funding_rate(&self) -> Option<Decimal> {
        self.prices.iter().last().and_then(|p| p.funding_rate)
    }

    pub fn last_open_interest(&self) -> Option<Decimal> {
        self.prices.iter().last().and_then(|p| p.open_interest)
    }

    pub fn last_oracle_price(&self) -> Option<Decimal> {
        self.prices.iter().last().and_then(|p| p.oracle_price)
    }

    pub fn atr(&self) -> (Decimal, Decimal, Decimal, Decimal, Decimal, Decimal) {
        match self.metrics.atr.get() {
            Some(v) => v,
            None => (
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
            ),
        }
    }

    pub fn atr_by_term(&self, term: &SampleTerm) -> Decimal {
        let atrs = self.atr();
        match term {
            SampleTerm::TradingTerm => atrs.0,
            SampleTerm::ShortTerm => atrs.1,
            SampleTerm::LongTerm => atrs.2,
        }
    }

    pub fn adx(&self) -> (Decimal, Decimal, Decimal, Decimal, Decimal, Decimal) {
        match self.metrics.adx.get() {
            Some(v) => v,
            None => (
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
            ),
        }
    }

    pub fn rsi(&self) -> (Decimal, Decimal, Decimal, Decimal, Decimal, Decimal) {
        match self.metrics.rsi.get() {
            Some(v) => v,
            None => (
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
            ),
        }
    }

    pub fn price(&self) -> (Decimal, Decimal, Decimal, Decimal, Decimal, Decimal) {
        match self.metrics.price.get() {
            Some(v) => v,
            None => (
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
            ),
        }
    }

    pub fn stochastic(&self) -> (Decimal, Decimal, Decimal, Decimal, Decimal, Decimal) {
        match self.metrics.stochastic.get() {
            Some(v) => v,
            None => (
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
                Decimal::ZERO,
            ),
        }
    }

    pub fn candle_pattern(
        &self,
    ) -> (
        CandlePattern,
        CandlePattern,
        CandlePattern,
        CandlePattern,
        CandlePattern,
        CandlePattern,
    ) {
        // Current and previous patterns for 5m candles
        let candle_pattern_5m = Candle::detect_pattern(&self.candles_5m);
        let prev_candle_pattern_5m = if self.candles_5m.len() > 1 {
            Candle::detect_pattern(&self.candles_5m[..self.candles_5m.len() - 1])
        } else {
            CandlePattern::None
        };

        // Current and previous patterns for 1h candles
        let candle_pattern_1h = Candle::detect_pattern(&self.candles_1h);
        let prev_candle_pattern_1h = if self.candles_1h.len() > 1 {
            Candle::detect_pattern(&self.candles_1h[..self.candles_1h.len() - 1])
        } else {
            CandlePattern::None
        };

        // Current and previous patterns for 4h candles
        let candle_pattern_4h = Candle::detect_pattern(&self.candles_4h);
        let prev_candle_pattern_4h = if self.candles_4h.len() > 1 {
            Candle::detect_pattern(&self.candles_4h[..self.candles_4h.len() - 1])
        } else {
            CandlePattern::None
        };

        (
            candle_pattern_5m,
            candle_pattern_1h,
            candle_pattern_4h,
            prev_candle_pattern_5m,
            prev_candle_pattern_1h,
            prev_candle_pattern_4h,
        )
    }

    pub fn get_min_max_price(&self, period: usize) -> Option<(Decimal, Decimal)> {
        let prices_len = self.prices.len();
        if prices_len <= period {
            return None;
        }

        let range_start = prices_len.saturating_sub(period);
        if range_start >= prices_len {
            return None;
        }

        let first_price = self.prices[range_start].price;
        let mut min = first_price;
        let mut max = first_price;

        for i in range_start..prices_len {
            let price = self.prices[i].price;
            min = Decimal::min(min, price);
            max = Decimal::max(max, price);
        }

        Some((min, max))
    }

    pub fn add_price(
        &mut self,
        price: Option<Decimal>,
        timestamp: Option<i64>,
        volume: Option<Decimal>,
        num_trades: Option<u64>,
        funding_rate: Option<Decimal>,
        open_interest: Option<Decimal>,
        oracle_price: Option<Decimal>,
    ) -> PricePoint {
        if self.prices.len() == self.config.max_size {
            self.prices.pop_front();
        }

        let price =
            price.unwrap_or_else(|| self.prices.back().map_or(Decimal::ZERO, |pp| pp.price));

        self.update_ema(price);
        self.update_rsi();
        self.update_adx();
        self.update_stochastic();
        self.update_atr();

        let price_point = PricePoint::new(
            price,
            timestamp,
            volume,
            num_trades,
            funding_rate,
            open_interest,
            oracle_price,
            None,
        );

        self.prices.push_back(price_point.clone());

        if self.config.only_read_price {
            return price_point;
        }

        let mut candles_5m = std::mem::take(&mut self.candles_5m);
        let mut current_candle_5m = self.current_candle_5m.take();
        self.update_candle(
            price,
            price_point.timestamp,
            300,
            &mut candles_5m,
            &mut current_candle_5m,
        );
        self.candles_5m = candles_5m;
        self.current_candle_5m = current_candle_5m;

        let mut candles_1h = std::mem::take(&mut self.candles_1h);
        let mut current_candle_1h = self.current_candle_1h.take();
        self.update_candle(
            price,
            price_point.timestamp,
            3600,
            &mut candles_1h,
            &mut current_candle_1h,
        );
        self.candles_1h = candles_1h;
        self.current_candle_1h = current_candle_1h;

        let mut candles_4h = std::mem::take(&mut self.candles_4h);
        let mut current_candle_4h = self.current_candle_4h.take();
        self.update_candle(
            price,
            price_point.timestamp,
            14400,
            &mut candles_4h,
            &mut current_candle_4h,
        );
        self.candles_4h = candles_4h;
        self.current_candle_4h = current_candle_4h;

        let score = self.compute_signal_score();
        self.score_history.push_back(score);
        if self.score_history.len() > self.config.trading_period {
            self.score_history.pop_front();
        }

        price_point
    }

    fn update_candle(
        &mut self,
        price: Decimal,
        timestamp: i64,
        interval: i64,
        candles: &mut Vec<Candle>,
        current_candle: &mut Option<Candle>,
    ) {
        let rounded_timestamp = (timestamp / interval) * interval;

        let one_day_ago = timestamp - 86400;
        candles.retain(|candle| candle.timestamp >= one_day_ago);

        if let Some(ref mut candle) = current_candle {
            if candle.timestamp == rounded_timestamp {
                candle.update(price);
            } else {
                candles.push(candle.clone());
                *current_candle = Some(Candle::new(price, price, price, price, rounded_timestamp));
            }
        } else {
            *current_candle = Some(Candle::new(price, price, price, price, rounded_timestamp));
        }
    }

    pub fn is_signaled(
        &self,
        strategy: TradingStrategy,
        trade_action: Option<TradeAction>,
        _index: usize,
        take_profit_ratio: Decimal,
        atr_spread: Decimal,
        atr_term: &SampleTerm,
        _seed: Option<u64>,
        tick_spread: i64,
        risk_reward: f64,
    ) -> Vec<TradeAction> {
        let mut actions: Vec<TradeAction> = vec![];

        if self.is_metrics_none() {
            log::debug!("is_open_signaled skipped: No metrics");
            return actions;
        }

        let (
            candle_pattern_0,
            candle_pattern_1,
            candle_pattern_2,
            candle_pattern_3,
            candle_pattern_4,
            candle_pattern_5,
        ) = self.candle_pattern();
        let candle_value_0 = candle_pattern_0.to_one_hot();
        let candle_value_1 = candle_pattern_1.to_one_hot();
        let candle_value_2 = candle_pattern_2.to_one_hot();
        let candle_value_3 = candle_pattern_3.to_one_hot();
        let candle_value_4 = candle_pattern_4.to_one_hot();
        let candle_value_5 = candle_pattern_5.to_one_hot();

        match strategy {
            TradingStrategy::RandomInago(trend_type) => {
                if let Some(detected) = self.detect_inago_opportunity() {
                    if random_bool(0.01, None) {
                        if detected == trend_type {
                            actions.push(Self::create_open_action(
                                &strategy, trend_type, None, None, None,
                            ));
                        }
                    }
                }
                return actions;
            }
            TradingStrategy::RandomMeanReversion(trend_type) => {
                if random_bool(0.01, None) {
                    let action = Self::create_open_action(&strategy, trend_type, None, None, None);
                    actions.push(action.clone());
                }
                return actions;
            }
            TradingStrategy::MarketMake => {
                match trade_action {
                    Some(action) => match action {
                        TradeAction::BuyOpen(_) => {
                            let action = Self::create_open_action(
                                &strategy,
                                TrendType::Up,
                                None,
                                None,
                                None,
                            );
                            actions.push(action.clone());
                        }
                        TradeAction::SellOpen(_) => {
                            let action = Self::create_open_action(
                                &strategy,
                                TrendType::Down,
                                None,
                                None,
                                None,
                            );
                            actions.push(action.clone());
                        }
                        TradeAction::BuyClose(_) => {
                            let action = Self::create_close_action(TrendType::Down);
                            actions.push(action.clone());
                        }
                        TradeAction::SellClose(_) => {
                            let action = Self::create_close_action(TrendType::Up);
                            actions.push(action.clone());
                        }
                        _ => panic!("not supported"),
                    },
                    None => {
                        if self.is_low_volatility() {
                            let action = Self::create_open_action(
                                &strategy,
                                TrendType::Up,
                                None,
                                None,
                                None,
                            );
                            actions.push(action.clone());
                            let action = Self::create_open_action(
                                &strategy,
                                TrendType::Down,
                                None,
                                None,
                                None,
                            );
                            actions.push(action.clone());
                        }
                    }
                }
                return actions;
            }
            TradingStrategy::Inago(_) | TradingStrategy::MeanReversion(_) => {}
            TradingStrategy::Hybrid | TradingStrategy::Rebalance => {
                panic!("Not supported");
            }
        }

        let trend_type = strategy.trend_type();

        let x = DenseMatrix::from_2d_array(&[&[
            // input_1
            self.price()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_2
            self.price()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_3
            self.price()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_4
            self.atr()
                .1
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_5
            self.atr()
                .2
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_6
            self.atr()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_7
            self.atr()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_8
            self.atr()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_9
            self.rsi()
                .1
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_10
            self.rsi()
                .2
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_11
            self.rsi()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_12
            self.rsi()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_13
            self.rsi()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_14
            self.last_volume()
                .unwrap_or_default()
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_15
            self.last_num_trades()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_16
            self.last_funding_rate()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_17
            self.last_open_interest()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_18
            (self.last_oracle_price().unwrap_or_default().round_dp(4) - self.price().0.round_dp(4))
                .to_f64()
                .expect("conversion failed"),
            // input_19
            tick_spread.to_f64().expect("conversion failed"),
            // input_20
            atr_spread.to_f64().expect("conversion failed"),
            // input_21
            0.0,
            // input_22
            if take_profit_ratio.is_zero() {
                0.0
            } else {
                1.0
            },
            // input_23
            atr_term
                .to_numeric()
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_24
            0.0,
            // input_25
            0.0,
            // input_26
            0.0,
            // input_27
            0.0,
            // input_28
            0.0,
            // input_29
            0.0,
            // input_30
            candle_value_0[0].to_f64().expect("conversion failed"),
            // input_31
            candle_value_0[1].to_f64().expect("conversion failed"),
            // input_32
            candle_value_0[2].to_f64().expect("conversion failed"),
            // input_33
            candle_value_0[3].to_f64().expect("conversion failed"),
            // input_34
            candle_value_0[4].to_f64().expect("conversion failed"),
            // input_35
            candle_value_0[5].to_f64().expect("conversion failed"),
            // input_36
            candle_value_0[6].to_f64().expect("conversion failed"),
            // input_37
            candle_value_0[7].to_f64().expect("conversion failed"),
            // input_38
            candle_value_0[8].to_f64().expect("conversion failed"),
            // input_39
            candle_value_0[9].to_f64().expect("conversion failed"),
            // input_40
            candle_value_0[10].to_f64().expect("conversion failed"),
            // input_41
            candle_value_0[11].to_f64().expect("conversion failed"),
            // input_42
            candle_value_0[12].to_f64().expect("conversion failed"),
            // input_43
            candle_value_0[13].to_f64().expect("conversion failed"),
            // input_44
            candle_value_0[14].to_f64().expect("conversion failed"),
            // input_45
            candle_value_0[15].to_f64().expect("conversion failed"),
            // input_46
            candle_value_1[0].to_f64().expect("conversion failed"),
            // input_47
            candle_value_1[1].to_f64().expect("conversion failed"),
            // input_48
            candle_value_1[2].to_f64().expect("conversion failed"),
            // input_49
            candle_value_1[3].to_f64().expect("conversion failed"),
            // input_50
            candle_value_1[4].to_f64().expect("conversion failed"),
            // input_51
            candle_value_1[5].to_f64().expect("conversion failed"),
            // input_52
            candle_value_1[6].to_f64().expect("conversion failed"),
            // input_53
            candle_value_1[7].to_f64().expect("conversion failed"),
            // input_54
            candle_value_1[8].to_f64().expect("conversion failed"),
            // input_55
            candle_value_1[9].to_f64().expect("conversion failed"),
            // input_56
            candle_value_1[10].to_f64().expect("conversion failed"),
            // input_57
            candle_value_1[11].to_f64().expect("conversion failed"),
            // input_58
            candle_value_1[12].to_f64().expect("conversion failed"),
            // input_59
            candle_value_1[13].to_f64().expect("conversion failed"),
            // input_60
            candle_value_1[14].to_f64().expect("conversion failed"),
            // input_61
            candle_value_1[15].to_f64().expect("conversion failed"),
            // input_62
            candle_value_2[0].to_f64().expect("conversion failed"),
            // input_63
            candle_value_2[1].to_f64().expect("conversion failed"),
            // input_64
            candle_value_2[2].to_f64().expect("conversion failed"),
            // input_65
            candle_value_2[3].to_f64().expect("conversion failed"),
            // input_66
            candle_value_2[4].to_f64().expect("conversion failed"),
            // input_67
            candle_value_2[5].to_f64().expect("conversion failed"),
            // input_68
            candle_value_2[6].to_f64().expect("conversion failed"),
            // input_69
            candle_value_2[7].to_f64().expect("conversion failed"),
            // input_70
            candle_value_2[8].to_f64().expect("conversion failed"),
            // input_71
            candle_value_2[9].to_f64().expect("conversion failed"),
            // input_72
            candle_value_2[10].to_f64().expect("conversion failed"),
            // input_73
            candle_value_2[11].to_f64().expect("conversion failed"),
            // input_74
            candle_value_2[12].to_f64().expect("conversion failed"),
            // input_75
            candle_value_2[13].to_f64().expect("conversion failed"),
            // input_76
            candle_value_2[14].to_f64().expect("conversion failed"),
            // input_77
            candle_value_2[15].to_f64().expect("conversion failed"),
            // input_78
            candle_value_3[0].to_f64().expect("conversion failed"),
            // input_79
            candle_value_3[1].to_f64().expect("conversion failed"),
            // input_80
            candle_value_3[2].to_f64().expect("conversion failed"),
            // input_81
            candle_value_3[3].to_f64().expect("conversion failed"),
            // input_82
            candle_value_3[4].to_f64().expect("conversion failed"),
            // input_83
            candle_value_3[5].to_f64().expect("conversion failed"),
            // input_84
            candle_value_3[6].to_f64().expect("conversion failed"),
            // input_85
            candle_value_3[7].to_f64().expect("conversion failed"),
            // input_86
            candle_value_3[8].to_f64().expect("conversion failed"),
            // input_87
            candle_value_3[9].to_f64().expect("conversion failed"),
            // input_88
            candle_value_3[10].to_f64().expect("conversion failed"),
            // input_89
            candle_value_3[11].to_f64().expect("conversion failed"),
            // input_90
            candle_value_3[12].to_f64().expect("conversion failed"),
            // input_91
            candle_value_3[13].to_f64().expect("conversion failed"),
            // input_92
            candle_value_3[14].to_f64().expect("conversion failed"),
            // input_93
            candle_value_3[15].to_f64().expect("conversion failed"),
            // input_94
            candle_value_4[0].to_f64().expect("conversion failed"),
            // input_95
            candle_value_4[1].to_f64().expect("conversion failed"),
            // input_96
            candle_value_4[2].to_f64().expect("conversion failed"),
            // input_97
            candle_value_4[3].to_f64().expect("conversion failed"),
            // input_98
            candle_value_4[4].to_f64().expect("conversion failed"),
            // input_99
            candle_value_4[5].to_f64().expect("conversion failed"),
            // input_100
            candle_value_4[6].to_f64().expect("conversion failed"),
            // input_101
            candle_value_4[7].to_f64().expect("conversion failed"),
            // input_102
            candle_value_4[8].to_f64().expect("conversion failed"),
            // input_103
            candle_value_4[9].to_f64().expect("conversion failed"),
            // input_104
            candle_value_4[10].to_f64().expect("conversion failed"),
            // input_105
            candle_value_4[11].to_f64().expect("conversion failed"),
            // input_106
            candle_value_4[12].to_f64().expect("conversion failed"),
            // input_107
            candle_value_4[13].to_f64().expect("conversion failed"),
            // input_108
            candle_value_4[14].to_f64().expect("conversion failed"),
            // input_109
            candle_value_4[15].to_f64().expect("conversion failed"),
            // input_110
            candle_value_5[0].to_f64().expect("conversion failed"),
            // input_111
            candle_value_5[1].to_f64().expect("conversion failed"),
            // input_112
            candle_value_5[2].to_f64().expect("conversion failed"),
            // input_113
            candle_value_5[3].to_f64().expect("conversion failed"),
            // input_114
            candle_value_5[4].to_f64().expect("conversion failed"),
            // input_115
            candle_value_5[5].to_f64().expect("conversion failed"),
            // input_116
            candle_value_5[6].to_f64().expect("conversion failed"),
            // input_117
            candle_value_5[7].to_f64().expect("conversion failed"),
            // input_118
            candle_value_5[8].to_f64().expect("conversion failed"),
            // input_119
            candle_value_5[9].to_f64().expect("conversion failed"),
            // input_120
            candle_value_5[10].to_f64().expect("conversion failed"),
            // input_121
            candle_value_5[11].to_f64().expect("conversion failed"),
            // input_122
            candle_value_5[12].to_f64().expect("conversion failed"),
            // input_123
            candle_value_5[13].to_f64().expect("conversion failed"),
            // input_124
            candle_value_5[14].to_f64().expect("conversion failed"),
            // input_125
            candle_value_5[15].to_f64().expect("conversion failed"),
        ]]);

        log::trace!("num of dense matrix x = {:?}", x);

        if let Some(rf) = &self.random_forest {
            match x {
                Ok(ref x_matrix) => {
                    #[cfg(feature = "classification")]
                    {
                        let (proba, expected_score) = rf.predict_proba(x_matrix);
                        let p_row = proba.get_row(0);
                        let p: Vec<f64> = p_row.iterator(0).copied().collect();
                        let p_loss = p[0];
                        let p_expired = p[1];
                        let p_take = p[2];

                        if p_loss >= p_expired && p_loss >= p_take {
                            log::debug!(
                                "[{:?}]Skip: Loss is most probable (p0 = {:.3})",
                                strategy,
                                p_loss
                            );
                            return actions;
                        }

                        if p_take < 0.2 {
                            log::debug!(
                                "[{:?}]Skip: TakeProfit prob too low ({:.3})",
                                strategy,
                                p_take
                            );
                            return actions;
                        }

                        let (w_loss, w_expired, w_take) = rf.weights();
                        let risk = w_loss.abs() * p_loss;
                        let reward = w_expired * p_expired + w_take * p_take;
                        let rr = reward / (risk + 1e-8);

                        if rr < risk_reward {
                            log::debug!(
                                "[{:?}]Skip: RiskReward -> reward: {:.5}, risk: {:.5}, R/R: {:.3}",
                                strategy,
                                reward,
                                risk,
                                rr
                            );
                            return actions;
                        }

                        if expected_score > self.config.min_expected_score {
                            log::info!(
                                "[{:?}]PredictProba -> Loss: {:.3}, ExpiredProfit: {:.3}, TakeProfit: {:.3}, ExpectedScore: {:.3}",
                                strategy, p_loss, p_expired, p_take, expected_score
                            );

                            let label = [0, 1, 2]
                                .iter()
                                .enumerate()
                                .max_by(|a, b| p[a.0].partial_cmp(&p[b.0]).unwrap())
                                .map(|(_, &label)| label)
                                .unwrap_or(1);

                            let (pred1, pred2) = rf.predict_timeout(x_matrix);
                            let (pred1_u32, pred2_u32): (u32, u32) = (pred1 as u32, pred2 as u32);

                            actions.push(Self::create_open_action(
                                &strategy,
                                *trend_type,
                                Some(pred1_u32),
                                Some(pred2_u32),
                                Some(label),
                            ));
                        }
                    }

                    #[cfg(feature = "regression")]
                    {
                        let pred = rf.predict(x_matrix);
                        let expected_score = pred[0];
                        if expected_score > self.config.min_expected_score {
                            log::info!(
                                "[{:?}]Execute: ExpectedScore (return rate) = {:.3}",
                                strategy,
                                expected_score
                            );
                            let (pred1, pred2) = rf.predict_timeout(x_matrix);
                            let (pred1_u32, pred2_u32): (u32, u32) = (pred1 as u32, pred2 as u32);

                            actions.push(Self::create_open_action(
                                *trend_type,
                                Some(pred1_u32),
                                Some(pred2_u32),
                                None,
                            ));
                        } else {
                            log::debug!(
                                "[{:?}]Skip: ExpectedScore (return rate) = {:.3}",
                                strategy,
                                expected_score
                            );
                        }
                    }
                }
                Err(e) => {
                    log::error!("Failed to build DenseMatrix: {:?}", e);
                }
            }
        }

        actions
    }

    fn is_metrics_none(&self) -> bool {
        self.metrics.adx.get().is_none()
            || self.metrics.atr.get().is_none()
            || self.metrics.price.get().is_none()
            || self.metrics.rsi.get().is_none()
            || self.metrics.stochastic.get().is_none()
    }

    fn create_open_action(
        strategy: &TradingStrategy,
        trend: TrendType,
        entry_timeout_tick_count: Option<u32>,
        max_holding_tick_count: Option<u32>,
        label: Option<i32>,
    ) -> TradeAction {
        let confidence = Decimal::from(label.unwrap_or(1)) / Decimal::new(2, 0);
        let spread = match strategy {
            TradingStrategy::MarketMake => Some(2),
            _ => None,
        };
        match trend {
            TrendType::Up => TradeAction::BuyOpen(TradeDetail::new(
                None,
                None,
                confidence,
                entry_timeout_tick_count,
                max_holding_tick_count,
                spread,
            )),
            TrendType::Down => TradeAction::SellOpen(TradeDetail::new(
                None,
                None,
                confidence,
                entry_timeout_tick_count,
                max_holding_tick_count,
                spread,
            )),
            _ => TradeAction::None,
        }
    }

    fn create_close_action(trend: TrendType) -> TradeAction {
        match trend {
            TrendType::Up => {
                TradeAction::SellClose(TradeDetail::new(None, None, Decimal::ONE, None, None, None))
            }
            TrendType::Down => {
                TradeAction::BuyClose(TradeDetail::new(None, None, Decimal::ONE, None, None, None))
            }
            _ => TradeAction::None,
        }
    }

    fn update_rsi(&mut self) {
        let periods = [
            self.config.trading_period,
            self.config.short_period,
            self.config.long_period,
        ];
        let values: Vec<_> = periods
            .iter()
            .filter_map(|&period| self.calculate_rsi(period))
            .collect();

        if values.len() == periods.len() {
            self.metrics.rsi.update(values[0], values[1], values[2]);
        }
    }

    fn update_adx(&mut self) {
        let periods = [
            self.config.trading_period,
            self.config.short_period,
            self.config.long_period,
        ];
        let values: Vec<_> = periods
            .iter()
            .filter_map(|&period| self.calculate_adx(period))
            .collect();

        if values.len() == periods.len() {
            self.metrics.adx.update(values[0], values[1], values[2]);
        }
    }

    fn update_stochastic(&mut self) {
        let periods = [
            self.config.trading_period,
            self.config.short_period,
            self.config.long_period,
        ];
        let values: Vec<_> = periods
            .iter()
            .filter_map(|&period| self.calculate_stochastic(period))
            .collect();

        if values.len() == periods.len() {
            self.metrics
                .stochastic
                .update(values[0], values[1], values[2]);
        }
    }

    fn update_ema(&mut self, price: Decimal) {
        self.metrics.price.update(price, price, price);
    }

    fn calculate_rsi(&self, period: usize) -> Option<Decimal> {
        let window_size = 10;

        if self.prices.len() < period {
            return None;
        }

        let mut gains = Decimal::ZERO;
        let mut losses = Decimal::ZERO;

        let recent_prices = self
            .prices
            .iter()
            .skip(self.prices.len().saturating_sub(period))
            .cloned()
            .collect::<Vec<_>>();

        for window in recent_prices.windows(window_size) {
            if let (Some(first), Some(last)) = (window.first(), window.last()) {
                let change = last.price - first.price;
                if change.is_sign_positive() {
                    gains += change;
                } else {
                    losses += change.abs();
                }
            }
        }

        let rs = if losses.is_zero() {
            Decimal::new(100, 0)
        } else {
            gains / losses
        };

        let one = Decimal::ONE;
        Some(one - (one / (one + rs)))
    }

    fn calculate_adx(&mut self, period: usize) -> Option<Decimal> {
        if self.prices.len() < period {
            return None;
        }

        let (atr, high, low) = match self.calculate_atr(period) {
            Some((atr, high, low)) => (atr, high, low),
            None => return None,
        };

        if atr.is_zero() {
            return None;
        }

        let mut plus_dm = Decimal::ZERO;
        let mut minus_dm = Decimal::ZERO;

        for price_point in &self
            .prices
            .iter()
            .skip(self.prices.len() - period)
            .collect::<Vec<_>>()
        {
            let up_move = high - price_point.price;
            let down_move = price_point.price - low;
            if up_move > down_move && up_move.is_sign_positive() {
                plus_dm += up_move;
            }
            if down_move > up_move && down_move.is_sign_positive() {
                minus_dm += down_move;
            }
        }

        let plus_di = plus_dm / atr;
        let minus_di = minus_dm / atr;

        let denominator = plus_di + minus_di;
        if denominator.is_zero() {
            return None;
        }

        Some((plus_di - minus_di).abs() / denominator)
    }

    fn calculate_stochastic(&self, period: usize) -> Option<Decimal> {
        if self.prices.len() < period {
            return None;
        }

        let (high, low) = self.extract_high_low(period);
        let current_close = match self.prices.iter().last() {
            Some(v) => v.price,
            None => {
                return None;
            }
        };

        if high == low {
            None
        } else {
            Some((current_close - low) / (high - low))
        }
    }

    fn calculate_atr(&self, period: usize) -> Option<(Decimal, Decimal, Decimal)> {
        if self.prices.len() <= period {
            // +1 is to include the previous_close price
            return None;
        }

        let (current_high, current_low) = self.extract_high_low(period);
        let previous_close = self.prices[self.prices.len() - period - 1].price;

        let tr = [
            current_high - current_low,
            (current_high - previous_close).abs(),
            (current_low - previous_close).abs(),
        ]
        .iter()
        .fold(Decimal::ZERO, |acc, &val| Decimal::max(acc, val));

        Some((tr, current_high, current_low))
    }

    fn update_atr(&mut self) {
        let periods = [
            self.config.trading_period,
            self.config.short_period,
            self.config.long_period,
        ];
        let values: Vec<_> = periods
            .iter()
            .filter_map(|&period| self.calculate_atr(period))
            .map(|x| x.0)
            .collect();

        if values.len() == periods.len() {
            self.metrics.atr.update(values[0], values[1], values[2]);
        }
    }

    fn extract_high_low(&self, period: usize) -> (Decimal, Decimal) {
        self.prices
            .iter()
            .skip(self.prices.len() - period)
            .fold((Decimal::MIN, Decimal::MAX), |(high, low), pp| {
                (high.max(pp.price), low.min(pp.price))
            })
    }

    pub fn detect_flash_crash(
        &self,
        atr_multiplier: Decimal,
        min_percentage: Option<Decimal>,
    ) -> Option<Decimal> {
        let current_price = self.last_price();
        let atr = self.atr().0;

        if atr.is_zero() || current_price.is_zero() {
            return None;
        }

        if let Some((min_price, max_price)) = self.get_min_max_price(self.config.flash_crash_period)
        {
            if min_price.is_zero()
                || max_price.is_zero()
                || min_price.is_sign_negative()
                || max_price.is_sign_negative()
            {
                log::warn!(
                    "Invalid min/max price detected: min_price={} max_price={}",
                    min_price,
                    max_price
                );
                return None;
            }

            let atr_threshold = atr * atr_multiplier;
            let percentage_threshold = current_price * min_percentage.unwrap_or_default();
            let threshold = Decimal::max(atr_threshold, percentage_threshold);

            for (label, ref_price) in [("dropped", min_price), ("surged", max_price)] {
                let price_change = (current_price - ref_price).round_dp(5);
                log::debug!(
                    "atr_threshold: {:.2}, threshold: {:.2}, price_change: {:.2}",
                    atr_threshold,
                    threshold,
                    price_change
                );

                if price_change.abs() > threshold {
                    log::warn!(
                        "Flash crash detected: Price {} rapidly! Change: {}({:.2} - {:.2}) (Threshold: {:.2})",
                        label,
                        price_change,
                        current_price,
                        ref_price,
                        threshold
                    );
                    return Some(price_change);
                }
            }
        }
        None
    }

    /// Detects short-term trend (Up, Down, or None) based on ensemble of RSI, ATR, and price change.
    pub fn detect_trade_term_trend(
        &self,
        rsi_threshold: Decimal,
        price_lookback: usize,
    ) -> Option<TrendType> {
        if self.prices.len() < price_lookback + 1 {
            log::debug!("Not enough data to detect short-term trend");
            return None;
        }

        let current_price = self.last_price();
        let past_price = self.prices[self.prices.len() - 1 - price_lookback].price;
        let price_change = current_price - past_price;
        let price_return = price_change / past_price;

        let rsi = self.rsi().0;
        let atr = self.atr().0;
        let adx = self.adx().0;

        // Voting system with thresholds
        let mut up_votes = 0;
        let mut down_votes = 0;

        // RSI signal
        if rsi > rsi_threshold {
            up_votes += 1;
        } else if rsi < Decimal::ONE - rsi_threshold {
            down_votes += 1;
        }

        // Price return signal
        if price_return > Decimal::new(1, 2) {
            up_votes += 1;
        } else if price_return < Decimal::new(-1, 2) {
            down_votes += 1;
        }

        // ATR signal: volatility breakout
        if atr > Decimal::ZERO && price_change.abs() > atr {
            if price_change.is_sign_positive() {
                up_votes += 1;
            } else {
                down_votes += 1;
            }
        }

        // ADX signal: trend strength confirmation
        if adx > Decimal::new(2, 1) {
            // ADX > 20
            if price_change.is_sign_positive() {
                up_votes += 1;
            } else {
                down_votes += 1;
            }
        }

        // Final decision based on majority
        match (up_votes, down_votes) {
            (u, d) if u > d && u >= 2 => Some(TrendType::Up),
            (u, d) if d > u && d >= 2 => Some(TrendType::Down),
            _ => None,
        }
    }

    fn detect_inago_opportunity(&self) -> Option<TrendType> {
        self.detect_dynamic_trend()
    }

    #[allow(dead_code)]
    fn detect_inago_reversion_opportunity(&self) -> Option<TrendType> {
        self.detect_dynamic_trend().map(|dir| match dir {
            TrendType::Up => TrendType::Down,
            TrendType::Down => TrendType::Up,
            x => x,
        })
    }

    fn detect_no_breakout(&self) -> bool {
        let (short, long, macd, adx_val, rsi_val) = {
            let price = self.price();
            let adx = self.adx();
            let rsi = self.rsi();

            let values = (price.1, price.2, price.3, adx.1, rsi.1);

            if [values.0, values.1, values.2, values.3, values.4]
                .iter()
                .any(|v| v.is_zero())
            {
                return false;
            }

            values
        };

        if adx_val > Decimal::new(25, 2) {
            if short > long && macd > Decimal::ZERO {
                return false;
            } else if short < long && macd < Decimal::ZERO {
                return false;
            }
        }

        if adx_val < Decimal::new(2, 1) {
            if rsi_val > Decimal::new(7, 1) {
                return false;
            } else if rsi_val < Decimal::new(3, 1) {
                return false;
            }
        }

        true
    }

    fn compute_signal_score(&self) -> f64 {
        let (_ma_s, _ma_ll, macd, adx_val, rsi_val) = {
            let p = self.price();
            let a = self.adx();
            let r = self.rsi();
            (p.1, p.2, p.3, a.1, r.1)
        };

        let norm_adx = adx_val.to_f64().unwrap_or(0.0).clamp(0.0, 1.0);
        let atr = self.atr().0.to_f64().unwrap_or(1.0);
        let norm_macd = (macd.to_f64().unwrap_or(0.0) / atr).clamp(-1.0, 1.0);
        let norm_rsi = ((rsi_val.to_f64().unwrap_or(50.0) - 50.0) / 50.0).clamp(-1.0, 1.0);

        norm_adx + norm_macd + norm_rsi
    }

    fn detect_dynamic_trend(&self) -> Option<TrendType> {
        let n = self.score_history.len();
        if n < 2 {
            return None;
        }
        let sum: f64 = self.score_history.iter().sum();
        let mean = sum / n as f64;
        let var = self
            .score_history
            .iter()
            .map(|&s| (s - mean).powi(2))
            .sum::<f64>()
            / n as f64;
        let std = var.sqrt();

        let latest = *self.score_history.back().unwrap();
        if latest > mean + std {
            Some(TrendType::Up)
        } else if latest < mean - std {
            Some(TrendType::Down)
        } else {
            None
        }
    }

    fn is_low_volatility(&self) -> bool {
        let atr = self.atr().0;
        let adx = self.adx().0;
        let price = self.last_price();

        let atr = if price.is_zero() {
            Decimal::ZERO
        } else {
            atr / price
        };

        let atr_thresh = env::var("MM_ATR_THRESHOLD").unwrap_or_else(|_| "0.10".to_string());
        let adx_thresh = env::var("MM_ADX_THRESHOLD").unwrap_or_else(|_| "0.15".to_string());
        let atr_thresh = Decimal::from_str(&atr_thresh).unwrap_or(Decimal::ZERO);
        let adx_thresh = Decimal::from_str(&adx_thresh).unwrap_or(Decimal::ZERO);

        if atr > atr_thresh {
            log::info!("Volatility is high → ATR: {:.3} > {:.3}", atr, atr_thresh,);
        }

        if adx > adx_thresh {
            log::info!("Volatility is high → ADX: {:.3} > {:.3}", adx, adx_thresh);
        }

        atr <= atr_thresh && adx <= adx_thresh
    }
}
