greed 0.5.2

A rust tool to automate trades 📈
Documentation
use crate::asset::AssetSymbol;
use crate::config::strategy::StrategyConfig;
use crate::config::tactic::TacticConfig;
use crate::error::GreedError;
use crate::platform::FinancialPlatform;
use crate::strategy::provider::StrategyRunnerProvider;
use crate::strategy::reader::read_tactics_from_config;
use crate::strategy::runner::StrategyRunner;
use crate::tactic::TacticRunner;
use async_trait::async_trait;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;

pub struct ConfigStrategyProvider {
    loop_interval: Duration,
    platform: Arc<dyn FinancialPlatform>,
    strategy_config: StrategyConfig,
    tactic_configs: Vec<TacticConfig>,
}

impl ConfigStrategyProvider {
    pub async fn new(
        config_path: PathBuf,
        loop_interval: Duration,
        platform: Arc<dyn FinancialPlatform>,
        strategy_config: StrategyConfig,
    ) -> Result<Self, GreedError> {
        let tactic_configs = read_tactics_from_config(&config_path, &strategy_config).await?;
        Ok(Self {
            loop_interval,
            platform,
            strategy_config,
            tactic_configs,
        })
    }
}

#[async_trait]
impl StrategyRunnerProvider for ConfigStrategyProvider {
    async fn provide_strategy_runner(&self) -> Result<StrategyRunner, GreedError> {
        let tactic_runners = self
            .tactic_configs
            .iter()
            .map(|tactic_config| {
                TacticRunner::new(
                    tactic_config.clone(),
                    self.platform.clone(),
                    self.strategy_config.properties(),
                )
            })
            .collect();
        let strategy_runner = StrategyRunner::new(
            self.loop_interval,
            self.strategy_config.properties().clone(),
            tactic_runners,
        );
        Ok(strategy_runner)
    }

    fn config_assets(&self) -> Vec<AssetSymbol> {
        self.tactic_configs
            .iter()
            .flat_map(|tactic_config| tactic_config.assets())
            .collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::fixture;
    use crate::platform::noop::NoOpPlatform;

    #[tokio::test]
    async fn provide_strategy_runner_no_tactics() {
        let provider = provider_for_config("config_minimal.toml").await;
        let strategy_runner = provider
            .provide_strategy_runner()
            .await
            .expect("should return runner");
        assert_eq!(strategy_runner.tactic_runner_count(), 0);
    }

    #[tokio::test]
    async fn provide_strategy_runner_with_tactics() {
        let provider = provider_for_config("config_multi_tactic.toml").await;
        let strategy_runner = provider
            .provide_strategy_runner()
            .await
            .expect("should return runner");
        assert_ne!(strategy_runner.tactic_runner_count(), 0);
    }

    #[tokio::test]
    async fn config_assets() {
        let provider = provider_for_config("config_multi_tactic.toml").await;
        let assets = provider.config_assets();
        assert_eq!(assets.len(), 2);
        assert!(assets.contains(&AssetSymbol::new("VTI")));
        assert!(assets.contains(&AssetSymbol::new("UVXY")));
    }

    async fn provider_for_config(config_name: &str) -> ConfigStrategyProvider {
        let strategy_config = StrategyConfig::LocalFile {
            path: config_name.to_string(),
            properties: Default::default(),
        };
        ConfigStrategyProvider::new(
            fixture::path(config_name),
            Duration::from_secs(1),
            NoOpPlatform::arc(),
            strategy_config,
        )
        .await
        .expect("should create provider")
    }
}