1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use crate::engine::state::{
EngineState,
asset::generate_empty_indexed_asset_states,
connectivity::generate_empty_indexed_connectivity_states,
instrument::generate_indexed_instrument_states,
order::Orders,
position::{OmsMode, PositionManager},
trading::TradingState,
};
use chrono::{DateTime, Utc};
use fnv::FnvHashMap;
use rustrade_execution::balance::{AssetBalance, Balance};
use rustrade_instrument::{
Keyed,
asset::{AssetIndex, ExchangeAsset, name::AssetNameInternal},
exchange::{ExchangeId, ExchangeIndex},
index::IndexedInstruments,
instrument::{Instrument, InstrumentIndex},
};
use rustrade_integration::collection::snapshot::Snapshot;
use tracing::debug;
/// Builder utility for an [`EngineState`] instance.
#[derive(Debug, Clone)]
pub struct EngineStateBuilder<'a, GlobalData, FnInstrumentData> {
instruments: &'a IndexedInstruments,
trading_state: Option<TradingState>,
time_engine_start: Option<DateTime<Utc>>,
global: GlobalData,
balances: FnvHashMap<ExchangeAsset<AssetNameInternal>, Balance>,
instrument_data_init: FnInstrumentData,
/// OMS mode applied to every instrument's [`PositionManager`] at construction.
///
/// Defaults to [`OmsMode::Netting`]. Use [`OmsMode::Hedging`] for strategies that hold
/// simultaneous long and short positions on the same instrument (e.g. options writing).
oms_mode: OmsMode,
}
impl<'a, GlobalData, FnInstrumentData> EngineStateBuilder<'a, GlobalData, FnInstrumentData> {
/// Construct a new `EngineStateBuilder` with a layout derived from [`IndexedInstruments`].
///
/// Note that the rest of the [`EngineState`] data can be generated from defaults if that
/// is all that is needed.
///
/// Note that `ConnectivityStates` will be generated with
/// [`generate_empty_indexed_connectivity_states`], defaulting to `Health::Reconnecting`.
pub fn new(
instruments: &'a IndexedInstruments,
global: GlobalData,
instrument_data_init: FnInstrumentData,
) -> Self {
Self {
instruments,
time_engine_start: None,
trading_state: None,
global,
balances: FnvHashMap::default(),
instrument_data_init,
oms_mode: OmsMode::Netting,
}
}
/// Optionally provide the initial `TradingState`.
///
/// Defaults to `TradingState::Disabled`.
pub fn trading_state(self, value: TradingState) -> Self {
Self {
trading_state: Some(value),
..self
}
}
/// Optionally provide the `time_engine_start`.
///
/// Providing this is useful for back-test scenarios where the time should be seeded with a
/// "historical" clock time (eg/ from first historical `MarketEvent`).
///
/// Defaults to `Utc::now`
pub fn time_engine_start(self, value: DateTime<Utc>) -> Self {
Self {
time_engine_start: Some(value),
..self
}
}
/// Optionally set the [`OmsMode`] for all instrument [`PositionManager`]s.
///
/// Defaults to [`OmsMode::Netting`] (at most one position per instrument, backward-compatible).
/// Set to [`OmsMode::Hedging`] for strategies that simultaneously hold long and short
/// positions on the same instrument (e.g. options writing alongside long positions).
///
/// # Note — `OmsMode::Hedging` and non-option instruments
///
/// `OmsMode` is applied uniformly to all instruments. Hedging mode is intended for
/// instruments where multiple concurrent positions are semantically valid (e.g. individual
/// options legs). Applying it to spot or futures instruments will track each order's fills
/// as a separate position slot (keyed by order ID) rather than a single net position,
/// which is almost certainly not what you want for those asset classes.
pub fn oms_mode(self, mode: OmsMode) -> Self {
Self {
oms_mode: mode,
..self
}
}
/// Optionally provide initial exchange asset `Balance`s.
///
/// Useful for back-test scenarios where seeding EngineState with initial `Balance`s is
/// required.
///
/// Note the internal implementation uses a `HashMap`, so duplicate
/// `ExchangeAsset<AssetNameInternal>` keys are overwritten.
pub fn balances<BalanceIter, KeyedBalance>(mut self, balances: BalanceIter) -> Self
where
BalanceIter: IntoIterator<Item = KeyedBalance>,
KeyedBalance: Into<Keyed<ExchangeAsset<AssetNameInternal>, Balance>>,
{
self.balances.extend(balances.into_iter().map(|keyed| {
let Keyed { key, value } = keyed.into();
(key, value)
}));
self
}
/// Use the builder data to generate the associated [`EngineState`].
///
/// If optional data is not provided (eg/ Balances), default values are used (eg/ zero Balance).
pub fn build<InstrumentData>(self) -> EngineState<GlobalData, InstrumentData>
where
FnInstrumentData: Fn(
&'a Keyed<InstrumentIndex, Instrument<Keyed<ExchangeIndex, ExchangeId>, AssetIndex>>,
) -> InstrumentData,
{
let Self {
instruments,
time_engine_start,
trading_state,
global,
balances,
instrument_data_init,
oms_mode,
} = self;
// Default if not provided
let time_engine_start = time_engine_start.unwrap_or_else(|| {
debug!("EngineStateBuilder using Utc::now as time_engine_start default");
Utc::now()
});
let trading = trading_state.unwrap_or_default();
// Construct empty ConnectivityStates
let connectivity = generate_empty_indexed_connectivity_states(instruments);
// Update empty AssetStates from provided exchange asset Balances
let mut assets = generate_empty_indexed_asset_states(instruments);
for (key, balance) in balances {
assets
.asset_mut(&key)
.update_from_balance(Snapshot(&AssetBalance {
asset: key.asset,
balance,
time_exchange: time_engine_start,
}))
}
// Generate empty InstrumentStates using provided FnInstrumentData etc.
let instruments = generate_indexed_instrument_states(
instruments,
time_engine_start,
move || PositionManager::new(oms_mode),
Orders::default,
instrument_data_init,
);
EngineState {
trading,
global,
connectivity,
assets,
instruments,
}
}
}