greed 0.5.2

A rust tool to automate trades 📈
Documentation
use crate::asset::AssetSymbol;
use crate::platform::bar::Bar;
use crate::statistics::median;

#[derive(Clone, Debug, Default, PartialEq)]
pub struct Bars {
    pub symbol: AssetSymbol,
    pub bars: Vec<Bar>,
}

impl Bars {
    pub fn with_bars(bars: Vec<Bar>) -> Self {
        Self {
            symbol: Default::default(),
            bars,
        }
    }
    pub fn average_median(&self) -> Option<f64> {
        self.median(|b| b.average())
    }

    pub fn close_median(&self) -> Option<f64> {
        self.median(|b| b.close)
    }

    fn median<F>(&self, func: F) -> Option<f64>
    where
        F: FnMut(&Bar) -> f64,
    {
        if self.is_empty() {
            return None;
        }
        let values = self.bars.iter().map(func).collect::<Vec<_>>();
        return median(values);
    }

    pub fn positive_percent_median(&self) -> Option<f64> {
        let average_median = self.average_median().unwrap_or_default();
        let positive_percents = self
            .bars
            .iter()
            .map(|b| b.displacement_from_value_percent(average_median))
            .filter(|&p| p >= 0.0)
            .collect::<Vec<_>>();
        median(positive_percents)
    }

    pub fn negative_percent_median(&self) -> Option<f64> {
        let average_median = self.average_median().unwrap_or_default();
        let negative_percents = self
            .bars
            .iter()
            .map(|b| b.displacement_from_value_percent(average_median))
            .filter(|&p| p <= 0.0)
            .collect::<Vec<_>>();
        median(negative_percents)
    }

    pub fn period_bar(&self) -> Option<Bar> {
        let first_bar = self.bars.first()?;
        let last_bar = self.bars.last()?;
        let joined = first_bar.join(last_bar);
        Some(joined)
    }

    pub fn is_empty(&self) -> bool {
        self.bars.is_empty()
    }

    #[cfg(test)]
    pub fn fixture(symbol: AssetSymbol, base_average: f64) -> Self {
        Self {
            symbol,
            bars: vec![
                Bar::fixture(base_average),
                Bar::fixture(base_average + 100.0),
                Bar::fixture(base_average + 200.0),
            ],
        }
    }
}

#[cfg(test)]
mod tests {
    use chrono::{DateTime, TimeZone, Utc};

    use super::*;

    #[test]
    fn average_median() {
        let bar_vec = (0..5)
            .map(|index| Bar {
                low: f64::from(index) * 100.0,
                high: f64::from(index) * 200.0,
                ..Default::default()
            })
            .collect::<Vec<_>>();
        let bars = Bars {
            bars: bar_vec,
            ..Default::default()
        };

        let median = bars.average_median();
        assert_eq!(median, Some(300.0))
    }

    #[test]
    fn close_median() {
        let bar_vec = (1..=5)
            .map(|index| Bar {
                close: f64::from(index) * 100.0,
                ..Default::default()
            })
            .collect::<Vec<_>>();
        let bars = Bars {
            bars: bar_vec,
            ..Default::default()
        };

        let median = bars.close_median();
        assert_eq!(median, Some(300.0))
    }

    #[test]
    fn close_median_empty() {
        let bars = Bars {
            ..Default::default()
        };

        let median = bars.close_median();
        assert_eq!(median, None)
    }

    #[test]
    fn positive_percent_median() {
        let bar_vec = (0..5)
            .map(|index| Bar {
                close: f64::from(index) * 150.0,
                low: f64::from(index) * 100.0,
                high: f64::from(index) * 200.0,
                ..Default::default()
            })
            .collect::<Vec<_>>();
        let bars = Bars {
            bars: bar_vec,
            ..Default::default()
        };

        let median = bars.positive_percent_median();
        assert_eq!(median, Some(50.0))
    }

    #[test]
    fn negative_percent_median() {
        let bar_vec = (0..5)
            .map(|index| Bar {
                low: f64::from(index + 1) * 200.0,
                high: f64::from(index + 1) * 100.0,
                ..Default::default()
            })
            .collect::<Vec<_>>();
        let bars = Bars {
            bars: bar_vec,
            ..Default::default()
        };

        let median = bars.negative_percent_median();
        assert_eq!(median, Some(-100.0))
    }

    #[test]
    fn positive_negative_median() {
        let bar_vec = (0..5)
            .map(|index| Bar {
                close: f64::from(5 + index) * 200.0,
                low: f64::from(index) * 200.0,
                high: f64::from(index) * 100.0,
                ..Default::default()
            })
            .collect::<Vec<_>>();
        let bars = Bars {
            bars: bar_vec,
            ..Default::default()
        };

        let median = bars.negative_percent_median();
        assert_eq!(median, None)
    }

    #[test]
    fn period_bar_empty() {
        let bars = Bars {
            ..Default::default()
        };
        let period_bar = bars.period_bar();
        assert_eq!(period_bar, None)
    }

    #[test]
    fn period_bar_several_bars() {
        let bar_vec = (0..5)
            .map(|index| Bar {
                timestamp: date(index),
                close: (index as f64) * 200.0,
                open: (index as f64) * 100.0,
                ..Default::default()
            })
            .collect::<Vec<_>>();
        let bars = Bars {
            bars: bar_vec,
            ..Default::default()
        };

        let period_bar = bars.period_bar();
        let expected = Bar {
            timestamp: date(0),
            open: 0.0,
            close: 800.0,
            ..Default::default()
        };
        assert_eq!(period_bar, Some(expected))
    }
    fn date(min: u32) -> DateTime<Utc> {
        Utc.with_ymd_and_hms(2023, 12, 25, 0, min, 0)
            .earliest()
            .expect("failed to create test date")
    }
}