Skip to main content

barter/engine/state/
builder.rs

1use crate::engine::state::{
2    EngineState, asset::generate_empty_indexed_asset_states,
3    connectivity::generate_empty_indexed_connectivity_states,
4    instrument::generate_indexed_instrument_states, order::Orders, position::PositionManager,
5    trading::TradingState,
6};
7use barter_execution::balance::{AssetBalance, Balance};
8use barter_instrument::{
9    Keyed,
10    asset::{AssetIndex, ExchangeAsset, name::AssetNameInternal},
11    exchange::{ExchangeId, ExchangeIndex},
12    index::IndexedInstruments,
13    instrument::{Instrument, InstrumentIndex},
14};
15use barter_integration::collection::snapshot::Snapshot;
16use chrono::{DateTime, Utc};
17use fnv::FnvHashMap;
18use tracing::debug;
19
20/// Builder utility for an [`EngineState`] instance.
21#[derive(Debug, Clone)]
22pub struct EngineStateBuilder<'a, GlobalData, FnInstrumentData> {
23    instruments: &'a IndexedInstruments,
24    trading_state: Option<TradingState>,
25    time_engine_start: Option<DateTime<Utc>>,
26    global: GlobalData,
27    balances: FnvHashMap<ExchangeAsset<AssetNameInternal>, Balance>,
28    instrument_data_init: FnInstrumentData,
29}
30
31impl<'a, GlobalData, FnInstrumentData> EngineStateBuilder<'a, GlobalData, FnInstrumentData> {
32    /// Construct a new `EngineStateBuilder` with a layout derived from [`IndexedInstruments`].
33    ///
34    /// Note that the rest of the [`EngineState`] data can be generated from defaults if that
35    /// is all that is needed.
36    ///
37    /// Note that `ConnectivityStates` will be generated with
38    /// [`generate_empty_indexed_connectivity_states`], defaulting to `Health::Reconnecting`.
39    pub fn new(
40        instruments: &'a IndexedInstruments,
41        global: GlobalData,
42        instrument_data_init: FnInstrumentData,
43    ) -> Self {
44        Self {
45            instruments,
46            time_engine_start: None,
47            trading_state: None,
48            global,
49            balances: FnvHashMap::default(),
50            instrument_data_init,
51        }
52    }
53
54    /// Optionally provide the initial `TradingState`.
55    ///
56    /// Defaults to `TradingState::Disabled`.
57    pub fn trading_state(self, value: TradingState) -> Self {
58        Self {
59            trading_state: Some(value),
60            ..self
61        }
62    }
63
64    /// Optionally provide the `time_engine_start`.
65    ///
66    /// Providing this is useful for back-test scenarios where the time should be seeded with a
67    /// "historical" clock time (eg/ from first historical `MarketEvent`).
68    ///
69    /// Defaults to `Utc::now`
70    pub fn time_engine_start(self, value: DateTime<Utc>) -> Self {
71        Self {
72            time_engine_start: Some(value),
73            ..self
74        }
75    }
76
77    /// Optionally provide initial exchange asset `Balance`s.
78    ///
79    /// Useful for back-test scenarios where seeding EngineState with initial `Balance`s is
80    /// required.
81    ///
82    /// Note the internal implementation uses a `HashMap`, so duplicate
83    /// `ExchangeAsset<AssetNameInternal>` keys are overwritten.
84    pub fn balances<BalanceIter, KeyedBalance>(mut self, balances: BalanceIter) -> Self
85    where
86        BalanceIter: IntoIterator<Item = KeyedBalance>,
87        KeyedBalance: Into<Keyed<ExchangeAsset<AssetNameInternal>, Balance>>,
88    {
89        self.balances.extend(balances.into_iter().map(|keyed| {
90            let Keyed { key, value } = keyed.into();
91
92            (key, value)
93        }));
94        self
95    }
96
97    /// Use the builder data to generate the associated [`EngineState`].
98    ///
99    /// If optional data is not provided (eg/ Balances), default values are used (eg/ zero Balance).
100    pub fn build<InstrumentData>(self) -> EngineState<GlobalData, InstrumentData>
101    where
102        FnInstrumentData: Fn(
103            &'a Keyed<InstrumentIndex, Instrument<Keyed<ExchangeIndex, ExchangeId>, AssetIndex>>,
104        ) -> InstrumentData,
105    {
106        let Self {
107            instruments,
108            time_engine_start,
109            trading_state,
110            global,
111            balances,
112            instrument_data_init,
113        } = self;
114
115        // Default if not provided
116        let time_engine_start = time_engine_start.unwrap_or_else(|| {
117            debug!("EngineStateBuilder using Utc::now as time_engine_start default");
118            Utc::now()
119        });
120        let trading = trading_state.unwrap_or_default();
121
122        // Construct empty ConnectivityStates
123        let connectivity = generate_empty_indexed_connectivity_states(instruments);
124
125        // Update empty AssetStates from provided exchange asset Balances
126        let mut assets = generate_empty_indexed_asset_states(instruments);
127        for (key, balance) in balances {
128            assets
129                .asset_mut(&key)
130                .update_from_balance(Snapshot(&AssetBalance {
131                    asset: key.asset,
132                    balance,
133                    time_exchange: time_engine_start,
134                }))
135        }
136
137        // Generate empty InstrumentStates using provided FnInstrumentData etc.
138        let instruments = generate_indexed_instrument_states(
139            instruments,
140            time_engine_start,
141            PositionManager::default,
142            Orders::default,
143            instrument_data_init,
144        );
145
146        EngineState {
147            trading,
148            global,
149            connectivity,
150            assets,
151            instruments,
152        }
153    }
154}