scuriolus 0.3.0

Scuriolus is a modular trading bot platform.
Documentation
pub mod kline;
pub mod word_trend;

use chrono::{DateTime, Utc};
use derive_getters::Getters;
use serde::{Deserialize, Serialize};
use std::hash::Hash;
use std::{fmt, marker::PhantomData};

use crate::provider::interval::Interval;

/// A Data represent input information about to be used by a [`crate::strategy::Strategy`].
/// It is supposed to be fetched from a [`crate::provider::Source`].
pub trait Data:
    Serialize + for<'a> Deserialize<'a> + fmt::Debug + Clone + PartialEq + 'static + Sync + Send
{
    fn basics(&self) -> &DataBasics;
    fn mut_basics(&mut self) -> &mut DataBasics;
    fn set_basics(&mut self, basics: DataBasics) {
        *self.mut_basics() = basics;
    }
    type Specifier: DataSpecifier;
}

/// A [`DataSpecifier`] carry additional information about specific [Data].
pub trait DataSpecifier:
    fmt::Debug + PartialEq + Clone + Eq + Hash + 'static + Sync + Send
{
    fn name(&self) -> String;
}

/// A [`DataQuery`] is a request for data.
#[derive(Debug, Clone, Getters, PartialEq)]
pub struct DataQuery<D: Data> {
    specifier: D::Specifier,
    interval: Interval,
    begin: DateTime<Utc>,
    end: DateTime<Utc>,
    _marker: PhantomData<D>,
}

impl<D: Data> DataQuery<D> {
    pub fn new(
        specific: D::Specifier,
        interval: Interval,
        begin: DateTime<Utc>,
        end: DateTime<Utc>,
    ) -> Self {
        let (below_start, _) = interval.get_time_bounds(begin).unwrap();
        let (below_end, above_end) = interval.get_time_bounds(end).unwrap();

        let final_end = if below_end == end { end } else { above_end };

        Self {
            specifier: specific,
            interval,
            begin: below_start,
            end: final_end,
            _marker: PhantomData,
        }
    }
}

/// A [`DataBasics`] bring generic information to a [`Data`].
#[derive(Debug, Serialize, Deserialize, Getters, Clone, PartialEq, Eq, Hash, Default)]
pub struct DataBasics {
    pub begin: DateTime<Utc>,
    pub end: DateTime<Utc>,
    pub update_date: DateTime<Utc>,
    pub source: String,
}

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

    use crate::{
        core::{CoreError, CoreResult},
        provider::{
            DataBasics, Interval,
            data::{Data, DataSpecifier},
        },
    };

    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Default)]
    pub struct TestData {
        pub content: String,
        pub basics: DataBasics,
    }

    impl TestData {
        pub fn new(content: String, basics: DataBasics) -> Self {
            Self { content, basics }
        }
    }

    #[derive(Debug, Clone, PartialEq, Default, Eq, Hash)]
    pub struct TestDataSpecifier(pub String);

    impl DataSpecifier for TestDataSpecifier {
        fn name(&self) -> String {
            "test".to_string()
        }
    }

    impl Data for TestData {
        fn basics(&self) -> &DataBasics {
            &self.basics
        }

        fn mut_basics(&mut self) -> &mut DataBasics {
            &mut self.basics
        }

        type Specifier = TestDataSpecifier;
    }

    pub fn data_vec_over<D: Data>(
        interval: Interval,
        mut start: DateTime<Utc>,
        end: DateTime<Utc>,
        data: D,
        source: String,
    ) -> CoreResult<Vec<D>> {
        if interval > Interval::OneDay {
            return Err(CoreError::param_error("interval not implemented"));
        }

        let mut vec = vec![];

        loop {
            let next_end = start + interval.time_delta();

            if next_end > end {
                break;
            }

            let basics = DataBasics {
                begin: start,
                end: next_end,
                update_date: Utc::now(),
                source: source.clone(),
            };

            let mut new_data = data.clone();
            new_data.set_basics(basics);

            vec.push(new_data);
            start = next_end;
        }

        Ok(vec)
    }
}

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

    use crate::provider::{
        data::{
            DataQuery,
            test::{TestData, TestDataSpecifier},
        },
        interval::Interval,
    };

    #[test]
    fn create_query() {
        let query = DataQuery::<TestData>::new(
            TestDataSpecifier::default(),
            Interval::OneMinute,
            Utc.with_ymd_and_hms(2022, 1, 1, 0, 0, 59).unwrap(),
            Utc.with_ymd_and_hms(2022, 1, 1, 0, 1, 1).unwrap(),
        );

        assert_eq!(query.interval, Interval::OneMinute);
        assert_eq!(
            query.begin,
            Utc.with_ymd_and_hms(2022, 1, 1, 0, 0, 0).unwrap()
        );
        assert_eq!(
            query.end,
            Utc.with_ymd_and_hms(2022, 1, 1, 0, 2, 0).unwrap()
        );
    }
}