1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RustQuant: A Rust library for quantitative finance tools.
// Copyright (C) 2023 https://github.com/avhz
// Dual licensed under Apache 2.0 and MIT.
// See:
// - LICENSE-APACHE.md
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
use crate::curves::{Curve, CurveModel};
use crate::time::{DayCountConvention, DayCounter};
use time::OffsetDateTime;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// STRUCTS, ENUMS, AND TRAITS
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Nelson-Siegel-Svensson (1994) model parameters.
pub struct NelsonSiegelSvensson {
beta0: f64,
beta1: f64,
beta2: f64,
beta3: f64,
lambda1: f64,
lambda2: f64,
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// IMPLEMENTATIONS, TRAITS, AND FUNCTIONS
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
impl NelsonSiegelSvensson {
/// Create a new Nelson-Siegel model.
pub fn new(beta0: f64, beta1: f64, beta2: f64, beta3: f64, lambda1: f64, lambda2: f64) -> Self {
Self {
beta0,
beta1,
beta2,
beta3,
lambda1,
lambda2,
}
}
}
impl CurveModel for NelsonSiegelSvensson {
/// Returns the forward rate for a given date.
fn forward_rate(&self, date: OffsetDateTime) -> f64 {
assert!(
date > OffsetDateTime::now_utc(),
"Date must be in the future."
);
let tau = DayCounter::day_count_factor(
OffsetDateTime::now_utc(),
date,
&DayCountConvention::Actual365,
);
let term1 = f64::exp(-tau / self.lambda1);
let term2 = (tau / self.lambda1) * term1;
let term3 = (tau / self.lambda2) * f64::exp(-tau / self.lambda2);
self.beta0 + self.beta1 * term1 + self.beta2 * term2 + self.beta3 * term3
}
/// Returns the spot rate for a given date.
fn spot_rate(&self, date: OffsetDateTime) -> f64 {
assert!(
date > OffsetDateTime::now_utc(),
"Date must be in the future."
);
let tau = DayCounter::day_count_factor(
OffsetDateTime::now_utc(),
date,
&DayCountConvention::Actual365,
);
let term1 = self.lambda1 * (1. - f64::exp(-tau / self.lambda1)) / tau;
let term2 = term1 - f64::exp(-tau / self.lambda1);
let term3 = self.lambda2 * (1. - f64::exp(-tau / self.lambda2)) / tau
- f64::exp(-tau / self.lambda2);
self.beta0 + self.beta1 * term1 + self.beta2 * term2 + self.beta3 * term3
}
fn discount_factor(&self, date: OffsetDateTime) -> f64 {
let tau = DayCounter::day_count_factor(
OffsetDateTime::now_utc(),
date,
&DayCountConvention::Actual365,
);
f64::exp(-self.spot_rate(date) * tau / 100.)
}
fn calibrate<C: Curve>(&self, _curve: C) -> Self {
unimplemented!()
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// UNIT TESTS
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#[cfg(test)]
mod tests_nelson_siegel_svensson {
use super::*;
// use crate::plot_vector;
use time::Duration;
#[test]
fn test_nelson_siegel_svensson() {
let nss = NelsonSiegelSvensson {
beta0: 0.0806,
beta1: -0.0031,
beta2: -0.0625,
beta3: -0.0198,
lambda1: 1.58,
lambda2: 0.15,
};
let dates = (2..365 * 30)
.map(|i| OffsetDateTime::now_utc() + Duration::days(i))
.collect::<Vec<OffsetDateTime>>();
let _forward_curve = dates
.iter()
.map(|date| nss.forward_rate(*date))
.collect::<Vec<_>>();
let _discount_curve = dates
.iter()
.map(|date| nss.discount_factor(*date))
.collect::<Vec<_>>();
// plot_vector!(forward_curve, "./images/nelson_siegel_svensson_forward.png");
// plot_vector!(
// discount_curve,
// "./images/nelson_siegel_svensson_discount.png"
// );
}
}