greed 0.5.2

A rust tool to automate trades 📈
Documentation
use crate::config::platform::PlatformType;
use crate::config::reader::read_config;
use crate::config::strategy::StrategyConfig;
use crate::config::tactic::TacticConfig;
use crate::error::GreedError;
use serde::{Deserialize, Serialize};
use std::path::Path;

pub mod platform;
pub mod quote_fetcher_config;
pub mod reader;
pub mod simple;
pub mod strategy;
pub mod tactic;

#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Config {
    #[serde(default)]
    pub platform: PlatformType,
    #[serde(default)]
    pub strategies: Vec<StrategyConfig>,
    #[serde(default)]
    pub tactics: Vec<TacticConfig>,
    #[serde(default = "default_interval")]
    pub interval: u64,
}

fn default_interval() -> u64 {
    60
}

impl Config {
    pub async fn from_path<P: AsRef<Path>>(path: P) -> Result<Config, GreedError> {
        read_config(path).await
    }
}

#[cfg(test)]
mod test {
    use crate::config::platform::PlatformType;
    use crate::config::strategy::{StrategyConfig, StrategyProperties};
    use crate::config::tactic::median::MedianPeriod;
    use crate::config::tactic::r#do::DoConfig;
    use crate::config::tactic::r#for::ForConfig;
    use crate::config::tactic::rule::RuleConfig;
    use crate::config::tactic::when::WhenConfig;
    use crate::config::tactic::TacticConfig;
    use crate::config::Config;
    use crate::fixture;

    #[test]
    fn default() {
        let default = Config::default();
        let expected = Config {
            platform: PlatformType::Alpaca,
            strategies: vec![],
            tactics: vec![],
            interval: 0,
        };

        assert_eq!(default, expected)
    }

    #[tokio::test]
    async fn deserialize_minimal_config() {
        let config = fixture::config("config_minimal.toml").await;
        let expected = Config {
            platform: PlatformType::Alpaca,
            strategies: vec![],
            tactics: vec![],
            interval: 60,
        };
        assert_eq!(expected, config)
    }

    #[tokio::test]
    async fn deserialize_single_tactic() {
        let config = fixture::config("config_single_tactic.toml").await;
        let expected = Config {
            platform: PlatformType::Alpaca,
            strategies: vec![],
            tactics: vec![TacticConfig {
                name: "ETF".to_string(),
                buy: RuleConfig {
                    for_config: ForConfig::Stock {
                        stock: "VTI".into(),
                    },
                    when_config: WhenConfig::BelowMedian {
                        below_median_percent: 5.0,
                        median_period: Default::default(),
                    },
                    do_config: DoConfig::Buy { buy_percent: 10.0 },
                },
                sell: RuleConfig {
                    for_config: ForConfig::Stock {
                        stock: "VTI".into(),
                    },
                    when_config: WhenConfig::GainAbove {
                        gain_above_percent: 5.0,
                    },
                    do_config: DoConfig::SellAll { sell_all: true },
                },
            }],
            interval: 300,
        };
        assert_eq!(expected, config)
    }

    #[tokio::test]
    async fn deserialize_multi_tactic_config() {
        let config = fixture::config("config_multi_tactic.toml").await;
        let expected = Config {
            platform: PlatformType::Alpaca,
            strategies: vec![],
            tactics: vec![
                TacticConfig {
                    name: "ETF".to_string(),
                    buy: RuleConfig {
                        for_config: ForConfig::Stock {
                            stock: "VTI".into(),
                        },
                        when_config: WhenConfig::BelowMedian {
                            below_median_percent: 5.0,
                            median_period: Default::default(),
                        },
                        do_config: DoConfig::Buy { buy_percent: 10.0 },
                    },
                    sell: RuleConfig {
                        for_config: ForConfig::Stock {
                            stock: "VTI".into(),
                        },
                        when_config: WhenConfig::GainAbove {
                            gain_above_percent: 5.0,
                        },
                        do_config: DoConfig::SellAll { sell_all: true },
                    },
                },
                TacticConfig {
                    name: "Chaos".to_string(),
                    buy: RuleConfig {
                        for_config: ForConfig::Stock {
                            stock: "UVXY".into(),
                        },
                        when_config: WhenConfig::BelowMedian {
                            below_median_percent: 2.0,
                            median_period: MedianPeriod::Week,
                        },
                        do_config: DoConfig::Buy { buy_percent: 5.0 },
                    },
                    sell: RuleConfig {
                        for_config: ForConfig::Stock {
                            stock: "UVXY".into(),
                        },
                        when_config: WhenConfig::GainAbove {
                            gain_above_percent: 3.0,
                        },
                        do_config: DoConfig::SellAll { sell_all: true },
                    },
                },
            ],
            interval: 300,
        };
        assert_eq!(expected, config)
    }

    #[tokio::test]
    async fn deserialize_strategic_config() {
        let config = fixture::config("config_strategic.toml").await;
        let expected = Config {
            platform: PlatformType::Alpaca,
            strategies: vec![
                StrategyConfig::LocalFile {
                    path: "config_minimal.toml".to_string(),
                    properties: StrategyProperties {
                        name: "Minimal".to_string(),
                        portfolio_percent: 100.0,
                    },
                },
                StrategyConfig::LocalFile {
                    path: "config_multi_tactic.toml".to_string(),
                    properties: StrategyProperties {
                        name: "Multi Tactic".to_string(),
                        portfolio_percent: 75.0,
                    },
                },
                StrategyConfig::LocalFile {
                    path: "simple_config_minimal.csv".to_string(),
                    properties: StrategyProperties {
                        name: "CSV".to_string(),
                        portfolio_percent: 25.0,
                    },
                },
            ],
            tactics: vec![TacticConfig {
                name: "Spy".to_string(),
                buy: RuleConfig {
                    for_config: ForConfig::Stock {
                        stock: "SPY".into(),
                    },
                    when_config: WhenConfig::BelowMedian {
                        below_median_percent: 5.0,
                        median_period: Default::default(),
                    },
                    do_config: DoConfig::Buy { buy_percent: 10.0 },
                },
                sell: RuleConfig {
                    for_config: ForConfig::Stock {
                        stock: "SPY".into(),
                    },
                    when_config: WhenConfig::GainAbove {
                        gain_above_percent: 5.0,
                    },
                    do_config: DoConfig::SellAll { sell_all: true },
                },
            }],
            interval: 5,
        };
        assert_eq!(config, expected)
    }
}