def test_BondEmbeddedOptionQUANTLIB(): # Based on example at the nice blog on Quantlib at # http://gouthamanbalaraman.com/blog/callable-bond-quantlib-python.html # I get a price of 68.97 for 1000 time steps which is higher than the # 68.38 found in blog article. But this is for 40 grid points. # Note also that a basis point vol of 0.120 is 12% which is VERY HIGH! valuation_date = Date(16, 8, 2016) settlement_date = valuation_date.addWeekDays(3) ########################################################################### discount_curve = DiscountCurveFlat(valuation_date, 0.035, FrequencyTypes.SEMI_ANNUAL) ########################################################################### issue_date = Date(15, 9, 2010) maturity_date = Date(15, 9, 2022) coupon = 0.025 freq_type = FrequencyTypes.QUARTERLY accrual_type = DayCountTypes.ACT_ACT_ICMA bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type) ########################################################################### # Set up the call and put times and prices ########################################################################### nextCallDate = Date(15, 9, 2016) call_dates = [nextCallDate] call_prices = [100.0] for _ in range(1, 24): nextCallDate = nextCallDate.addMonths(3) call_dates.append(nextCallDate) call_prices.append(100.0) put_dates = [] put_prices = [] # the value used in blog of 12% bp vol is unrealistic sigma = 0.12 # basis point volatility a = 0.03 puttableBond = BondEmbeddedOption(issue_date, maturity_date, coupon, freq_type, accrual_type, call_dates, call_prices, put_dates, put_prices) testCases.header("BOND PRICE", "PRICE") v = bond.clean_price_from_discount_curve(settlement_date, discount_curve) testCases.print("Bond Pure Price:", v) testCases.header("TIME", "NumTimeSteps", "BondWithOption", "BondPure") timeSteps = range(100, 1000, 100) values = [] for numTimeSteps in timeSteps: model = FinModelRatesHW(sigma, a, numTimeSteps) start = time.time() v = puttableBond.value(settlement_date, discount_curve, model) end = time.time() period = end - start testCases.print(period, numTimeSteps, v['bondwithoption'], v['bondpure']) values.append(v['bondwithoption']) if plotGraphs: plt.figure() plt.title("Puttable Bond Price Convergence") plt.plot(timeSteps, values)
def test_BDTExampleThree(): # Valuation of a swaption as in Leif Andersen's paper - see Table 1 on # SSRN-id155208.pdf settlement_date = Date(1, 1, 2020) times = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) dates = settlement_date.add_years(times) rate = 0.06 dfs = 1.0 / (1.0 + rate / 2.0)**(2.0 * times) curve = DiscountCurve(settlement_date, dates, dfs) coupon = 0.06 freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA strike_price = 100.0 face = 100.0 # Andersen paper num_time_steps = 200 exercise_type = FinExerciseTypes.EUROPEAN years_to_maturity = 4.0 expiryYears = 2.0 maturity_date = settlement_date.add_years(years_to_maturity) issue_date = Date(maturity_date._d, maturity_date._m, 2000) sigma = 0.2012 expiry_date = settlement_date.add_years(expiryYears) tmat = (maturity_date - settlement_date) / gDaysInYear texp = (expiry_date - settlement_date) / gDaysInYear bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type) coupon_times = [] coupon_flows = [] cpn = bond._coupon / bond._frequency for flow_date in bond._coupon_dates: if flow_date > expiry_date: flow_time = (flow_date - settlement_date) / gDaysInYear coupon_times.append(flow_time) coupon_flows.append(cpn) coupon_times = np.array(coupon_times) coupon_flows = np.array(coupon_flows) price = bond.clean_price_from_discount_curve(settlement_date, curve) model = BDTTree(sigma, num_time_steps) model.build_tree(tmat, times, dfs) v = model.bermudan_swaption(texp, tmat, strike_price, face, coupon_times, coupon_flows, exercise_type) assert round(price, 5) == 100.01832 assert round(v['pay'] * 100, 2) == 0.00 assert round(v['rec'] * 100, 2) == 8883.21 exercise_type = FinExerciseTypes.BERMUDAN years_to_maturity = 10.0 expiryYears = 5.0 maturity_date = settlement_date.add_years(years_to_maturity) issue_date = Date(maturity_date._d, maturity_date._m, 2000) sigma = 0.1522 expiry_date = settlement_date.add_years(expiryYears) tmat = (maturity_date - settlement_date) / gDaysInYear texp = (expiry_date - settlement_date) / gDaysInYear bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type) coupon_times = [] coupon_flows = [] cpn = bond._coupon / bond._frequency for flow_date in bond._coupon_dates: if flow_date > expiry_date: flow_time = (flow_date - settlement_date) / gDaysInYear coupon_times.append(flow_time) coupon_flows.append(cpn) coupon_times = np.array(coupon_times) coupon_flows = np.array(coupon_flows) price = bond.clean_price_from_discount_curve(settlement_date, curve) model = BDTTree(sigma, num_time_steps) model.build_tree(tmat, times, dfs) v = model.bermudan_swaption(texp, tmat, strike_price, face, coupon_times, coupon_flows, exercise_type) assert round(price, 5) == 100.08625 assert round(v['pay'] * 100, 2) == 263.28 assert round(v['rec'] * 100, 2) == 7437.00
def test_BondOptionZEROVOLConvergence(): # Build discount curve settlement_date = Date(1, 9, 2019) rate = 0.05 discount_curve = DiscountCurveFlat(settlement_date, rate, FrequencyTypes.ANNUAL) # Bond details issue_date = Date(1, 9, 2014) maturity_date = Date(1, 9, 2025) coupon = 0.06 freq_type = FrequencyTypes.ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type) # Option Details expiry_date = Date(1, 12, 2021) face = 100.0 dfExpiry = discount_curve.df(expiry_date) fwdCleanValue = bond.clean_price_from_discount_curve( expiry_date, discount_curve) fwdFullValue = bond.full_price_from_discount_curve(expiry_date, discount_curve) # print("BOND FwdCleanBondPx", fwdCleanValue) # print("BOND FwdFullBondPx", fwdFullValue) # print("BOND Accrued:", bond._accrued_interest) spotCleanValue = bond.clean_price_from_discount_curve( settlement_date, discount_curve) testCases.header("STRIKE", "STEPS", "CALL_INT", "CALL_INT_PV", "CALL_EUR", "CALL_AMER", "PUT_INT", "PUT_INT_PV", "PUT_EUR", "PUT_AMER") num_time_steps = range(100, 200, 100) strike_prices = [90, 100, 110] for strike_price in strike_prices: callIntrinsic = max(spotCleanValue - strike_price, 0) putIntrinsic = max(strike_price - spotCleanValue, 0) callIntrinsicPV = max(fwdCleanValue - strike_price, 0) * dfExpiry putIntrinsicPV = max(strike_price - fwdCleanValue, 0) * dfExpiry for num_steps in num_time_steps: sigma = 0.0000001 a = 0.1 model = BKTree(sigma, a, num_steps) option_type = OptionTypes.EUROPEAN_CALL bond_option1 = BondOption(bond, expiry_date, strike_price, face, option_type) v1 = bond_option1.value(settlement_date, discount_curve, model) option_type = OptionTypes.AMERICAN_CALL bond_option2 = BondOption(bond, expiry_date, strike_price, face, option_type) v2 = bond_option2.value(settlement_date, discount_curve, model) option_type = OptionTypes.EUROPEAN_PUT bond_option3 = BondOption(bond, expiry_date, strike_price, face, option_type) v3 = bond_option3.value(settlement_date, discount_curve, model) option_type = OptionTypes.AMERICAN_PUT bond_option4 = BondOption(bond, expiry_date, strike_price, face, option_type) v4 = bond_option4.value(settlement_date, discount_curve, model) testCases.print(strike_price, num_steps, callIntrinsic, callIntrinsicPV, v1, v2, putIntrinsic, putIntrinsicPV, v3, v4)
def test_BDTExampleTwo(): # Valuation of a European option on a coupon bearing bond # This follows example in Fig 28.11 of John Hull's book (6th Edition) # but does not have the exact same dt so there are some differences settlement_date = Date(1, 12, 2019) issue_date = Date(1, 12, 2015) expiry_date = settlement_date.add_tenor("18m") maturity_date = settlement_date.add_tenor("10Y") coupon = 0.05 freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type) coupon_times = [] coupon_flows = [] cpn = bond._coupon / bond._frequency num_flows = len(bond._coupon_dates) for i in range(1, num_flows): pcd = bond._coupon_dates[i - 1] ncd = bond._coupon_dates[i] if pcd < settlement_date and ncd > settlement_date: flow_time = (pcd - settlement_date) / gDaysInYear coupon_times.append(flow_time) coupon_flows.append(cpn) for flow_date in bond._coupon_dates: if flow_date > settlement_date: flow_time = (flow_date - settlement_date) / gDaysInYear coupon_times.append(flow_time) coupon_flows.append(cpn) coupon_times = np.array(coupon_times) coupon_flows = np.array(coupon_flows) strike_price = 105.0 face = 100.0 tmat = (maturity_date - settlement_date) / gDaysInYear texp = (expiry_date - settlement_date) / gDaysInYear times = np.linspace(0, tmat, 11) dates = settlement_date.add_years(times) dfs = np.exp(-0.05 * times) curve = DiscountCurve(settlement_date, dates, dfs) price = bond.clean_price_from_discount_curve(settlement_date, curve) assert round(price, 4) == 99.5420 sigma = 0.20 # Test convergence num_time_steps = 5 exercise_type = FinExerciseTypes.AMERICAN model = BDTTree(sigma, num_time_steps) model.build_tree(tmat, times, dfs) v = model.bond_option(texp, strike_price, face, coupon_times, coupon_flows, exercise_type) assert round(v['call'], 4) == 0.5043 assert round(v['put'], 4) == 8.2242
def test_BondOptionAmericanConvergenceTWO(): # Build discount curve settlement_date = Date(1, 12, 2019) discount_curve = DiscountCurveFlat(settlement_date, 0.05) # Bond details issue_date = Date(1, 12, 2015) maturity_date = settlement_date.add_tenor("10Y") coupon = 0.05 freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type) expiry_date = settlement_date.add_tenor("18m") face = 100.0 spotValue = bond.clean_price_from_discount_curve(settlement_date, discount_curve) testCases.header("LABEL", "VALUE") testCases.print("BOND PRICE", spotValue) testCases.header("TIME", "N", "EUR_CALL", "AMER_CALL", "EUR_PUT", "AMER_PUT") sigma = 0.01 a = 0.1 hwModel = HWTree(sigma, a) K = 102.0 vec_ec = [] vec_ac = [] vec_ep = [] vec_ap = [] num_stepsVector = range(100, 500, 100) for num_steps in num_stepsVector: hwModel = HWTree(sigma, a, num_steps) start = time.time() europeanCallBondOption = BondOption(bond, expiry_date, K, face, FinOptionTypes.EUROPEAN_CALL) v_ec = europeanCallBondOption.value(settlement_date, discount_curve, hwModel) americanCallBondOption = BondOption(bond, expiry_date, K, face, FinOptionTypes.AMERICAN_CALL) v_ac = americanCallBondOption.value(settlement_date, discount_curve, hwModel) europeanPutBondOption = BondOption(bond, expiry_date, K, face, FinOptionTypes.EUROPEAN_PUT) v_ep = europeanPutBondOption.value(settlement_date, discount_curve, hwModel) americanPutBondOption = BondOption(bond, expiry_date, K, face, FinOptionTypes.AMERICAN_PUT) v_ap = americanPutBondOption.value(settlement_date, discount_curve, hwModel) end = time.time() period = end - start testCases.print(period, num_steps, v_ec, v_ac, v_ep, v_ap) vec_ec.append(v_ec) vec_ac.append(v_ac) vec_ep.append(v_ep) vec_ap.append(v_ap) if plotGraphs: plt.figure() plt.plot(num_stepsVector, vec_ac, label="American Call") plt.legend() plt.figure() plt.plot(num_stepsVector, vec_ap, label="American Put") plt.legend()
def test_BondOption(): settlement_date = Date(1, 12, 2019) issue_date = Date(1, 12, 2018) maturity_date = settlement_date.add_tenor("10Y") coupon = 0.05 freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type) times = np.linspace(0, 10.0, 21) dfs = np.exp(-0.05 * times) dates = settlement_date.add_years(times) discount_curve = DiscountCurve(settlement_date, dates, dfs) expiry_date = settlement_date.add_tenor("18m") strike_price = 105.0 face = 100.0 ########################################################################### strikes = [80, 85, 90, 95, 100, 105, 110, 115, 120] option_type = FinOptionTypes.EUROPEAN_CALL testCases.header("LABEL", "VALUE") price = bond.clean_price_from_discount_curve(settlement_date, discount_curve) testCases.print("Fixed Income Price:", price) num_time_steps = 100 testCases.banner("HW EUROPEAN CALL") testCases.header("STRIKE", "VALUE") for strike_price in strikes: sigma = 0.01 a = 0.1 bond_option = BondOption(bond, expiry_date, strike_price, face, option_type) model = HWTree(sigma, a, num_time_steps) v = bond_option.value(settlement_date, discount_curve, model) testCases.print(strike_price, v) ########################################################################### option_type = FinOptionTypes.AMERICAN_CALL price = bond.clean_price_from_discount_curve(settlement_date, discount_curve) testCases.header("LABEL", "VALUE") testCases.print("Fixed Income Price:", price) testCases.banner("HW AMERICAN CALL") testCases.header("STRIKE", "VALUE") for strike_price in strikes: sigma = 0.01 a = 0.1 bond_option = BondOption(bond, expiry_date, strike_price, face, option_type) model = HWTree(sigma, a) v = bond_option.value(settlement_date, discount_curve, model) testCases.print(strike_price, v) ########################################################################### option_type = FinOptionTypes.EUROPEAN_PUT testCases.banner("HW EUROPEAN PUT") testCases.header("STRIKE", "VALUE") price = bond.clean_price_from_discount_curve(settlement_date, discount_curve) for strike_price in strikes: sigma = 0.01 a = 0.1 bond_option = BondOption(bond, expiry_date, strike_price, face, option_type) model = HWTree(sigma, a) v = bond_option.value(settlement_date, discount_curve, model) testCases.print(strike_price, v) ########################################################################### option_type = FinOptionTypes.AMERICAN_PUT testCases.banner("HW AMERICAN PUT") testCases.header("STRIKE", "VALUE") price = bond.clean_price_from_discount_curve(settlement_date, discount_curve) for strike_price in strikes: sigma = 0.02 a = 0.1 bond_option = BondOption(bond, expiry_date, strike_price, face, option_type) model = HWTree(sigma, a) v = bond_option.value(settlement_date, discount_curve, model) testCases.print(strike_price, v)
def test_HullWhiteCallableBond(): # Valuation of a European option on a coupon bearing bond settlement_date = Date(1, 12, 2019) issue_date = Date(1, 12, 2018) maturity_date = settlement_date.add_tenor("10Y") coupon = 0.05 freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type) coupon_times = [] coupon_flows = [] cpn = bond._coupon / bond._frequency for flow_date in bond._flow_dates[1:]: if flow_date > settlement_date: flow_time = (flow_date - settlement_date) / gDaysInYear coupon_times.append(flow_time) coupon_flows.append(cpn) coupon_times = np.array(coupon_times) coupon_flows = np.array(coupon_flows) ########################################################################### # Set up the call and put times and prices ########################################################################### call_dates = [] call_prices = [] callPx = 120.0 call_dates.append(settlement_date.add_tenor("2Y")) call_prices.append(callPx) call_dates.append(settlement_date.add_tenor("3Y")) call_prices.append(callPx) call_dates.append(settlement_date.add_tenor("4Y")) call_prices.append(callPx) call_dates.append(settlement_date.add_tenor("5Y")) call_prices.append(callPx) call_dates.append(settlement_date.add_tenor("6Y")) call_prices.append(callPx) call_dates.append(settlement_date.add_tenor("7Y")) call_prices.append(callPx) call_dates.append(settlement_date.add_tenor("8Y")) call_prices.append(callPx) call_times = [] for dt in call_dates: t = (dt - settlement_date) / gDaysInYear call_times.append(t) put_dates = [] put_prices = [] putPx = 98.0 put_dates.append(settlement_date.add_tenor("2Y")) put_prices.append(putPx) put_dates.append(settlement_date.add_tenor("3Y")) put_prices.append(putPx) put_dates.append(settlement_date.add_tenor("4Y")) put_prices.append(putPx) put_dates.append(settlement_date.add_tenor("5Y")) put_prices.append(putPx) put_dates.append(settlement_date.add_tenor("6Y")) put_prices.append(putPx) put_dates.append(settlement_date.add_tenor("7Y")) put_prices.append(putPx) put_dates.append(settlement_date.add_tenor("8Y")) put_prices.append(putPx) put_times = [] for dt in put_dates: t = (dt - settlement_date) / gDaysInYear put_times.append(t) ########################################################################### tmat = (maturity_date - settlement_date) / gDaysInYear curve = DiscountCurveFlat(settlement_date, 0.05, FrequencyTypes.CONTINUOUS) dfs = [] times = [] for dt in bond._flow_dates: if dt > settlement_date: t = (dt - settlement_date) / gDaysInYear df = curve.df(dt) times.append(t) dfs.append(df) dfs = np.array(dfs) times = np.array(times) ########################################################################### v1 = bond.clean_price_from_discount_curve(settlement_date, curve) sigma = 0.02 # basis point volatility a = 0.01 # Test convergence num_steps_list = [100, 200, 500, 1000] tmat = (maturity_date - settlement_date) / gDaysInYear testCases.header("NUMSTEPS", "TIME", "BOND_ONLY", "CALLABLE_BOND") for num_time_steps in num_steps_list: start = time.time() model = HWTree(sigma, a, num_time_steps) model.build_tree(tmat, times, dfs) v2 = model.callable_puttable_bond_tree(coupon_times, coupon_flows, call_times, call_prices, put_times, put_prices, 100.0) end = time.time() period = end - start testCases.print(num_time_steps, period, v1, v2)
def test_BondOptionDerivaGem(): # See https://github.com/domokane/FinancePy/issues/98 settlement_date = Date(1, 12, 2019) rate = 0.05 dcType = DayCountTypes.THIRTY_360_BOND fixedFreq = FrequencyTypes.SEMI_ANNUAL discount_curve = DiscountCurveFlat(settlement_date, rate, fixedFreq, dcType) issue_date = Date(1, 12, 2018) expiry_date = settlement_date.add_tenor("18m") maturity_date = settlement_date.add_tenor("10Y") coupon = 0.05 freqType = FrequencyTypes.SEMI_ANNUAL accrualType = DayCountTypes.THIRTY_360_BOND bond = Bond(issue_date, maturity_date, coupon, freqType, accrualType) strike_price = 100.0 face = 100.0 europeanCallBondOption = BondOption(bond, expiry_date, strike_price, face, OptionTypes.EUROPEAN_CALL) cp = bond.clean_price_from_discount_curve(expiry_date, discount_curve) fp = bond.full_price_from_discount_curve(expiry_date, discount_curve) # print("Fixed Income Clean Price: %9.3f"% cp) # print("Fixed Income Full Price: %9.3f"% fp) num_steps = 500 sigma = 0.0125 a = 0.1 modelHW = HWTree(sigma, a, num_steps) ec = europeanCallBondOption.value(settlement_date, discount_curve, modelHW) ########################################################################### couponTimes = [] couponFlows = [] cpn = bond._coupon / bond._frequency numFlows = len(bond._coupon_dates) for i in range(0, numFlows): pcd = bond._coupon_dates[i - 1] ncd = bond._coupon_dates[i] if ncd > settlement_date: if len(couponTimes) == 0: flowTime = (pcd - settlement_date) / gDaysInYear couponTimes.append(flowTime) couponFlows.append(cpn) flowTime = (ncd - settlement_date) / gDaysInYear couponTimes.append(flowTime) couponFlows.append(cpn) couponTimes = np.array(couponTimes) couponFlows = np.array(couponFlows) y = 0.05 times = np.linspace(0, 10, 21) dfs = np.power(1 + y / 2, -times * 2) sigma = 0.0125 a = 0.1 model = HWTree(sigma, a, None) # Test convergence texp = (expiry_date - settlement_date) / gDaysInYear tmat = (maturity_date - settlement_date) / gDaysInYear # Jamshidian approach vjam = model.european_bond_option_jamshidian(texp, strike_price, face, couponTimes, couponFlows, times, dfs) # print("Jamshidian:", vjam) model._num_time_steps = 100 model.build_tree(tmat, times, dfs) exerciseType = FinExerciseTypes.EUROPEAN vHW = model.bond_option(texp, strike_price, face, couponTimes, couponFlows, exerciseType)