finql 0.12.0

A quantitative finance toolbox
Documentation
use std::str::FromStr;

use crate::datatypes::{
    date_time_helper::{
        date_from_str, date_time_from_str_standard, naive_date_to_date_time, unix_to_date_time,
    },
    CashFlow, Currency, Quote, Ticker,
};
use async_trait::async_trait;
use chrono::{DateTime, Local};
use eodhistoricaldata_api as eod_api;

use super::{MarketQuoteError, MarketQuoteProvider};

pub struct EODHistData {
    connector: eod_api::EodHistConnector,
}

impl EODHistData {
    pub fn new(token: String) -> EODHistData {
        EODHistData {
            connector: eod_api::EodHistConnector::new(token),
        }
    }
}

#[async_trait]
impl MarketQuoteProvider for EODHistData {
    /// Fetch latest quote
    async fn fetch_latest_quote(&self, ticker: &Ticker) -> Result<Quote, MarketQuoteError> {
        let eod_quote = self.connector.get_latest_quote(&ticker.name).await?;

        let time = unix_to_date_time(eod_quote.timestamp as u64);
        Ok(Quote {
            id: None,
            ticker: ticker.id.unwrap(),
            price: eod_quote.close,
            time,
            volume: Some(eod_quote.volume as f64),
        })
    }

    /// Fetch historic quotes between start and end date
    async fn fetch_quote_history(
        &self,
        ticker: &Ticker,
        start: DateTime<Local>,
        end: DateTime<Local>,
    ) -> Result<Vec<Quote>, MarketQuoteError> {
        let eod_quotes = self
            .connector
            .get_quote_history(
                &ticker.name,
                start.naive_local().date(),
                end.naive_local().date(),
            )
            .await?;

        let mut quotes = Vec::new();
        for quote in &eod_quotes {
            let time = date_time_from_str_standard(&quote.date, 18, ticker.tz.clone())?;
            let volume = quote.volume.map(|vol| vol as f64);
            if let Some(price) = quote.close {
                quotes.push(Quote {
                    id: None,
                    ticker: ticker.id.unwrap(),
                    price,
                    time,
                    volume,
                })
            }
        }
        Ok(quotes)
    }

    /// Fetch historic dividend payments between start and end date
    async fn fetch_dividend_history(
        &self,
        ticker: &Ticker,
        start: DateTime<Local>,
        end: DateTime<Local>,
    ) -> Result<Vec<CashFlow>, MarketQuoteError> {
        let dividends_since_start = self
            .connector
            .get_dividend_history(&ticker.name, start.naive_local().date())
            .await?;
        let mut div_cash_flows = Vec::new();
        for div in dividends_since_start {
            let pay_date = date_from_str(&div.payment_date, "%Y-%m-%d")?;
            if naive_date_to_date_time(&pay_date, 18, ticker.tz.clone())? <= end {
                let currency = Currency::from_str(&div.currency)?;
                div_cash_flows.push(CashFlow::new(div.value, currency, pay_date));
            }
        }
        Ok(div_cash_flows)
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use chrono::TimeZone;

    use crate::datatypes::Currency;

    use super::*;
    use crate::market_quotes::MarketDataSource;

    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
    async fn test_eod_fetch_quote() {
        let token = "OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX".to_string();
        let eod = EODHistData::new(token);
        let ticker = Ticker {
            id: Some(1),
            asset: 1,
            name: "AAPL".to_string(),
            currency: Currency::from_str("USD").unwrap(),
            source: MarketDataSource::EodHistData.to_string(),
            priority: 1,
            factor: 1.0,
            tz: None,
            cal: None,
        };
        let quote = eod.fetch_latest_quote(&ticker).await.unwrap();
        assert!(quote.price != 0.0);
    }

    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
    async fn test_eod_fetch_history() {
        let token = "OeAFFmMliFG5orCUuwAKQ8l4WWFQ67YX".to_string();
        let eod = EODHistData::new(token.to_string());
        let ticker = Ticker {
            id: Some(1),
            asset: 1,
            name: "AAPL".to_string(),
            currency: Currency::from_str("USD").unwrap(),
            source: MarketDataSource::EodHistData.to_string(),
            priority: 1,
            factor: 1.0,
            tz: None,
            cal: None,
        };
        let start = Local.ymd(2020, 1, 1).and_hms_milli(0, 0, 0, 0);
        let end = Local.ymd(2020, 1, 31).and_hms_milli(23, 59, 59, 999);
        let quotes = eod.fetch_quote_history(&ticker, start, end).await.unwrap();
        assert_eq!(quotes.len(), 21);
        assert!(quotes[0].price != 0.0);
    }
}