use std::{fmt, result::Result};
use chrono::Duration;
pub mod error;
use error::{LookbackValidationError, MinIterationIntervalValidationError, PeriodValidationError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OhlcResolution {
OneMinute,
ThreeMinutes,
FiveMinutes,
TenMinutes,
FifteenMinutes,
ThirtyMinutes,
FortyFiveMinutes,
OneHour,
TwoHours,
ThreeHours,
FourHours,
OneDay,
}
impl OhlcResolution {
pub const fn as_minutes(&self) -> u32 {
match self {
Self::OneMinute => 1,
Self::ThreeMinutes => 3,
Self::FiveMinutes => 5,
Self::TenMinutes => 10,
Self::FifteenMinutes => 15,
Self::ThirtyMinutes => 30,
Self::FortyFiveMinutes => 45,
Self::OneHour => 60,
Self::TwoHours => 120,
Self::ThreeHours => 180,
Self::FourHours => 240,
Self::OneDay => 1440,
}
}
pub const fn as_seconds(&self) -> u32 {
self.as_minutes() * 60
}
}
impl fmt::Display for OhlcResolution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OneMinute => write!(f, "1m"),
Self::ThreeMinutes => write!(f, "3m"),
Self::FiveMinutes => write!(f, "5m"),
Self::TenMinutes => write!(f, "10m"),
Self::FifteenMinutes => write!(f, "15m"),
Self::ThirtyMinutes => write!(f, "30m"),
Self::FortyFiveMinutes => write!(f, "45m"),
Self::OneHour => write!(f, "1h"),
Self::TwoHours => write!(f, "2h"),
Self::ThreeHours => write!(f, "3h"),
Self::FourHours => write!(f, "4h"),
Self::OneDay => write!(f, "1d"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct Period(u64);
impl Period {
pub const MIN: Self = Self(1);
pub const MAX: Self = Self(Lookback::MAX.num_minutes() as u64);
pub fn as_duration(&self, resolution: OhlcResolution) -> Duration {
Duration::minutes(self.0 as i64 * resolution.as_minutes() as i64)
}
pub const fn as_u64(&self) -> u64 {
self.0
}
pub const fn as_usize(&self) -> usize {
self.0 as usize
}
pub const fn as_i64(&self) -> i64 {
self.0 as i64
}
pub const fn as_f64(&self) -> f64 {
self.0 as f64
}
}
impl TryFrom<u8> for Period {
type Error = PeriodValidationError;
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
Self::try_from(value as u64)
}
}
impl TryFrom<u16> for Period {
type Error = PeriodValidationError;
fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
Self::try_from(value as u64)
}
}
impl TryFrom<u32> for Period {
type Error = PeriodValidationError;
fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
Self::try_from(value as u64)
}
}
impl TryFrom<u64> for Period {
type Error = PeriodValidationError;
fn try_from(value: u64) -> std::result::Result<Self, Self::Error> {
if value < Self::MIN.0 {
return Err(PeriodValidationError::TooShort { value });
}
if value > Self::MAX.0 {
return Err(PeriodValidationError::TooLong { value });
}
Ok(Self(value))
}
}
impl TryFrom<i8> for Period {
type Error = PeriodValidationError;
fn try_from(value: i8) -> std::result::Result<Self, Self::Error> {
Self::try_from(value.max(0) as u64)
}
}
impl TryFrom<i16> for Period {
type Error = PeriodValidationError;
fn try_from(value: i16) -> std::result::Result<Self, Self::Error> {
Self::try_from(value.max(0) as u64)
}
}
impl TryFrom<i32> for Period {
type Error = PeriodValidationError;
fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
Self::try_from(value.max(0) as u64)
}
}
impl TryFrom<i64> for Period {
type Error = PeriodValidationError;
fn try_from(value: i64) -> std::result::Result<Self, Self::Error> {
Self::try_from(value.max(0) as u64)
}
}
impl TryFrom<usize> for Period {
type Error = PeriodValidationError;
fn try_from(value: usize) -> std::result::Result<Self, Self::Error> {
Self::try_from(value as u64)
}
}
impl TryFrom<isize> for Period {
type Error = PeriodValidationError;
fn try_from(value: isize) -> std::result::Result<Self, Self::Error> {
Self::try_from(value.max(0) as u64)
}
}
impl fmt::Display for Period {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct MinIterationInterval(Duration);
impl MinIterationInterval {
pub const MIN: Self = Self(Duration::seconds(5));
pub const MAX: Self = Self(Duration::hours(1));
pub fn seconds(secs: u64) -> Result<Self, MinIterationIntervalValidationError> {
Self::try_from(Duration::seconds(secs as i64))
}
pub fn minutes(mins: u64) -> Result<Self, MinIterationIntervalValidationError> {
Self::try_from(Duration::minutes(mins as i64))
}
pub fn as_duration(&self) -> Duration {
self.0
}
}
impl TryFrom<Duration> for MinIterationInterval {
type Error = MinIterationIntervalValidationError;
fn try_from(value: Duration) -> Result<Self, Self::Error> {
if value < Self::MIN.0 {
return Err(MinIterationIntervalValidationError::TooShort { value });
}
if value > Self::MAX.0 {
return Err(MinIterationIntervalValidationError::TooLong { value });
}
Ok(Self(value))
}
}
impl fmt::Display for MinIterationInterval {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Lookback {
resolution: OhlcResolution,
period: Period,
}
impl Lookback {
pub const MAX: Duration = Duration::days(500);
pub fn new<P>(resolution: OhlcResolution, period: P) -> Result<Self, LookbackValidationError>
where
P: TryInto<Period>,
P::Error: Into<LookbackValidationError>,
{
let period = period.try_into().map_err(Into::into)?;
let duration = period.as_duration(resolution);
if duration > Self::MAX {
return Err(LookbackValidationError::ExceedsMaxLookback { duration });
}
Ok(Self { resolution, period })
}
pub fn resolution(&self) -> OhlcResolution {
self.resolution
}
pub fn period(&self) -> Period {
self.period
}
pub fn as_duration(&self) -> Duration {
self.period.as_duration(self.resolution)
}
}
impl Default for Lookback {
fn default() -> Self {
Self {
resolution: OhlcResolution::FiveMinutes,
period: Period(20),
}
}
}
impl fmt::Display for Lookback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} x {}", self.period, self.resolution)
}
}