nautilus_analysis/statistics/
sharpe_ratio.rs1use crate::{statistic::PortfolioStatistic, Returns};
17
18#[repr(C)]
19#[derive(Debug)]
20#[cfg_attr(
21 feature = "python",
22 pyo3::pyclass(module = "nautilus_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
70 use super::*;
71
72 fn create_returns(values: Vec<f64>) -> BTreeMap<UnixNanos, f64> {
73 let mut new_return = BTreeMap::new();
74 let one_day_in_nanos = 86_400_000_000_000;
75 let start_time = 1_600_000_000_000_000_000;
76
77 for (i, &value) in values.iter().enumerate() {
78 let timestamp = start_time + i as u64 * one_day_in_nanos;
79 new_return.insert(UnixNanos::from(timestamp), value);
80 }
81
82 new_return
83 }
84
85 #[test]
86 fn test_empty_returns() {
87 let ratio = SharpeRatio::new(None);
88 let returns = create_returns(vec![]);
89 let result = ratio.calculate_from_returns(&returns);
90 assert!(result.is_some());
91 assert!(result.unwrap().is_nan());
92 }
93
94 #[test]
95 fn test_zero_std_dev() {
96 let ratio = SharpeRatio::new(None);
97 let returns = create_returns(vec![0.01; 10]);
98 let result = ratio.calculate_from_returns(&returns);
99 assert!(result.is_some());
100 assert!(result.unwrap().is_nan());
101 }
102
103 #[test]
104 fn test_valid_sharpe_ratio() {
105 let ratio = SharpeRatio::new(Some(252));
106 let returns = create_returns(vec![0.01, -0.02, 0.015, -0.005, 0.025]);
107 let result = ratio.calculate_from_returns(&returns);
108 assert!(result.is_some());
109 assert_eq!(result.unwrap(), 4.48998886412873);
110 }
111
112 #[test]
113 fn test_name() {
114 let ratio = SharpeRatio::new(None);
115 assert_eq!(ratio.name(), "SharpeRatio");
116 }
117}