Skip to content

Commit

Permalink
chore: Curve struct and impl, with placeholder modules for various …
Browse files Browse the repository at this point in the history
…curves.
  • Loading branch information
avhz authored and avhz committed Jul 14, 2024
1 parent 4cab13f commit a3a4fee
Show file tree
Hide file tree
Showing 26 changed files with 634 additions and 589 deletions.
186 changes: 186 additions & 0 deletions src/data/curves/curve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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::error::RustQuantError;
use crate::math::{
interpolation::{Interpolator, LinearInterpolator},
InterpolationIndex,
};
use std::{collections::BTreeMap, hash::Hash};

/// Curve index trait.
pub trait CurveIndex: Ord + Hash + InterpolationIndex + Clone + Copy {}
impl<T> CurveIndex for T where T: Ord + Hash + InterpolationIndex + Clone + Copy {}

/// Curve data structure.
pub struct Curve<C>
where
C: CurveIndex,
{
/// The nodes of the curve.
pub nodes: BTreeMap<C, f64>,
}

macro_rules! impl_curve {
($index:ty) => {
impl Curve<$index> {
/// Create a new curve.
pub fn new() -> Self {
Self {
nodes: BTreeMap::new(),
}
}

/// Get the first key in the curve.
pub fn first_key(&self) -> Option<&$index> {
self.nodes.keys().next()
}

/// Get the last key in the curve.
pub fn last_key(&self) -> Option<&$index> {
self.nodes.keys().next_back()
}

/// Get the first value in the curve.
pub fn first_value(&self) -> Option<&f64> {
self.nodes.values().next()
}

/// Get the last value in the curve.
pub fn last_value(&self) -> Option<&f64> {
self.nodes.values().next_back()
}

/// Add a node to the curve.
pub fn insert(&mut self, index: $index, value: f64) {
self.nodes.insert(index, value);
}

/// Get a value for a specific index.
pub fn get(&self, index: $index) -> Option<&f64> {
self.nodes.get(&index)
}

/// Get a mutable reference to a value for a specific index.
pub fn get_mut(&mut self, index: $index) -> Option<&mut f64> {
self.nodes.get_mut(&index)
}

/// Create a Curve from a vector of indices and values.
pub fn new_from_slice(indices: &[$index], values: &[f64]) -> Self {
let mut curve = Self::new();

for (index, value) in indices.iter().zip(values.iter()) {
curve.insert(*index, *value);
}

curve
}

/// Create a Curve from a function.
pub fn new_from_function<F>(f: F, indices: &[$index]) -> Self
where
F: Fn($index) -> f64,
{
let mut curve = Self::new();

for index in indices {
curve.insert(*index, f(*index));
}

curve
}

/// Create a Curve from a constant value.
pub fn new_from_constant(value: f64, indices: &[$index]) -> Self {
let mut curve = Self::new();

for index in indices {
curve.insert(*index, value);
}

curve
}

/// Get the bracketing indices for a specific index.
pub fn get_brackets(&self, index: $index) -> ($index, $index) {
let first = self.first_key().unwrap();
let last = self.last_key().unwrap();

if index <= *first {
return (*first, *first);
}

if index >= *last {
return (*last, *last);
}

let left = self.nodes.range(..index).next_back().unwrap().0;
let right = self.nodes.range(index..).next().unwrap().0;

return (*left, *right);
}

/// Shift the curve by a constant value.
pub fn shift(&mut self, shift: f64) {
for value in self.nodes.values_mut() {
*value += shift;
}
}

/// Interpolate the curve at a specific index.
///
/// Note: This method modifies the curve by adding the interpolated value.
pub fn interpolate(&mut self, index: $index) {
let xs: Vec<$index> = self.nodes.keys().cloned().collect();
let ys: Vec<f64> = self.nodes.values().cloned().collect();

let interpolator = LinearInterpolator::new(xs, ys).unwrap();

self.insert(index, interpolator.interpolate(index).unwrap());
}

/// Interpolate the curve at multiple indices.
///
/// Note: This method modifies the curve by adding the interpolated values.
pub fn interpolate_many(&mut self, indices: &[$index]) {
let xs: Vec<$index> = self.nodes.keys().cloned().collect();
let ys: Vec<f64> = self.nodes.values().cloned().collect();

let interpolator = LinearInterpolator::new(xs, ys).unwrap();

for index in indices {
self.insert(*index, interpolator.interpolate(*index).unwrap());
}
}
}
};
}

// Implement the Curve for temporal types.
impl_curve!(time::Date);
impl_curve!(time::Time);
impl_curve!(time::OffsetDateTime);
impl_curve!(time::PrimitiveDateTime);

// THE FOLLOWING CANNOT BE IMPLEMENTED DUE TO RESTRICTIONS WITHIN THE INTERPOLATION MODULE.

// Implement the Curve for unsigned integer types.
// impl_curve!(u64);
// impl_curve!(u32);
// impl_curve!(u16);
// impl_curve!(u8);
// impl_curve!(usize);

// Implement the Curve for signed integer types.
// impl_curve!(i64);
// impl_curve!(i32);
// impl_curve!(i16);
// impl_curve!(i8);
// impl_curve!(isize);
27 changes: 27 additions & 0 deletions src/data/curves/curve_model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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 time::Date;

/// A trait for curve models.
#[allow(clippy::module_name_repetitions)]
pub trait CurveModel {
/// Returns the forward rate for a given date.
fn forward_rate(&self, date: Date) -> f64;

/// Returns the spot rate for a given date.
fn spot_rate(&self, date: Date) -> f64;

/// Returns the discount factor for a given date.
fn discount_factor(&self, date: Date) -> f64;

// /// Calibrates the model to a set of market rates.
// #[must_use]
// fn calibrate<C: Curve>(&self, curve: C) -> Self;
}
91 changes: 91 additions & 0 deletions src/data/curves/curve_trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/// Base trait for all curves to implement.
pub trait Curve {
/// Initial date of the curve.
fn initial_date(&self) -> Date;

/// Final date of the curve.
fn terminal_date(&self) -> Date;

/// Updates the rate for the given date.
fn update_rate(&mut self, date: Date, rate: f64);

/// Create a new curve from a set of dates and rates.
fn from_dates_and_rates(dates: &[Date], rates: &[f64]) -> Self;

/// Create a new curve from an initial date, a set of rates, and a set of
/// durations.
/// The dates are calculated as the initial date plus the duration, thus
/// there must be:
/// - One initial date
/// - n rates
/// - n-1 durations
fn from_initial_date_rates_and_durations(
initial_date: Date,
rates: &[f64],
durations: &[Duration],
) -> Self;

/// Function to find the interval of dates that contains the given date.
/// The interval is defined by the two dates that are closest to the given
/// date, just before and just after.
fn find_date_interval(&self, date: Date) -> (Date, Date);

/// Returns the rate for the given date, using linear interpolation for
/// dates between the curve's initial and terminal dates.
/// If the date is outside the curve's range, we panic.
///
/// We use the following formula for the interpolation:
///
/// $$
/// y = \frac{y_0 (x_1 - x) + y_1 (x - x_0)}{x_1 - x_0}
/// $$
///
/// Note: there must be at least two points in the curve, otherwise
/// we consider the curve to be a flat rate, and return the same rate
/// for all dates.
fn rate(&self, date: Date) -> f64;

/// Returns the discount factor for the given date.
/// This is a convenience function that calls [`rate`](Curve::rate) to get the rate for
/// the given date, and then calculates the discount factor using the
/// formula:
/// $$
/// p(t) = e^{- r \cdot t}
/// $$
fn discount_factor(&self, date: Date) -> f64 {
let t = DayCountConvention::default().day_count_factor(self.initial_date(), date);

f64::exp(-self.rate(date) * t)
}

/// Returns multiple discount factors for the given dates.
/// This is a convenience function that calls [`discount_factor`](Curve::discount_factor) for each
/// date.
fn discount_factors(&self, dates: &[Date]) -> Vec<f64> {
dates
.iter()
.map(|date| self.discount_factor(*date))
.collect::<Vec<f64>>()
}
}

#[allow(clippy::module_name_repetitions)]
/// Yield curve struct.
pub struct YieldCurve {
/// Map of dates and rates.
/// The dates are the keys and the rates are the values.
/// The reason for using a [BTreeMap] is that it is sorted by date,
/// which makes sense for a term structure.
pub rates: BTreeMap<Date, f64>,
// /// A model for the curve.
// pub model: Option<M>,
}
8 changes: 8 additions & 0 deletions src/data/curves/discount_curve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 changes: 8 additions & 0 deletions src/data/curves/forward_curve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 changes: 8 additions & 0 deletions src/data/curves/inflation_curve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 changes: 16 additions & 0 deletions src/data/curves/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/// Basic curve data structure.
pub mod curve;
pub use curve::*;

/// Curve model trait.
pub mod curve_model;
pub use curve_model::*;
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit a3a4fee

Please sign in to comment.