convex_math/extrapolation/
mod.rs

1//! Extrapolation methods for yield curves.
2//!
3//! This module provides extrapolation algorithms for extending curves beyond
4//! their last observed data point:
5//!
6//! - [`FlatExtrapolator`]: Constant extension from last point
7//! - [`LinearExtrapolator`]: Linear slope continuation
8//! - [`SmithWilson`]: Regulatory standard (EIOPA) with Ultimate Forward Rate
9//!
10//! # Choosing an Extrapolation Method
11//!
12//! | Method | Use Case | Properties |
13//! |--------|----------|------------|
14//! | Flat | Simple, conservative | Constant forward rate |
15//! | Linear | Trend continuation | May go negative |
16//! | Smith-Wilson | **Regulatory (Solvency II)** | Converges to UFR |
17//!
18//! # Regulatory Context
19//!
20//! For European insurance regulation (Solvency II), [`SmithWilson`] is required.
21//! It ensures smooth convergence to the Ultimate Forward Rate (UFR) beyond the
22//! Last Liquid Point (LLP), typically 20 years for EUR.
23//!
24//! # Example
25//!
26//! ```rust
27//! use convex_math::extrapolation::{SmithWilson, Extrapolator};
28//!
29//! // Create Smith-Wilson extrapolator for EUR (EIOPA parameters)
30//! let sw = SmithWilson::eiopa_eur();
31//!
32//! // Extrapolate from a 20Y rate to 60Y
33//! let rate_20y = 0.035;
34//! let rate_60y = sw.extrapolate(60.0, 20.0, rate_20y, 0.001);
35//! ```
36
37mod flat;
38mod linear;
39mod smith_wilson;
40
41pub use flat::FlatExtrapolator;
42pub use linear::LinearExtrapolator;
43pub use smith_wilson::SmithWilson;
44
45/// Trait for extrapolation methods.
46///
47/// Extrapolators extend curves beyond their last observed point.
48pub trait Extrapolator: Send + Sync {
49    /// Extrapolates to time `t` given the last known point.
50    ///
51    /// # Arguments
52    ///
53    /// * `t` - Target time for extrapolation
54    /// * `last_t` - Time of last known point
55    /// * `last_value` - Value at last known point
56    /// * `last_derivative` - Derivative at last known point (slope)
57    fn extrapolate(&self, t: f64, last_t: f64, last_value: f64, last_derivative: f64) -> f64;
58
59    /// Returns the name of the extrapolation method.
60    fn name(&self) -> &'static str;
61}
62
63/// Configuration for extrapolation beyond curve boundaries.
64#[derive(Debug, Clone, Copy, PartialEq, Default)]
65pub enum ExtrapolationMethod {
66    /// No extrapolation - return error outside range
67    None,
68    /// Constant value from boundary
69    #[default]
70    Flat,
71    /// Linear continuation with boundary slope
72    Linear,
73    /// Smith-Wilson with UFR convergence
74    SmithWilson {
75        /// Ultimate forward rate
76        ufr: f64,
77        /// Convergence speed (alpha)
78        alpha: f64,
79    },
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use approx::assert_relative_eq;
86
87    #[test]
88    fn test_flat_extrapolator() {
89        let extrap = FlatExtrapolator;
90
91        let last_t = 10.0;
92        let last_value = 0.05;
93        let last_deriv = 0.001;
94
95        // Flat should ignore derivative and return last value
96        let value = extrap.extrapolate(15.0, last_t, last_value, last_deriv);
97        assert_relative_eq!(value, last_value, epsilon = 1e-10);
98
99        let value = extrap.extrapolate(100.0, last_t, last_value, last_deriv);
100        assert_relative_eq!(value, last_value, epsilon = 1e-10);
101    }
102
103    #[test]
104    fn test_linear_extrapolator() {
105        let extrap = LinearExtrapolator;
106
107        let last_t = 10.0;
108        let last_value = 0.05;
109        let last_deriv = 0.001; // 0.1% per year
110
111        // Linear should continue with the slope
112        let value = extrap.extrapolate(15.0, last_t, last_value, last_deriv);
113        let expected = last_value + last_deriv * (15.0 - last_t);
114        assert_relative_eq!(value, expected, epsilon = 1e-10);
115    }
116
117    #[test]
118    fn test_smith_wilson_convergence() {
119        let sw = SmithWilson::new(0.042, 0.1, 20.0);
120
121        let last_t = 20.0;
122        let last_value = 0.035;
123        let last_deriv = 0.0005;
124
125        // Should converge towards UFR at long maturities
126        let value_30 = sw.extrapolate(30.0, last_t, last_value, last_deriv);
127        let value_60 = sw.extrapolate(60.0, last_t, last_value, last_deriv);
128        let value_100 = sw.extrapolate(100.0, last_t, last_value, last_deriv);
129
130        // Values should approach UFR (0.042)
131        assert!(value_30 > last_value); // Moving towards UFR
132        assert!(value_60 > value_30);
133        assert!((value_100 - 0.042).abs() < (value_60 - 0.042).abs());
134    }
135}