use rust_decimal::Decimal;
use crate::MetricsError;
pub fn total_return(equity: &[Decimal]) -> Result<Decimal, MetricsError> {
if equity.len() < 2 {
return Err(MetricsError::InsufficientData {
required: 2,
actual: equity.len(),
});
}
let start = equity[0];
let end = equity[equity.len() - 1];
if start == Decimal::ZERO {
return Err(MetricsError::DivisionByZero {
context: "starting equity is zero",
});
}
Ok(((end - start) / start) * Decimal::from(100))
}
pub fn cagr(equity: &[Decimal], years: Decimal) -> Result<Decimal, MetricsError> {
if equity.len() < 2 {
return Err(MetricsError::InsufficientData {
required: 2,
actual: equity.len(),
});
}
if years <= Decimal::ZERO {
return Err(MetricsError::InvalidParameter(
"years must be positive".into(),
));
}
let start = equity[0];
let end = equity[equity.len() - 1];
if start == Decimal::ZERO {
return Err(MetricsError::DivisionByZero {
context: "starting equity is zero",
});
}
let ratio = end / start;
let ln_ratio = ln_approx(ratio);
let exponent = ln_ratio / years;
let growth_factor = exp_approx(exponent);
Ok((growth_factor - Decimal::ONE) * Decimal::from(100))
}
pub fn annualized_return(
equity: &[Decimal],
periods_per_year: u32,
) -> Result<Decimal, MetricsError> {
if equity.len() < 2 {
return Err(MetricsError::InsufficientData {
required: 2,
actual: equity.len(),
});
}
let periods = Decimal::from(equity.len() - 1);
let years = periods / Decimal::from(periods_per_year);
cagr(equity, years)
}
fn ln_approx(x: Decimal) -> Decimal {
if x <= Decimal::ZERO {
return Decimal::ZERO;
}
let one = Decimal::ONE;
let y = x - one;
if y.abs() < Decimal::from(1) {
let y2 = y * y;
let y3 = y2 * y;
let y4 = y3 * y;
let y5 = y4 * y;
let y6 = y5 * y;
let y7 = y6 * y;
let y8 = y7 * y;
let y9 = y8 * y;
let y10 = y9 * y;
y - y2 / Decimal::from(2) + y3 / Decimal::from(3) - y4 / Decimal::from(4)
+ y5 / Decimal::from(5)
- y6 / Decimal::from(6)
+ y7 / Decimal::from(7)
- y8 / Decimal::from(8)
+ y9 / Decimal::from(9)
- y10 / Decimal::from(10)
} else {
let mut val = x;
let mut multiplier = Decimal::ONE;
while val > Decimal::from(2) {
val = decimal_sqrt(val);
multiplier *= Decimal::from(2);
}
multiplier * ln_approx(val)
}
}
fn exp_approx(x: Decimal) -> Decimal {
let x2 = x * x;
let x3 = x2 * x;
let x4 = x3 * x;
let x5 = x4 * x;
let x6 = x5 * x;
let x7 = x6 * x;
let x8 = x7 * x;
let x9 = x8 * x;
let x10 = x9 * x;
let x11 = x10 * x;
let x12 = x11 * x;
Decimal::ONE
+ x
+ x2 / Decimal::from(2)
+ x3 / Decimal::from(6)
+ x4 / Decimal::from(24)
+ x5 / Decimal::from(120)
+ x6 / Decimal::from(720)
+ x7 / Decimal::from(5040)
+ x8 / Decimal::from(40320)
+ x9 / Decimal::from(362880)
+ x10 / Decimal::from(3628800)
+ x11 / Decimal::from(39916800)
+ x12 / Decimal::from(479001600)
}
use crate::math::decimal_sqrt;
#[cfg(test)]
#[path = "returns_tests.rs"]
mod tests;