1use rust_decimal::Decimal;
4
5use crate::MetricsError;
6
7pub fn total_return(equity: &[Decimal]) -> Result<Decimal, MetricsError> {
26 if equity.len() < 2 {
27 return Err(MetricsError::InsufficientData {
28 required: 2,
29 actual: equity.len(),
30 });
31 }
32
33 let start = equity[0];
34 let end = equity[equity.len() - 1];
35
36 if start == Decimal::ZERO {
37 return Err(MetricsError::DivisionByZero {
38 context: "starting equity is zero",
39 });
40 }
41
42 Ok(((end - start) / start) * Decimal::from(100))
43}
44
45pub fn cagr(equity: &[Decimal], years: Decimal) -> Result<Decimal, MetricsError> {
57 if equity.len() < 2 {
58 return Err(MetricsError::InsufficientData {
59 required: 2,
60 actual: equity.len(),
61 });
62 }
63
64 if years <= Decimal::ZERO {
65 return Err(MetricsError::InvalidParameter(
66 "years must be positive".into(),
67 ));
68 }
69
70 let start = equity[0];
71 let end = equity[equity.len() - 1];
72
73 if start == Decimal::ZERO {
74 return Err(MetricsError::DivisionByZero {
75 context: "starting equity is zero",
76 });
77 }
78
79 let ratio = end / start;
80
81 let ln_ratio = ln_approx(ratio);
85 let exponent = ln_ratio / years;
86 let growth_factor = exp_approx(exponent);
87
88 Ok((growth_factor - Decimal::ONE) * Decimal::from(100))
89}
90
91pub fn annualized_return(
99 equity: &[Decimal],
100 periods_per_year: u32,
101) -> Result<Decimal, MetricsError> {
102 if equity.len() < 2 {
103 return Err(MetricsError::InsufficientData {
104 required: 2,
105 actual: equity.len(),
106 });
107 }
108
109 let periods = Decimal::from(equity.len() - 1);
110 let years = periods / Decimal::from(periods_per_year);
111
112 cagr(equity, years)
113}
114
115fn ln_approx(x: Decimal) -> Decimal {
118 if x <= Decimal::ZERO {
119 return Decimal::ZERO;
120 }
121
122 let one = Decimal::ONE;
125 let y = x - one;
126
127 if y.abs() < Decimal::from(1) {
128 let y2 = y * y;
130 let y3 = y2 * y;
131 let y4 = y3 * y;
132 let y5 = y4 * y;
133 let y6 = y5 * y;
134 let y7 = y6 * y;
135 let y8 = y7 * y;
136 let y9 = y8 * y;
137 let y10 = y9 * y;
138
139 y - y2 / Decimal::from(2) + y3 / Decimal::from(3) - y4 / Decimal::from(4)
140 + y5 / Decimal::from(5)
141 - y6 / Decimal::from(6)
142 + y7 / Decimal::from(7)
143 - y8 / Decimal::from(8)
144 + y9 / Decimal::from(9)
145 - y10 / Decimal::from(10)
146 } else {
147 let mut val = x;
150 let mut multiplier = Decimal::ONE;
151
152 while val > Decimal::from(2) {
153 val = decimal_sqrt(val);
154 multiplier *= Decimal::from(2);
155 }
156
157 multiplier * ln_approx(val)
158 }
159}
160
161fn exp_approx(x: Decimal) -> Decimal {
163 let x2 = x * x;
166 let x3 = x2 * x;
167 let x4 = x3 * x;
168 let x5 = x4 * x;
169 let x6 = x5 * x;
170 let x7 = x6 * x;
171 let x8 = x7 * x;
172 let x9 = x8 * x;
173 let x10 = x9 * x;
174 let x11 = x10 * x;
175 let x12 = x11 * x;
176
177 Decimal::ONE
178 + x
179 + x2 / Decimal::from(2)
180 + x3 / Decimal::from(6)
181 + x4 / Decimal::from(24)
182 + x5 / Decimal::from(120)
183 + x6 / Decimal::from(720)
184 + x7 / Decimal::from(5040)
185 + x8 / Decimal::from(40320)
186 + x9 / Decimal::from(362880)
187 + x10 / Decimal::from(3628800)
188 + x11 / Decimal::from(39916800)
189 + x12 / Decimal::from(479001600)
190}
191
192use crate::math::decimal_sqrt;
193
194#[cfg(test)]
195#[path = "returns_tests.rs"]
196mod tests;