use crate::asset::AssetSymbol;
use crate::config::strategy::StrategyProperties;
use crate::config::Config;
use crate::platform::FinancialPlatform;
use crate::tactic::TacticRunner;
use async_trait::async_trait;
use duckdb::Connection;
use log::{info, warn};
#[cfg(test)]
use std::any::Any;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::time::sleep;
#[async_trait(?Send)]
pub trait StrategyRunner {
async fn run(&self, config_assets: &[AssetSymbol]);
#[cfg(test)]
fn as_any(&self) -> &dyn Any;
}
pub struct TacticStrategyRunner {
loop_interval: Duration,
strategy_properties: StrategyProperties,
tactic_runners: Vec<TacticRunner>,
}
impl TacticStrategyRunner {
pub fn new(
loop_interval: Duration,
strategy_properties: StrategyProperties,
tactic_runners: Vec<TacticRunner>,
) -> Self {
Self {
loop_interval,
strategy_properties,
tactic_runners,
}
}
pub fn from_config(
config: &Config,
platform: &Arc<dyn FinancialPlatform>,
connection: &Arc<Mutex<Connection>>,
) -> Self {
let loop_interval = Duration::from_secs(config.interval);
let tactic_runners = config
.tactics
.clone()
.into_iter()
.map(|tactic_config| {
TacticRunner::new(
tactic_config.clone(),
platform.clone(),
Default::default(),
connection.clone(),
)
})
.collect();
Self::new(loop_interval, Default::default(), tactic_runners)
}
#[cfg(test)]
pub fn tactic_runner_count(&self) -> usize {
self.tactic_runners.len()
}
}
#[async_trait(?Send)]
impl StrategyRunner for TacticStrategyRunner {
async fn run(&self, config_assets: &[AssetSymbol]) {
let name = self.strategy_properties.name.clone();
if !name.is_empty() {
info!("🚀 running strategy: {}", self.strategy_properties.name);
}
for tactic_runner in &self.tactic_runners {
let _ = tactic_runner
.run(config_assets)
.await
.inspect_err(|e| warn!("{e}"));
sleep(self.loop_interval).await;
}
}
#[cfg(test)]
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixture;
use crate::platform::noop::NoOpPlatform;
#[tokio::test]
async fn from_config_empty_tactics() {
let config = fixture::config("config_minimal.toml").await;
let platform = NoOpPlatform::arc();
let connection = fixture::db_connection();
let runner = TacticStrategyRunner::from_config(&config, &platform, &connection);
assert_eq!(runner.loop_interval, Duration::from_secs(60));
assert_eq!(runner.tactic_runner_count(), 0);
}
#[tokio::test]
async fn from_config_with_tactics() {
let config = fixture::config("config_single_tactic.toml").await;
let platform = NoOpPlatform::arc();
let connection = fixture::db_connection();
let runner = TacticStrategyRunner::from_config(&config, &platform, &connection);
assert_eq!(runner.loop_interval, Duration::from_secs(300));
assert_eq!(runner.tactic_runner_count(), 1);
}
}