use anyhow::Error;
use chrono::{DateTime, Utc};
use std::{
future::Future,
pin::Pin,
sync::{Arc, atomic::Ordering},
time::Duration,
};
use tokio::{
sync::{Notify, watch::Receiver},
task::JoinHandle,
time::sleep,
};
use crate::{
clock::{Clock, ClockFactory, ClockRemote},
core::{
Core, CoreResult,
market::{Market, MarketFactory},
},
strategy::{AtomicState, ErrorInStrat, State, StratStat, Strategy, StrategyFactory},
};
const KILL_TIMEOUT: Duration = Duration::from_secs(5);
pub struct App<Cl: Clock> {
name: String,
handle: Option<JoinHandle<()>>,
remote: Option<Cl::Remote>,
clock: Cl,
strategy_state: Arc<AtomicState>,
strategy_notify: Arc<Notify>,
strategy_channel: Receiver<Result<StratStat, ErrorInStrat>>,
}
impl<Cl: Clock> App<Cl> {
pub async fn new<
ClockF: ClockFactory<_Clock = Cl>,
M: Market<_Clock = Cl>,
MF: MarketFactory<_Market = M>,
S: Strategy<_Market = M, _Clock = Cl>,
SF: StrategyFactory<_Strategy = S>,
>(
name: String,
market_factory: MF,
strategy_factory: SF,
clock_factory: ClockF,
) -> CoreResult<Self> {
let (remote, clock) = clock_factory.build()?;
let core = Core::new(name.clone(), market_factory, clock.clone()).await?;
let mut strategy = strategy_factory.with_core(core).build();
let strategy_state = strategy.state().clone();
let strategy_notify = strategy.notify().clone();
let strategy_channel = strategy.stat_channel();
let handle = Some(tokio::spawn(async move {
tracing::debug!("Strategy created");
strategy.run().await;
tracing::debug!("Strategy is over");
}));
Ok(Self {
name,
handle,
remote,
clock,
strategy_state,
strategy_notify,
strategy_channel,
})
}
pub fn start(&self) {
if self.handle.is_none() {
tracing::warn!("App is not available");
return;
}
self.send(State::Starting);
}
pub fn send(&self, state: State) {
self.strategy_state.store(state, Ordering::Relaxed);
self.strategy_notify.notify_waiters();
tracing::debug!("New state sent : {}", state);
}
pub async fn terminate(&mut self) -> Result<StratStat, Error> {
if self.handle.is_none() {
return Err(Error::msg("App is not running"));
}
self.send(State::Terminated);
self.wait_for_the_bot().await;
if let Err(err) = self.handle.take().unwrap().await {
tracing::error!("Failed to stop the app properly: {}", err);
}
self.strategy_channel.changed().await?;
let stat = self.strategy_channel.borrow_and_update().clone()?;
Ok(stat)
}
pub async fn kill(&mut self) {
if self.handle.is_none() {
tracing::warn!("App is not running");
return;
}
self.send(State::Killed);
sleep(KILL_TIMEOUT).await;
self.handle.take().unwrap().abort();
}
pub async fn get_stats(&mut self) -> Result<StratStat, Error> {
self.send(State::Stats);
self.strategy_channel.changed().await?;
let stat = self.strategy_channel.borrow_and_update().clone()?;
Ok(stat)
}
pub fn advance_toward(&self, date: DateTime<Utc>) -> bool {
match self.remote.as_ref() {
Some(remote) => {
remote.advance_toward(date);
true
}
None => false,
}
}
pub fn wait_for_the_bot(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
if let Some(remote) = self.remote.as_ref() {
remote.wait_for_the_clock().await;
}
})
}
pub fn get_time(&self) -> DateTime<Utc> {
self.clock.now()
}
pub fn get_name(&self) -> &String {
&self.name
}
}