nautilus_analysis/statistics/
sharpe_ratio.rs1use crate::{Returns, statistic::PortfolioStatistic};
17
18#[repr(C)]
19#[derive(Debug)]
20#[cfg_attr(
21 feature = "python",
22 pyo3::pyclass(module = "posei_trader.core.nautilus_pyo3.analysis")
23)]
24pub struct SharpeRatio {
25 period: usize,
26}
27
28impl SharpeRatio {
29 #[must_use]
31 pub fn new(period: Option<usize>) -> Self {
32 Self {
33 period: period.unwrap_or(252),
34 }
35 }
36}
37
38impl PortfolioStatistic for SharpeRatio {
39 type Item = f64;
40
41 fn name(&self) -> String {
42 stringify!(SharpeRatio).to_string()
43 }
44
45 fn calculate_from_returns(&self, raw_returns: &Returns) -> Option<Self::Item> {
46 if !self.check_valid_returns(raw_returns) {
47 return Some(f64::NAN);
48 }
49
50 let returns = self.downsample_to_daily_bins(raw_returns);
51 let mean = returns.values().sum::<f64>() / returns.len() as f64;
52 let std = self.calculate_std(&returns);
53
54 if std < f64::EPSILON {
55 return Some(f64::NAN);
56 }
57
58 let annualized_ratio = (mean / std) * (self.period as f64).sqrt();
59
60 Some(annualized_ratio)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use std::collections::BTreeMap;
67
68 use nautilus_core::UnixNanos;
69 use rstest::rstest;
70
71 use super::*;
72
73 fn create_returns(values: Vec<f64>) -> BTreeMap<UnixNanos, f64> {
74 let mut new_return = BTreeMap::new();
75 let one_day_in_nanos = 86_400_000_000_000;
76 let start_time = 1_600_000_000_000_000_000;
77
78 for (i, &value) in values.iter().enumerate() {
79 let timestamp = start_time + i as u64 * one_day_in_nanos;
80 new_return.insert(UnixNanos::from(timestamp), value);
81 }
82
83 new_return
84 }
85
86 #[rstest]
87 fn test_empty_returns() {
88 let ratio = SharpeRatio::new(None);
89 let returns = create_returns(vec![]);
90 let result = ratio.calculate_from_returns(&returns);
91 assert!(result.is_some());
92 assert!(result.unwrap().is_nan());
93 }
94
95 #[rstest]
96 fn test_zero_std_dev() {
97 let ratio = SharpeRatio::new(None);
98 let returns = create_returns(vec![0.01; 10]);
99 let result = ratio.calculate_from_returns(&returns);
100 assert!(result.is_some());
101 assert!(result.unwrap().is_nan());
102 }
103
104 #[rstest]
105 fn test_valid_sharpe_ratio() {
106 let ratio = SharpeRatio::new(Some(252));
107 let returns = create_returns(vec![0.01, -0.02, 0.015, -0.005, 0.025]);
108 let result = ratio.calculate_from_returns(&returns);
109 assert!(result.is_some());
110 assert_eq!(result.unwrap(), 4.48998886412873);
111 }
112
113 #[rstest]
114 fn test_name() {
115 let ratio = SharpeRatio::new(None);
116 assert_eq!(ratio.name(), "SharpeRatio");
117 }
118}