API Documentation
Cashflow Objects
Cashflow Payoffs
fixed cashflow payoff |
|
interest rate cashflow payoff |
|
European option payoff function |
- class dcf.payoffs.FixedCashFlowPayOff(pay_date, amount=1.0, forward_curve=None)[source]
Bases:
CashFlowPayOfffixed cashflow payoff
- Parameters:
pay_date – cashflow payment date \(t\)
amount – notional amount \(N\) (might be a callable function)
forward_curve – price forward curve \(P(t)\)
A fixed cashflow payoff \(X(t)\) at \(t\) is given directly by the notional amount \(N + P(t)\)
Invoking details method \(X()\) or \(X(t, P)\) with a forward_curve object \(P\) as argument returns the cashflow details as a dict-like object.
>>> from dcf import FixedCashFlowPayOff >>> cf = FixedCashFlowPayOff(0.25, amount=123.456) >>> cf.details() Details( {'pay date': 0.25, 'cashflow': 123.456} )
The actual expected cashflow payoff amount of \(X\) (which is again just the fixed amount \(N\)) can be obtained by casting to float.
>>> cf() 123.456
- class dcf.payoffs.RateCashFlowPayOff(pay_date, start, end, amount=1.0, day_count=None, fixing_offset=None, fixed_rate=None, forward_curve=None)[source]
Bases:
CashFlowPayOffinterest rate cashflow payoff
- Parameters:
pay_date – cashflow payment date
start – interst accrued period start date \(s\)
end – interst accrued period end date \(e\)
amount – notional amount \(N\)
day_count – function to calculate accrued period year fraction \(\tau\)
fixing_offset – time difference between interest rate fixing date and interest period payment date \(\delta\)
fixed_rate – agreed fixed rate \(c\)
forward_curve – float rate forward curve either as numerical value or function. If forward_curve is None no float rate is applied, even not if
dcf.payoffs.RateCashFlowPayOff.details()is invoked with a forward curve. (optional; default is None, i.e. no float rate is applied)
A contigent interest rate cashflow payoff \(X\) is given for a float rate \(f\) at \(T=s-\delta\)
\[X(f(T)) = (f(T) + c)\ \tau(s,e)\ N\]Invoking \(X(t, f)\) with a forward_curve object \(f\) as argument returns the actual expected cashflow payoff amount of \(X\).
>>> from dcf import RateCashFlowPayOff
>>> cf = RateCashFlowPayOff(pay_date=1.0, start=1.25, end=1.5, amount=1.0, fixed_rate=0.005, forward_curve=0) >>> cf.details() Details( { 'pay date': 1.0, 'cashflow': 0.00125, 'notional': 1.0, 'is rec': True, 'fixed rate': 0.005, 'start date': 1.25, 'end date': 1.5, 'year fraction': 0.25, 'forward rate': 0, 'fixing date': 1.25, 'forward-curve-id': ...} )
>>> cf() 0.00125
suppying an iterest forward curve changes float forward rate
>>> forward_curve = 0.05 >>> cf.details(forward_curve=forward_curve) Details( { 'pay date': 1.0, 'cashflow': 0.01375, 'notional': 1.0, 'is rec': True, 'fixed rate': 0.005, 'start date': 1.25, 'end date': 1.5, 'year fraction': 0.25, 'forward rate': 0.05, 'fixing date': 1.25, 'forward-curve-id': ...} )
>>> cf(forward_curve=forward_curve) # expected cashflow 0.01375
If forward_curve is None (default) no float rate is applied. Even if forward_curve is given for details calculation.
>>> cf = RateCashFlowPayOff(pay_date=1.0, start=1.25, end=1.5, amount=1.0, fixed_rate=0.005) >>> cf.details(forward_curve=forward_curve) Details( { 'pay date': 1.0, 'cashflow': 0.00125, 'notional': 1.0, 'is rec': True, 'fixed rate': 0.005, 'start date': 1.25, 'end date': 1.5, 'year fraction': 0.25} )
- class dcf.payoffs.OptionCashFlowPayOff(pay_date, expiry=None, amount=1.0, strike=None, option_type='call', *, forward_curve=None, option_curve=None)[source]
Bases:
CashFlowPayOffEuropean option payoff function
- Parameters:
pay_date – cashflow payment date
expiry – option exipry date \(T\) (optional; by default pay_date)
amount – option notional amount \(N\) (optional: default is 1.0)
strike – strike price \(K\) (optional: default is None i.e. at-the-money strike)
option_type – str or OptionCashFlowPayOff.OPTION_TYPES enum to prick for option type call, put, cap, floor (optional with default call)
An European call option \(C_K(S(T))\) is the right to buy an agreed amount \(N\) of an asset with future price \(S(T)\) at a future point in time \(T\) (the option exipry date) for a pre-agreed strike price \(K\).
The call option payoff provides the expected profit from such transaction, i.e.
\[C_K(S(T)) = N \cdot E[ \max(S(T)-K,0) ]\]Resp. a put option \(P_K(S(T))\) is the right to sell an asset at a pre-agreed strike price. Hence, the put option payoff provides the expected profit from such transaction, i.e.
\[P_K(S(T)) = N \cdot E[ \max(K-S(T),0) ]\]As the asset price \(S(t)\) is unknown at time \(t < T\), the estimation of \(C_K(S(T))\) resp. \(P_K(S(T))\) requires assumptions on the as randomness understood unkown behavior of \(S\) until \(T\).
This is provided by an option_curve instance implementing a option_curve and is invoked by calling an
dcf.payoffs.OptionCashFlowPayOffobject.First, setup a classical log-normal Black-Scholes model.
>>> from yieldcurves import OptionPricingCurve >>> from math import exp >>> f = lambda t: 100.0 * exp(t * 0.05) # spot price 100 and yield of 5% >>> v = lambda *_: 0.1 # flat volatility of 10% >>> m = OptionPricingCurve.black76(f, volatility=v)
Then, build a call option payoff.
>>> from dcf import OptionCashFlowPayOff >>> c = OptionCashFlowPayOff(pay_date=0.33, expiry=0.25, strike=110.0) >>> # get expected option payoff >>> c.details(option_curve=m) Details( { 'pay date': 0.33, 'cashflow': 0.107267..., 'option type': 'call', 'is put': False, 'is long': True, 'notional': 1.0, 'strike': 110.0, 'forward': 101.257845..., 'valuation date': 0, 'expiry date': 0.25, 'time to expiry': 0.25, 'volatility': 0.1, 'option model': 'Black76', 'forward-curve-id': ..., 'volatility-curve-id': ..., 'option-curve-id': ...} )
>>> float(c.details(forward_curve=m)) 0.107267...
And a put option payoff.
>>> p = OptionCashFlowPayOff(pay_date=0.33, expiry=0.25, strike=110.0, option_type='put') >>> # get expected option payoff >>> p.details(option_curve=m) Details( { 'pay date': 0.33, 'cashflow': 8.849422..., 'option type': 'put', 'is put': True, 'is long': True, 'notional': 1.0, 'strike': 110.0, 'forward': 101.257845..., 'valuation date': 0, 'expiry date': 0.25, 'time to expiry': 0.25, 'volatility': 0.1, 'option model': 'Black76', 'forward-curve-id': ..., 'volatility-curve-id': ..., 'option-curve-id': ...} )
>>> float(p.details(option_curve=m)) 8.849422...
>>> p(option_curve=m) 8.849422...
- PUT_TYPES = ('put', 'floor')
- CALL_TYPES = ('call', 'cap')
- OPTION_TYPES = {'call': 'call', 'cap': 'cap', 'floor': 'floor', 'put': 'put'}
CashFlow List
- class dcf.payoffs.CashFlowList(iterable=(), /)[source]
Bases:
TSListcashflow payoff container
cashflow payoff container
- Parameters:
iterable
- classmethod from_fixed_cashflows(payment_date_list, amount_list=1.0, forward_curve=None)[source]
basic cashflow list object
- Parameters:
payment_date_list – list of cashflow payment dates
amount_list – list of cashflow amounts
forward_curve – curve to derive forward values
- classmethod from_rate_cashflows(payment_date_list, amount_list=1.0, origin=None, day_count=None, fixing_offset=None, pay_offset=None, fixed_rate=None, forward_curve=None)[source]
list of interest rate cashflows
- Parameters:
payment_date_list – pay dates, assuming that pay dates agree with end dates of interest accrued period
amount_list – notional amounts
origin – start date of first interest accrued period
day_count – day count convention
fixing_offset – time difference between interest rate fixing date and interest period payment date
pay_offset – time difference between interest period end date and interest payment date
fixed_rate – agreed fixed rate
forward_curve – interest rate curve for forward estimation
Let \(t_0\) be the list origin and \(t_i\) \(i=1, \dots n\) the payment_date_list with \(N_i\) \(i=1, \dots n\) the notional amount_list.
Moreover, let \(\tau\) be the day_count function, \(c\) the fixed_rate and \(f\) the forward_curve.
Then, the rate cashflow \(cf_i\) payed at time \(t_i\) will be with \(s_i = t_{i-1} - \delta\), \(e_i = t_i -\delta\) as well as \(d_i = s_i - \epsilon\) for pay_offset \(\delta\) and fixing_offset \(\epsilon\),
\[cf_i = N_i \cdot \tau(s_i,e_i) \cdot (c + f(d_i)).\]Note, the pay_offset \(\delta\) is not applied in case of the first cashflow, then \(s_1=t_0\).
- classmethod from_option_cashflows(payment_date_list, amount_list=1.0, strike_list=None, option_type='call', is_digital=False, fixing_offset=None, pay_offset=None, forward_curve=None)[source]
list of European option payoffs
- Parameters:
payment_date_list – list of cashflow payment dates \(t_k\)
amount_list – list of option notional amounts \(N_k\)
strike_list – list of option strike prices \(K_k\)
option_type – enum to prick for option type call, put, cap, floor (optional with default call)
is_digital – bool flag if option is digital/binary option (optional with default False)
fixing_offset – offset \(\delta\) between underlying fixing date and cashflow end date
pay_offset – offset \(\epsilon\) between cashflow end date and payment date
forward_curve – curve to derive underlying forward value
List of
dcf.payoffs.OptionCashFlowPayOffordcf.payoffs.DigitalOptionCashFlowPayOff.
- classmethod from_contingent_rate_cashflows(payment_date_list, amount_list=1.0, origin=None, day_count=None, fixing_offset=None, pay_offset=None, fixed_rate=None, cap_strike=None, floor_strike=None, forward_curve=None)[source]
list of contingent collared rate cashflows
- Parameters:
payment_date_list – pay dates, assuming that pay dates agree with end dates of interest accrued period
amount_list – notional amounts
origin – start date of first interest accrued period
day_count – day count convention
fixing_offset – time difference between interest rate fixing date and interest period payment date
pay_offset – time difference between interest period end date and interest payment date
fixed_rate – agreed fixed rate
cap_strike – upper interest rate boundary \(L\)
floor_strike – lower interest rate boundary \(K\)
forward_curve – curve to derive underlying forward value
Each object consists of a list of
dcf.payoffs.RateCashFlowPayOfffollowed eventually bydcf.payoffs.OptionCashFlowPayOfffor any gievn floor_strike and/or cap_strike, i.e. to add up to a collared payoff functions.\[X_i(f(T_i)) = [\max(K, \min(f(T_i), L)) + c]\ \tau(s,e)\ N\]with, according to a payment date \(p_i\), \(p_i-\epsilon=e_i\), \(e_i=s_{i+1}\) and \(s_i-\delta=T_i\).
- property domain
payment date list
- property origin
cashflow list start date
- property fixed_rate
Valuation Routines
Expected Payoff
- dcf.pricer.ecf(cashflow_list: CashFlowPayOff | CashFlowList, valuation_date: Any, *, forward_curve: Callable | float | dict | None = None)[source]
expected cashflow payoffs
- Parameters:
cashflow_list – list of cashflows
valuation_date – date to discount to
forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)
- Returns:
dict of expected cashflow payoffs with pay_date keys
>>> from dcf import ecf, CashFlowList
>>> cf_list = CashFlowList.from_fixed_cashflows([0., 3.], amount_list=[-100., 100.]) >>> cf_list += CashFlowList.from_rate_cashflows([0., 1., 2., 3.], amount_list=100., fixed_rate=0.05) >>> ecf(cf_list, valuation_date=0.0) {0.0: -95.0, 1.0: 5.0, 2.0: 5.0, 3.0: 105.0}
>>> import dcf >>> dcf.pricer.INCLUDE_VALUATION_DATE = False >>> ecf(cf_list, valuation_date=0.0) {1.0: 5.0, 2.0: 5.0, 3.0: 105.0}
Present Value
- dcf.pricer.pv(cashflow_list: CashFlowPayOff | CashFlowList, valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None)[source]
calculates the present value by discounting cashflows
- Parameters:
cashflow_list – list of cashflows
valuation_date – date to discount to
discount_curve – discount factors are obtained from this curve
forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)
- Returns:
float - as the sum of all discounted future cashflows
Let \(cf_1 \dots cf_n\) be the list of cashflows with payment dates \(t_1, \dots, t_n\).
Moreover, let \(t\) be the valuation date and \(T=\{t_i \mid t \leq t_i \}\).
Then the present value is given as
\[v(t) = \sum_{t_i \in T} df(t, t_i) \cdot cf_i\]with \(df(t, t_i)\), the discount factor discounting form \(t_i\) to \(t\).
Note, get_present_value includes cashflows at valuation date. Therefor it represents a start-of-day valuation than a end-of-day valuation.
>>> from yieldcurves import YieldCurve >>> from dcf import ecf, pv, CashFlowList >>> import dcf
>>> curve = YieldCurve.from_interpolation([0.0], [0.05]) >>> cf_list = CashFlowList.from_fixed_cashflows([0, 1, 2, 3], [100, 100, 100, 100])
>>> dcf.pricer.INCLUDE_VALUATION_DATE = True >>> sod = pv(cf_list, 0.0, discount_curve=curve) >>> sod 371.677...
>>> eod = sod - ecf(cf_list, 0.0)[0.0] >>> eod 271.677...
>>> dcf.pricer.INCLUDE_VALUATION_DATE = False >>> pv(cf_list, 0.0, discount_curve=curve) 271.677...
Interest Accrued
- dcf.pricer.iac(cashflow_list: CashFlowList, valuation_date: Any, *, forward_curve: Callable | float | dict | None = None)[source]
calculates interest accrued for rate cashflows
- param cashflow_list:
requires a day_count property
- param valuation_date:
calculation date
- param forward_curve:
payoff model (optional; default: None, i.e. model attached to cashflow_list)
- return:
float - proportion of interest in current interest period
Let \(t\) be the valuation date and \(s, e\) start resp. end date of current rate period, i.e. \(s \leq t < e\).
Let \(\tau\) be the day count function to calculate year fractions.
Finally, let \(cf\) be the next interest rate cashflow.
The accrued interest until \(t\) is given as
\[cf_{accrued} = cf \cdot \frac{\tau(s, t)}{\tau(s, e)}.\]Note, this function takes even expected payoffs of options incl. caplets and floorlets into account which probably should be excluded.
Example
>>> from dcf import iac, CashFlowList
setup 5y coupon bond
>>> n = 1_000_000 >>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0., fixed_rate=0.001) >>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n) >>> bond = coupon_leg + redemption_leg
bond with cashflow tables
>>> print(coupon_leg) pay date cashflow notional is rec fixed rate start date end date year fraction ---------- ---------- ---------- -------- ------------ ------------ ---------- --------------- 1.0 1_000.0 1_000_000 True 0.001 0.0 1.0 1.0 2.0 1_000.0 1_000_000 True 0.001 1.0 2.0 1.0 3.0 1_000.0 1_000_000 True 0.001 2.0 3.0 1.0 4.0 1_000.0 1_000_000 True 0.001 3.0 4.0 1.0 5.0 1_000.0 1_000_000 True 0.001 4.0 5.0 1.0
>>> print(redemption_leg) pay date cashflow ---------- ----------- 5.0 1_000_000.0
>>> iac(bond, valuation_date=3.25) 250.0
>>> iac(bond, valuation_date=3.5) 500.0
>>> iac(bond, valuation_date=4.5) 500.0
>>> # doesn't take fixed cashflows into account >>> iac(redemption_leg, valuation_date=3.25) 0.0
Yield To Maturity
- dcf.pricer.ytm(cashflow_list: CashFlowList, valuation_date: Any, *, forward_curve: Callable | float | dict | None = None, present_value: float = 0.0, compounding_frequency: int | None = None, method: str | Callable = 'secant_method', **kwargs)[source]
yield-to-maturity or effective interest rate
- Parameters:
cashflow_list – list of cashflows
valuation_date – date to discount to (optional; default: cashflow_list.origin)
forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)
present_value – price to meet by discounting (optional; default: 0.0)
compounding_frequency – compounding frequency (optional; default: None, i.e. continous compounding)
method – solver method If given as string invokes a method from curves.numerics # noqa E501 otherwise method should be a solver impelementing
method(err, **kwargs)return float result and whereerris the error function to be solved. kwargs provide arguments for method. (optional: default is secant_method with lower and upper guess of 0.01 and 0.05 and tolerance of 1e-10)args – arguments for method
kwargs – keyword arguments for method
- Returns:
float - as flat interest rate to discount all future cashflows in order to meet given present_value
Let \(cf_1 \dots cf_n\) be the list of cashflows with payment dates \(t_1, \dots, t_n\).
Moreover, let \(t\) be the valuation date and \(T=\{t_i \mid t \leq t_i \}\).
Then the yield-to-maturity is the interest rate \(y\) such that the present_value \(\hat{v}\) is given as
\[\hat{v} = \sum_{t_i \in T} df(t, t_i) \cdot cf_i\]with the discount factor either \(df(t, t_i) = \exp(-y \cdot (t_i-t))\) or \(\frac{1}{(1 + y / \tau)^{\tau \cdot (t_i-t)}}\), depending on compounding_frequency \(\tau\), discounting from \(t_i\) to \(t\).
Example
yield-to-matrurity of 5y fixed coupon bond
>>> from yieldcurves import YieldCurve >>> from dcf import CashFlowList >>> from dcf import pv, ytm
>>> curve = YieldCurve.from_interpolation([0.0], [0.05]).df >>> n = 1_000_000 >>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0.0, fixed_rate=0.001) >>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n) >>> bond = coupon_leg + redemption_leg
bond with cashflow tables
>>> print(coupon_leg) pay date cashflow notional is rec fixed rate start date end date year fraction ---------- ---------- ---------- -------- ------------ ------------ ---------- --------------- 1.0 1_000.0 1_000_000 True 0.001 0.0 1.0 1.0 2.0 1_000.0 1_000_000 True 0.001 1.0 2.0 1.0 3.0 1_000.0 1_000_000 True 0.001 2.0 3.0 1.0 4.0 1_000.0 1_000_000 True 0.001 3.0 4.0 1.0 5.0 1_000.0 1_000_000 True 0.001 4.0 5.0 1.0
>>> print(redemption_leg) pay date cashflow ---------- ----------- 5.0 1_000_000.0
get yield-to-maturity at par (gives coupon rate)
>>> ytm(bond, 0.0, present_value=n) 0.0009...
get current yield-to-maturity as given by 1.5% risk free rate (gives risk free rate)
>>> present_value = pv(bond, 0.0, curve) >>> present_value 783115.0894...
>>> ytm(bond, 0.0, present_value=present_value) 0.049999...
Fair Rate
- dcf.pricer.fair(cashflow_list: CashFlowList, valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None, present_value: float = 0.0, method: str | Callable = 'secant_method', **kwargs)[source]
coupon rate to meet given value
- Parameters:
cashflow_list – list of cashflows
discount_curve – discount factors are obtained from this curve
valuation_date – date to discount to
forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)
present_value – price to meet by discounting (optional: default: 0.0)
method – solver method If given as string invokes a method from curves.numerics otherwise method should be a solver impolementing
method(err, **kwargs)return float result and whereerris the error function to be solved. kwargs provide arguments for method. (optional: default is secant_method with lower and upper guess of 0.01 and 0.05 and tolerance of 1e-10)args – arguments for method
kwargs – keyword arguments for method
- Returns:
float - the fair coupon rate as fixed_rate of a
dcf.payoffs.RateCashFlowPayOff
Let \(cf_i(c) = N_i \cdot \tau(s_i,e_i) \cdot (c + f(d_i))\) be the \(i\)-th cashflow in the cashflow_list.
Here, the fair rate is the fixed_rate \(c=\hat{c}\) such that the present_value \(\hat{v}\) is given as
\[\hat{v} = \sum_{t_i \in T} df(t, t_i) \cdot cf_i(\hat{c})\]with \(df(t, t_i)\), the discount factor discounting form \(t_i\) to \(t\).
Note, get_fair_rate requires the cashflow_list to have an attribute fixed_rate which is perturbed to find the solution for \(\hat{c}\).
Example
>>> from yieldcurves import YieldCurve >>> from dcf import pv, fair, CashFlowList
setup 5y coupon bond
>>> curve = YieldCurve.from_interpolation([0.], [0.015]).df >>> n = 1_000_000 >>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0., fixed_rate=0.001) >>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n) >>> bond = coupon_leg + redemption_leg
find fair rate to give par bond
>>> present_value = pv(redemption_leg, 0.0, curve) >>> fair_rate = fair(coupon_leg, 0.0, curve, present_value=n-present_value) >>> fair_rate 0.0151...
check it’s a par bond (pv=notional)
>>> for cf in coupon_leg: ... cf.fixed_rate = fair_rate >>> pv(bond, 0.0, curve) 1000000.0...
Basis Point Value
- dcf.pricer.bpv(cashflow_list: CashFlowList, valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None, delta_curve: Callable | Iterable[Callable] | None = None, shift: float = 0.0001)[source]
basis point value (bpv), i.e. value change by one interest rate shifted one basis point
- Parameters:
cashflow_list – list of cashflows
discount_curve – discount factors are obtained from this curve
valuation_date – date to discount to
forward_curve – payoff model (optional; default: model attached to cashflow_list)
delta_curve – curve (or list of curves) which will be shifted (optional; default: default_curve)
shift – shift size to derive bpv (optional; default: 0.0001)
- Returns:
float - basis point value (bpv)
Let \(v(t, r)\) be the present value of the given cashflow_list depending on interest rate curve \(r\) which can be used as forward curve to estimate float rates or as zero rate curve to derive discount factors (or both).
Then, with shift_size \(s\), the bpv is given as
\[\Delta(t) = 0.0001 \cdot \frac{v(t, r + s) - v(t, r)}{s}\]Example
>>> from yieldcurves import YieldCurve >>> from dcf import pv, bpv, CashFlowList
setup 5y coupon bond
>>> n = 1_000_000 >>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0., fixed_rate=0.001) >>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n) >>> bond = coupon_leg + redemption_leg
together with a flat yield curve
>>> curve = YieldCurve(0.015) >>> pv(bond, 0.0, curve.df) 932524.5493...
calculate bpv as bond delta
>>> yc = YieldCurve(curve) >>> bpv(bond, 0.0, yc.df, delta_curve=yc.curve) -465.1755...
double check by direct valuation
>>> present_value = pv(bond, 0.0, curve.df) >>> shifted = YieldCurve(0.015 + 0.0001) >>> pv(bond, 0.0, shifted.df) - present_value -465.1755...
Bucketed Delta
- dcf.pricer.delta(cashflow_list: CashFlowList, valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None, delta_curve: Callable | Iterable[Callable] | None = None, delta_grid: Iterable[Any] | None = None, shift: float = 0.0001)[source]
list of bpv delta for partly (bucketed) shifted interest rate curve
- Parameters:
cashflow_list – list of cashflows
discount_curve – discount factors are obtained from this curve
valuation_date – date to discount to (optional; default is discount_curve.origin)
forward_curve – payoff model (optional; default: model attached to cashflow_list)
delta_curve – curve (or list of curves) which will be shifted (optional; default is discount_curve)
delta_grid – grid dates to build partly shifts (optional; default is delta_curve.domain)
shift – shift size to derive bpv (optional: default is a basis point i.e. 0.0001)
- Returns:
list(float) - basis point value for each delta_grid point
Let \(v(t, r)\) be the present value of the given cashflow_list depending on interest rate curve \(r\) which can be used as forward curve to estimate float rates or as zero rate curve to derive discount factors (or both).
Then, with shift_size \(s\) and shifting \(s_j\),
\[\Delta_j(t) = 0.0001 \cdot \frac{v(t, r + s_j) - v(t, r)}{s}\]and the full bucketed delta vector is \(\big(\Delta_1(t), \Delta_2(t), \dots, \Delta_{m-1}(t) \Delta_m(t)\big)\).
Overall the shifting \(s_1, \dots s_n\) is a partition of the unity, i.e. \(\sum_{j=1}^m s_j = s\).
Each \(s_j\) for \(i=2, \dots, m-1\) is a function of the form of an triangle, i.e. for a delta_grid \(t_1, \dots, t_m\)
\[ s_j(t) = \left\{ \begin{array}{cl} 0 & \text{ for } t < t_{j-1} \\ s \cdot \frac{t-t_{j-1}}{t_j-t_{j-1}} & \text{ for } t_{j-1} \leq t < t_j \\ s \cdot \frac{t_{j+1}-t}{t_{j+1}-t_j} & \text{ for } t_j \leq t < t_{j+1} \\ 0 & \text{ for } t_{j+1} \leq t \\ \end{array} \right. \]while
\[ s_1(t) = \left\{ \begin{array}{cl} s & \text{ for } t < t_1 \\ s \cdot \frac{t_2-t}{t_2-t_1} & \text{ for } t_1 \leq t < t_2 \\ 0 & \text{ for } t_2 \leq t \\ \end{array} \right. \]and
\[ s_m(t) = \left\{ \begin{array}{cl} 0 & \text{ for } t < t_{m-1} \\ s \cdot \frac{t-t_{m-1}}{t_m-t_{m-1}} & \text{ for } t_{m-1} \leq t < t_m \\ s & \text{ for } t_m \leq t \\ \end{array} \right. \]Example
same example as
dcf.pricer.bpv()but with buckets>>> from yieldcurves import YieldCurve >>> from dcf import pv, bpv, delta, CashFlowList
setup 5y coupon bond
>>> curve = YieldCurve.from_interpolation([0.,1.,2.,3.,4.,5.], [0.01, 0.011, 0.014, 0.012, 0.01, 0.013]) >>> n = 1_000_000 >>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0., fixed_rate=0.001) >>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n) >>> bond = coupon_leg + redemption_leg
calculate bpv as bond delta
>>> yc = YieldCurve(curve) >>> bucked_bpv = delta(bond, 0.0, yc.df, delta_curve=yc.curve, delta_grid=[0.,1.,2.,3.,4.,5.]) >>> bucked_bpv (0.0, -0.098901..., -0.194458..., -0.289348..., -0.384238..., -468.885034...)
check by summing up (should give flat bpv)
>>> sum(bucked_bpv) -469.851981...
>>> bpv(bond, 0.0, yc.df, delta_curve=yc.curve) -469.851981...
Curve Bootstrapping
- class dcf.pricer.fit(cashflow_list: Iterable[CashFlowList], valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None, price_list: Iterable[float] | None = None, fitting_curve: Callable | None = None, fitting_grid: Iterable[float] | None = None, interpolation_type: str | Callable | None = None, method: str | Callable = 'secant_method', **kwargs)[source]
Bases:
fit interpolated curve to prices
- Parameters:
cashflow_list – list of cashflows instruments, i.e. list of lists of cashflows
valuation_date – date to discount to
discount_curve – discount factors are obtained from this curve
forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)
price_list – list of prices to match (optional; default assumes all prices to be 0.0)
fitting_curve – curve to fit by inplace adding another curve, e.g. see curves.Curve() (otional; default is a yield curve derived from discount_curve)
fitting_grid – list of year fractions (float) which define the interpolation grid (optional; default: year fraction to last pay date of cashflow list in cashflow_list. to caluculate year fraction either discount_curve or
dcf.daycount.day_count()is used.)interpolation_type – function used for interpolation (optional; default: piecewise_linear as defined in yieldcurves.interpolation package, i.e. constant extrapolation and linear interpolation)
method – root finding method, for details see yieldcurves.tools.numerics. (optional; default: secant_method)
bounds – inital or bounday values (depending on method) for details see yieldcurves.tools.numerics. (optional; default: (-0.1, 0.2))
tolerance – zero tolerance for details see yieldcurves.tools.numerics. (optional; default: 1e-10)
- Returns:
dictionary of curve point and value
dcf.pricer.fit()uses the fit function in yieldcurves.interpolation.Example (with Year Fractions)
>>> from dcf import pv, fit, CashFlowList >>> from yieldcurves import YieldCurve, DateCurve
build cashflow instruments
>>> today = 0.0 >>> schedule = [1., 2., 3., 4., 5. ] >>> cashflow_list = [] >>> for d in schedule: ... pay_dates = [s for s in schedule if s <= d] ... cashflow_list.append(CashFlowList.from_fixed_cashflows(pay_dates))
setup curve to derive target values and generate data for calibration
>>> curve = YieldCurve.from_interpolation(schedule, [0.01, 0.009, 0.012, 0.014, 0.011]) >>> targets = [pv(c, 0.0, curve.df) for c in cashflow_list] # target values to match
invoke curve fitting
>>> fit(cashflow_list, today, 1.0, price_list=targets) {1.0: 0.009999999999999995, 2.0: 0.00900000082473929, 3.0: 0.011999999441079148, 4.0: 0.01400000004983385, 5.0: 0.010999999964484619}
or
>>> yc = YieldCurve(0.0) # curve to calibrate to >>> rates = fit(cashflow_list, today, yc.df, price_list=targets) # curve fitting >>> rates {1.0: 0.009999999999999995, 2.0: 0.00900000082473929, 3.0: 0.011999999441079148, 4.0: 0.01400000004983385, 5.0: 0.010999999964484619}
setup new curve
>>> yc2 = YieldCurve.from_interpolation(rates.keys(), rates.values()) >>> yc2 YieldCurve(piecewise_linear([1.0, 2.0, 3.0, 4.0, 5.0], [0.009999999999999995, 0.00900000082473929, 0.011999999441079148, 0.01400000004983385, 0.010999999964484619]))
double check results
>>> err = [abs(pv(cf, 0.0, yc2.df) - v) for cf, v in zip(cashflow_list, targets)] >>> max(err) < 1e-7 True
The above is acctually the same as
>>> yc = DateCurve(YieldCurve(0.0), origin=0.0) >>> grid = [yc.year_fraction(max(cf.domain)) for cf in cashflow_list] >>> fit(cashflow_list, today, yc.df, price_list=targets, fitting_curve=yc.curve.curve, fitting_grid=grid) {1.0: 0.009999999999999995, 2.0: 0.00900000082473929, 3.0: 0.011999999441079148, 4.0: 0.01400000004983385, 5.0: 0.010999999964484619}
Example (with BusinessDate())
>>> from businessdate import BusinessDate, BusinessSchedule
build cashflow instruments
>>> today = BusinessDate(20240101) >>> schedule = BusinessSchedule(today + '1y', today + '5y', step='1y') >>> cashflow_list = [] >>> for i, d in enumerate(schedule): ... pay_dates = [s for s in schedule if s <= d] ... cashflow_list.append(CashFlowList.from_fixed_cashflows(pay_dates))
setup curve to derive target values and generate data for calibration
>>> curve = DateCurve(YieldCurve.from_interpolation(schedule, [0.01, 0.009, 0.012, 0.014, 0.011]), origin=today) >>> targets = [pv(c, today, curve.df) for c in cashflow_list]
invoke curve fitting
>>> yc = DateCurve(YieldCurve(0.0), origin=today) >>> fit(cashflow_list, today, yc.df, price_list=targets) {1.002053388090349: 0.009747946614987986, 2.001368925393566: 0.01249726670743294, 3.0006844626967832: 0.013256157081358156, 4.0: 0.010999999998261295, 5.002053388090349: 0.011000000004102856}
The above is acctually the same as
>>> yc = DateCurve(YieldCurve(0.0), origin=today) >>> grid = [yc.year_fraction(max(cf.domain)) for cf in cashflow_list] >>> fit(cashflow_list, today, yc.df, price_list=targets, fitting_curve=yc.curve.curve, fitting_grid=grid) {1.002053388090349: 0.009747946614987986, 2.001368925393566: 0.01249726670743294, 3.0006844626967832: 0.013256157081358156, 4.0: 0.010999999998261295, 5.002053388090349: 0.011000000004102856}
Fundamentals
Build Functions
- dcf.plans.same(num, amount=1.0)[source]
all same payment plan
- Parameters:
num – number of payments \(n\)
amount – amount of each payment \(N\)
- Returns:
list(float) payment plan \(X_i\) for \(i=1 \dots n\)
Payment plan with
\[X_i = N \text{ for all } i=1 \dots n\]
- dcf.plans.bullet(num, amount=1.0)[source]
bullet payment plan
- Parameters:
num – number of payments \(n\)
amount – amount of last bullet payment \(N\)
- Returns:
list(float) payment plan \(X_i\) for \(i=1 \dots n\)
Payment plan with
\[X_i = N \text{ for } i=n \text{ else } 0\]
- dcf.plans.amortize(num, amount=1.0)[source]
linear amortize payment plan
- Parameters:
num – number of payments \(n\)
amount – amount of total sum of payment \(N\)
- Returns:
list(float) payment plan \(X_i\) for \(i=1 \dots n\)
Payment plan with
\[X_i = N/n \text{ for } i=1 \dots n\]
- dcf.plans.consumer(num, amount=1.0, fixed_rate=0.01)[source]
consumer loan annuity payment plan
- Parameters:
num – number of payments \(n\)
amount – amount of payment total \(N\)
fixed_rate – amortization rate \(r\)
- Returns:
list(float) payment plan \(X_i\) for \(i=1 \dots n\)
Actutal payment plan total \(T = N (1 + n \cdot r)\) such that
\[X_i = T / n\]
- dcf.plans.outstanding(plan, amount=1.0, sign=False)[source]
sums up plans to remaining oustanding anmount
- Parameters:
plan – payment plan \(X_i\)
amount – inital amount \(N\)
sign – \(\sigma\) sign of plan payments (optional, default: -1)
- Returns:
list(float) outstanding plan
Adds or substracts payment plan payments \(X_i\) from inital amount \(N\) such that
\[O_i = N + \sigma \cdot \sum_{k=1}^{i-1} X_i\]