barter/statistic/summary/
mod.rs

1use crate::{
2    engine::state::{asset::AssetStates, instrument::InstrumentStates, position::PositionExited},
3    statistic::{
4        summary::{
5            asset::{TearSheetAsset, TearSheetAssetGenerator},
6            instrument::{TearSheet, TearSheetGenerator},
7        },
8        time::TimeInterval,
9    },
10};
11use barter_execution::balance::AssetBalance;
12use barter_instrument::{
13    asset::{AssetIndex, ExchangeAsset, name::AssetNameInternal},
14    instrument::{InstrumentIndex, name::InstrumentNameInternal},
15};
16use barter_integration::{collection::FnvIndexMap, snapshot::Snapshot};
17use chrono::{DateTime, TimeDelta, Utc};
18use derive_more::Constructor;
19use rust_decimal::Decimal;
20use serde::{Deserialize, Serialize};
21
22pub mod asset;
23pub mod dataset;
24pub mod display;
25pub mod instrument;
26pub mod pnl;
27
28#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Constructor)]
29pub struct TradingSummary<Interval> {
30    /// Trading session start time defined by the [`Engine`](crate::engine::Engine) clock.
31    pub time_engine_start: DateTime<Utc>,
32
33    /// Trading session end time defined by the [`Engine`](crate::engine::Engine) clock.
34    pub time_engine_end: DateTime<Utc>,
35
36    /// Instrument [`TearSheet`]s.
37    ///
38    /// Note that an Instrument is unique to an exchange, so, for example, Binance btc_usdt_spot
39    /// and Okx btc_usdt_spot will be summarised by distinct [`TearSheet`]s.
40    pub instruments: FnvIndexMap<InstrumentNameInternal, TearSheet<Interval>>,
41
42    /// [`ExchangeAsset`] [`TearSheet`]s.
43    pub assets: FnvIndexMap<ExchangeAsset<AssetNameInternal>, TearSheetAsset>,
44}
45
46impl<Interval> TradingSummary<Interval> {
47    /// Duration of trading that the `TradingSummary` covers.
48    pub fn trading_duration(&self) -> TimeDelta {
49        self.time_engine_end
50            .signed_duration_since(self.time_engine_start)
51    }
52}
53
54/// Generator for a [`TradingSummary`].
55#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Constructor)]
56pub struct TradingSummaryGenerator {
57    /// Theoretical rate of return of an investment with zero risk.
58    ///
59    /// See docs: <https://www.investopedia.com/terms/r/risk-freerate.asp>
60    pub risk_free_return: Decimal,
61
62    /// Trading session summary start time defined by the [`Engine`](crate::engine::Engine) clock.
63    pub time_engine_start: DateTime<Utc>,
64
65    /// Trading session summary most recent update time defined by the
66    /// [`Engine`](crate::engine::Engine) clock.
67    pub time_engine_now: DateTime<Utc>,
68
69    /// Instrument [`TearSheetGenerator`]s.
70    ///
71    /// Note that an Instrument is unique to an exchange, so, for example, Binance btc_usdt_spot
72    /// and Okx btc_usdt_spot will be summarised by distinct [`TearSheet`]s.
73    pub instruments: FnvIndexMap<InstrumentNameInternal, TearSheetGenerator>,
74
75    /// [`ExchangeAsset`] [`TearSheetAssetGenerator`]s.
76    pub assets: FnvIndexMap<ExchangeAsset<AssetNameInternal>, TearSheetAssetGenerator>,
77}
78
79impl TradingSummaryGenerator {
80    /// Initialise a [`TradingSummaryGenerator`] from a `risk_free_return` value, and initial
81    /// indexed state.
82    pub fn init<InstrumentData>(
83        risk_free_return: Decimal,
84        time_engine_start: DateTime<Utc>,
85        time_engine_now: DateTime<Utc>,
86        instruments: &InstrumentStates<InstrumentData>,
87        assets: &AssetStates,
88    ) -> Self {
89        Self {
90            risk_free_return,
91            time_engine_start,
92            time_engine_now,
93            instruments: instruments
94                .0
95                .values()
96                .map(|state| {
97                    (
98                        state.instrument.name_internal.clone(),
99                        state.tear_sheet.clone(),
100                    )
101                })
102                .collect(),
103            assets: assets
104                .0
105                .iter()
106                .map(|(asset, state)| (asset.clone(), state.statistics.clone()))
107                .collect(),
108        }
109    }
110
111    /// Update the [`TradingSummaryGenerator`] `time_now`.
112    pub fn update_time_now(&mut self, time_now: DateTime<Utc>) {
113        self.time_engine_now = time_now;
114    }
115
116    /// Update the [`TradingSummaryGenerator`] from the next [`PositionExited`].
117    pub fn update_from_position<AssetKey, InstrumentKey>(
118        &mut self,
119        position: &PositionExited<AssetKey, InstrumentKey>,
120    ) where
121        Self: InstrumentTearSheetManager<InstrumentKey>,
122    {
123        if self.time_engine_now < position.time_exit {
124            self.time_engine_now = position.time_exit;
125        }
126
127        self.instrument_mut(&position.instrument)
128            .update_from_position(position)
129    }
130
131    /// Update the [`TradingSummaryGenerator`] from the next [`Snapshot`] [`AssetBalance`].
132    pub fn update_from_balance<AssetKey>(&mut self, balance: Snapshot<&AssetBalance<AssetKey>>)
133    where
134        Self: AssetTearSheetManager<AssetKey>,
135    {
136        if self.time_engine_now < balance.0.time_exchange {
137            self.time_engine_now = balance.0.time_exchange;
138        }
139
140        self.asset_mut(&balance.0.asset)
141            .update_from_balance(balance)
142    }
143
144    /// Generate the latest [`TradingSummary`] at the specific [`TimeInterval`].
145    ///
146    /// For example, pass [`Annual365`](super::time::Annual365) to generate a crypto-centric
147    /// (24/7 trading) annualised [`TradingSummary`].
148    pub fn generate<Interval>(&mut self, interval: Interval) -> TradingSummary<Interval>
149    where
150        Interval: TimeInterval,
151    {
152        let instruments = self
153            .instruments
154            .iter_mut()
155            .map(|(instrument, tear_sheet)| {
156                (
157                    instrument.clone(),
158                    tear_sheet.generate(self.risk_free_return, interval),
159                )
160            })
161            .collect();
162
163        let assets = self
164            .assets
165            .iter_mut()
166            .map(|(asset, tear_sheet)| (asset.clone(), tear_sheet.generate()))
167            .collect();
168
169        TradingSummary {
170            time_engine_start: self.time_engine_start,
171            time_engine_end: self.time_engine_now,
172            instruments,
173            assets,
174        }
175    }
176}
177
178pub trait InstrumentTearSheetManager<InstrumentKey> {
179    fn instrument(&self, key: &InstrumentKey) -> &TearSheetGenerator;
180    fn instrument_mut(&mut self, key: &InstrumentKey) -> &mut TearSheetGenerator;
181}
182
183impl InstrumentTearSheetManager<InstrumentNameInternal> for TradingSummaryGenerator {
184    fn instrument(&self, key: &InstrumentNameInternal) -> &TearSheetGenerator {
185        self.instruments
186            .get(key)
187            .unwrap_or_else(|| panic!("TradingSummaryGenerator does not contain: {key}"))
188    }
189
190    fn instrument_mut(&mut self, key: &InstrumentNameInternal) -> &mut TearSheetGenerator {
191        self.instruments
192            .get_mut(key)
193            .unwrap_or_else(|| panic!("TradingSummaryGenerator does not contain: {key}"))
194    }
195}
196
197impl InstrumentTearSheetManager<InstrumentIndex> for TradingSummaryGenerator {
198    fn instrument(&self, key: &InstrumentIndex) -> &TearSheetGenerator {
199        self.instruments
200            .get_index(key.index())
201            .map(|(_key, state)| state)
202            .unwrap_or_else(|| panic!("TradingSummaryGenerator does not contain: {key}"))
203    }
204
205    fn instrument_mut(&mut self, key: &InstrumentIndex) -> &mut TearSheetGenerator {
206        self.instruments
207            .get_index_mut(key.index())
208            .map(|(_key, state)| state)
209            .unwrap_or_else(|| panic!("TradingSummaryGenerator does not contain: {key}"))
210    }
211}
212
213pub trait AssetTearSheetManager<AssetKey> {
214    fn asset(&self, key: &AssetKey) -> &TearSheetAssetGenerator;
215    fn asset_mut(&mut self, key: &AssetKey) -> &mut TearSheetAssetGenerator;
216}
217
218impl AssetTearSheetManager<AssetIndex> for TradingSummaryGenerator {
219    fn asset(&self, key: &AssetIndex) -> &TearSheetAssetGenerator {
220        self.assets
221            .get_index(key.index())
222            .map(|(_key, state)| state)
223            .unwrap_or_else(|| panic!("TradingSummaryGenerator does not contain: {key}"))
224    }
225
226    fn asset_mut(&mut self, key: &AssetIndex) -> &mut TearSheetAssetGenerator {
227        self.assets
228            .get_index_mut(key.index())
229            .map(|(_key, state)| state)
230            .unwrap_or_else(|| panic!("TradingSummaryGenerator does not contain: {key}"))
231    }
232}
233
234impl AssetTearSheetManager<ExchangeAsset<AssetNameInternal>> for TradingSummaryGenerator {
235    fn asset(&self, key: &ExchangeAsset<AssetNameInternal>) -> &TearSheetAssetGenerator {
236        self.assets
237            .get(key)
238            .unwrap_or_else(|| panic!("TradingSummaryGenerator does not contain: {key:?}"))
239    }
240
241    fn asset_mut(
242        &mut self,
243        key: &ExchangeAsset<AssetNameInternal>,
244    ) -> &mut TearSheetAssetGenerator {
245        self.assets
246            .get_mut(key)
247            .unwrap_or_else(|| panic!("TradingSummaryGenerator does not contain: {key:?}"))
248    }
249}