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}