@@ -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
0 commit comments