use crate::{
backtest::{
market_data::BacktestMarketData,
summary::{BacktestSummary, MultiBacktestSummary},
},
engine::{
Processor,
clock::HistoricalClock,
execution_tx::MultiExchangeTxMap,
state::{EngineState, instrument::data::InstrumentDataState},
},
error::BarterError,
risk::RiskManager,
statistic::time::TimeInterval,
strategy::{
algo::AlgoStrategy, close_positions::ClosePositionsStrategy,
on_disconnect::OnDisconnectStrategy, on_trading_disabled::OnTradingDisabled,
},
system::{builder::EngineFeedMode, config::ExecutionConfig},
};
use crate::{
engine::Engine,
execution::builder::{ExecutionBuild, ExecutionBuilder},
system::builder::{AuditMode, SystemBuild},
};
use barter_data::event::MarketEvent;
use barter_execution::AccountEvent;
use barter_instrument::{index::IndexedInstruments, instrument::InstrumentIndex};
use futures::future::try_join_all;
use rust_decimal::Decimal;
use smol_str::SmolStr;
use std::{fmt::Debug, sync::Arc};
pub mod market_data;
pub mod summary;
#[derive(Debug, Clone)]
pub struct BacktestArgsConstant<MarketData, SummaryInterval, State> {
pub instruments: IndexedInstruments,
pub executions: Vec<ExecutionConfig>,
pub market_data: MarketData,
pub summary_interval: SummaryInterval,
pub engine_state: State,
}
#[derive(Debug, Clone)]
pub struct BacktestArgsDynamic<Strategy, Risk> {
pub id: SmolStr,
pub risk_free_return: Decimal,
pub strategy: Strategy,
pub risk: Risk,
}
pub async fn run_backtests<
MarketData,
SummaryInterval,
Strategy,
Risk,
GlobalData,
InstrumentData,
>(
args_constant: Arc<
BacktestArgsConstant<MarketData, SummaryInterval, EngineState<GlobalData, InstrumentData>>,
>,
args_dynamic_iter: impl IntoIterator<Item = BacktestArgsDynamic<Strategy, Risk>>,
) -> Result<MultiBacktestSummary<SummaryInterval>, BarterError>
where
MarketData: BacktestMarketData<Kind = InstrumentData::MarketEventKind>,
SummaryInterval: TimeInterval,
Strategy: AlgoStrategy<State = EngineState<GlobalData, InstrumentData>>
+ ClosePositionsStrategy<State = EngineState<GlobalData, InstrumentData>>
+ OnTradingDisabled<
HistoricalClock,
EngineState<GlobalData, InstrumentData>,
MultiExchangeTxMap,
Risk,
> + OnDisconnectStrategy<
HistoricalClock,
EngineState<GlobalData, InstrumentData>,
MultiExchangeTxMap,
Risk,
> + Send
+ 'static,
<Strategy as OnTradingDisabled<
HistoricalClock,
EngineState<GlobalData, InstrumentData>,
MultiExchangeTxMap,
Risk,
>>::OnTradingDisabled: Debug + Clone + Send,
<Strategy as OnDisconnectStrategy<
HistoricalClock,
EngineState<GlobalData, InstrumentData>,
MultiExchangeTxMap,
Risk,
>>::OnDisconnect: Debug + Clone + Send,
Risk: RiskManager<State = EngineState<GlobalData, InstrumentData>> + Send + 'static,
GlobalData: for<'a> Processor<&'a MarketEvent<InstrumentIndex, InstrumentData::MarketEventKind>>
+ for<'a> Processor<&'a AccountEvent>
+ Debug
+ Clone
+ Default
+ Send
+ 'static,
InstrumentData: InstrumentDataState + Default + Send + 'static,
{
let time_start = std::time::Instant::now();
let backtest_futures = args_dynamic_iter
.into_iter()
.map(|args_dynamic| backtest(Arc::clone(&args_constant), args_dynamic));
let summaries = try_join_all(backtest_futures).await?;
Ok(MultiBacktestSummary::new(
std::time::Instant::now().duration_since(time_start),
summaries,
))
}
pub async fn backtest<MarketData, SummaryInterval, Strategy, Risk, GlobalData, InstrumentData>(
args_constant: Arc<
BacktestArgsConstant<MarketData, SummaryInterval, EngineState<GlobalData, InstrumentData>>,
>,
args_dynamic: BacktestArgsDynamic<Strategy, Risk>,
) -> Result<BacktestSummary<SummaryInterval>, BarterError>
where
MarketData: BacktestMarketData<Kind = InstrumentData::MarketEventKind>,
SummaryInterval: TimeInterval,
Strategy: AlgoStrategy<State = EngineState<GlobalData, InstrumentData>>
+ ClosePositionsStrategy<State = EngineState<GlobalData, InstrumentData>>
+ OnTradingDisabled<
HistoricalClock,
EngineState<GlobalData, InstrumentData>,
MultiExchangeTxMap,
Risk,
> + OnDisconnectStrategy<
HistoricalClock,
EngineState<GlobalData, InstrumentData>,
MultiExchangeTxMap,
Risk,
> + Send
+ 'static,
<Strategy as OnTradingDisabled<
HistoricalClock,
EngineState<GlobalData, InstrumentData>,
MultiExchangeTxMap,
Risk,
>>::OnTradingDisabled: Debug + Clone + Send,
<Strategy as OnDisconnectStrategy<
HistoricalClock,
EngineState<GlobalData, InstrumentData>,
MultiExchangeTxMap,
Risk,
>>::OnDisconnect: Debug + Clone + Send,
Risk: RiskManager<State = EngineState<GlobalData, InstrumentData>> + Send + 'static,
GlobalData: for<'a> Processor<&'a MarketEvent<InstrumentIndex, InstrumentData::MarketEventKind>>
+ for<'a> Processor<&'a AccountEvent>
+ Debug
+ Clone
+ Default
+ Send
+ 'static,
InstrumentData: InstrumentDataState + Send + 'static,
{
let clock = args_constant
.market_data
.time_first_event()
.await
.map(HistoricalClock::new)?;
let market_stream = args_constant.market_data.stream().await?;
let ExecutionBuild {
execution_tx_map,
account_channel,
futures,
} = args_constant
.executions
.clone()
.into_iter()
.try_fold(
ExecutionBuilder::new(&args_constant.instruments),
|builder, config| match config {
ExecutionConfig::Mock(mock_config) => builder.add_mock(mock_config, clock.clone()),
},
)?
.build();
let engine = Engine::new(
clock,
args_constant.engine_state.clone(),
execution_tx_map,
args_dynamic.strategy,
args_dynamic.risk,
);
let system = SystemBuild::new(
engine,
EngineFeedMode::Stream,
AuditMode::Disabled,
market_stream,
account_channel,
futures,
)
.init()
.await?;
let (engine, _shutdown_audit) = system.shutdown_after_backtest().await?;
let trading_summary = engine
.trading_summary_generator(args_dynamic.risk_free_return)
.generate(args_constant.summary_interval);
Ok(BacktestSummary {
id: args_dynamic.id,
risk_free_return: args_dynamic.risk_free_return,
trading_summary,
})
}