[go: up one dir, main page]

Skip to content

Commit

Permalink
Amended FinTestCases and Fixed bugs in date generation (add tenor)
Browse files Browse the repository at this point in the history
  • Loading branch information
domokane committed Sep 26, 2021
1 parent c3ffdf7 commit 2d43d10
Show file tree
Hide file tree
Showing 23 changed files with 171 additions and 127 deletions.
11 changes: 7 additions & 4 deletions financepy/market/curves/discount_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ def __init__(self,
self._dfs = np.array(self._dfs)
self._interp_type = interp_type
self._freq_type = FrequencyTypes.CONTINUOUS
self._day_count_type = None # Not needed for this curve
# This needs to be thought about - I just assign an arbitrary value
self._day_count_type = DayCountTypes.ACT_ACT_ISDA
self._interpolator = Interpolator(self._interp_type)
self._interpolator.fit(self._times, self._dfs)

Expand Down Expand Up @@ -280,11 +281,13 @@ def swap_rate(self,
###############################################################################

def df(self,
dt: (list, Date)):
dt: (list, Date),
day_count = DayCountTypes.ACT_ACT_ISDA):
""" Function to calculate a discount factor from a date or a
vector of dates. """
vector of dates. The day count determines how dates get converted to
years. I allow this to default to ACT_ACT_ISDA unless specified. """

times = times_from_dates(dt, self._valuation_date, self._day_count_type)
times = times_from_dates(dt, self._valuation_date, day_count)
dfs = self._df(times)

if isinstance(dfs, float):
Expand Down
2 changes: 1 addition & 1 deletion financepy/market/volatility/equity_vol_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def _delta_fit(k, *args):


@njit(float64(float64, float64, float64, float64, int64, int64, float64,
float64, float64[:]), fastmath=True)
float64, float64[:]), cache=True, fastmath=True)
def _solver_for_smile_strike(s, t, r, q,
option_type_value,
volatilityTypeValue,
Expand Down
2 changes: 1 addition & 1 deletion financepy/models/bk_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ def callable_puttable_bond_tree_fast(coupon_times, coupon_flows,
###############################################################################


@njit(fastmath=True)
@njit(fastmath=True, cache=True)
def build_tree_fast(a, sigma, tree_times, num_time_steps, discount_factors):
""" Calibrate the tree to a term structure of interest rates. """

Expand Down
2 changes: 1 addition & 1 deletion financepy/models/option_implied_dbn.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
###############################################################################

@njit(float64[:](float64, float64, float64, float64, float64[:],
float64[:]), cache=True)
float64[:]), cache=True, fastmath=True)
def option_implied_dbn(s, t, r, q, strikes, sigmas):
""" This function calculates the option smile/skew-implied probability
density function times the interval width. """
Expand Down
7 changes: 6 additions & 1 deletion financepy/products/rates/dual_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,12 @@ def _validate_inputs(self,
self._usedDeposits = ibor_deposits
self._usedFRAs = ibor_fras
self._usedSwaps = ibor_swaps
self._day_count_type = None

# Need the floating leg basis for the curve
if len(self._usedSwaps) > 0:
self._day_count_type = ibor_swaps[0]._float_leg._day_count_type
else:
self._day_count_type = None

###############################################################################

Expand Down
7 changes: 6 additions & 1 deletion financepy/products/rates/ibor_single_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,13 @@ def _validate_inputs(self,
self._usedDeposits = ibor_deposits
self._usedFRAs = ibor_fras
self._usedSwaps = ibor_swaps
self._day_count_type = None

# Need the floating leg basis for the curve
if len(self._usedSwaps) > 0:
self._day_count_type = ibor_swaps[0]._float_leg._day_count_type
else:
self._day_count_type = None

###############################################################################

def _build_curve_using_1d_solver(self):
Expand Down
17 changes: 8 additions & 9 deletions financepy/products/rates/ibor_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def __init__(self,
bus_day_adjust_type,
date_gen_rule_type)

self._floatLeg = SwapFloatLeg(effective_date,
self._float_leg = SwapFloatLeg(effective_date,
self._termination_date,
float_leg_type,
float_spread,
Expand Down Expand Up @@ -125,7 +125,7 @@ def value(self,
fixed_leg_value = self._fixed_leg.value(valuation_date,
discount_curve)

float_leg_value = self._floatLeg.value(valuation_date,
float_leg_value = self._float_leg.value(valuation_date,
discount_curve,
index_curve,
firstFixingRate)
Expand All @@ -139,10 +139,9 @@ def pv01(self, valuation_date, discount_curve):
""" Calculate the value of 1 basis point coupon on the fixed leg. """

pv = self._fixed_leg.value(valuation_date, discount_curve)

# Needs to be positive even if it is a payer leg
pv = np.abs(pv)
pv01 = pv / self._fixed_leg._coupon / self._fixed_leg._notional
# Needs to be positive even if it is a payer leg
pv01 = np.abs(pv01)
return pv01

###########################################################################
Expand Down Expand Up @@ -177,7 +176,7 @@ def swap_rate(self,
df_t = discount_curve.df(self._maturity_date)
float_leg_pv = (df0 - df_t)
else:
float_leg_pv = self._floatLeg.value(valuation_date,
float_leg_pv = self._float_leg.value(valuation_date,
discount_curve,
index_curve,
first_fixing)
Expand Down Expand Up @@ -239,7 +238,7 @@ def print_float_leg_pv(self):
""" Prints the fixed leg amounts without any valuation details. Shows
the dates and sizes of the promised fixed leg flows. """

self._floatLeg.print_valuation()
self._float_leg.print_valuation()

###########################################################################

Expand All @@ -248,7 +247,7 @@ def print_flows(self):
the dates and sizes of the promised fixed leg flows. """

self._fixed_leg.print_payments()
self._floatLeg.print_payments()
self._float_leg.print_payments()

###########################################################################

Expand All @@ -257,7 +256,7 @@ def __repr__(self):
s = label_to_string("OBJECT TYPE", type(self).__name__)
s += self._fixed_leg.__repr__()
s += "\n"
s += self._floatLeg.__repr__()
s += self._float_leg.__repr__()
return s

###########################################################################
Expand Down
69 changes: 31 additions & 38 deletions financepy/products/rates/ois.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ...utils.frequency import FrequencyTypes
from ...utils.calendar import CalendarTypes, DateGenRuleTypes
from ...utils.calendar import Calendar, BusDayAdjustTypes
from ...utils.helpers import check_argument_types
from ...utils.helpers import check_argument_types, label_to_string
from ...utils.math import ONE_MILLION
from ...utils.global_types import SwapTypes
from ...market.curves.discount_curve import DiscountCurve
Expand Down Expand Up @@ -65,12 +65,12 @@ def __init__(self,
termination_date_or_tenor: (Date, str), # Date contract ends
fixed_leg_type: SwapTypes,
fixed_coupon: float, # Fixed coupon (annualised)
fixedFreqType: FrequencyTypes,
fixed_freq_type: FrequencyTypes,
fixed_day_count_type: DayCountTypes,
notional: float = ONE_MILLION,
payment_lag: int = 0, # Number of days after period payment occurs
float_spread: float = 0.0,
floatFreqType: FrequencyTypes = FrequencyTypes.ANNUAL,
float_freq_type: FrequencyTypes = FrequencyTypes.ANNUAL,
float_day_count_type: DayCountTypes = DayCountTypes.THIRTY_E_360,
calendar_type: CalendarTypes = CalendarTypes.WEEKEND,
bus_day_adjust_type: BusDayAdjustTypes = BusDayAdjustTypes.FOLLOWING,
Expand Down Expand Up @@ -100,17 +100,17 @@ def __init__(self,

self._effective_date = effective_date

floatLegType = SwapTypes.PAY
float_leg_type = SwapTypes.PAY
if fixed_leg_type == SwapTypes.PAY:
floatLegType = SwapTypes.RECEIVE
float_leg_type = SwapTypes.RECEIVE

principal = 0.0

self._fixed_leg = SwapFixedLeg(effective_date,
self._termination_date,
fixed_leg_type,
fixed_coupon,
fixedFreqType,
fixed_freq_type,
fixed_day_count_type,
notional,
principal,
Expand All @@ -119,11 +119,11 @@ def __init__(self,
bus_day_adjust_type,
date_gen_rule_type)

self._floatLeg = SwapFloatLeg(effective_date,
self._float_leg = SwapFloatLeg(effective_date,
self._termination_date,
floatLegType,
float_leg_type,
float_spread,
floatFreqType,
float_freq_type,
float_day_count_type,
notional,
principal,
Expand All @@ -136,18 +136,18 @@ def __init__(self,

def value(self,
valuation_date: Date,
oisCurve: DiscountCurve,
firstFixingRate=None):
ois_curve: DiscountCurve,
first_fixing_rate=None):
""" Value the interest rate swap on a value date given a single Ibor
discount curve. """

fixed_leg_value = self._fixed_leg.value(valuation_date,
oisCurve)
ois_curve)

float_leg_value = self._floatLeg.value(valuation_date,
oisCurve,
oisCurve,
firstFixingRate)
float_leg_value = self._float_leg.value(valuation_date,
ois_curve,
ois_curve,
first_fixing_rate)

value = fixed_leg_value + float_leg_value
return value
Expand All @@ -157,37 +157,31 @@ def value(self,
def pv01(self, valuation_date, discount_curve):
""" Calculate the value of 1 basis point coupon on the fixed leg. """

pv = self._fixed_leg.value(valuation_date, discount_curve)

# Needs to be positive even if it is a payer leg
pv = np.abs(pv)
pv = self._fixed_leg.value(valuation_date, discount_curve)
pv01 = pv / self._fixed_leg._coupon / self._fixed_leg._notional

# Needs to be positive even if it is a payer leg and/or coupon < 0
pv01 = np.abs(pv01)
return pv01

##########################################################################

def swap_rate(self, valuation_date, oisCurve):
def swap_rate(self, valuation_date, ois_curve, first_fixing_rate = None):
""" Calculate the fixed leg coupon that makes the swap worth zero.
If the valuation date is before the swap payments start then this
is the forward swap rate as it starts in the future. The swap rate
is then a forward swap rate and so we use a forward discount
factor. If the swap fixed leg has begun then we have a spot
starting swap. """

pv01 = self.pv01(valuation_date, oisCurve)

if valuation_date < self._effective_date:
df0 = oisCurve.df(self._effective_date)
else:
df0 = oisCurve.df(valuation_date)
pv01 = self.pv01(valuation_date, ois_curve)

floatLegPV = 0.0

dfT = oisCurve.df(self._maturity_date)
floatLegPV = (df0 - dfT)
# Removed the next line as it seems unneccesary
# floatLegPV /= self._fixed_leg._notional
cpn = floatLegPV / pv01
float_leg_value = self._float_leg.value(valuation_date,
ois_curve,
ois_curve,
first_fixing_rate)

cpn = float_leg_value / pv01
return cpn

###############################################################################
Expand All @@ -204,7 +198,7 @@ def print_float_leg_pv(self):
""" Prints the fixed leg amounts without any valuation details. Shows
the dates and sizes of the promised fixed leg flows. """

self._floatLeg.print_valuation()
self._float_leg.print_valuation()

###############################################################################

Expand All @@ -213,16 +207,15 @@ def print_flows(self):
the dates and sizes of the promised fixed leg flows. """

self._fixed_leg.print_payments()

self._floatLeg.print_payments()
self._float_leg.print_payments()

##########################################################################

def __repr__(self):
s = label_to_string("OBJECT TYPE", type(self).__name__)
s += self._fixed_leg.__repr__()
s += "\n"
s += self._floatLeg.__repr__()
s += self._float_leg.__repr__()
return s

###############################################################################
Expand Down
7 changes: 6 additions & 1 deletion financepy/products/rates/ois_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,12 @@ def _validate_inputs(self,
self._usedDeposits = oisDeposits
self._usedFRAs = oisFRAs
self._usedSwaps = oisSwaps
self._day_count_type = None

# Need the floating leg basis for the curve
if len(self._usedSwaps) > 0:
self._day_count_type = oisSwaps[0]._float_leg._day_count_type
else:
self._day_count_type = None

###############################################################################

Expand Down
12 changes: 9 additions & 3 deletions financepy/products/rates/swap_float_leg.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ def value(self,
numPayments = len(self._payment_dates)
firstPayment = False

index_basis = index_curve._day_count_type
index_day_counter = DayCount(index_basis)

for iPmnt in range(0, numPayments):

pmntDate = self._payment_dates[iPmnt]
Expand All @@ -168,7 +171,10 @@ def value(self,

startAccruedDt = self._startAccruedDates[iPmnt]
endAccruedDt = self._endAccruedDates[iPmnt]
alpha = self._year_fracs[iPmnt]
pay_alpha = self._year_fracs[iPmnt]

(index_alpha, num, _) = index_day_counter.year_frac(startAccruedDt,
endAccruedDt)

if firstPayment is False and firstFixingRate is not None:

Expand All @@ -179,9 +185,9 @@ def value(self,

dfStart = index_curve.df(startAccruedDt)
dfEnd = index_curve.df(endAccruedDt)
fwd_rate = (dfStart / dfEnd - 1.0) / alpha
fwd_rate = (dfStart / dfEnd - 1.0) / index_alpha

pmntAmount = (fwd_rate + self._spread) * alpha * notional
pmntAmount = (fwd_rate + self._spread) * pay_alpha * notional

dfPmnt = discount_curve.df(pmntDate) / dfValue
pmntPV = pmntAmount * dfPmnt
Expand Down
9 changes: 9 additions & 0 deletions financepy/utils/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ def add_tenor(self,
newDates = []

for tenStr in tenor:

tenStr = tenStr.upper()
DAYS = 1
WEEKS = 2
Expand Down Expand Up @@ -727,6 +728,13 @@ def add_tenor(self,
elif periodType == MONTHS:
for _ in range(0, num_periods):
newDate = newDate.add_months(1)

# in case we landed on a 28th Feb and lost the month day we add this logic
y = newDate._y
m = newDate._m
d = min(self._d, newDate.eom()._d)
newDate = Date(d, m, y)

elif periodType == YEARS:
for _ in range(0, num_periods):
newDate = newDate.add_months(12)
Expand Down Expand Up @@ -962,3 +970,4 @@ def test_type():
print("TEST TYPE", gDateFormatType)

###############################################################################

Loading

0 comments on commit 2d43d10

Please sign in to comment.