barter/statistic/metric/
sharpe.rs1use crate::statistic::time::TimeInterval;
2use rust_decimal::{Decimal, MathematicalOps};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, PartialOrd, Default, Deserialize, Serialize)]
12pub struct SharpeRatio<Interval> {
13 pub value: Decimal,
14 pub interval: Interval,
15}
16
17impl<Interval> SharpeRatio<Interval>
18where
19 Interval: TimeInterval,
20{
21 pub fn calculate(
23 risk_free_return: Decimal,
24 mean_return: Decimal,
25 std_dev_returns: Decimal,
26 returns_period: Interval,
27 ) -> Self {
28 if std_dev_returns.is_zero() {
29 Self {
30 value: Decimal::MAX,
31 interval: returns_period,
32 }
33 } else {
34 let excess_returns = mean_return - risk_free_return;
35 let ratio = excess_returns.checked_div(std_dev_returns).unwrap();
36 Self {
37 value: ratio,
38 interval: returns_period,
39 }
40 }
41 }
42
43 pub fn scale<TargetInterval>(self, target: TargetInterval) -> SharpeRatio<TargetInterval>
47 where
48 TargetInterval: TimeInterval,
49 {
50 let target_secs = Decimal::from(target.interval().num_seconds());
52 let current_secs = Decimal::from(self.interval.interval().num_seconds());
53
54 let scale = target_secs
55 .abs()
56 .checked_div(current_secs.abs())
57 .unwrap_or(Decimal::MAX)
58 .sqrt()
59 .expect("ensured seconds are Positive");
60
61 SharpeRatio {
62 value: self.value.checked_mul(scale).unwrap_or(Decimal::MAX),
63 interval: target,
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::statistic::time::{Annual252, Daily};
72 use chrono::TimeDelta;
73 use rust_decimal_macros::dec;
74
75 #[test]
76 fn test_sharpe_ratio_with_zero_std_dev() {
77 let risk_free_return = dec!(0.001);
78 let mean_return = dec!(0.002);
79 let std_dev_returns = dec!(0.0);
80 let time_period = TimeDelta::hours(2);
81
82 let result =
83 SharpeRatio::calculate(risk_free_return, mean_return, std_dev_returns, time_period);
84 assert_eq!(result.value, Decimal::MAX);
85 }
86
87 #[test]
88 fn test_sharpe_ratio_calculate_with_custom_interval() {
89 let risk_free_return = dec!(0.0015); let mean_return = dec!(0.0025); let std_dev_returns = dec!(0.02); let time_period = TimeDelta::hours(2);
94
95 let actual =
96 SharpeRatio::calculate(risk_free_return, mean_return, std_dev_returns, time_period);
97
98 let expected = SharpeRatio {
99 value: dec!(0.05),
100 interval: time_period,
101 };
102
103 assert_eq!(actual.value, expected.value);
104 assert_eq!(actual.interval, expected.interval);
105 }
106
107 #[test]
108 fn test_sharpe_ratio_calculate_with_daily_interval() {
109 let risk_free_return = dec!(0.0015); let mean_return = dec!(0.0025); let std_dev_returns = dec!(0.02); let time_period = Daily;
114
115 let actual =
116 SharpeRatio::calculate(risk_free_return, mean_return, std_dev_returns, time_period);
117
118 let expected = SharpeRatio {
119 value: dec!(0.05),
120 interval: time_period,
121 };
122
123 assert_eq!(actual.value, expected.value);
124 assert_eq!(actual.interval, expected.interval);
125 }
126
127 #[test]
128 fn test_sharpe_ratio_scale_from_daily_to_annual_252() {
129 let input = SharpeRatio {
130 value: dec!(0.05),
131 interval: Daily,
132 };
133
134 let actual = input.scale(Annual252);
135
136 let expected = SharpeRatio {
137 value: dec!(0.7937253933193771771504847261),
138 interval: Annual252,
139 };
140
141 assert_eq!(actual.value, expected.value);
142 assert_eq!(actual.interval, expected.interval);
143 }
144}