rateslib/curves/interpolation/
intp_linear_zero_rate.rs

1use crate::curves::interpolation::utils::linear_zero_interp;
2use crate::curves::nodes::NodesTimestamp;
3use crate::curves::CurveInterpolation;
4use crate::dual::Number;
5use bincode::config::legacy;
6use bincode::serde::{decode_from_slice, encode_to_vec};
7use chrono::NaiveDateTime;
8use pyo3::prelude::*;
9use pyo3::types::{PyBytes, PyTuple};
10use pyo3::{pyclass, pymethods, Bound, PyResult, Python};
11use serde::{Deserialize, Serialize};
12use std::cmp::PartialEq;
13
14/// Define linear zero rate interpolation of nodes.
15///
16/// This interpolation can only be used with discount factors node values.
17#[pyclass(module = "rateslib.rs")]
18#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
19pub struct LinearZeroRateInterpolator {}
20
21#[pymethods]
22
23impl LinearZeroRateInterpolator {
24    #[new]
25    pub fn new() -> Self {
26        LinearZeroRateInterpolator {}
27    }
28
29    // Pickling
30    pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {
31        *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;
32        Ok(())
33    }
34    pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
35        Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))
36    }
37    pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
38        Ok(PyTuple::empty(py))
39    }
40}
41
42impl CurveInterpolation for LinearZeroRateInterpolator {
43    fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number {
44        let x = date.and_utc().timestamp();
45        let index = self.node_index(nodes, x);
46
47        macro_rules! interp {
48            ($Variant: ident, $indexmap: expr) => {{
49                let (x0, _) = $indexmap.get_index(0_usize).unwrap();
50                let (x2, y2) = $indexmap.get_index(index + 1_usize).unwrap();
51                let (x1, y1) = $indexmap.get_index(index).unwrap();
52                Number::$Variant(linear_zero_interp(
53                    *x0 as f64, *x1 as f64, y1, *x2 as f64, y2, x as f64,
54                ))
55            }};
56        }
57        match nodes {
58            NodesTimestamp::F64(m) => interp!(F64, m),
59            NodesTimestamp::Dual(m) => interp!(Dual, m),
60            NodesTimestamp::Dual2(m) => interp!(Dual2, m),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::curves::nodes::Nodes;
69    use crate::scheduling::ndt;
70    use indexmap::IndexMap;
71
72    fn nodes_timestamp_fixture() -> NodesTimestamp {
73        let nodes = Nodes::F64(IndexMap::from_iter(vec![
74            (ndt(2000, 1, 1), 1.0_f64),
75            (ndt(2001, 1, 1), 0.99_f64),
76            (ndt(2002, 1, 1), 0.98_f64),
77        ]));
78        NodesTimestamp::from(nodes)
79    }
80
81    #[test]
82    fn test_log_linear() {
83        let nts = nodes_timestamp_fixture();
84        let ll = LinearZeroRateInterpolator::new();
85        let result = ll.interpolated_value(&nts, &ndt(2001, 7, 1));
86        // r1 = -ln(0.99) / 366, r2 = -ln(0.98) / 731
87        // r = r1 + (181 / 365) * (r2 - r1)
88        // expected = exp(-r * 547) r1 = 0.985044328
89        assert_eq!(result, Number::F64(0.9850443279738612));
90    }
91
92    #[test]
93    fn test_log_linear_first_period() {
94        let nts = nodes_timestamp_fixture();
95        let ll = LinearZeroRateInterpolator::new();
96        let result = ll.interpolated_value(&nts, &ndt(2000, 7, 1));
97        // r1 = r2, r2 = -ln(0.99) / 366
98        // r = r1
99        // expected = exp(-r * 182) = 0.99501476
100        assert_eq!(result, Number::F64(0.9950147597711371));
101    }
102}