nautilus_analysis/
statistic.rs1use std::{collections::BTreeMap, fmt::Debug};
17
18use nautilus_model::{orders::Order, position::Position};
19
20use crate::Returns;
21
22const IMPL_ERR: &str = "is not implemented for";
23
24#[allow(unused_variables)]
25pub trait PortfolioStatistic: Debug {
26 type Item;
27
28 fn name(&self) -> String;
29
30 fn calculate_from_returns(&self, returns: &Returns) -> Option<Self::Item> {
31 panic!("`calculate_from_returns` {IMPL_ERR} `{}`", self.name());
32 }
33
34 fn calculate_from_realized_pnls(&self, realized_pnls: &[f64]) -> Option<Self::Item> {
35 panic!(
36 "`calculate_from_realized_pnls` {IMPL_ERR} `{}`",
37 self.name()
38 );
39 }
40
41 #[allow(dead_code)]
42 fn calculate_from_orders(&self, orders: Vec<Box<dyn Order>>) -> Option<Self::Item> {
43 panic!("`calculate_from_orders` {IMPL_ERR} `{}`", self.name());
44 }
45
46 fn calculate_from_positions(&self, positions: &[Position]) -> Option<Self::Item> {
47 panic!("`calculate_from_positions` {IMPL_ERR} `{}`", self.name());
48 }
49
50 fn check_valid_returns(&self, returns: &Returns) -> bool {
51 !returns.is_empty()
52 }
53
54 fn downsample_to_daily_bins(&self, returns: &Returns) -> Returns {
55 let nanos_per_day = 86_400_000_000_000; let mut daily_bins = BTreeMap::new();
57
58 for (×tamp, &value) in returns {
59 let day_start = timestamp - (timestamp.as_u64() % nanos_per_day);
61
62 *daily_bins.entry(day_start).or_insert(0.0) += value;
64 }
65
66 daily_bins
67 }
68
69 fn calculate_std(&self, returns: &Returns) -> f64 {
70 let n = returns.len() as f64;
71 if n < 2.0 {
72 return f64::NAN;
73 }
74
75 let mean = returns.values().sum::<f64>() / n;
76
77 let variance = returns.values().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1.0);
78
79 variance.sqrt()
80 }
81}