Source code for finpricing.market.survival_curve_ns
from ..utils.date import Date
import datetime
import math
import scipy
import numpy as np
from typing import Union
[docs]
class SurvivalCurve:
def __init__(self, anchor_date) -> None:
self.anchor_date = anchor_date
[docs]
def survival(self, date: Union[Date, datetime.date]):
return NotImplementedError("Please implement survival() method in derived class")
[docs]
class SurvivalCurveNelsonSiegel(SurvivalCurve):
def __init__(self,
anchor_date: Union[Date, datetime.date],
pivot_dates: list[Union[Date, datetime.date]],
params: list[float]=None) -> None:
anchor_date = Date.convert_from_datetime(anchor_date)
super().__init__(anchor_date)
self.pivot_dates = Date.convert_from_datetimes(pivot_dates)
self.params = [0., 0., 0.] if params is None else params
# derived attributes
self.pivots = [ date - anchor_date for date in self.pivot_dates ]
[docs]
@staticmethod
def f(t, a):
threshold = 1e-1
if t < threshold:
return 1.0 / a - t / ( 2 * a ** 2 ) + t ** 2 / ( 6 * a ** 3 )
else:
return ( 1.0 - math.exp( -t / a ) ) / t
[docs]
@staticmethod
def f_integral(t, a):
return math.log( t / a ) + scipy.special.exp1( t / a ) + np.euler_gamma
[docs]
def hazard_rates(self, t: float) -> float:
return self.params[0] + sum(param * self.f(t, pivot) for param, pivot in zip(self.params[1:], self.pivots))
[docs]
def survival(self, date: Union[Date, datetime.date]):
date = Date.convert_from_datetime(date)
tenor = date - self.anchor_date
if tenor == 0:
return 1.0
integral = self.params[0] * tenor
for i, p in enumerate(self.pivots):
integral += self.params[i + 1] * self.f_integral(tenor, p)
return math.exp(-integral)
[docs]
def getSurvivalCurve(self, params: list[float]):
"""create a new survival curve with the same anchor date and pivot dates but different parameters"""
return SurvivalCurveNelsonSiegel(
anchor_date=self.anchor_date,
pivot_dates=self.pivot_dates,
params=params
)