Skip to content

Commit 2bce0e5

Browse files
committed
settle_dt used as standard
1 parent e38db05 commit 2bce0e5

19 files changed

+1149
-1144
lines changed

financepy/products/bonds/bond.py

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def __init__(
133133
if freq_type == FrequencyTypes.ZERO:
134134
raise FinError("Zero coupon bonds must use BondZero class.")
135135

136-
if dc_type == dc_type.ZERO:
136+
if dc_type == DayCountTypes.ZERO:
137137
raise FinError("Zero coupon bonds must use BondZero class.")
138138

139139
self.cpn = coupon
@@ -378,6 +378,7 @@ def forward_price(
378378
forward_dt: Date,
379379
clean_price: float,
380380
repo_rate: float,
381+
repo_dc_type: DayCountTypes = DayCountTypes.ACT_360,
381382
):
382383

383384
if clean_price < 0 or repo_rate < 0:
@@ -386,17 +387,17 @@ def forward_price(
386387
if settle_dt >= forward_dt:
387388
raise ValueError("Settlement date must be before forward date")
388389

389-
dc = DayCount(DayCountTypes.ACT_ACT_ISDA)
390-
t_fwd, _, _ = dc.year_frac(settle_dt, forward_dt)
390+
repo_dc = DayCount(repo_dc_type)
391+
t_fwd, _, _ = repo_dc.year_frac(settle_dt, forward_dt)
391392

392393
accrued_settle = self.accrued_interest(settle_dt)
393394
accrued_forward = self.accrued_interest(forward_dt)
394395

395396
fv_cpns = 0.0
396397
for dt, amt in zip(self.payment_dts[1:], self.flow_amounts[1:]):
397398
if settle_dt < dt <= forward_dt:
398-
t_cpn_to_forward, _, _ = dc.year_frac(dt, forward_dt)
399-
fv_cpns += amt * self.par * (1 + repo_rate * t_cpn_to_forward)
399+
t_cpn_to_forward, _, _ = repo_dc.year_frac(dt, forward_dt)
400+
fv_cpns += amt * self.par * (1.0 + repo_rate * t_cpn_to_forward)
400401

401402
full_price = clean_price + accrued_settle
402403
fwd_price = full_price * (1.0 + repo_rate * t_fwd)
@@ -679,9 +680,7 @@ def dirty_price_from_discount_curve(
679680
raise FinError("Bond settles after it matures.")
680681

681682
self._calc_pcd_ncd(settle_dt)
682-
683683
cal = Calendar(self.cal_type)
684-
685684
self.ex_div_dt = cal.add_business_days(self._ncd, -self.ex_div_days)
686685

687686
pay_first_cpn = 1.0
@@ -692,35 +691,33 @@ def dirty_price_from_discount_curve(
692691
df = 1.0
693692
df_settle_dt = discount_curve.df(settle_dt)
694693

695-
cpn_dt = self.cpn_dts[1]
696694
pmt_dt = self.payment_dts[1]
697695

698-
if cpn_dt > settle_dt:
699-
df = discount_curve.df(pmt_dt)
700-
flow = self.cpn / self.freq
701-
pv = flow * df
696+
if self.cpn_dts[1] > settle_dt:
697+
pv = (self.cpn / self.freq) * discount_curve.df(pmt_dt)
702698
px += pv * pay_first_cpn
703699

704700
for cpn_dt, pmt_dt in zip(self.cpn_dts[2:], self.payment_dts[2:]):
705-
706701
# coupons paid on a settlement date are paid to the seller
707702
if cpn_dt > settle_dt:
708-
df = discount_curve.df(pmt_dt)
709-
flow = self.cpn / self.freq
710-
pv = flow * df
703+
pv = (self.cpn / self.freq) * discount_curve.df(pmt_dt)
711704
px += pv
712705

713-
px += df
714-
px = px / df_settle_dt
706+
px += discount_curve.df(self.payment_dts[-1])
715707

716-
return px * self.par
708+
# Forward price bond to settlement date
709+
px = self.par * px / df_settle_dt
710+
return px
717711

718712
###########################################################################
719713

720714
def current_yield(self, clean_price):
721715
"""Calculate the current yield of the bond which is the
722716
coupon divided by the clean price (not the full price)"""
723717

718+
if clean_price <= 0:
719+
raise FinError("Clean price must be positive for current_yield")
720+
724721
y = self.cpn * self.par / clean_price
725722
return y
726723

@@ -729,7 +726,7 @@ def current_yield(self, clean_price):
729726
def yield_to_maturity(
730727
self,
731728
settle_dt: Date,
732-
clean_price: float,
729+
clean_price: float | list | np.ndarray,
733730
convention: YTMCalcType = YTMCalcType.US_TREASURY,
734731
):
735732
"""Calculate the bond's yield to maturity by solving the price
@@ -846,18 +843,18 @@ def asset_swap_spread(
846843
self.accrued_interest(settle_dt, 1.0)
847844
accrued_amount = self.accrued_int * self.par
848845
bond_price = clean_price + accrued_amount
846+
849847
# Calculate the price of the bond discounted on the Ibor curve
850848
pv_ibor = 0.0
851849
prev_dt = self._pcd
852850

853851
for dt in self.payment_dts[1:]:
854-
855852
# coupons paid on a settlement date are paid to the seller
856853
if dt > settle_dt:
857854
df = discount_curve.df(dt)
858855
pv_ibor += df * self.cpn / self.freq
859856

860-
pv_ibor += df
857+
pv_ibor += discount_curve.df(self.payment_dts[-1])
861858

862859
# Calculate the PV01 of the floating leg of the asset swap
863860
# I assume here that the coupon starts accruing on the settlement date
@@ -888,22 +885,22 @@ def asset_swap_spread(
888885

889886
def z_spread(
890887
self,
891-
settlement_date: Date,
888+
settle_dt: Date,
892889
clean_price: float,
893890
discount_curve: DiscountCurve,
894891
):
895892
"""Calculate the z-spread of the bond. The discount curve
896893
is a Ibor curve that is passed in."""
897894

898-
self.accrued_int = self.accrued_interest(settlement_date, 1.0)
895+
self.accrued_int = self.accrued_interest(settle_dt, 1.0)
899896
accrued_amount = self.accrued_int * self.par
900897
bond_price = clean_price + accrued_amount
901898

902899
def _bond_price_diff_from_z_spread(z_spr_try):
903-
flat_curve = DiscountCurvePWFONF.flat_curve(settlement_date, z_spr_try)
900+
flat_curve = DiscountCurvePWFONF.flat_curve(settle_dt, z_spr_try)
904901
bumped_curve = CompositeDiscountCurve([discount_curve, flat_curve])
905902
curve_bond_price = self.dirty_price_from_discount_curve(
906-
settlement_date, bumped_curve
903+
settle_dt, bumped_curve
907904
)
908905
return curve_bond_price - bond_price
909906

@@ -914,7 +911,8 @@ def _bond_price_diff_from_z_spread(z_spr_try):
914911
tol=1e-8,
915912
maxiter=50,
916913
fprime2=None,
917-
)[0]
914+
)
915+
918916
return z_spread
919917

920918
###########################################################################
@@ -1029,10 +1027,14 @@ def dirty_price_from_survival_curve(
10291027
defaulting_pv_pay_start = 0.0
10301028
defaulting_pv_pay_end = 0.0
10311029

1032-
for dt in self.payment_dts[1:]:
1030+
seen_future_flow = False
10331031

1032+
for dt in self.payment_dts[1:]:
10341033
# coupons paid on a settlement date are paid to the seller
10351034
if dt > settle_dt:
1035+
1036+
seen_future_flow = True
1037+
10361038
df = discount_curve.df(dt)
10371039
q = survival_curve.survival_prob(dt)
10381040

@@ -1050,9 +1052,13 @@ def dirty_price_from_survival_curve(
10501052
prev_q = q
10511053
prev_df = df
10521054

1053-
pv = pv + 0.50 * defaulting_pv_pay_start
1054-
pv = pv + 0.50 * defaulting_pv_pay_end
1055-
pv = pv + df * q
1055+
if seen_future_flow:
1056+
pv = pv + 0.50 * defaulting_pv_pay_start
1057+
pv = pv + 0.50 * defaulting_pv_pay_end
1058+
pv = pv + df * q
1059+
else:
1060+
pv += 0.0
1061+
10561062
pv *= self.par
10571063
return pv
10581064

financepy/products/bonds/yield_curve.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class does not allow discounting to be done and so does not inherit from
3434

3535
def __init__(
3636
self,
37-
settlement_date: Date,
37+
settle_dt: Date,
3838
bonds: list,
3939
ylds: Union[np.ndarray, list],
4040
curve_fit,
@@ -43,7 +43,7 @@ def __init__(
4343
specified. Bounds can be provided if you wish to enforce lower and
4444
upper limits on the respective model parameters."""
4545

46-
self._settlement_date = settlement_date
46+
self._settle_dt = settle_dt
4747
self._bonds = bonds
4848
self._ylds = np.array(ylds)
4949
self._curve_fit = curve_fit
@@ -53,7 +53,7 @@ def __init__(
5353

5454
years_to_maturities = []
5555
for bond in bonds:
56-
years_to_maturity = (bond._maturity_dt - settlement_date) / G_DAYS_IN_YEAR
56+
years_to_maturity = (bond._maturity_dt - settle_dt) / G_DAYS_IN_YEAR
5757
years_to_maturities.append(years_to_maturity)
5858
self._years_to_maturity = np.array(years_to_maturities)
5959

@@ -123,7 +123,7 @@ def curve_fit(self):
123123
def interpolated_yield(self, maturity_dt: Date):
124124
"""Interpolates the yield for a given maturity date."""
125125
if isinstance(maturity_dt, Date):
126-
t = (maturity_dt - self._settlement_date) / G_DAYS_IN_YEAR
126+
t = (maturity_dt - self._settle_dt) / G_DAYS_IN_YEAR
127127
elif isinstance(maturity_dt, list):
128128
t = maturity_dt
129129
elif isinstance(maturity_dt, np.ndarray):
@@ -185,7 +185,7 @@ def plot(self, title, ylabel="Yield To Maturity (%)"):
185185

186186
def __repr__(self):
187187
s = label_to_string("OBJECT TYPE", type(self).__name__)
188-
s += label_to_string("SETTLEMENT DATE", self._settlement_date)
188+
s += label_to_string("SETTLEMENT DATE", self._settle_dt)
189189
s += label_to_string("BOND", self._bonds)
190190
s += label_to_string("YIELDS", self._ylds)
191191
s += label_to_string("CURVE FIT", self._curve_fit)

golden_tests/TestFinIborBenchmarksReport.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ def test_ibor_benchmarks_report():
3636
depo_dcc_type = DayCountTypes.ACT_360
3737
depos = []
3838
spot_days = 2
39-
settlement_date = valuation_date.add_weekdays(spot_days)
40-
depo = IborDeposit(settlement_date, "3M", 4.2 / 100.0, depo_dcc_type, cal_type=cal)
39+
settle_dt = valuation_date.add_weekdays(spot_days)
40+
depo = IborDeposit(settle_dt, "3M", 4.2 / 100.0, depo_dcc_type, cal_type=cal)
4141
depos.append(depo)
4242

4343
fra_dcc_type = DayCountTypes.ACT_360
4444
fras = []
4545
fra = IborFRA(
46-
settlement_date.add_tenor("3M"),
46+
settle_dt.add_tenor("3M"),
4747
"3M",
4848
4.20 / 100.0,
4949
fra_dcc_type,
@@ -57,7 +57,7 @@ def test_ibor_benchmarks_report():
5757
fixed_freq_type = FrequencyTypes.SEMI_ANNUAL
5858

5959
swap = IborSwap(
60-
settlement_date,
60+
settle_dt,
6161
"1Y",
6262
swap_type,
6363
4.20 / 100.0,
@@ -67,7 +67,7 @@ def test_ibor_benchmarks_report():
6767
)
6868
swaps.append(swap)
6969
swap = IborSwap(
70-
settlement_date,
70+
settle_dt,
7171
"2Y",
7272
swap_type,
7373
4.30 / 100.0,
@@ -77,7 +77,7 @@ def test_ibor_benchmarks_report():
7777
)
7878
swaps.append(swap)
7979
swap = IborSwap(
80-
settlement_date,
80+
settle_dt,
8181
"3Y",
8282
swap_type,
8383
4.70 / 100.0,
@@ -87,7 +87,7 @@ def test_ibor_benchmarks_report():
8787
)
8888
swaps.append(swap)
8989
swap = IborSwap(
90-
settlement_date,
90+
settle_dt,
9191
"5Y",
9292
swap_type,
9393
5.40 / 100.0,
@@ -97,7 +97,7 @@ def test_ibor_benchmarks_report():
9797
)
9898
swaps.append(swap)
9999
swap = IborSwap(
100-
settlement_date,
100+
settle_dt,
101101
"7Y",
102102
swap_type,
103103
5.70 / 100.0,
@@ -107,7 +107,7 @@ def test_ibor_benchmarks_report():
107107
)
108108
swaps.append(swap)
109109
swap = IborSwap(
110-
settlement_date,
110+
settle_dt,
111111
"10Y",
112112
swap_type,
113113
6.00 / 100.0,
@@ -117,7 +117,7 @@ def test_ibor_benchmarks_report():
117117
)
118118
swaps.append(swap)
119119
swap = IborSwap(
120-
settlement_date,
120+
settle_dt,
121121
"12Y",
122122
swap_type,
123123
6.10 / 100.0,
@@ -127,7 +127,7 @@ def test_ibor_benchmarks_report():
127127
)
128128
swaps.append(swap)
129129
swap = IborSwap(
130-
settlement_date,
130+
settle_dt,
131131
"15Y",
132132
swap_type,
133133
5.90 / 100.0,
@@ -137,7 +137,7 @@ def test_ibor_benchmarks_report():
137137
)
138138
swaps.append(swap)
139139
swap = IborSwap(
140-
settlement_date,
140+
settle_dt,
141141
"20Y",
142142
swap_type,
143143
5.60 / 100.0,
@@ -147,7 +147,7 @@ def test_ibor_benchmarks_report():
147147
)
148148
swaps.append(swap)
149149
swap = IborSwap(
150-
settlement_date,
150+
settle_dt,
151151
"25Y",
152152
swap_type,
153153
5.55 / 100.0,

golden_tests/TestFinIborCurveParRateShock.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ def test_ibor_curve_par_rate_shocker():
3030
depo_dcc_type = DayCountTypes.ACT_360
3131
depos = []
3232
spot_days = 2
33-
settlement_date = valuation_date.add_weekdays(spot_days)
34-
depo = IborDeposit(settlement_date, "3M", 4.2 / 100.0, depo_dcc_type, cal_type=cal)
33+
settle_dt = valuation_date.add_weekdays(spot_days)
34+
depo = IborDeposit(settle_dt, "3M", 4.2 / 100.0, depo_dcc_type, cal_type=cal)
3535
depos.append(depo)
3636

3737
fra_dcc_type = DayCountTypes.ACT_360
3838
fras = []
3939
fra = IborFRA(
40-
settlement_date.add_tenor("3M"),
40+
settle_dt.add_tenor("3M"),
4141
"3M",
4242
4.20 / 100.0,
4343
fra_dcc_type,
@@ -51,7 +51,7 @@ def test_ibor_curve_par_rate_shocker():
5151
fixed_freq_type = FrequencyTypes.SEMI_ANNUAL
5252

5353
swap = IborSwap(
54-
settlement_date,
54+
settle_dt,
5555
"1Y",
5656
swap_type,
5757
4.20 / 100.0,
@@ -61,7 +61,7 @@ def test_ibor_curve_par_rate_shocker():
6161
)
6262
swaps.append(swap)
6363
swap = IborSwap(
64-
settlement_date,
64+
settle_dt,
6565
"2Y",
6666
swap_type,
6767
4.30 / 100.0,
@@ -71,7 +71,7 @@ def test_ibor_curve_par_rate_shocker():
7171
)
7272
swaps.append(swap)
7373
swap = IborSwap(
74-
settlement_date,
74+
settle_dt,
7575
"3Y",
7676
swap_type,
7777
4.70 / 100.0,

0 commit comments

Comments
 (0)