In this post, we will learn how to value treasury futures contract using QuantLib. The treasury futures contract gives the buyer the right to buy the underlying by the time the contract expires. The underlying is usually delivered from a basket of securities. So in order to properly value the futures contract, we would need to find the deliverable. Here we start by doing a naive calculation by constructing a fictional security. We will see what is wrong about this approach. As a next step we will perform the cheapest to deliver calculation and subsequently use that deliverable to value the same contract.
In [1]:
import QuantLib as ql
import math
In [2]:
calc_date = ql.Date(30,11,2015)
ql.Settings.instance().evaluationDate = calc_date
day_count = ql.ActualActual()
calendar = ql.UnitedStates()
bussiness_convention = ql.Following
end_of_month = False
settlement_days = 0
face_amount = 100
coupon_frequency = ql.Period(ql.Semiannual)
In [3]:
# <!-- collapse=True -->
# build curve
prices = [99.9935,99.9576,99.8119,99.5472,99.8867,
100.0664,99.8711,100.0547,100.3047,100.2266]
coupon_rates = [0.0000, 0.0000, 0.0000, 0.0000, 0.00875,
0.0125, 0.01625, 0.02, 0.0225, 0.03]
maturity_dates = [ql.Date(24,12,2015), ql.Date(25,2,2016),
ql.Date(26,5,2016), ql.Date(10,11,2016),
ql.Date(30,11,2017), ql.Date(15,11,2018),
ql.Date(30,11,2020), ql.Date(30,11,2022),
ql.Date(15,11,2025), ql.Date(15,11,2045)]
issue_dates = [ql.Date(25,6,2015), ql.Date(27,8,2015),
ql.Date(28,5,2015), ql.Date(12,11,2015),
ql.Date(30,11,2015), ql.Date(16,11,2015),
ql.Date(30,11,2015), ql.Date(30,11,2015),
ql.Date(16,11,2015), ql.Date(16,11,2015)]
coupon_frequency = ql.Period(6, ql.Months)
bond_helpers = []
for coupon, issue_date, maturity_date, price \
in zip(coupon_rates, issue_dates, maturity_dates, prices):
schedule = ql.Schedule(calc_date,
maturity_date,
coupon_frequency,
calendar,
bussiness_convention,
bussiness_convention,
ql.DateGeneration.Backward,
False)
helper = ql.FixedRateBondHelper(ql.QuoteHandle(ql.SimpleQuote(price)),
settlement_days,
face_amount,
schedule,
[coupon],
day_count,
bussiness_convention
)
bond_helpers.append(helper)
In [4]:
yield_curve = ql.PiecewiseCubicZero(calc_date, bond_helpers, day_count)
yield_curve_handle = ql.YieldTermStructureHandle(yield_curve)
for d in maturity_dates:
print "|%20s %9.6f|"%(d, yield_curve.discount(d))
Here we want to understand how to model treasury futures contract. Let us look at the TYZ5, the treasury futures on the 10 year note for delivery in December 2015. The notional deliverable is a 10-year 6% coupon note. In reality, the seller of the futures contract can deliver from a basket of securities.
For now, lets assume that the deliverable is actually a 6% coupon 10-year note issued as of the calculation date. Let us construct a 10 year treasury note and value this security. The futures price for the TYZ5 is $127.0625.
In [5]:
# <!-- collapse=True -->
def create_tsy_security(bond_issue_date,
bond_maturity_date,
coupon_rate,
coupon_frequency=ql.Period(6, ql.Months),
day_count=ql.ActualActual(),
calendar=ql.UnitedStates()
):
face_value = 100.
settlement_days = 0
schedule = ql.Schedule(bond_issue_date,
bond_maturity_date,
coupon_frequency,
calendar,
ql.ModifiedFollowing,
ql.ModifiedFollowing,
ql.DateGeneration.Forward,
False)
security = ql.FixedRateBond(settlement_days,
face_value,
schedule,
[coupon_rate],
day_count
)
return security
In [6]:
bond_issue_date = calc_date
delivery_date = ql.Date(1,12,2015)
bond_maturity_date = bond_issue_date + ql.Period(10, ql.Years)
day_count = ql.ActualActual()
coupon_frequency = ql.Period(6, ql.Months)
coupon_rate = 6/100.
deliverable = create_tsy_security(bond_issue_date,
bond_maturity_date,
coupon_rate,
coupon_frequency,
day_count,
calendar
)
bond_engine = ql.DiscountingBondEngine(yield_curve_handle)
deliverable.setPricingEngine(bond_engine)
Lets calculate the Z-Spread for this deliverable. The Z-Spread is the static spread added to the yield curve to match the price of the security. This spread is a measure of the risk associated with the security. For a treasury security, you would expect this to be zero.
In [7]:
futures_price = 127.0625
clean_price = futures_price*yield_curve.discount(delivery_date)
zspread = ql.BondFunctions.zSpread(deliverable, clean_price,
yield_curve, day_count,
ql.Compounded, ql.Semiannual,
calc_date)*10000
print "Z-Spread =%3.0fbp" % (zspread)
Here we get a spread of 71 basis points. This is unusually high for a treasury futures contract.
Above we used a fictional 6% coupon bond as the deliverable. In reality, the deliverable is picked from a basket of securities based on what is the cheapest to deliver. Cheapest to deliver is not the cheapest in price. The seller of the futures contract, has to buy the delivery security from the market and sell it at an adjusted futures price. The adjusted futures price is given as:
Adjusted Futures Price = Futures Price x Conversion Factor
The gain or loss to the seller is given by the basis,
Basis = Cash Price - Adjusted Futures Price
So the cheapest to deliver is expected to be the security with the lowest basis. The conversion factor for a security is the price of the security with a 6% yield. Let us look at a basket of securities that is underlying this futures contract to understand this aspect.
In [8]:
# <!-- collapse=True -->
# basket describes the coupon rate, maturity date, price
day_count = ql.ActualActual()
basket = [(1.625, ql.Date(15,8,2022), 97.921875),
(1.625, ql.Date(15,11,2022), 97.671875),
(1.75, ql.Date(30,9,2022), 98.546875),
(1.75, ql.Date(15,5,2023), 97.984375),
(1.875, ql.Date(31,8,2022), 99.375),
(1.875, ql.Date(31,10,2022),99.296875),
(2.0, ql.Date(31,7,2022), 100.265625),
(2.0, ql.Date(15,2,2023), 100.0625),
(2.0, ql.Date(15,2,2025), 98.296875),
(2.0, ql.Date(15,8,2025), 98.09375),
(2.125, ql.Date(30,6,2022), 101.06250),
(2.125, ql.Date(15,5,2025),99.25),
(2.25, ql.Date(15,11,2024),100.546875),
(2.25, ql.Date(15,11,2025),100.375),
(2.375, ql.Date(15,8,2024),101.671875),
(2.5, ql.Date(15,8,2023),103.25),
(2.5,ql.Date(15,5,2024),102.796875),
(2.75, ql.Date(15,11,2023),105.0625),
(2.75,ql.Date(15,2,2024),104.875)
]
securities = []
min_basis = 100; min_basis_index = -1
for i, b in enumerate(basket):
coupon, maturity, price = b
issue = maturity - ql.Period(10,ql.Years)
s = create_tsy_security(issue,maturity, coupon/100.)
bond_engine = ql.DiscountingBondEngine(yield_curve_handle)
s.setPricingEngine(bond_engine)
cf = ql.BondFunctions.cleanPrice(s,0.06,
day_count, ql.Compounded,
ql.Semiannual, calc_date)/100.
adjusted_futures_price = futures_price * cf
basis = price-adjusted_futures_price
if basis< min_basis:
min_basis = basis
min_basis_index = i
securities.append((s,cf))
ctd_info = basket[min_basis_index]
ctd_bond,ctd_cf = securities[min_basis_index]
ctd_price = ctd_info[2]
print "%-30s = %lf" % ("Minimum Basis", min_basis)
print "%-30s = %lf" % ("Conversion Factor", ctd_cf)
print "%-30s = %lf" % ("Coupon", ctd_info[0])
print "%-30s = %s" % ("Maturity", ctd_info[1])
print "%-30s = %lf" % ("Price", ctd_info[2])
The basis is the loss for a notional of \$100 that the seller accrues to close this contract. For a single futures contract (which has a \$100000 notional), there is a loss of \$450.60.
NOTE: You will need my pull request to execute the FixedRateBondForward
class since it is not exposed in SWIG at the moment.
In [9]:
futures_maturity_date = ql.Date(21,12,2015)
futures = ql.FixedRateBondForward(calc_date, futures_maturity_date,
ql.Position.Long, 0.0, settlement_days,
day_count, calendar, bussiness_convention,
ctd_bond, yield_curve_handle, yield_curve_handle)
The valuation of the futures contract and the underlying is shown below:
In [10]:
# <!-- collapse=True -->
model_futures_price = futures.cleanForwardPrice()/ctd_cf
implied_yield = futures.impliedYield(ctd_price/ctd_cf, futures_price,
calc_date, ql.Compounded, day_count).rate()
z_spread = ql.BondFunctions.zSpread(ctd_bond, ctd_price, yield_curve,
day_count, ql.Compounded, ql.Semiannual,
calc_date)
ytm = ql.BondFunctions._yield(ctd_bond, ctd_price, day_count,
ql.Compounded, ql.Semiannual, calc_date)
print "%-30s = %lf" % ("Model Futures Price", model_futures_price)
print "%-30s = %lf" % ("Market Futures Price", futures_price)
print "%-30s = %lf" % ("Model Adjustment", model_futures_price-futures_price)
print "%-30s = %2.3f%%" % ("Implied Yield", implied_yield*100)
print "%-30s = %2.1fbps" % ("Forward Z-Spread", z_spread*10000)
print "%-30s = %2.3f%%" % ("Forward YTM ", ytm*100)
[1] Understanding Treasury Futures, CME Group PDF
In this notebook, we looked into understanding and valuing treasury futures contracts. We used the QuantLib FixedRateBondForward
class in order to model the futures contract. But, we also made a cheapest to deliver calculation to figure out the deliverable.