scuriolus 0.3.0

Scuriolus is a modular trading bot platform.
Documentation
use chrono::{DateTime, Datelike as _, DurationRound as _, TimeDelta, TimeZone as _, Utc, Weekday};
use strum_macros::{Display, EnumIter};

use crate::core::{CoreError, CoreResult};

/// Avalaible Kline intervals
#[derive(
    Debug,
    Display,
    serde::Serialize,
    serde::Deserialize,
    Clone,
    Copy,
    PartialEq,
    EnumIter,
    Eq,
    PartialOrd,
    Ord,
    Hash,
)]
pub enum Interval {
    /// 1 minute
    OneMinute,

    /// 5 minutes
    FiveMinutes,

    /// 15 minutes
    FifteenMinutes,

    /// 30 minutes
    ThirtyMinutes,

    /// 1 hour
    OneHour,

    /// 4 hours
    FourHours,

    /// 1 day
    OneDay,

    /// 1 week
    OneWeek,

    /// 1 month
    OneMonth,
}

impl Interval {
    pub fn next_interval_to_zoom_out(&self) -> Option<Interval> {
        match self {
            Interval::OneMinute => Some(Interval::FiveMinutes),
            Interval::FiveMinutes => Some(Interval::FifteenMinutes),
            Interval::FifteenMinutes => Some(Interval::OneHour),
            Interval::ThirtyMinutes => Some(Interval::OneHour),
            Interval::OneHour => Some(Interval::OneDay),
            Interval::FourHours => Some(Interval::OneDay),
            Interval::OneDay => Some(Interval::OneWeek),
            Interval::OneWeek => None,
            Interval::OneMonth => None,
        }
    }

    pub fn time_delta(&self) -> TimeDelta {
        match self {
            Interval::OneMinute => TimeDelta::minutes(1),
            Interval::FiveMinutes => TimeDelta::minutes(5),
            Interval::FifteenMinutes => TimeDelta::minutes(15),
            Interval::ThirtyMinutes => TimeDelta::minutes(30),
            Interval::OneHour => TimeDelta::hours(1),
            Interval::FourHours => TimeDelta::hours(4),
            Interval::OneDay => TimeDelta::days(1),
            Interval::OneWeek => TimeDelta::weeks(1),
            Interval::OneMonth => TimeDelta::days(30),
        }
    }

    /// Given a target date, returns the start and end date of the interval
    /// this target date belongs to.
    ///
    /// For the precision of 1 week, the start date will always be a Monday.
    /// If the date is exactly on a boundary, the interval starting on this boundary will be returned
    ///
    ///  ## Arguments
    ///
    /// * `target` - The target date to get the bounds for
    pub fn get_time_bounds(
        &self,
        target: DateTime<Utc>,
    ) -> CoreResult<(DateTime<Utc>, DateTime<Utc>)> {
        match self {
            Interval::OneWeek => {
                let mut start = target.duration_trunc(Interval::OneDay.time_delta())?;
                let mut end = target.duration_round_up(Interval::OneDay.time_delta())?;

                while start.weekday() != Weekday::Mon {
                    start -= TimeDelta::days(1);
                }
                while end.weekday() != Weekday::Mon && end <= start {
                    end += TimeDelta::days(1);
                }

                Ok((end, start))
            }
            Interval::OneMonth => {
                let start = Utc
                    .with_ymd_and_hms(target.year(), target.month(), 1, 0, 0, 0)
                    .single()
                    .ok_or(CoreError::comput_error("Error creating start date"))?;
                let end = if target.month() == 12 {
                    Utc.with_ymd_and_hms(target.year() + 1, 1, 1, 0, 0, 0)
                } else {
                    Utc.with_ymd_and_hms(target.year(), target.month() + 1, 1, 0, 0, 0)
                }
                .single()
                .ok_or(CoreError::comput_error("Error creating start date"))?;
                Ok((start, end))
            }
            _ => {
                let start = target.duration_trunc(self.time_delta())?;
                let end = target.duration_round_up(self.time_delta())?;
                if start != end {
                    Ok((start, end))
                } else {
                    Ok((start, end + self.time_delta()))
                }
            }
        }
    }

    pub fn short_string(&self) -> String {
        match self {
            Interval::OneMinute => "1m",
            Interval::FiveMinutes => "5m",
            Interval::FifteenMinutes => "15m",
            Interval::ThirtyMinutes => "30m",
            Interval::OneHour => "1h",
            Interval::FourHours => "4h",
            Interval::OneDay => "1d",
            Interval::OneWeek => "1w",
            Interval::OneMonth => "1M",
        }
        .to_string()
    }
}