convex_math/extrapolation/
linear.rs

1//! Linear extrapolation.
2
3use super::Extrapolator;
4
5/// Linear extrapolation - continues with the last known slope.
6///
7/// This method extends the curve by continuing the linear trend from
8/// the last observed point using its derivative (slope).
9///
10/// # Properties
11///
12/// - **Simple**: Uses only last point and slope
13/// - **Trend-following**: Assumes the current trend continues
14/// - **Risk**: May produce negative rates if slope is negative
15///
16/// # Use Cases
17///
18/// - Short-term extrapolation where trend continuation is expected
19/// - Preliminary analysis before applying more sophisticated methods
20///
21/// # Warning
22///
23/// Linear extrapolation can produce unrealistic results (negative rates)
24/// for long maturities. For regulatory or production use, consider
25/// [`SmithWilson`](super::SmithWilson) instead.
26///
27/// # Example
28///
29/// ```rust
30/// use convex_math::extrapolation::{LinearExtrapolator, Extrapolator};
31///
32/// let extrap = LinearExtrapolator;
33///
34/// // Last observed: 5% at 10 years with 0.1% slope per year
35/// let rate = extrap.extrapolate(15.0, 10.0, 0.05, 0.001);
36/// assert!((rate - 0.055).abs() < 1e-10);  // 5% + 0.1% * 5 = 5.5%
37/// ```
38#[derive(Debug, Clone, Copy, Default)]
39pub struct LinearExtrapolator;
40
41impl LinearExtrapolator {
42    /// Creates a new linear extrapolator.
43    #[must_use]
44    pub fn new() -> Self {
45        Self
46    }
47}
48
49impl Extrapolator for LinearExtrapolator {
50    fn extrapolate(&self, t: f64, last_t: f64, last_value: f64, last_derivative: f64) -> f64 {
51        // Linear continuation: y = y0 + slope * (t - t0)
52        last_value + last_derivative * (t - last_t)
53    }
54
55    fn name(&self) -> &'static str {
56        "Linear"
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use approx::assert_relative_eq;
64
65    #[test]
66    fn test_linear_extrapolation() {
67        let extrap = LinearExtrapolator::new();
68
69        let last_t = 10.0;
70        let last_value = 0.05;
71        let last_deriv = 0.001; // 0.1% per year
72
73        // At t=15: 0.05 + 0.001 * (15-10) = 0.055
74        let value = extrap.extrapolate(15.0, last_t, last_value, last_deriv);
75        assert_relative_eq!(value, 0.055, epsilon = 1e-10);
76
77        // At t=20: 0.05 + 0.001 * (20-10) = 0.06
78        let value = extrap.extrapolate(20.0, last_t, last_value, last_deriv);
79        assert_relative_eq!(value, 0.06, epsilon = 1e-10);
80    }
81
82    #[test]
83    fn test_linear_with_negative_slope() {
84        let extrap = LinearExtrapolator;
85
86        let last_t = 20.0;
87        let last_value = 0.04;
88        let last_deriv = -0.0005; // Decreasing rates
89
90        // At t=30: 0.04 + (-0.0005) * 10 = 0.035
91        let value = extrap.extrapolate(30.0, last_t, last_value, last_deriv);
92        assert_relative_eq!(value, 0.035, epsilon = 1e-10);
93    }
94
95    #[test]
96    fn test_linear_at_boundary() {
97        let extrap = LinearExtrapolator;
98
99        let last_t = 10.0;
100        let last_value = 0.05;
101        let last_deriv = 0.001;
102
103        // At the boundary point, should return exact last value
104        let value = extrap.extrapolate(last_t, last_t, last_value, last_deriv);
105        assert_relative_eq!(value, last_value, epsilon = 1e-15);
106    }
107
108    #[test]
109    fn test_linear_with_zero_slope() {
110        let extrap = LinearExtrapolator;
111
112        let last_t = 10.0;
113        let last_value = 0.03;
114        let last_deriv = 0.0;
115
116        // Zero slope should give constant value (same as flat)
117        for t in [15.0, 20.0, 50.0, 100.0] {
118            let value = extrap.extrapolate(t, last_t, last_value, last_deriv);
119            assert_relative_eq!(value, last_value, epsilon = 1e-15);
120        }
121    }
122
123    #[test]
124    fn test_linear_name() {
125        let extrap = LinearExtrapolator;
126        assert_eq!(extrap.name(), "Linear");
127    }
128}