greed 0.5.2

A rust tool to automate trades 📈
Documentation
use crate::config::tactic::rule::RuleConfig;
use crate::config::tactic::TacticConfig;
use crate::lowercase_enum_display;
use crate::tactic::null::NullRule;
use crate::tactic::r#do::do_factory::DoFactory;
use crate::tactic::r#do::{DoResult, DoRule};
use crate::tactic::r#for::for_factory::ForFactory;
use crate::tactic::r#for::ForRule;
use crate::tactic::skip::SkipReason;
use crate::tactic::state::TacticState;
use crate::tactic::when::when_factory::WhenFactory;
use crate::tactic::when::WhenRule;

pub struct RuleSet {
    for_rule: Box<dyn ForRule>,
    when_rule: Box<dyn WhenRule>,
    do_rule: Box<dyn DoRule>,
}

impl Default for RuleSet {
    fn default() -> Self {
        Self {
            for_rule: Box::new(NullRule),
            when_rule: Box::new(NullRule),
            do_rule: Box::new(NullRule),
        }
    }
}

impl RuleSet {
    pub fn from_config(rule_config: RuleConfig) -> Self {
        Self {
            for_rule: ForFactory::create_rule(rule_config.for_config),
            when_rule: WhenFactory::create_rule(rule_config.when_config),
            do_rule: DoFactory::create_rule(rule_config.do_config),
        }
    }

    pub fn evaluate(&self, state: &TacticState) -> DoResult {
        let for_result = self.for_rule.evaluate(state);
        if for_result.is_empty() {
            return DoResult::skip(SkipReason::NoTargetAssets);
        }

        let when_result = self.when_rule.evaluate(state, for_result);
        if !when_result.conditions_satisfied {
            return DoResult::skip(SkipReason::ConditionsUnsatisfied);
        }

        self.do_rule.evaluate(state, when_result)
    }
}

pub struct TacticRuleset {
    pub buy: RuleSet,
    pub sell: RuleSet,
}

impl TacticRuleset {
    pub fn from_config(tactic_config: TacticConfig) -> Self {
        Self {
            buy: RuleSet::from_config(tactic_config.buy),
            sell: RuleSet::from_config(tactic_config.sell),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum RuleType {
    Buy,
    Sell,
}

lowercase_enum_display!(RuleType);

#[cfg(test)]
mod tests {
    use crate::tactic::r#do::do_sellall::DoSellAllRule;
    use crate::tactic::r#for::for_stock::ForStockRule;
    use crate::tactic::when::when_always::WhenAlwaysRule;

    use super::*;

    #[test]
    fn evaluate_skip_no_assets() {
        let rule_set = RuleSet::default();
        let state = TacticState::default();
        let result = rule_set.evaluate(&state);
        assert_eq!(DoResult::skip(SkipReason::NoTargetAssets), result);
    }

    #[test]
    fn evaluate_skip_conditions_unsatisfied() {
        let rule_set = RuleSet {
            for_rule: ForStockRule::boxed("SPY"),
            ..Default::default()
        };
        let state = TacticState::default();
        let result = rule_set.evaluate(&state);
        assert_eq!(DoResult::skip(SkipReason::ConditionsUnsatisfied), result);
    }

    #[test]
    fn evaluate_successful() {
        let rule_set = RuleSet {
            for_rule: ForStockRule::boxed("SPY"),
            when_rule: WhenAlwaysRule::boxed(),
            do_rule: DoSellAllRule::boxed(),
        };
        let state = TacticState::default();
        let result = rule_set.evaluate(&state);
        let expected = DoResult {
            actions: vec![],
            skipped: true,
            skip_reason: SkipReason::NoTargetAssets,
        };
        assert_eq!(expected, result);
    }
}