use debot_db::PricePoint;
use debot_ml::RandomForest;
use debot_utils::DynamicLimitedSizeMap;
use debot_utils::LimitedSizeMap;
use debot_utils::ValuePair;
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::{CrossOverType, TrendMetrics},
    TradeAction,
};
use std::{cell::RefCell, collections::VecDeque};

#[derive(Debug, Clone, PartialEq)]
pub enum MarketCondition {
    UpBreak,
    DownBreak,
    UpInit,
    DownInit,
    HotMarket,
    ColdMarket,
    RangeBound,
    Unknown,
}

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

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

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

pub struct MarketData {
    name: String,
    prices: VecDeque<PricePoint>,
    price_mean: Decimal,
    price_variance: Decimal,
    config: MarketDataConfig,
    metrics: MarketDataMetrics,
    probability: TransactionProbability,
    trend: TrendType,
    random_forest: Option<RandomForest>,
}

#[derive(Clone)]
struct TransactionProbability {
    max_entry_size: usize,
    fill_probability: LimitedSizeMap<Decimal, DynamicLimitedSizeMap<Decimal, ValuePair<u32>>>,
}

impl TransactionProbability {
    fn new(max_size: usize) -> Self {
        TransactionProbability {
            max_entry_size: max_size,
            fill_probability: LimitedSizeMap::new(max_size),
        }
    }
}

#[derive(Clone, Copy, Debug, Eq)]
pub enum TradingStrategy {
    RandomWalk(TrendType),
    MachineLearning(TrendType),
    PassiveTrade(Decimal),
    MarketMake,
}

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

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::MachineLearning(TrendType::Unknown),
                TradingStrategy::MachineLearning(_),
            ) => true,
            (
                TradingStrategy::MachineLearning(_),
                TradingStrategy::MachineLearning(TrendType::Unknown),
            ) => true,
            (
                TradingStrategy::MachineLearning(TrendType::Up),
                TradingStrategy::MachineLearning(TrendType::Up),
            ) => true,
            (
                TradingStrategy::MachineLearning(TrendType::Down),
                TradingStrategy::MachineLearning(TrendType::Down),
            ) => true,
            (TradingStrategy::MarketMake, TradingStrategy::MarketMake) => true,
            (TradingStrategy::PassiveTrade(_), TradingStrategy::PassiveTrade(_)) => true,
            _ => false,
        }
    }
}

impl MarketData {
    pub fn new(
        name: String,
        short_period: usize,
        long_period: usize,
        trading_period: usize,
        max_size: usize,
        crossover_hold_seconds: i64,
        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,
            crossover_hold_seconds,
        );
        MarketData {
            name,
            prices: VecDeque::with_capacity(max_size),
            config,
            metrics,
            price_mean: Decimal::ZERO,
            price_variance: Decimal::ZERO,
            probability: TransactionProbability::new(1024),
            trend: TrendType::Unknown,
            random_forest,
        }
    }

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

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

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

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

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

    pub fn trend(&self) -> TrendType {
        self.trend
    }

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

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

    pub fn price_variance(&self) -> Decimal {
        self.price_variance
    }

    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_transaction_price(&mut self, trade_price: Decimal) {
        if self.prices.len() < self.config.trading_period {
            return;
        }
        let mid_price_index = self.prices.len() - self.config.trading_period;

        let mid_prices: Vec<Decimal> = self
            .prices
            .iter()
            .skip(mid_price_index)
            .map(|pp| pp.price)
            .collect();

        for mid_price in mid_prices {
            match self.probability.fill_probability.get_mut(&mid_price) {
                Some(value_pair_map) => {
                    let mut changes = Vec::new();

                    for (price, _value_pair) in value_pair_map.iter() {
                        let condition = if trade_price > mid_price {
                            mid_price <= *price && *price <= trade_price
                        } else {
                            trade_price <= *price && *price <= mid_price
                        };

                        if condition {
                            changes.push((*price, 1, 0));
                        } else {
                            changes.push((*price, 0, 1));
                        }
                    }

                    for (price, add1, add2) in changes {
                        value_pair_map.add(price, add1, add2);
                    }

                    if value_pair_map.get(&trade_price).is_none() {
                        value_pair_map.add(trade_price, 1, 0);
                    }
                }
                None => {
                    let mut value_pair_map = DynamicLimitedSizeMap::<Decimal, ValuePair<u32>>::new(
                        self.probability.max_entry_size,
                    );
                    value_pair_map.add(trade_price, 1, 0);
                    self.probability
                        .fill_probability
                        .insert(mid_price, value_pair_map);
                }
            }
        }
    }

    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 = match price {
            Some(price) => price,
            None => {
                if self.prices.len() == 0 {
                    Decimal::ZERO
                } else {
                    match self.prices.iter().last() {
                        Some(v) => v.price,
                        None => Decimal::ZERO,
                    }
                }
            }
        };

        let price_point = PricePoint::new(price, timestamp);

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

        self.update_mean_and_variance(self.config.trading_period);
        self.update_ema(price);
        self.update_rsi();
        self.update_adx();
        self.update_stochastic();
        self.update_atr();

        self.trend = self.metrics.price.borrow().trend;

        price_point
    }

    pub fn is_open_signaled(
        &mut self,
        strategy: TradingStrategy,
        mid_price: Decimal,
        amount_in_usd: Decimal,
        take_profit_ratio: Decimal,
        atr_spread: Option<Decimal>,
    ) -> Vec<TradeAction> {
        let mut actions: Vec<TradeAction> = vec![];

        match strategy {
            TradingStrategy::MarketMake => {
                actions = self.get_market_make_actions(mid_price, amount_in_usd);
            }
            TradingStrategy::RandomWalk(trend_type) => {
                let action = Self::create_open_action(trend_type);
                actions.push(action.clone());
            }
            TradingStrategy::MachineLearning(trend_type) => {
                let x = DenseMatrix::from_2d_array(&[&[
                    self.adx()
                        .0
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.adx()
                        .1
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.adx()
                        .2
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.atr()
                        .0
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.atr()
                        .1
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.atr()
                        .2
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.rsi()
                        .0
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.rsi()
                        .1
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.rsi()
                        .2
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.macd()
                        .0
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.macd()
                        .1
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.macd()
                        .2
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.stochastic()
                        .0
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.stochastic()
                        .1
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.stochastic()
                        .2
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    take_profit_ratio
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    self.price_variance()
                        .round_dp(4)
                        .to_f64()
                        .expect("conversion failed"),
                    atr_spread
                        .unwrap_or_default()
                        .to_f64()
                        .expect("conversion failed"),
                    0.0,
                ]]);
                let prediction = self.random_forest.as_ref().unwrap().predict(x);
                if prediction {
                    let action = Self::create_open_action(trend_type);
                    actions.push(action.clone());
                }
            }
            TradingStrategy::PassiveTrade(_) => {}
        }

        actions
    }

    pub fn is_close_signaled(
        &mut self,
        strategy: TradingStrategy,
        amount_in_usd: Decimal,
        is_profitable_position: bool,
    ) -> TradeAction {
        let mut price_borrowed = self.metrics.price.borrow_mut();

        match strategy {
            TradingStrategy::PassiveTrade(_) => match price_borrowed.is_crossover() {
                CrossOverType::BuyCross | CrossOverType::GoldenCross => {
                    if is_profitable_position {
                        Self::create_trim_action(TrendType::Up, amount_in_usd)
                    } else {
                        TradeAction::None
                    }
                }
                CrossOverType::SellCross | CrossOverType::DeadCross => {
                    if is_profitable_position {
                        Self::create_trim_action(TrendType::Down, amount_in_usd)
                    } else {
                        TradeAction::None
                    }
                }
                _ => TradeAction::None,
            },
            _ => 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,
        }
    }

    #[allow(dead_code)]
    fn create_close_action(trend: TrendType) -> TradeAction {
        match trend {
            TrendType::StrongUp => {
                TradeAction::BuyClose(TradeDetail::new(None, None, Decimal::ONE))
            }
            TrendType::StrongDown => {
                TradeAction::SellClose(TradeDetail::new(None, None, Decimal::ONE))
            }
            _ => TradeAction::None,
        }
    }

    fn create_trim_action(trend: TrendType, amount_in_usd: Decimal) -> TradeAction {
        match trend {
            TrendType::StrongUp => {
                TradeAction::BuyTrim(TradeDetail::new(None, Some(amount_in_usd), Decimal::ONE))
            }
            TrendType::StrongDown => {
                TradeAction::SellTrim(TradeDetail::new(None, Some(amount_in_usd), Decimal::ONE))
            }
            _ => TradeAction::None,
        }
    }

    pub fn init_crossover_state(&mut self) {
        self.metrics.price.borrow_mut().init_crossover_state();
    }

    fn get_fill_probability(&self, current_price: Decimal, target_price: Decimal) -> Decimal {
        if let Some(value_pair_map) = self.probability.fill_probability.get(&current_price) {
            if let Some(value_pair) = value_pair_map.get(&target_price) {
                let alpha = Decimal::from(value_pair.first);
                let beta = Decimal::from(value_pair.second);
                alpha / (alpha + beta)
            } else {
                Decimal::ZERO
            }
        } else {
            Decimal::ZERO
        }
    }

    fn get_market_make_actions(
        &self,
        mid_price: Decimal,
        amount_in_usd: Decimal,
    ) -> Vec<TradeAction> {
        let mut actions: Vec<TradeAction> = vec![];
        let confidence = Decimal::ONE;

        if let Some((buy_price, sell_price)) = self.get_best_market_make_price(mid_price) {
            let token_amount = amount_in_usd / sell_price;
            let buy_action = TradeAction::BuyOpen(TradeDetail::new(
                Some(buy_price),
                Some(token_amount),
                confidence,
            ));
            actions.push(buy_action);

            let sell_action = TradeAction::SellOpen(TradeDetail::new(
                Some(sell_price),
                Some(token_amount),
                confidence,
            ));
            actions.push(sell_action);
        }

        actions
    }

    fn get_best_market_make_price(&self, mid_price: Decimal) -> Option<(Decimal, Decimal)> {
        if let Some(value_pair_map) = self.probability.fill_probability.get(&mid_price) {
            let mut best_e = Decimal::MIN;
            let mut best_price = None;

            log::trace!("Fill Probabilities:");
            log::trace!("  mid_price: {}", mid_price);
            for (price, value_pair) in value_pair_map.iter() {
                let fill_probability =
                    (value_pair.first as f64) / ((value_pair.first + value_pair.second) as f64);
                log::trace!(
                    "    target_price: {}, fill_probability: {:.2}%",
                    price,
                    fill_probability * 100.0
                );
            }

            for (&buy_price, _) in value_pair_map.iter() {
                for (&sell_price, _) in value_pair_map.iter() {
                    if buy_price >= sell_price {
                        continue;
                    }

                    let bp = self.get_fill_probability(mid_price, buy_price);
                    let sp = self.get_fill_probability(mid_price, sell_price);

                    if bp.is_zero() || sp.is_zero() {
                        log::warn!("Not reached");
                        continue;
                    }

                    let eprofit = bp * sp * (sell_price - buy_price);
                    let one = Decimal::ONE;
                    let eloss = (one - (one - bp) * (one - sp) - bp * sp) * self.atr().0;
                    let e = eprofit - eloss;
                    let ratio = e / mid_price * Decimal::new(100, 0);

                    log::trace!(
                        "e = {:.3}({:.3} - {:.3}[{:.2}%]), buy_price = {}, sell_price = {}, atr = {:.3}",
                        e,
                        eprofit,
                        eloss,
                        ratio,
                        buy_price,
                        sell_price,
                        self.atr().0,
                    );

                    if e > Decimal::ZERO && e > best_e {
                        best_e = e;
                        best_price = Some((buy_price, sell_price));
                        log::debug!(
                            "best_e = {:.3}[{:.2}%], buy_price = {}, sell_price = {}",
                            e,
                            ratio,
                            buy_price,
                            sell_price,
                        );
                    }
                }
            }

            return best_price;
        }

        None
    }

    fn update_mean_and_variance(&mut self, period: usize) {
        let prices_window = if self.prices.len() > period {
            self.prices
                .iter()
                .skip(self.prices.len() - period)
                .collect::<Vec<_>>()
        } else {
            self.prices.iter().collect::<Vec<_>>()
        };

        let count = Decimal::from(prices_window.len());
        if count == Decimal::ZERO {
            return;
        }

        let sum: Decimal = prices_window.iter().map(|pp| pp.price).sum();
        let new_mean = sum / count;

        let new_variance = prices_window
            .iter()
            .map(|pp| {
                let diff = pp.price - new_mean;
                diff * diff
            })
            .sum::<Decimal>()
            / count;

        self.price_mean = new_mean;
        self.price_variance = new_variance;
    }

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

    fn is_upper_band_reached(&self, deviation: f64) -> bool {
        let price_mean = match self.price_mean.to_f64() {
            Some(v) => v,
            None => return false,
        };

        let price_variance = match self.price_variance.to_f64() {
            Some(v) => v,
            None => return false,
        };

        let last_price = match self.last_price().to_f64() {
            Some(v) => v,
            None => return false,
        };

        let upper_band = price_mean + deviation * price_variance.sqrt();
        last_price >= upper_band
    }

    fn is_lower_band_reached(&self, deviation: f64) -> bool {
        let price_mean = match self.price_mean.to_f64() {
            Some(v) => v,
            None => return false,
        };

        let price_variance = match self.price_variance.to_f64() {
            Some(v) => v,
            None => return false,
        };

        let last_price = match self.last_price().to_f64() {
            Some(v) => v,
            None => return false,
        };

        let lower_band = price_mean - deviation * price_variance.sqrt();
        last_price <= lower_band
    }
}
