Skip to content

Commit

Permalink
Renamed full price to dirty price
Browse files Browse the repository at this point in the history
  • Loading branch information
domokane committed Aug 22, 2023
1 parent 0a82b19 commit adcaefa
Show file tree
Hide file tree
Showing 30 changed files with 1,800 additions and 324 deletions.
2 changes: 1 addition & 1 deletion financepy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cr = "\n"

s = "####################################################################" + cr
s += "# FINANCEPY BETA Version " + str('0.300') + " - This build: 29 May 2023 at 11:50 #" + cr
s += "# FINANCEPY BETA Version " + str('0.300') + " - This build: 22 Aug 2023 at 17:29 #" + cr
s += "# This software is distributed FREE AND WITHOUT ANY WARRANTY #" + cr
s += "# Report bugs as issues at https://github.com/domokane/FinancePy #" + cr
s += "####################################################################"
Expand Down
164 changes: 83 additions & 81 deletions financepy/products/bonds/bond.py

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions financepy/products/bonds/bond_callable.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ def __init__(self,
self._freq_type = freq_type
self._accrual_type = accrual_type

ex_div_days= 0

self._bond = Bond(issue_date,
maturity_date,
coupon,
freq_type,
accrual_type,
face_amount)
ex_div_days)

# Validate call and put schedules
for dt in call_dates:
Expand Down Expand Up @@ -168,7 +170,7 @@ def value(self,
df_times = discount_curve._times
df_values = discount_curve._dfs

face_amount = self._bond._face_amount
face_amount = 100.0

if isinstance(model, HWTree):

Expand Down Expand Up @@ -228,7 +230,7 @@ def __repr__(self):
s += label_to_string("COUPON", self._coupon)
s += label_to_string("FREQUENCY", self._freq_type)
s += label_to_string("ACCRUAL TYPE", self._accrual_type)
s += label_to_string("FACE AMOUNT", self._face_amount)
s += label_to_string("EX-DIV DAYS", self._ex_div_days)

s += label_to_string("NUM CALL DATES", len(self._call_dates))
for i in range(0, len(self._call_dates)):
Expand Down
5 changes: 3 additions & 2 deletions financepy/products/bonds/bond_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,16 @@ def conversion_factor(self,
roundedTmatInMonths = int(tmat * 4.0) * 3
newMat = self._first_delivery_date.add_months(roundedTmatInMonths)
face = 1.0

ex_div_days = 0

issue_date = Date(newMat._d, newMat._m, 2000)

newBond = Bond(issue_date,
newMat,
bond._coupon,
bond._freq_type,
bond._accrual_type,
face)
ex_div_days)

p = newBond.clean_price_from_ytm(self._first_delivery_date,
self._coupon)
Expand Down
7 changes: 6 additions & 1 deletion financepy/products/bonds/bond_market.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ def get_bond_market_conventions(country):
semi_annual = FrequencyTypes.SEMI_ANNUAL
act_act = DayCountTypes.ACT_ACT_ICMA
thirtye360 = DayCountTypes.THIRTY_E_360

thirty360 = DayCountTypes.THIRTY_360_BOND

# TODO: CHECK CONVENTIONS

# RETURNS
Expand Down Expand Up @@ -105,6 +106,10 @@ def get_bond_market_conventions(country):
return (act_act, semi_annual, 2, 0, None)
elif country == BondMarkets.NETHERLANDS:
return (act_act, annual, 2, 0, None)
elif country == BondMarkets.NEW_ZEALAND:
return (act_act, annual, 2, 0, None)
elif country == BondMarkets.NORWAY:
return (thirty360, annual, 2, 2, None)
elif country == BondMarkets.PORTUGAL:
return (act_act, annual, 2, 0, None)
elif country == BondMarkets.SLOVAKIA:
Expand Down
79 changes: 41 additions & 38 deletions financepy/products/bonds/bond_zero.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def _f(y, *args):
settlement_date = args[1]
price = args[2]
convention = args[3]
px = bond.full_price_from_ytm(settlement_date, y, convention)
px = bond.dirty_price_from_ytm(settlement_date, y, convention)
obj_fn = px - price
return obj_fn

Expand All @@ -40,7 +40,7 @@ def _g(oas, *args):
settlement_date = args[1]
price = args[2]
discount_curve = args[3]
px = bond.full_price_from_oas(settlement_date, discount_curve, oas)
px = bond.dirty_price_from_oas(settlement_date, discount_curve, oas)
obj_fn = px - price
return obj_fn

Expand Down Expand Up @@ -75,22 +75,22 @@ def __init__(self,
self._issue_date = issue_date
self._maturity_date = maturity_date
self._accrual_type = DayCountTypes.ZERO
self._face_amount = face_amount # This is the bond holding size
self._issue_price = issue_price # Price of issue, usually discounted
self._par = 100.0 # This is how price is quoted and amount at maturity
self._redemption = 1.0 # This is amount paid at maturity
self._freq_type = FrequencyTypes.ZERO
self._coupon_dates = [issue_date, maturity_date]
self._payment_dates = [issue_date, maturity_date]
self._flow_amounts = [0.0, 0.0] # coupon payments are zero

self._calendar_type = CalendarTypes.WEEKEND
self._ex_div_days = 0

self._accrued_interest = None
self._accrued_days = 0.0
self._alpha = 0.0

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

def full_price_from_ytm(self,
def dirty_price_from_ytm(self,
settlement_date: Date,
ytm: float,
convention: YTMCalcType = YTMCalcType.ZERO):
Expand All @@ -101,7 +101,7 @@ def full_price_from_ytm(self,
if convention != YTMCalcType.ZERO:
raise FinError("Need to use YTMCalcType.ZERO for zero coupon bond")

self.calc_accrued_interest(settlement_date)
self.accrued_interest(settlement_date, 1.0)

ytm = np.array(ytm) # VECTORIZED
ytm = ytm + 0.000000000012345 # SNEAKY LOW-COST TRICK TO AVOID y=0
Expand Down Expand Up @@ -134,15 +134,16 @@ def full_price_from_ytm(self,

def principal(self,
settlement_date: Date,
y: float,
ytm: float,
face: (float),
convention: YTMCalcType = YTMCalcType.ZERO):
""" Calculate the principal value of the bond based on the face
amount from its discount margin and making assumptions about the
future Ibor rates. """

full_price = self.full_price_from_ytm(settlement_date, y, convention)
full_price = self.dirty_price_from_ytm(settlement_date, y, convention)

principal = full_price * self._face_amount / self._par
principal = dirty_price * face / self._par
principal = principal - self._accrued_interest
return principal

Expand All @@ -156,8 +157,8 @@ def dollar_duration(self,
known as the DV01 in Bloomberg. """

dy = 0.0001 # 1 basis point
p0 = self.full_price_from_ytm(settlement_date, ytm - dy, convention)
p2 = self.full_price_from_ytm(settlement_date, ytm + dy, convention)
p0 = self.dirty_price_from_ytm(settlement_date, ytm - dy, convention)
p2 = self.dirty_price_from_ytm(settlement_date, ytm + dy, convention)
durn = -(p2 - p0) / dy / 2.0
return durn

Expand All @@ -171,7 +172,7 @@ def macauley_duration(self,
given its yield to maturity. """

dd = self.dollar_duration(settlement_date, ytm, convention)
fp = self.full_price_from_ytm(settlement_date, ytm, convention)
fp = self.dirty_price_from_ytm(settlement_date, ytm, convention)
md = dd * (1.0 + ytm) / fp
return md

Expand All @@ -185,7 +186,7 @@ def modified_duration(self,
given its yield to maturity. """

dd = self.dollar_duration(settlement_date, ytm, convention)
fp = self.full_price_from_ytm(settlement_date, ytm, convention)
fp = self.dirty_price_from_ytm(settlement_date, ytm, convention)
md = dd / fp
return md

Expand All @@ -199,9 +200,9 @@ def convexity_from_ytm(self,
function is vectorised with respect to the yield input. """

dy = 0.0001
p0 = self.full_price_from_ytm(settlement_date, ytm - dy, convention)
p1 = self.full_price_from_ytm(settlement_date, ytm, convention)
p2 = self.full_price_from_ytm(settlement_date, ytm + dy, convention)
p0 = self.dirty_price_from_ytm(settlement_date, ytm - dy, convention)
p1 = self.dirty_price_from_ytm(settlement_date, ytm, convention)
p2 = self.dirty_price_from_ytm(settlement_date, ytm + dy, convention)
conv = ((p2 + p0) - 2.0 * p1) / dy / dy / p1 / self._par
return conv

Expand All @@ -214,9 +215,9 @@ def clean_price_from_ytm(self,
""" Calculate the bond clean price from the yield to maturity. This
function is vectorised with respect to the yield input. """

full_price = self.full_price_from_ytm(settlement_date, ytm, convention)
accrued_amount = self._accrued_interest * self._par / self._face_amount
clean_price = full_price - accrued_amount
full_price = self.dirty_price_from_ytm(settlement_date, ytm, convention)
accrued = self.accrued_interest(settlement_date, self._par)
clean_price = full_price - accrued
return clean_price

###########################################################################
Expand All @@ -229,16 +230,16 @@ def clean_price_from_discount_curve(self,
not to the settlement date. """

self.calc_accrued_interest(settlement_date)
full_price = self.full_price_from_discount_curve(settlement_date,
full_price = self.dirty_price_from_discount_curve(settlement_date,
discount_curve)

accrued = self._accrued_interest * self._par / self._face_amount
accrued = self.accrued_interest(settlement_date, self._par)
clean_price = full_price - accrued
return clean_price

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

def full_price_from_discount_curve(self,
def dirty_price_from_discount_curve(self,
settlement_date: Date,
discount_curve: DiscountCurve):
""" Calculate the bond price using a provided discount curve to PV the
Expand Down Expand Up @@ -304,13 +305,17 @@ def yield_to_maturity(self,
raise FinError("Unknown type for clean_price "
+ str(type(clean_price)))

self.calc_accrued_interest(settlement_date)
accrued_amount = self._accrued_interest * self._par / self._face_amount
full_prices = (clean_prices + accrued_amount)
self.accrued_interest(settlement_date, 1.0)

accrued_amount = self._accrued_interest * self._par

dirty_prices = (clean_prices + accrued_amount)

ytms = []

for full_price in full_prices:
argtuple = (self, settlement_date, full_price, convention)
for dirty_price in dirty_prices:

argtuple = (self, settlement_date, dirty_price, convention)

ytm = optimize.newton(_f,
x0=0.05, # guess initial value of 5%
Expand All @@ -329,10 +334,9 @@ def yield_to_maturity(self,

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

def calc_accrued_interest(self,
settlement_date: Date,
num_ex_dividend_days: int = 0,
calendar_type: CalendarTypes = CalendarTypes.WEEKEND):
def accrued_interest(self,
settlement_date: Date,
face: (float)):
""" Calculate the amount of coupon that has accrued between the
previous coupon date and the settlement date. Note that for some day
count schemes (such as 30E/360) this is not actually the number of days
Expand All @@ -355,9 +359,9 @@ def calc_accrued_interest(self,
break

dc = DayCount(self._accrual_type)
cal = Calendar(calendar_type)
cal = Calendar(self._calendar_type)
exDividend_date = cal.add_business_days(
self._ncd, -num_ex_dividend_days)
self._ncd, -self._ex_div_days)

(acc_factor, num, _) = dc.year_frac(self._pcd,
settlement_date,
Expand All @@ -370,7 +374,7 @@ def calc_accrued_interest(self,
self._alpha = 1.0 - acc_factor
interest = (self._par - self._issue_price) * (settlement_date - self._issue_date) / \
(self._maturity_date - self._issue_date)
self._accrued_interest = interest * self._face_amount / self._par
self._accrued_interest = interest
self._accrued_days = num

return self._accrued_interest
Expand Down Expand Up @@ -612,8 +616,8 @@ def calc_ror(self,
IRR and the PnL.
"""

buy_price = self.full_price_from_ytm(begin_date, begin_ytm, convention)
sell_price = self.full_price_from_ytm(end_date, end_ytm, convention)
buy_price = self.dirty_price_from_ytm(begin_date, begin_ytm, convention)
sell_price = self.dirty_price_from_ytm(end_date, end_ytm, convention)

dates_cfs = zip(self._coupon_dates, self._flow_amounts)

Expand Down Expand Up @@ -652,7 +656,6 @@ def __repr__(self):
s += label_to_string("Issue Price ", self._issue_price)
s += label_to_string("FREQUENCY", self._freq_type)
s += label_to_string("ACCRUAL TYPE", self._accrual_type)
s += label_to_string("FACE AMOUNT", self._face_amount, "")
return s

###########################################################################
Expand Down
Loading

0 comments on commit adcaefa

Please sign in to comment.