use crate::rate::pct_change;
use crate::{FinPrimError, FloatLike};
pub fn twr<T: FloatLike>(values: &[(T, T)], annualization_period: Option<T>) -> Result<T, FinPrimError<T>> {
let total_return = values.windows(2).try_fold(T::one(), |acc, window| {
let (start_value, _) = window[0];
let (end_value, end_cashflow) = window[1];
let adjusted_end = end_value - end_cashflow;
pct_change(start_value, adjusted_end)
.map(|pct| acc * (pct + T::one()))
.map_err(|_| FinPrimError::DivideByZero)
})?;
Ok(annualization_period
.map(|period| (total_return).powf(T::one() / period) - T::one())
.unwrap_or(total_return - T::one()))
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
extern crate std;
#[cfg(not(feature = "std"))]
use std::vec;
#[test]
fn test_twr() {
let values = vec![
(1000.0, 0.0),
(1600.0, 400.0),
(1450.0, -200.0),
(1700.0, 200.0),
(2200.0, 300.0),
];
let twr_qtr = twr(&values, None).unwrap();
let expected_qtr: f64 = 0.43078093;
assert!((twr_qtr - expected_qtr).abs() < 1e-5);
let twr_yr = twr(&values, Some(4.0)).unwrap();
let expected_yr = 0.093688;
assert!((twr_yr - expected_yr).abs() < 1e-5);
let values_bankruptcy = vec![
(1000.0, 0.0),
(1600.0, 400.0),
(1450.0, -200.0),
(1700.0, 200.0),
(2200.0, 300.0),
(2500.0, 0.0),
(3000.0, -300.0),
(1700.0, -1500.0),
(0.0, 0.0),
];
let twr_bankruptcy = twr(&values_bankruptcy, Some(2.0)).unwrap();
let expected_bankruptcy = -1.0;
assert_eq!(twr_bankruptcy, expected_bankruptcy);
let values_6qtr = vec![
(1000.0, 0.0),
(1600.0, 400.0),
(1450.0, -200.0),
(1700.0, 200.0),
(2200.0, 300.0),
(2500.0, 0.0),
(3000.0, -300.0),
];
let twr_6qtr = twr(&values_6qtr, Some(1.5)).unwrap();
let expected_6qtr = 0.663832;
assert!((twr_6qtr - expected_6qtr).abs() < 1e-5);
let values_2 = vec![(1000.0, 0.0), (1600.0, 400.0)];
let twr_2 = twr(&values_2, None).unwrap();
assert!(twr_2 - 0.2 < 1e-5);
}
}