scuriolus 0.3.0

Scuriolus is a modular trading bot platform.
Documentation
mod running_cheat_clock;
mod stable_cheat_clock;
mod true_clock;

use chrono::{DateTime, TimeDelta, Utc};
use std::{fmt, future::Future, time::Duration};
use tokio::{select, sync::Notify, time::sleep};

use crate::core::CoreResult;

pub use {
    running_cheat_clock::{RunningCheatClock, RunningCheatClockFactory, RunningCheatClockRemote},
    stable_cheat_clock::{StableCheatClock, StableCheatClockFactory, StableCheatClockRemote},
    true_clock::{TrueClock, TrueClockFactory},
};

/// Trait for something giving time with `now()`.
pub trait ClockBase: Send + Sync + fmt::Debug + 'static {
    const NAME: &'static str;
    fn now(&self) -> DateTime<Utc>;
}

/// A [`Clock`] is a tool giving time but also able to wait.
pub trait Clock: Send + Sync + fmt::Debug + ClockBase + Clone + 'static {
    type Remote: ClockRemote;
    /// Waits for the given duration.
    fn sleep(&self, delay: TimeDelta) -> impl Future<Output = ()> + Send;
    /// Waits until the given deadline.
    fn sleep_until(&self, deadline: DateTime<Utc>) -> impl Future<Output = ()> + Send;
    /// Catch up with the remote.
    fn synchronize(&self);
}

#[cfg(test)]
/// The refresh rate of the remote clock.
pub const REMOTE_REFRESH_RATE: Duration = Duration::from_millis(10);
#[cfg(not(test))]
/// The refresh rate of the remote clock.
pub const REMOTE_REFRESH_RATE: Duration = Duration::from_secs(1);

/// The refresh rate for clocks that might lost sync with their remote.
pub const SAFETY_REFRESH_DELAY: Duration = Duration::from_secs(120);

/// Across thread [`Clock`] controler
pub trait ClockRemote: ClockBase {
    #[cfg(test)]
    fn not_blocking(&self);

    fn remote_now(&self) -> DateTime<Utc>;
    fn advance_toward(&self, date: DateTime<Utc>);
    fn notify(&self) -> &Notify;
    fn is_sync(&self) -> bool;

    fn wait_for_the_clock(&self) -> impl std::future::Future<Output = ()> + Send {
        async {
            loop {
                if self.is_sync() {
                    break;
                }

                select! {
                    _ = sleep(REMOTE_REFRESH_RATE) => {},
                    _ = self.notify().notified() => {}
                }
            }
        }
    }
}

type ClockPackage<C> = (Option<<C as Clock>::Remote>, C);

/// Factory's trait for [`ClockRemote`] and [`Clock`].
pub trait ClockFactory {
    type _Clock: Clock;
    fn build(self) -> CoreResult<ClockPackage<Self::_Clock>>;
}

#[derive(Debug)]
pub struct NoRemote {
    _private: (),
}

impl ClockBase for NoRemote {
    fn now(&self) -> DateTime<Utc> {
        unreachable!()
    }

    const NAME: &'static str = "NoRemote";
}

impl ClockRemote for NoRemote {
    #[cfg(test)]
    fn not_blocking(&self) {
        unreachable!()
    }

    fn remote_now(&self) -> DateTime<Utc> {
        unreachable!()
    }

    fn advance_toward(&self, _: DateTime<Utc>) {
        unreachable!()
    }

    fn notify(&self) -> &Notify {
        unreachable!()
    }

    fn is_sync(&self) -> bool {
        unreachable!()
    }
}