Skip to content

Commit fa66d3a

Browse files
committed
Amended bond code to use payment dates rather than coupon dates
1 parent d4491be commit fa66d3a

File tree

13 files changed

+66
-40
lines changed

13 files changed

+66
-40
lines changed

financepy/products/bonds/bond.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -692,18 +692,20 @@ def dirty_price_from_discount_curve(
692692
df = 1.0
693693
df_settle_dt = discount_curve.df(settle_dt)
694694

695-
dt = self.payment_dts[1]
696-
if dt > settle_dt:
697-
df = discount_curve.df(dt)
695+
cpn_dt = self.cpn_dts[1]
696+
pmt_dt = self.payment_dts[1]
697+
698+
if cpn_dt > settle_dt:
699+
df = discount_curve.df(pmt_dt)
698700
flow = self.cpn / self.freq
699701
pv = flow * df
700702
px += pv * pay_first_cpn
701703

702-
for dt in self.payment_dts[2:]:
704+
for cpn_dt, pmt_dt in zip(self.cpn_dts[2:], self.payment_dts[2:]):
703705

704706
# coupons paid on a settlement date are paid to the seller
705-
if dt > settle_dt:
706-
df = discount_curve.df(dt)
707+
if cpn_dt > settle_dt:
708+
df = discount_curve.df(pmt_dt)
707709
flow = self.cpn / self.freq
708710
pv = flow * df
709711
px += pv

financepy/products/bonds/bond_annuity.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def __init__(
5050
self.par = 100.0
5151

5252
self.cpn_dts = []
53+
5354
self.settle_dt = Date(1, 1, 1900)
5455
self.accrued_int = None
5556
self.accrued_days = 0.0

financepy/products/bonds/bond_callable.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ def __init__(
121121
self.put_dts = put_dts
122122
self.put_prices = put_prices
123123
self.par = 100.0
124-
self.bond._calculate_unadjusted_cpn_dts()
124+
self.bond._calculate_cpn_dts()
125+
self.bond._calculate_payment_dts()
125126

126127
####################################################################################
127128

@@ -136,7 +137,7 @@ def value(self, settle_dt: Date, discount_curve: DiscountCurve, model):
136137
cpn_times = []
137138
cpn_amounts = []
138139

139-
for flow_dt in self.bond.cpn_dts[1:]:
140+
for flow_dt in self.bond.payment_dts[1:]:
140141
if flow_dt > settle_dt:
141142
cpn_time = (flow_dt - settle_dt) / G_DAYS_IN_YEARS
142143
cpn_times.append(cpn_time)
@@ -209,10 +210,11 @@ def value(self, settle_dt: Date, discount_curve: DiscountCurve, model):
209210

210211
elif isinstance(model, BKTree):
211212

212-
"""Because we not have a closed form bond price we need to build
213-
the tree out to the bond maturity which is after option expiry."""
213+
# Because we not have a closed form bond price we need to build
214+
# the tree out to the bond maturity which is after option expiry.
214215

215216
model.build_tree(t_mat, df_times, df_values)
217+
216218
v1 = model.callable_puttable_bond_tree(
217219
cpn_times,
218220
cpn_amounts,
@@ -222,8 +224,11 @@ def value(self, settle_dt: Date, discount_curve: DiscountCurve, model):
222224
put_prices,
223225
face_amount,
224226
)
227+
225228
model.num_time_steps += 1
229+
226230
model.build_tree(t_mat, df_times, df_values)
231+
227232
v2 = model.callable_puttable_bond_tree(
228233
cpn_times,
229234
cpn_amounts,

financepy/products/bonds/bond_convertible.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from ...utils.calendar import CalendarTypes
2323
from ...utils.calendar import BusDayAdjustTypes
2424
from ...utils.calendar import DateGenRuleTypes
25+
from ...utils.calendar import Calendar
2526

2627
from ...market.curves.discount_curve import DiscountCurve
2728
from ...market.curves.interpolator import InterpTypes, _uinterpolate
@@ -339,7 +340,11 @@ def __init__(
339340

340341
self._pcd = None
341342
self._ncd = None
343+
344+
self.cal_type = cal_type
345+
342346
self.cpn_dts = []
347+
self.payment_dts = []
343348

344349
###########################################################################
345350

@@ -367,6 +372,17 @@ def _calculate_cpn_dts(self, settle_dt: Date):
367372
self._pcd = self.cpn_dts[0]
368373
self._ncd = self.cpn_dts[1]
369374

375+
calendar = Calendar(self.cal_type)
376+
377+
self.payment_dts = []
378+
379+
# I do not adjust the first date as it is the issue date
380+
self.payment_dts.append(self.cpn_dts[0])
381+
382+
for cpn_dt in self.cpn_dts[1:]:
383+
pmt_dt = calendar.adjust(cpn_dt, bd_type)
384+
self.payment_dts.append(pmt_dt)
385+
370386
self.accrued_int = None
371387
self.accrued_interest(settle_dt, 1.0)
372388

@@ -424,7 +440,7 @@ def value(
424440

425441
cpn = self.cpn / self.freq
426442

427-
for dt in self.cpn_dts[1:]:
443+
for dt in self.payment_dts[1:]:
428444
flow_time = (dt - settle_dt) / G_DAYS_IN_YEARS
429445
cpn_times.append(flow_time)
430446
cpn_flows.append(cpn)

financepy/products/bonds/bond_option.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,11 @@ def value(self, value_dt: Date, discount_curve: DiscountCurve, model):
7070

7171
# We need all the flows in case the option is American
7272
# and some occur before expiry
73-
flow_dts = self.bond.cpn_dts
73+
flow_dts = self.bond.payment_dts
7474
flow_amounts = self.bond.flow_amounts
7575

7676
cpn_times = []
7777
cpn_flows = []
78-
7978
num_flows = len(self.bond.cpn_dts)
8079

8180
# Want the first flow to be the previous coupon date

financepy/products/inflation/FinInflationBond.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def __init__(
8484
self.accrued_days = 0.0
8585
self.alpha = 0.0
8686

87-
self._calculate_unadjusted_cpn_dts()
87+
self._calculate_cpn_dts()
8888
self._calculate_flow_amounts()
8989

9090
###########################################################################

golden_tests/TestFinBond.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,9 @@ def test_bond_ex_dividend():
508508
test_cases.print(settle_dt, dirty_price, accrued, clean_price)
509509

510510

511+
# print(settle_dt, dirty_price, accrued, clean_price)
512+
513+
511514
########################################################################################
512515

513516

golden_tests/run_all_tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def main(start_index=0, end_index=None):
4040
module_name = basename(module_path)[:-3]
4141

4242
print(
43-
f"TEST: {idx + 1:3d} out of {num_modules:3d}: MODULE: {module_name:<35} ",
43+
f"TEST: {idx + 1:3d} of {num_modules:3d}: {module_name:<35} ",
4444
end="",
4545
)
4646

@@ -57,13 +57,13 @@ def main(start_index=0, end_index=None):
5757

5858
# print(f"WARNINGS: {num_warnings:3d} ERRORS: {num_errors:3d} ", end="")
5959
print(
60-
f"TIME: {elapsed:6.3f} s "
60+
f"TIME: {elapsed:6.3f} s "
6161
f"WARNINGS: {num_warnings:3d} ERRORS: {num_errors:3d}",
6262
end="",
6363
)
6464

6565
if num_errors > 0:
66-
print("*" * num_errors, end="")
66+
print("*" * 1, end="")
6767

6868
print()
6969

unit_tests/test_FinBond.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,8 @@ def test_key_rate_durations_bloomberg_example():
355355
-0.001,
356356
-0.009,
357357
-0.022,
358-
1.432,
359-
2.527,
358+
1.423,
359+
2.541,
360360
0.00,
361361
0.00,
362362
0.00,

unit_tests/test_FinBondEmbeddedOption.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def test_quantlib_clean_price_from_discount_curve():
160160
settle_dt_quantlib, discount_curve_quantlib
161161
)
162162

163-
assert round(v, 4) == 94.6318
163+
assert round(v, 4) == 94.6313
164164

165165

166166
########################################################################################
@@ -174,8 +174,8 @@ def test_quantlib_bk():
174174

175175
v = puttable_bond_quantlib.value(settle_dt_quantlib, discount_curve_quantlib, model)
176176

177-
assert round(v["bondwithoption"], 4) == 89.7614
178-
assert round(v["bondpure"], 4) == 95.0619
177+
assert round(v["bondwithoption"], 4) == 89.7546
178+
assert round(v["bondpure"], 4) == 95.0614
179179

180180

181181
########################################################################################
@@ -190,5 +190,5 @@ def test_quantlib_hw():
190190

191191
v = puttable_bond_quantlib.value(settle_dt_quantlib, discount_curve_quantlib, model)
192192

193-
assert round(v["bondwithoption"], 4) == 68.8665
194-
assert round(v["bondpure"], 4) == 95.0619
193+
assert round(v["bondwithoption"], 4) == 68.8612
194+
assert round(v["bondpure"], 4) == 95.0614

0 commit comments

Comments
 (0)