use debot_db::{CandlePattern, DebugLog, PricePoint, SampleTerm, TradingStrategy, TrendType};
use debot_ml::RandomForest;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use smartcore::linalg::basic::matrix::DenseMatrix;

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

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BreakoutDirection {
    Up,
    Down,
    Both,
}

#[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,
}

#[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>,
}

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,
    ) -> MarketData {
        let config = MarketDataConfig {
            short_period,
            long_period,
            trading_period,
            flash_crash_period,
            max_size,
            only_read_price,
        };
        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,
        }
    }

    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) {
        // 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,
            prev_candle_pattern_5m,
            prev_candle_pattern_1h,
        )
    }

    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 mut debug = DebugLog::default();
        debug.input_1 = self.price().0;
        debug.input_2 = self.price().1;
        debug.input_3 = self.price().2;
        debug.input_4 = self.price().3;
        debug.input_5 = self.price().4;
        debug.input_6 = self.price().5;
        debug.input_7 = self.rsi().1;
        debug.input_8 = self.rsi().2;
        debug.input_9 = self.rsi().3;
        debug.input_10 = self.rsi().4;
        debug.input_11 = self.rsi().5;
        debug.input_12 = self.adx().1;
        debug.input_13 = self.adx().2;
        debug.input_14 = self.adx().3;
        debug.input_15 = self.adx().4;
        debug.input_16 = self.adx().5;

        let is_in_range = self.is_current_min_max_within_previous_range();
        debug.output_1 = if is_in_range.unwrap_or(false) {
            Decimal::ONE
        } else {
            Decimal::ZERO
        };

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

        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;

        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,
        _index: usize,
        take_profit_ratio: Decimal,
        atr_spread: Option<Decimal>,
        open_tick_count_max: u32,
        atr_term: &SampleTerm,
        flash_crash_atr_multiplier: Decimal,
        _predict_profit_ratio: bool,
        _seed: Option<u64>,
        flash_crash_min_percentage: Option<Decimal>,
    ) -> 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) =
            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();

        match strategy {
            TradingStrategy::Grid(trend_type) => {
                if self.is_range_bound_expected() {
                    let action = Self::create_open_action(trend_type, None);
                    actions.push(action.clone());
                } else {
                    actions.push(Self::create_close_action(trend_type));
                }
            }
            TradingStrategy::RandomMeanReversion(trend_type) => {
                if let Some(price_change) =
                    self.detect_flash_crash(flash_crash_atr_multiplier, flash_crash_min_percentage)
                {
                    if (price_change > Decimal::ZERO && trend_type == TrendType::Down)
                        || (price_change < Decimal::ZERO && trend_type == TrendType::Up)
                    {
                        let action = Self::create_open_action(trend_type, None);
                        actions.push(action.clone());
                    }
                }
            }
            TradingStrategy::RandomInago(trend_type) => {
                if let Some(break_out) = self.detect_breakout(self.config.trading_period) {
                    if break_out == BreakoutDirection::Up && trend_type == TrendType::Up
                        || break_out == BreakoutDirection::Down && trend_type == TrendType::Down
                    {
                        let action = Self::create_open_action(trend_type, None);
                        actions.push(action.clone());
                    }
                }
            }
            TradingStrategy::MeanReversion(trend_type) | TradingStrategy::Inago(trend_type) => {
                if matches!(strategy, TradingStrategy::MeanReversion(_)) {
                    if let Some(price_change) = self
                        .detect_flash_crash(flash_crash_atr_multiplier, flash_crash_min_percentage)
                    {
                        match (price_change.is_sign_positive(), trend_type) {
                            (true, TrendType::Up) | (false, TrendType::Down) => {
                                return actions;
                            }
                            _ => {}
                        }
                    } else {
                        return actions;
                    }
                }
                if matches!(strategy, TradingStrategy::Inago(_)) {
                    if let Some(break_out) = self.detect_breakout(self.config.trading_period) {
                        match (break_out, trend_type) {
                            (BreakoutDirection::Up, TrendType::Up)
                            | (BreakoutDirection::Down, TrendType::Down) => {}
                            _ => {
                                return actions;
                            }
                        }
                    }
                }

                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
                    0.0,
                    // input_20
                    atr_spread
                        .unwrap_or_default()
                        .to_f64()
                        .expect("conversion failed"),
                    // input_21
                    open_tick_count_max.to_f64().expect("conversion failed"),
                    // 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
                    flash_crash_atr_multiplier
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_25
                    flash_crash_min_percentage
                        .unwrap_or_default()
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // 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"),
                ]]);

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

                if self.random_forest.as_ref().unwrap().predict(&x)[0] != 0 {
                    actions.push(Self::create_open_action(trend_type, None));
                }
            }
        }

        actions
    }

    fn is_range_bound_expected(&self) -> bool {
        let x = DenseMatrix::from_2d_array(&[&[
            // input_1
            self.price()
                .0
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_2
            self.price()
                .1
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_3
            self.price()
                .2
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_4
            self.price()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_5
            self.price()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_6
            self.price()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_7
            self.rsi()
                .1
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_8
            self.rsi()
                .2
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_9
            self.rsi()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_10
            self.rsi()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_11
            self.rsi()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_12
            self.adx()
                .1
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_13
            self.adx()
                .2
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_14
            self.adx()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_15
            self.adx()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_16
            self.adx()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_17
            self.last_volume()
                .unwrap_or_default()
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_18
            self.last_num_trades()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_19
            self.last_funding_rate()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_20
            self.last_open_interest()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_21
            (self.last_oracle_price().unwrap_or_default().round_dp(4) - self.price().0.round_dp(4))
                .to_f64()
                .expect("conversion failed"),
        ]]);

        self.random_forest.as_ref().unwrap().predict(&x)[0] != 0
    }

    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(trend: TrendType, tick_to_fill: Option<u32>) -> TradeAction {
        match trend {
            TrendType::Up => {
                TradeAction::BuyOpen(TradeDetail::new(None, None, Decimal::ONE, tick_to_fill))
            }
            TrendType::Down => {
                TradeAction::SellOpen(TradeDetail::new(None, None, Decimal::ONE, tick_to_fill))
            }
            _ => TradeAction::None,
        }
    }

    fn create_close_action(trend: TrendType) -> TradeAction {
        match trend {
            TrendType::Up => {
                TradeAction::BuyClose(TradeDetail::new(None, None, Decimal::ONE, None))
            }
            TrendType::Down => {
                TradeAction::SellClose(TradeDetail::new(None, None, Decimal::ONE, 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,
        };

        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::info!(
                    "atr_threshold: {}, threshold: {}, price_change: {}",
                    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
    }

    #[allow(dead_code)]
    fn old_atr_by_term(&self, term: &SampleTerm) -> Option<(Decimal, Decimal)> {
        let period = match term {
            SampleTerm::TradingTerm => self.config.trading_period,
            SampleTerm::ShortTerm => self.config.short_period,
            SampleTerm::LongTerm => self.config.long_period,
        };

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

        let end_idx = self.prices.len() - 1;
        let start_idx = end_idx.saturating_sub(period);

        let mut tr_values = Vec::new();
        for i in (start_idx + 1)..=end_idx {
            let current_price = self.prices[i].price;
            let prev_price = self.prices[i - 1].price;
            let high = current_price.max(prev_price);
            let low = current_price.min(prev_price);

            let tr = [
                high - low,
                (high - prev_price).abs(),
                (low - prev_price).abs(),
            ]
            .iter()
            .fold(Decimal::ZERO, |acc, &val| acc.max(val));

            tr_values.push(tr);
        }

        if tr_values.is_empty() {
            return None;
        }

        let min_tr = tr_values.iter().copied().reduce(Decimal::min).unwrap();
        let max_tr = tr_values.iter().copied().reduce(Decimal::max).unwrap();
        Some((min_tr, max_tr))
    }

    fn is_current_min_max_within_previous_range(&self) -> Option<bool> {
        let period = self.config.trading_period;
        if self.prices.len() < period * 2 {
            return None;
        }

        let current_range_start = self.prices.len() - period;
        let mut current_min = self.prices[current_range_start].price;
        let mut current_max = self.prices[current_range_start].price;

        for i in current_range_start..self.prices.len() {
            let price = self.prices[i].price;
            if price < current_min {
                current_min = price;
            }
            if price > current_max {
                current_max = price;
            }
        }

        let prev_range_start = self.prices.len() - period * 2;
        let prev_range_end = current_range_start; // (== self.prices.len() - period)
        let mut prev_min = self.prices[prev_range_start].price;
        let mut prev_max = self.prices[prev_range_start].price;

        for i in prev_range_start..prev_range_end {
            let price = self.prices[i].price;
            if price < prev_min {
                prev_min = price;
            }
            if price > prev_max {
                prev_max = price;
            }
        }

        Some(current_min >= prev_min && current_max <= prev_max)
    }

    fn detect_breakout(&self, period: usize) -> Option<BreakoutDirection> {
        if self.prices.len() < period * 2 {
            return None;
        }

        let current_range_start = self.prices.len() - period;
        let mut current_min = self.prices[current_range_start].price;
        let mut current_max = self.prices[current_range_start].price;
        for i in current_range_start..self.prices.len() {
            let price = self.prices[i].price;
            current_min = current_min.min(price);
            current_max = current_max.max(price);
        }

        let prev_range_end = current_range_start;
        let prev_range_start = self.prices.len() - period * 2;
        let mut prev_min = self.prices[prev_range_start].price;
        let mut prev_max = self.prices[prev_range_start].price;
        for i in prev_range_start..prev_range_end {
            let price = self.prices[i].price;
            prev_min = prev_min.min(price);
            prev_max = prev_max.max(price);
        }

        let up_break = current_max > prev_max;
        let down_break = current_min < prev_min;

        match (up_break, down_break) {
            (true, true) => Some(BreakoutDirection::Both),
            (true, false) => Some(BreakoutDirection::Up),
            (false, true) => Some(BreakoutDirection::Down),
            (false, false) => None,
        }
    }
}
