Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pricing Bonds with face values other than $100 #1768

Open
bkhoor opened this issue Aug 15, 2023 · 3 comments
Open

Pricing Bonds with face values other than $100 #1768

bkhoor opened this issue Aug 15, 2023 · 3 comments

Comments

@bkhoor
Copy link

bkhoor commented Aug 15, 2023

For pricing bonds that are issued with face values other than $100 (e.g. baby bonds which are issued with FV of $25), there is an automatic conversion of any prices calculated to be out of $100 in both BondFunctions analytics and the internal bond analytics. In the example below, I set a face amount to be $25 in the pricer, but when I call cleanPrice() or other relevant method, the price is automatically converted internally by multiplying with 100/notional. To convert the returned clean price back to the desired unit of par, I have to do it on my end by multiplying by faceAmount/100 (or alternatively I can use following to get cleanPrice: settlementValue - accruedAmount/(100/faceAmount)). Similarly, if I try to pass in the original price and retrieve a zspread, the price is assumed to be on $100 FV and the zspread calculated is wrong, so I have to convert the price the same way before passing it into the zspread method.

Is there a more direct way to retrieve clean price and other bond analytics in the unit specified by the notional without have to do additional pre/post conversion?

Please run below code for example.

Thanks for any thoughts.

import QuantLib as ql

#set evaluation date
eval_date_ql = ql.Date(15, 8, 2023)
ql.Settings.instance().setEvaluationDate(eval_date_ql)

#instrument information:
price = 14.6586
zspread = 0.08
maturity_date_ql = ql.Date(15, 8, 2053)
face_amount = 25.

#create flat rates curve with ability to add spread:
term_structure = ql.FlatForward(eval_date_ql, ql.QuoteHandle(ql.SimpleQuote(0.01)),
                                ql.Thirty360(), ql.Compounded, 4)

curve_handle = ql.YieldTermStructureHandle(term_structure)
spreads = [ql.SimpleQuote(0.0) for t in range(2)]
zero_curve = ql.SpreadedLinearZeroInterpolatedTermStructure(curve_handle,
                                                            [ql.QuoteHandle(q) for q in spreads],
                                                            [eval_date_ql, maturity_date_ql])

zero_curve.enableExtrapolation()
curve_handle = ql.YieldTermStructureHandle(zero_curve)


#create spreaded curve handle
spread_handle = ql.QuoteHandle(ql.SimpleQuote(zspread))
ts_spreaded = ql.ZeroSpreadedTermStructure(curve_handle, spread_handle,
                                           ql.Compounded, 4)
ts_spreaded_handle = ql.YieldTermStructureHandle(ts_spreaded)
schedule = ql.Schedule(eval_date_ql, maturity_date_ql, ql.Period(4),
                       ql.UnitedStates(), ql.ModifiedFollowing,
                       ql.ModifiedFollowing, ql.DateGeneration.Backward, False)

#Bond Pricer:
fixed_rate_bond = ql.FixedRateBond(2, face_amount, schedule, [0.05], ql.Thirty360(), ql.Following, 100.0)
fixed_rate_bond.setPricingEngine(ql.DiscountingBondEngine(ts_spreaded_handle))

#Calculating clean price
cleanPrice = fixed_rate_bond.cleanPrice()
cleanPrice2 = fixed_rate_bond.settlementValue() - fixed_rate_bond.accruedAmount()/(100/face_amount)

print(f"Clean Price from pricer: {cleanPrice}")
print(f"Clean Price we want: {cleanPrice2}")

#calculating Z-spread:
zspread = ql.BondFunctions.zSpread(fixed_rate_bond, price,
                                   ql.ImpliedTermStructure(curve_handle,
                                                           eval_date_ql+2),
                                   ql.Thirty360(), ql.Compounded, 4)
print(f"Z-spread from pricer: {zspread}")

zspread2 = ql.BondFunctions.zSpread(fixed_rate_bond, price*4,
                                   ql.ImpliedTermStructure(curve_handle,
                                                           eval_date_ql+2),
                                   ql.Thirty360(), ql.Compounded, 4)
print(f"Z-spread we want: {zspread2}")
@github-actions
Copy link
Contributor

This issue was automatically marked as stale because it has been open 60 days with no activity. Remove stale label or comment, or this will be closed in two weeks.

@github-actions github-actions bot added the stale label Oct 15, 2023
@HristoRaykov
Copy link
Contributor

These baby bonds and preferred shares that trade on equity exchanges are traded in shares like common stocks and are quoted in dollar value. Bonds in general trade over the counter and are quoted as a percent of face value. Most of them are in denominations of 100 and 1000. So if you buy a bond at 70 with 1000 face value you don't buy shares as in equity exchanges but lots of face value. For example if you buy 5000 face value you will pay 5000*70/100=3500. So you have to convert market price as a percent of par to work correctly and convert back calculated clean and dirty prices as they are in percent.

@github-actions github-actions bot removed the stale label Oct 21, 2023
Copy link
Contributor

This issue was automatically marked as stale because it has been open 60 days with no activity. Remove stale label or comment, or this will be closed in two weeks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants