Skip to content

Commit

Permalink
Fixed bugs.
Browse files Browse the repository at this point in the history
  • Loading branch information
domokane committed Aug 1, 2024
1 parent 6148f94 commit 0bb5168
Show file tree
Hide file tree
Showing 13 changed files with 833 additions and 36 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.360') + " - This build: 31 Jul 2024 at 23:01 #" + cr
s += "# FINANCEPY BETA Version " + str('0.360') + " - This build: 01 Aug 2024 at 13:01 #" + 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
3 changes: 3 additions & 0 deletions financepy/products/bonds/bond.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ def dirty_price_from_ytm(self,
function is vectorised with respect to the yield input. It implements
a number of standard conventions for calculating the YTM. """

if settle_dt > self.maturity_dt:
raise FinError("Bond settlement is after maturity date")

if convention not in YTMCalcType:
raise FinError("Yield convention unknown." + str(convention))

Expand Down
14 changes: 10 additions & 4 deletions financepy/products/bonds/bond_zero.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,15 @@ def accrued_interest(self,
weekends or you can specify a country calendar for business days."""

num_flows = len(self.cpn_dts)

if num_flows == 0:
raise FinError("Accrued interest - not enough flow dates.")

if settle_dt > self.maturity_dt:
raise FinError("Bond Zero settlement after maturity date")

for i_flow in range(1, num_flows):

# cpns paid on the settlement date are paid to the seller
if self.cpn_dts[i_flow] > settle_dt:
self.pcd = self.cpn_dts[i_flow - 1]
Expand All @@ -361,14 +365,16 @@ def accrued_interest(self,

dc = DayCount(self.dc_type)
cal = Calendar(self.cal_type)
ex_dividend_dt = cal.add_business_days(
self.ncd, -self.ex_div_days)

ex_dividend_dt = cal.add_business_days(self.ncd, -self.ex_div_days)


(acc_factor, num, _) = dc.year_frac(self.pcd,
settle_dt,
self.ncd,
FrequencyTypes.ZERO)


if settle_dt > ex_dividend_dt:
acc_factor = acc_factor - 1.0

Expand Down Expand Up @@ -671,7 +677,7 @@ def __repr__(self):
s = label_to_string("OBJECT TYPE", type(self).__name__)
s += label_to_string("ISSUE DATE", self.issue_dt)
s += label_to_string("MATURITY DATE", self.maturity_dt)
s += label_to_string("cpn (%)", 0)
s += label_to_string("COUPON (%)", 0)
s += label_to_string("ISSUE PRICE", self.issue_price)
s += label_to_string("FREQUENCY", self.freq_type)
s += label_to_string("DAY COUNT TYPE", self.dc_type)
Expand Down
2 changes: 1 addition & 1 deletion financepy/utils/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,7 @@ def __str__(self):
###############################################################################

def __repr__(self):
s = self.cal_type
s = self.cal_type.name
return s

###############################################################################
91 changes: 91 additions & 0 deletions golden_tests/TestFinBondZSpread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import os
import pandas as pd
import matplotlib.pyplot as plt

import sys
sys.path.append("..")

from financepy.utils.global_vars import gPercent
from financepy.utils.calendar import CalendarTypes
from financepy.utils.day_count import DayCountTypes
from financepy.utils.frequency import FrequencyTypes
from financepy.utils.date import Date, from_datetime
from financepy.market.curves.interpolator import InterpTypes
from financepy.market.curves.discount_curve import DiscountCurve
from financepy.market.curves.discount_curve_flat import DiscountCurveFlat
from financepy.products.bonds.bond import Bond
from financepy.products.rates.ibor_single_curve import IborSingleCurve
from financepy.products.rates.ibor_benchmarks_report import dataframe_to_benchmarks

# Set to True to run this file spandalone and see some useful info
DIAGNOSTICS_MODE = False

from FinTestCases import FinTestCases, globalTestCaseMode

test_cases = FinTestCases(__file__, globalTestCaseMode)


def test_z_spread_flat_curve():

settlement = Date(19, 9, 2012)
base_curve = DiscountCurveFlat(settlement, flat_rate=1*gPercent)
return _test_z_spread_for_curve(base_curve)


def test_z_spread_actual_curve():

path = os.path.join(os.path.dirname(__file__), './data/GBP_OIS_20120919.csv')
dfbm = pd.read_csv(path, index_col=0)

dfbm['base_date'] = pd.to_datetime(dfbm['base_date'], errors='ignore', format='%d/%m/%Y')
dfbm['start_date'] = pd.to_datetime(dfbm['start_date'], errors='ignore', format='%d/%m/%Y') # allow tenors
dfbm['maturity_date'] = pd.to_datetime(dfbm['maturity_date'], errors='ignore', format='%d/%m/%Y') # allow tenors

valuation_date = from_datetime(dfbm.loc[0, 'base_date'])
cal = CalendarTypes.UNITED_KINGDOM
bms = dataframe_to_benchmarks(dfbm, asof_date=valuation_date, calendar_type=cal)
depos = bms['IborDeposit']
fras = bms['IborFRA']
swaps = bms['IborSwap']

fras.sort(key=lambda fra: fra.maturity_dt)
libor_curve = IborSingleCurve(valuation_date, depos, fras, swaps, InterpTypes.LINEAR_ZERO_RATES)

return _test_z_spread_for_curve(libor_curve)


def _test_z_spread_for_curve(base_curve: DiscountCurve):
path = os.path.join(os.path.dirname(__file__), './data/gilt_bond_prices.txt')
bondDataFrame = pd.read_csv(path, sep='\t')
bondDataFrame['mid'] = 0.5*(bondDataFrame['bid'] + bondDataFrame['ask'])

bondDataFrame['maturity'] = pd.to_datetime(bondDataFrame['maturity'], format="%d-%b-%y")
freq_type = FrequencyTypes.SEMI_ANNUAL
accrual_type = DayCountTypes.ACT_ACT_ICMA

for bdfIndex, bondRow in bondDataFrame.iterrows():
matDatetime = bondRow['maturity']
maturityDt = from_datetime(matDatetime)
issueDt = Date(maturityDt.d, maturityDt.m, 2000)
coupon = bondRow['coupon']/100.0
clean_price = bondRow['mid']
bond = Bond(issueDt, maturityDt, coupon, freq_type, accrual_type)
z_spread = bond.z_spread(base_curve.value_dt, clean_price, base_curve)
asset_swap_spread = bond.asset_swap_spread(base_curve.value_dt, clean_price, base_curve)
bondDataFrame.loc[bdfIndex, 'z_spread'] = z_spread
bondDataFrame.loc[bdfIndex, 'asset_swap_spread'] = asset_swap_spread

if DIAGNOSTICS_MODE:
print(bondDataFrame)
plt.plot(bondDataFrame['maturity'], bondDataFrame['gross redemption yield'], '.', label='yield')
plt.plot(bondDataFrame['maturity'], bondDataFrame['z_spread']*100, '.', label='z_spread')
plt.plot(bondDataFrame['maturity'], bondDataFrame['asset_swap_spread']*100, '.', label='asset_swap_spread')
plt.legend(loc='best')
plt.show()

assert bondDataFrame['z_spread'].isnull().values.any() == False


if __name__ == '__main__':
test_z_spread_flat_curve()
test_z_spread_actual_curve()
26 changes: 19 additions & 7 deletions golden_tests/TestFinBondZeroCoupon.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,31 +50,43 @@ def test_bond_zero():
test_cases.header('YTM', 'accrued')
test_cases.print(ytm, accrued_interest)

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

def test_bond_zero_ror():

path = os.path.join(os.path.dirname(__file__), './/data//test_cases_bond_ror.csv')
path = ".//data//test_cases_bond_zero_ror.csv"
df = pd.read_csv(path, parse_dates=['buy_date', 'sell_date'])

# A 1-year bond with zero coupon per year. code: 092103011
bond = BondZero(
issue_dt=Date(23, 7, 2021),
maturity_dt=Date(24, 8, 2022),
issue_price=97.67
)
test_cases.header('bond_code', 'buy_date', 'buy_ytm', 'buy_price', 'sell_date', 'sell_ytm', 'sell_price',
'simple_return', 'irr')

test_cases.header('bond_code', 'buy_date', 'buy_ytm', 'buy_price',
'sell_date', 'sell_ytm', 'sell_price',
'simple_return', 'irr')

for row in df.itertuples(index=False):

buy_dt = Date(row.buy_date.day, row.buy_date.month, row.buy_date.year)
sell_dt = Date(row.sell_date.day, row.sell_date.month, row.sell_date.year)

buy_price = bond.dirty_price_from_ytm(buy_dt, row.buy_ytm, YTMCalcType.ZERO)
sell_price = bond.dirty_price_from_ytm(sell_dt, row.sell_ytm, YTMCalcType.ZERO)

simple, irr, pnl = bond.calc_ror(buy_dt, sell_dt, row.buy_ytm, row.sell_ytm)

test_cases.print(row.bond_code, buy_dt, row.buy_ytm, buy_price,
sell_dt, row.sell_ytm, sell_price, simple, irr)

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


test_bond_zero()
test_bond_zero_ror()
test_cases.compareTestCases()
try:
test_bond_zero()
test_bond_zero_ror()
test_cases.compareTestCases()
except Exception as e:
print(f"Unexpected error:{e}", sys.exc_info()[0])

25 changes: 16 additions & 9 deletions golden_tests/TestFinIborBenchmarksReport.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import pandas as pd
from os.path import dirname, join

import sys
sys.path.append("..")

import pandas as pd
from os.path import dirname, join

from financepy.utils.date import Date
from financepy.utils.global_types import SwapTypes
Expand All @@ -18,6 +17,10 @@

from financepy.products.rates.ibor_benchmarks_report import ibor_benchmarks_report, dataframe_to_benchmarks

from FinTestCases import FinTestCases, globalTestCaseMode

test_cases = FinTestCases(__file__, globalTestCaseMode)


def test_ibor_benchmarks_report():
valuation_date = Date(6, 10, 2001)
Expand Down Expand Up @@ -67,13 +70,13 @@ def test_ibor_benchmarks_report():
curve = IborSingleCurve(valuation_date, depos, fras, swaps,
interp_type, check_refit=False, do_build=do_build)

bechmarks_report = ibor_benchmarks_report(curve)
benchmarks_report = ibor_benchmarks_report(curve)

# print(bechmarks_report)
# print(benchmarks_report)

# Confirm that there are no NaNs. In particular this means that different types of benchmarks
# return exactly the same keys, just like we want it, with a couple of exceptions
assert (bechmarks_report
assert (benchmarks_report
.drop(columns=['fixed_freq_type', 'fixed_leg_type'])
.isnull().values.any()
) == False
Expand All @@ -99,6 +102,10 @@ def test_dataframe_to_benchmarks():
assert len(benchmarks['IborSwap']) == 10


if __name__ == '__main__':
test_ibor_benchmarks_report()
test_dataframe_to_benchmarks()
try:
test_ibor_benchmarks_report()
test_dataframe_to_benchmarks()
except Exception as e:
print(f"Unexpected error:{e}", sys.exc_info()[0])


4 changes: 4 additions & 0 deletions golden_tests/TestFinIborCurveParRateShock.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
from financepy.utils.date import Date
from financepy.utils.calendar import CalendarTypes

from FinTestCases import FinTestCases, globalTestCaseMode

test_cases = FinTestCases(__file__, globalTestCaseMode)


def test_ibor_curve_par_rate_shocker():
valuation_date = Date(6, 10, 2001)
Expand Down
5 changes: 5 additions & 0 deletions golden_tests/TestFinIborCurveRiskEngine.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
sys.path.append("..")

from helpers import buildIborSingleCurve
from financepy.utils.date import Date
from financepy.utils.global_vars import gBasisPoint
from financepy.utils.global_types import SwapTypes
Expand All @@ -17,6 +18,10 @@
from financepy.products.rates.ibor_single_curve import IborSingleCurve
import financepy.products.rates.ibor_curve_risk_engine as re

from FinTestCases import FinTestCases, globalTestCaseMode

test_cases = FinTestCases(__file__, globalTestCaseMode)

# when set to True this file can be run standalone and will produce some useful output.
# Set to False to use as part of a testing framework
DIAGNOSTICS_MODE = False
Expand Down
33 changes: 33 additions & 0 deletions golden_tests/data/GBP_OIS_20120919.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
,base_date,type,start_date,maturity_date,day_count_type,notional,contract_rate,fixed_leg_type,fixed_freq_type,curve,currency
0,19/09/2012,IborDeposit,19/09/2012,19/12/2012,ACT_360,10000,0.003815,PAY,,OIS,GBP
1,19/09/2012,IborFRA,19/12/2012,19/03/2013,ACT_360,10000,0.003322,PAY,,OIS,GBP
2,19/09/2012,IborFRA,20/03/2013,20/06/2013,ACT_360,10000,0.003205,PAY,,OIS,GBP
3,19/09/2012,IborFRA,19/06/2013,19/09/2013,ACT_360,10000,0.003265,PAY,,OIS,GBP
4,19/09/2012,IborFRA,18/09/2013,18/12/2013,ACT_360,10000,0.00341,PAY,,OIS,GBP
5,19/09/2012,IborFRA,18/12/2013,18/03/2014,ACT_360,10000,0.003675,PAY,,OIS,GBP
6,19/09/2012,IborFRA,19/03/2014,19/06/2014,ACT_360,10000,0.003995,PAY,,OIS,GBP
7,19/09/2012,IborFRA,18/06/2014,18/09/2014,ACT_360,10000,0.00447,PAY,,OIS,GBP
8,19/09/2012,IborFRA,17/09/2014,17/12/2014,ACT_360,10000,0.00502,PAY,,OIS,GBP
9,19/09/2012,IborFRA,17/12/2014,17/03/2015,ACT_360,10000,0.005595,PAY,,OIS,GBP
10,19/09/2012,IborFRA,18/03/2015,18/06/2015,ACT_360,10000,0.00611,PAY,,OIS,GBP
11,19/09/2012,IborFRA,17/06/2015,17/09/2015,ACT_360,10000,0.006715,PAY,,OIS,GBP
12,19/09/2012,IborFRA,16/09/2015,16/12/2015,ACT_360,10000,0.007415,PAY,,OIS,GBP
13,19/09/2012,IborSwap,19/09/2012,19/09/2016,ACT_360,10000,0.005579274,PAY,ANNUAL,OIS,GBP
14,19/09/2012,IborSwap,19/09/2012,19/09/2017,ACT_360,10000,0.007227034,PAY,ANNUAL,OIS,GBP
15,19/09/2012,IborSwap,19/09/2012,19/09/2018,ACT_360,10000,0.009073759,PAY,ANNUAL,OIS,GBP
16,19/09/2012,IborSwap,19/09/2012,19/09/2019,ACT_360,10000,0.011003603,PAY,ANNUAL,OIS,GBP
17,19/09/2012,IborSwap,19/09/2012,19/09/2020,ACT_360,10000,0.01292279,PAY,ANNUAL,OIS,GBP
18,19/09/2012,IborSwap,19/09/2012,19/09/2021,ACT_360,10000,0.01476606,PAY,ANNUAL,OIS,GBP
19,19/09/2012,IborSwap,19/09/2012,19/09/2022,ACT_360,10000,0.016469326,PAY,ANNUAL,OIS,GBP
20,19/09/2012,IborSwap,19/09/2012,19/09/2024,ACT_360,10000,0.019226422,PAY,ANNUAL,OIS,GBP
21,19/09/2012,IborSwap,19/09/2012,19/09/2027,ACT_360,10000,0.022052563,PAY,ANNUAL,OIS,GBP
22,19/09/2012,IborSwap,19/09/2012,19/09/2032,ACT_360,10000,0.025029801,PAY,ANNUAL,OIS,GBP
23,19/09/2012,IborSwap,19/09/2012,19/09/2037,ACT_360,10000,0.026712763,PAY,ANNUAL,OIS,GBP
24,19/09/2012,IborSwap,19/09/2012,19/09/2042,ACT_360,10000,0.027739254,PAY,ANNUAL,OIS,GBP
25,19/09/2012,IborSwap,19/09/2012,19/09/2047,ACT_360,10000,0.028452841,PAY,ANNUAL,OIS,GBP
26,19/09/2012,IborSwap,19/09/2012,19/09/2052,ACT_360,10000,0.028903302,PAY,ANNUAL,OIS,GBP
27,19/09/2012,IborSwap,19/09/2012,19/09/2057,ACT_360,10000,0.029121657,PAY,ANNUAL,OIS,GBP
28,19/09/2012,IborSwap,19/09/2012,19/09/2062,ACT_360,10000,0.029225286,PAY,ANNUAL,OIS,GBP
29,19/09/2012,IborSwap,19/09/2012,19/09/2072,ACT_360,10000,0.029407055,PAY,ANNUAL,OIS,GBP
30,19/09/2012,IborSwap,19/09/2012,19/09/2082,ACT_360,10000,0.029593652,PAY,ANNUAL,OIS,GBP
31,19/09/2012,IborSwap,19/09/2012,19/09/2092,ACT_360,10000,0.029716109,PAY,ANNUAL,OIS,GBP
Loading

0 comments on commit 0bb5168

Please sign in to comment.