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

use crate::trend_metrics::TrendType;
use crate::{trade_action::TradeDetail, trend_metrics::TrendMetrics, TradeAction};
use rand::Rng;
use std::collections::VecDeque;

#[derive(Debug, Clone)]
pub struct Candle {
    pub open: Decimal,
    pub high: Decimal,
    pub low: Decimal,
    pub close: Decimal,
    pub timestamp: i64,
}

impl Candle {
    pub fn new(open: Decimal, high: Decimal, low: Decimal, close: Decimal, timestamp: i64) -> Self {
        Self {
            open,
            high,
            low,
            close,
            timestamp,
        }
    }

    pub fn update(&mut self, price: Decimal) {
        if price > self.high {
            self.high = price;
        }
        if price < self.low {
            self.low = price;
        }
        self.close = price;
    }

    pub fn detect_pattern(candles: &[Candle]) -> CandlePattern {
        if candles.len() < 3 {
            return CandlePattern::None;
        }

        let latest = &candles[candles.len() - 1];
        let prev = &candles[candles.len() - 2];
        let prev2 = &candles[candles.len() - 3];

        let body = (latest.close - latest.open).abs();
        let upper_shadow = latest.high - Decimal::max(latest.open, latest.close);
        let lower_shadow = Decimal::min(latest.open, latest.close) - latest.low;

        // Hammer or Inverted Hammer
        if lower_shadow > body * Decimal::from(2) && upper_shadow < body {
            return CandlePattern::Hammer;
        } else if upper_shadow > body * Decimal::from(2) && lower_shadow < body {
            return CandlePattern::InvertedHammer;
        }

        // Engulfing (Bullish or Bearish)
        if latest.close > latest.open && latest.close > prev.high && latest.open < prev.low {
            return CandlePattern::BullishEngulfing;
        } else if latest.open > latest.close && latest.open > prev.high && latest.close < prev.low {
            return CandlePattern::BearishEngulfing;
        }

        // Doji
        if body < Decimal::new(1, 1) * (latest.high - latest.low) {
            return CandlePattern::Doji;
        }

        // Marubozu
        if upper_shadow.is_zero() && lower_shadow.is_zero() {
            return CandlePattern::Marubozu;
        }

        // Spinning Top
        if upper_shadow > body && lower_shadow > body {
            return CandlePattern::SpinningTop;
        }

        // Harami (Bullish or Bearish)
        if (prev.open > prev.close
            && latest.open > latest.close
            && latest.open < prev.close
            && latest.close > prev.open)
            || (prev.open < prev.close
                && latest.open < latest.close
                && latest.open > prev.close
                && latest.close < prev.open)
        {
            return CandlePattern::Harami;
        }

        // Harami Cross
        if prev.close == prev.open && (prev.high - prev.low) > (prev.close - prev.open).abs() {
            if (latest.open > prev.close && latest.close < prev.open)
                || (latest.open < prev.close && latest.close > prev.open)
            {
                return CandlePattern::HaramiCross;
            }
        }

        // Morning Star
        if prev2.open > prev2.close
            && prev.open < prev.close
            && latest.open > latest.close
            && latest.close > (prev.open + prev.close) / Decimal::new(2, 0)
        {
            return CandlePattern::MorningStar;
        }

        // Evening Star
        if prev2.open < prev2.close
            && prev.open > prev.close
            && latest.open < latest.close
            && latest.close < (prev.open + prev.close) / Decimal::new(2, 0)
        {
            return CandlePattern::EveningStar;
        }

        // Three White Soldiers
        if prev2.close < prev2.open
            && prev.close > prev.open
            && latest.close > latest.open
            && prev2.close < prev.close
            && prev.close < latest.close
            && latest.open > prev.close
        {
            return CandlePattern::ThreeWhiteSoldiers;
        }

        // Three Black Crows
        if prev2.close > prev2.open
            && prev.close < prev.open
            && latest.close < latest.open
            && prev2.close > prev.close
            && prev.close > latest.close
            && latest.open < prev.close
        {
            return CandlePattern::ThreeBlackCrows;
        }

        // Piercing Pattern
        if prev.open > prev.close
            && latest.open < latest.close
            && latest.open < prev.close
            && latest.close > (prev.open + prev.close) / Decimal::new(2, 0)
        {
            return CandlePattern::PiercingPattern;
        }

        // Dark Cloud Cover
        if prev.open < prev.close
            && latest.open > latest.close
            && latest.open > prev.close
            && latest.close < (prev.open + prev.close) / Decimal::new(2, 0)
        {
            return CandlePattern::DarkCloudCover;
        }

        CandlePattern::None
    }
}

#[derive(Debug, Clone)]
pub enum SampleTerm {
    TradingTerm,
    ShortTerm,
    LongTerm,
}

impl SampleTerm {
    pub fn to_numeric(&self) -> Decimal {
        match self {
            SampleTerm::TradingTerm => Decimal::new(1, 0),
            SampleTerm::ShortTerm => Decimal::new(2, 0),
            SampleTerm::LongTerm => Decimal::new(3, 0),
        }
    }
}

#[derive(Debug, Clone)]
struct MarketDataConfig {
    short_period: usize,
    long_period: usize,
    trading_period: usize,
    max_size: usize,
}

#[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_1h: Vec<Candle>,
    candles_4h: Vec<Candle>,
    current_candle_1h: Option<Candle>,
    current_candle_4h: Option<Candle>,
}

#[derive(Clone, Copy, Debug, Eq, Hash)]
pub enum TradingStrategy {
    RandomWalk(TrendType),
    MeanReversion(TrendType),
}

impl TradingStrategy {
    pub fn trend_type(&self) -> &TrendType {
        match self {
            TradingStrategy::RandomWalk(t) | TradingStrategy::MeanReversion(t) => t,
        }
    }
}

impl PartialEq for TradingStrategy {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (TradingStrategy::RandomWalk(TrendType::Unknown), TradingStrategy::RandomWalk(_)) => {
                true
            }
            (TradingStrategy::RandomWalk(_), TradingStrategy::RandomWalk(TrendType::Unknown)) => {
                true
            }
            (
                TradingStrategy::RandomWalk(TrendType::Up),
                TradingStrategy::RandomWalk(TrendType::Up),
            ) => true,
            (
                TradingStrategy::RandomWalk(TrendType::Down),
                TradingStrategy::RandomWalk(TrendType::Down),
            ) => true,
            (
                TradingStrategy::MeanReversion(TrendType::Unknown),
                TradingStrategy::MeanReversion(_),
            ) => true,
            (
                TradingStrategy::MeanReversion(_),
                TradingStrategy::MeanReversion(TrendType::Unknown),
            ) => true,
            (
                TradingStrategy::MeanReversion(TrendType::Up),
                TradingStrategy::MeanReversion(TrendType::Up),
            ) => true,
            (
                TradingStrategy::MeanReversion(TrendType::Down),
                TradingStrategy::MeanReversion(TrendType::Down),
            ) => true,
            _ => false,
        }
    }
}

impl MarketData {
    pub fn new(
        name: String,
        short_period: usize,
        long_period: usize,
        trading_period: usize,
        max_size: usize,
        random_forest: Option<RandomForest>,
    ) -> MarketData {
        let config = MarketDataConfig {
            short_period,
            long_period,
            trading_period,
            max_size,
        };
        let metrics = MarketDataMetrics::new(short_period, long_period, trading_period);
        MarketData {
            name,
            prices: VecDeque::with_capacity(max_size),
            config,
            metrics,
            random_forest,
            candles_1h: Vec::new(),
            candles_4h: Vec::new(),
            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 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 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_1h,
            prev_candle_pattern_1h,
            candle_pattern_4h,
            prev_candle_pattern_4h,
        )
    }

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

        let range_start = prices_len - period;
        let mut min = Decimal::MAX;
        let mut max = Decimal::MIN;

        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>) -> 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));
        let price_point = PricePoint::new(price, timestamp);
        self.prices.push_back(price_point.clone());

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

        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 current_interval = timestamp / interval * interval;

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

    pub fn is_open_signaled(
        &self,
        strategy: TradingStrategy,
        take_profit_ratio: Decimal,
        atr_spread: Option<Decimal>,
        open_tick_count_max: u32,
        risk_reward: Decimal,
        atr_term: &SampleTerm,
    ) -> 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_1h, _, candle_pattern_4h, _) = self.candle_pattern();

        log::debug!(
            "candle_pattern 1h/4h = {:?}/{:?}",
            candle_pattern_1h,
            candle_pattern_4h
        );
        let candle_value_1h = candle_pattern_1h.to_one_hot();
        let candle_value_4h = candle_pattern_4h.to_one_hot();

        match strategy {
            TradingStrategy::RandomWalk(trend_type) => {
                if Self::random_bool(0.1) {
                    let action = Self::create_open_action(trend_type);
                    actions.push(action.clone());
                }
            }
            TradingStrategy::MeanReversion(trend_type) => {
                let x = DenseMatrix::from_2d_array(&[&[
                    // input_1
                    0.0,
                    // input_2
                    atr_term
                        .to_numeric()
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_3
                    self.price()
                        .3
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_4
                    self.price()
                        .4
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_5
                    self.price()
                        .5
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_6
                    self.atr()
                        .1
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_7
                    self.atr()
                        .2
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_8
                    self.atr()
                        .3
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_9
                    self.atr()
                        .4
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_10
                    self.atr()
                        .5
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_11
                    self.rsi()
                        .1
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_12
                    self.rsi()
                        .2
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_13
                    self.rsi()
                        .3
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_14
                    self.rsi()
                        .4
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_15
                    self.rsi()
                        .5
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_16
                    take_profit_ratio
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    // input_17
                    atr_spread
                        .unwrap_or_default()
                        .to_f64()
                        .expect("conversion failed"),
                    // input_18
                    open_tick_count_max.to_f64().expect("conversion failed"),
                    // input_19
                    risk_reward.round_dp(4).to_f64().expect("conversion failed"),
                    // input_20
                    candle_value_1h[0].to_f64().expect("conversion failed"),
                    // input_21
                    candle_value_1h[1].to_f64().expect("conversion failed"),
                    // input_22
                    candle_value_1h[2].to_f64().expect("conversion failed"),
                    // input_23
                    candle_value_1h[3].to_f64().expect("conversion failed"),
                    // input_24
                    candle_value_1h[4].to_f64().expect("conversion failed"),
                    // input_25
                    candle_value_1h[5].to_f64().expect("conversion failed"),
                    // input_26
                    candle_value_1h[6].to_f64().expect("conversion failed"),
                    // input_27
                    candle_value_1h[7].to_f64().expect("conversion failed"),
                    // input_28
                    candle_value_1h[8].to_f64().expect("conversion failed"),
                    // input_29
                    candle_value_1h[9].to_f64().expect("conversion failed"),
                    // input_30
                    candle_value_1h[10].to_f64().expect("conversion failed"),
                    // input_31
                    candle_value_1h[11].to_f64().expect("conversion failed"),
                    // input_32
                    candle_value_1h[12].to_f64().expect("conversion failed"),
                    // input_33
                    candle_value_1h[13].to_f64().expect("conversion failed"),
                    // input_34
                    candle_value_1h[14].to_f64().expect("conversion failed"),
                    // input_35
                    candle_value_1h[15].to_f64().expect("conversion failed"),
                    // input_36
                    0.0,
                    // input_37
                    0.0,
                    // input_38
                    0.0,
                    // input_39
                    0.0,
                    // input_40
                    candle_value_4h[0].to_f64().expect("conversion failed"),
                    // input_41
                    candle_value_4h[1].to_f64().expect("conversion failed"),
                    // input_42
                    candle_value_4h[2].to_f64().expect("conversion failed"),
                    // input_43
                    candle_value_4h[3].to_f64().expect("conversion failed"),
                    // input_44
                    candle_value_4h[4].to_f64().expect("conversion failed"),
                    // input_45
                    candle_value_4h[5].to_f64().expect("conversion failed"),
                    // input_46
                    candle_value_4h[6].to_f64().expect("conversion failed"),
                    // input_47
                    candle_value_4h[7].to_f64().expect("conversion failed"),
                    // input_48
                    candle_value_4h[8].to_f64().expect("conversion failed"),
                    // input_49
                    candle_value_4h[9].to_f64().expect("conversion failed"),
                    // input_50
                    candle_value_4h[10].to_f64().expect("conversion failed"),
                    // input_51
                    candle_value_4h[11].to_f64().expect("conversion failed"),
                    // input_52
                    candle_value_4h[12].to_f64().expect("conversion failed"),
                    // input_53
                    candle_value_4h[13].to_f64().expect("conversion failed"),
                    // input_54
                    candle_value_4h[14].to_f64().expect("conversion failed"),
                    // input_55
                    candle_value_4h[15].to_f64().expect("conversion failed"),
                    // input_56
                    0.0,
                    // input_57
                    0.0,
                    // input_58
                    0.0,
                    // input_59
                    0.0,
                ]]);

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

                let prediction = self.random_forest.as_ref().unwrap().predict(x);
                if prediction {
                    if Self::random_bool(0.5) {
                        let action = Self::create_open_action(trend_type);
                        actions.push(action.clone());
                    }
                }
            }
        }

        actions
    }

    fn random_bool(probability: f64) -> bool {
        let mut rng = rand::thread_rng();
        rng.gen_bool(probability)
    }

    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()
    }

    pub fn is_close_signaled(
        &self,
        _strategy: TradingStrategy,
        _amount_in_usd: Decimal,
        _is_profitable_position: bool,
    ) -> TradeAction {
        TradeAction::None
    }

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