def _blsimpv(price, spot, strike, risk_free_rate, time, option_type, dividend): spot = SimpleQuote(spot) daycounter = ActualActual(ISMA) risk_free_ts = FlatForward(today(), risk_free_rate, daycounter) dividend_ts = FlatForward(today(), dividend, daycounter) volatility_ts = BlackConstantVol(today(), NullCalendar(), .3, daycounter) process = BlackScholesMertonProcess(spot, dividend_ts, risk_free_ts, volatility_ts) exercise_date = today() + Period(time * 365, Days) exercise = EuropeanExercise(exercise_date) payoff = PlainVanillaPayoff(option_type, strike) option = EuropeanOption(payoff, exercise) engine = AnalyticEuropeanEngine(process) option.set_pricing_engine(engine) accuracy = 0.001 max_evaluations = 1000 min_vol = 0.01 max_vol = 2 vol = option.implied_volatility(price, process, accuracy, max_evaluations, min_vol, max_vol) return vol
def test_smith(self): # test against result published in # Journal of Computational Finance Vol. 11/1 Fall 2007 # An almost exact simulation method for the heston model settlement_date = today() self.settings.evaluation_date = settlement_date daycounter = ActualActual() timeToMaturity = 4 exercise_date = settlement_date + timeToMaturity * 365 c_payoff = PlainVanillaPayoff(Call, 100) exercise = EuropeanExercise(exercise_date) risk_free_ts = flat_rate(0., daycounter) dividend_ts = flat_rate(0., daycounter) s0 = SimpleQuote(100.0) v0 = 0.0194 kappa = 1.0407 theta = 0.0586 sigma = 0.5196 rho = -.6747 nb_steps_a = 100 nb_paths = 20000 seed = 12347 process = HestonProcess(risk_free_ts, dividend_ts, s0, v0, kappa, theta, sigma, rho, QUADRATICEXPONENTIAL) model = HestonModel(process) option = VanillaOption(c_payoff, exercise) engine = AnalyticHestonEngine(model, 144) option.set_pricing_engine(engine) price_fft = option.net_present_value engine = MCEuropeanHestonEngine(process, antithetic_variate=True, steps_per_year=nb_steps_a, required_samples=nb_paths, seed=seed) option.set_pricing_engine(engine) price_mc = option.net_present_value expected = 15.1796 tolerance = .05 self.assertAlmostEqual(price_fft, expected, delta=tolerance) self.assertAlmostEqual(price_mc, expected, delta=tolerance)
def test_european_vanilla_option_usage(self): european_exercise = EuropeanExercise(self.maturity) european_option = VanillaOption(self.payoff, european_exercise) analytic_european_engine = AnalyticEuropeanEngine( self.black_scholes_merton_process) european_option.set_pricing_engine(analytic_european_engine) self.assertAlmostEquals(3.844308, european_option.net_present_value, 6)
def test_analytic_versus_black(self): settlement_date = today() self.settings.evaluation_date = settlement_date daycounter = ActualActual() exercise_date = settlement_date + 6 * Months payoff = PlainVanillaPayoff(Put, 30) exercise = EuropeanExercise(exercise_date) risk_free_ts = flat_rate(0.1, daycounter) dividend_ts = flat_rate(0.04, daycounter) s0 = SimpleQuote(32.0) v0 = 0.05 kappa = 5.0 theta = 0.05 sigma = 1.0e-4 rho = 0.0 process = HestonProcess( risk_free_ts, dividend_ts, s0, v0, kappa, theta, sigma, rho ) option = VanillaOption(payoff, exercise) engine = AnalyticHestonEngine(HestonModel(process), 144) option.set_pricing_engine(engine) calculated = option.net_present_value year_fraction = daycounter.year_fraction( settlement_date, exercise_date ) forward_price = 32 * np.exp((0.1 - 0.04) * year_fraction) expected = blackFormula( payoff.type, payoff.strike, forward_price, np.sqrt(0.05 * year_fraction) ) * np.exp(-0.1 * year_fraction) tolerance = 2.0e-7 self.assertAlmostEqual( calculated, expected, delta=tolerance )
def test_bucket_analysis_option(self): settings = Settings() calendar = TARGET() todays_date = Date(15, May, 1998) settlement_date = Date(17, May, 1998) settings.evaluation_date = todays_date option_type = Put underlying = 40 strike = 40 dividend_yield = 0.00 risk_free_rate = 0.001 volatility = 0.20 maturity = Date(17, May, 1999) daycounter = Actual365Fixed() underlyingH = SimpleQuote(underlying) payoff = PlainVanillaPayoff(option_type, strike) flat_term_structure = FlatForward(reference_date=settlement_date, forward=risk_free_rate, daycounter=daycounter) flat_dividend_ts = FlatForward(reference_date=settlement_date, forward=dividend_yield, daycounter=daycounter) flat_vol_ts = BlackConstantVol(settlement_date, calendar, volatility, daycounter) black_scholes_merton_process = BlackScholesMertonProcess( underlyingH, flat_dividend_ts, flat_term_structure, flat_vol_ts) european_exercise = EuropeanExercise(maturity) european_option = VanillaOption(payoff, european_exercise) analytic_european_engine = AnalyticEuropeanEngine( black_scholes_merton_process) european_option.set_pricing_engine(analytic_european_engine) ba_eo = bucket_analysis([[underlyingH]], [european_option], [1], 0.50, 1) self.assertTrue(2, ba_eo) self.assertTrue(type(tuple), ba_eo) self.assertEqual(1, len(ba_eo[0][0])) self.assertAlmostEqual(-0.4582666150152517, ba_eo[0][0][0])
def test_bucket_analysis_option(self): settings = Settings() calendar = TARGET() todays_date = Date(15, May, 1998) settlement_date = Date(17, May, 1998) settings.evaluation_date = todays_date option_type = Put underlying = 40 strike = 40 dividend_yield = 0.00 risk_free_rate = 0.001 volatility = SimpleQuote(0.20) maturity = Date(17, May, 1999) daycounter = Actual365Fixed() underlyingH = SimpleQuote(underlying) payoff = PlainVanillaPayoff(option_type, strike) flat_term_structure = FlatForward(reference_date=settlement_date, forward=risk_free_rate, daycounter=daycounter) flat_dividend_ts = FlatForward(reference_date=settlement_date, forward=dividend_yield, daycounter=daycounter) flat_vol_ts = BlackConstantVol(settlement_date, calendar, volatility, daycounter) black_scholes_merton_process = BlackScholesMertonProcess( underlyingH, flat_dividend_ts, flat_term_structure, flat_vol_ts) european_exercise = EuropeanExercise(maturity) european_option = VanillaOption(payoff, european_exercise) analytic_european_engine = AnalyticEuropeanEngine( black_scholes_merton_process) european_option.set_pricing_engine(analytic_european_engine) delta, gamma = bucket_analysis([underlyingH, volatility], [european_option], shift=1e-4, type=Centered) self.assertAlmostEqual(delta[0], european_option.delta) self.assertAlmostEqual(delta[1], european_option.vega) self.assertAlmostEqual(gamma[0], european_option.gamma, 5)
def _blsprice(spot, strike, risk_free_rate, time, volatility, option_type='Call', dividend=0.0, calc='price'): """ Black-Scholes option pricing model + greeks. """ _spot = SimpleQuote(spot) daycounter = ActualActual(ISMA) risk_free_ts = FlatForward(today(), risk_free_rate, daycounter) dividend_ts = FlatForward(today(), dividend, daycounter) volatility_ts = BlackConstantVol(today(), NullCalendar(), volatility, daycounter) process = BlackScholesMertonProcess(_spot, dividend_ts, risk_free_ts, volatility_ts) exercise_date = today() + Period(time * 365, Days) exercise = EuropeanExercise(exercise_date) payoff = PlainVanillaPayoff(option_type, strike) option = EuropeanOption(payoff, exercise) engine = AnalyticEuropeanEngine(process) option.set_pricing_engine(engine) if calc == 'price': res = option.npv elif calc == 'delta': res = option.delta elif calc == 'gamma': res = option.gamma elif calc == 'theta': res = option.theta elif calc == 'rho': res = option.rho elif calc == 'vega': res = option.vega elif calc == 'lambda': res = option.delta * spot / option.npv else: raise ValueError('calc type %s is unknown' % calc) return res
def test_implied_volatility(self): european_exercise = EuropeanExercise(self.maturity) european_option = VanillaOption(self.payoff, european_exercise) vol = SimpleQuote(.18) proc = ImpliedVolatilityHelper.clone(self.black_scholes_merton_process, vol) analytic_european_engine = AnalyticEuropeanEngine(proc) implied_volatility = ImpliedVolatilityHelper.calculate( european_option, analytic_european_engine, vol, 3.844308, .001, 500, .1, .5) self.assertAlmostEqual(.20, implied_volatility, 4)
def test_str(self): quote_str = str(self.underlyingH) self.assertEquals('Simple Quote: 36.000000', quote_str) payoff_str = str(self.payoff) self.assertEquals('Payoff: Vanilla Put @ 40.000000', payoff_str) exercise = EuropeanExercise(self.maturity) exercise_str = str(exercise) self.assertEquals('Exercise type: European', exercise_str) option = VanillaOption(self.payoff, exercise) self.assertEquals('Exercise type: European', str(option.exercise)) vanilla_str = str(option) self.assertEquals('VanillaOption Exercise type: European Payoff: Vanilla', vanilla_str)
def test_mc_variance_swap(self): """ test mc variance engine vs expected result """ vols = [] dates = [] interm_date = self.today + int(0.1 * 365 + 0.5) exercise = EuropeanExercise(self.ex_date) dates.append(interm_date) dates.append(self.ex_date) vols.append(0.1) vols.append(self.values['v']) # Exercising code using BlackVarianceCurve because BlackVarianceSurface # is unreliable. Result should be v*v for arbitrary t1 and v1 # (as long as 0<=t1<t and 0<=v1<v) vol_ts = BlackVarianceCurve(self.today, dates, vols, self.dc, True) stoch_process = BlackScholesMertonProcess(self.spot, self.q_ts, self.r_ts, vol_ts) engine = MCVarianceSwapEngine( stoch_process, time_steps_per_year=250, required_samples=1023, seed=42, ) variance_swap = VarianceSwap( self.values['type'], self.values['strike'], self.values['nominal'], self.today, self.ex_date, ) variance_swap.set_pricing_engine(engine) calculated = variance_swap.variance expected = 0.04 tol = 3.0e-4 error = abs(calculated - expected) self.assertTrue(error < tol)
def test_analytic_cont_geom_av_price(self): """ "Testing analytic continuous geometric average-price Asians...") data from "Option Pricing Formulas", Haug, pag.96-97 """ exercise = EuropeanExercise(self.settlement_date) option = ContinuousAveragingAsianOption(Geometric, self.payoff, exercise) engine = AnalyticContinuousGeometricAveragePriceAsianEngine( self.black_scholes_merton_process) option.set_pricing_engine(engine) tolerance = 1.0e-4 self.assertAlmostEqual(4.6922, option.net_present_value, delta=tolerance) # trying to approximate the continuous version with the discrete version running_accumulator = 1.0 past_fixings = 0 fixing_dates = [self.today + i for i in range(91)] engine2 = AnalyticDiscreteGeometricAveragePriceAsianEngine( self.black_scholes_merton_process) option2 = DiscreteAveragingAsianOption( Geometric, self.payoff, exercise, fixing_dates, past_fixings=past_fixings, running_accum=running_accumulator, ) option2.set_pricing_engine(engine2) tolerance = 3.0e-3 self.assertAlmostEqual(4.6922, option.net_present_value, delta=tolerance)
def heston_pricer(trade_date, options, params, rates, spot): """ Price a list of European options with heston model. """ spot = SimpleQuote(spot) risk_free_ts = df_to_zero_curve(rates[nm.INTEREST_RATE], trade_date) dividend_ts = df_to_zero_curve(rates[nm.DIVIDEND_YIELD], trade_date) process = HestonProcess(risk_free_ts, dividend_ts, spot, **params) model = HestonModel(process) engine = AnalyticHestonEngine(model, 64) settlement_date = pydate_to_qldate(trade_date) settings = Settings() settings.evaluation_date = settlement_date modeled_values = np.zeros(len(options)) for index, row in options.T.iteritems(): expiry_date = row[nm.EXPIRY_DATE] strike = row[nm.STRIKE] option_type = Call if row[nm.OPTION_TYPE] == nm.CALL_OPTION else Put payoff = PlainVanillaPayoff(option_type, strike) expiry_qldate = pydate_to_qldate(expiry_date) exercise = EuropeanExercise(expiry_qldate) option = VanillaOption(payoff, exercise) option.set_pricing_engine(engine) modeled_values[index] = option.net_present_value prices = options.filter( items=[nm.EXPIRY_DATE, nm.STRIKE, nm.OPTION_TYPE, nm.SPOT]) prices[nm.PRICE] = modeled_values prices[nm.TRADE_DATE] = trade_date return prices
def _get_option_npv(self): """ Suboptimal getter for the npv. FIXME: We currently have to recreate most of the objects because we do not expose enough of the QuantLib api. """ # convert datetime object to QlDate maturity = QlDate.from_datetime(self.maturity) underlyingH = SimpleQuote(self.underlying) # bootstrap the yield/dividend/vol curves flat_term_structure = FlatForward( reference_date = settlement_date, forward = self.risk_free_rate, daycounter = self.daycounter ) flat_dividend_ts = FlatForward( reference_date = settlement_date, forward = self.dividend_yield, daycounter = self.daycounter ) flat_vol_ts = BlackConstantVol( settlement_date, calendar, self.volatility, self.daycounter ) black_scholes_merton_process = BlackScholesMertonProcess( underlyingH, flat_dividend_ts, flat_term_structure,flat_vol_ts ) payoff = PlainVanillaPayoff(self.option_type, self.strike) european_exercise = EuropeanExercise(maturity) european_option = VanillaOption(payoff, european_exercise) analytic_european_engine = AnalyticEuropeanEngine(black_scholes_merton_process) european_option.set_pricing_engine(analytic_european_engine) return european_option.net_present_value
def blsprice(spot, strike, risk_free_rate, time, volatility, option_type='Call', dividend=0.0): """ """ spot = SimpleQuote(spot) daycounter = Actual360() risk_free_ts = FlatForward(today(), risk_free_rate, daycounter) dividend_ts = FlatForward(today(), dividend, daycounter) volatility_ts = BlackConstantVol(today(), NullCalendar(), volatility, daycounter) process = BlackScholesMertonProcess(spot, dividend_ts, risk_free_ts, volatility_ts) exercise_date = today() + 90 exercise = EuropeanExercise(exercise_date) payoff = PlainVanillaPayoff(option_type, strike) option = EuropeanOption(payoff, exercise) engine = AnalyticEuropeanEngine(process) option.set_pricing_engine(engine) return option.npv
def dividendOption(): # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++ General Parameter for all the computation +++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # declaration of the today's date (date where the records are done) todaysDate = Date(24, Jan, 2012) # INPUT Settings.instance( ).evaluation_date = todaysDate #!\ IMPORTANT COMMAND REQUIRED FOR ALL VALUATIONS calendar = UnitedStates() # INPUT settlement_days = 2 # INPUT # Calcul of the settlement date : need to add a period of 2 days to the todays date settlementDate = calendar.advance(todaysDate, period=Period(settlement_days, Days)) dayCounter = Actual360() # INPUT currency = USDCurrency() # INPUT print("Date of the evaluation: ", todaysDate) print("Calendar used: ", calendar.name) print("Number of settlement Days: ", settlement_days) print("Date of settlement: ", settlementDate) print("Convention of day counter: ", dayCounter.name()) print("Currency of the actual context:\t\t", currency.name) # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++ Description of the underlying +++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ underlying_name = "IBM" underlying_price = 191.75 # INPUT underlying_vol = 0.2094 # INPUT print("**********************************") print("Name of the underlying: ", underlying_name) print("Price of the underlying at t0: ", underlying_price) print("Volatility of the underlying: ", underlying_vol) # For a great managing of price and vol objects --> Handle underlying_priceH = SimpleQuote(underlying_price) # We suppose the vol constant : his term structure is flat --> BlackConstantVol object flatVolTS = BlackConstantVol(settlementDate, calendar, underlying_vol, dayCounter) # ++++++++++++++++++++ Description of Yield Term Structure # Libor data record print("**********************************") print("Description of the Libor used for the Yield Curve construction") Libor_dayCounter = Actual360() liborRates = [] liborRatesTenor = [] # INPUT : all the following data are input : the rate and the corresponding tenor # You could make the choice of more or less data # --> However you have tho choice the instruments with different maturities liborRates = [ 0.002763, 0.004082, 0.005601, 0.006390, 0.007125, 0.007928, 0.009446, 0.01110 ] liborRatesTenor = [ Period(tenor, Months) for tenor in [1, 2, 3, 4, 5, 6, 9, 12] ] for tenor, rate in zip(liborRatesTenor, liborRates): print(tenor, "\t\t\t", rate) # Swap data record # description of the fixed leg of the swap Swap_fixedLegTenor = Period(12, Months) # INPUT Swap_fixedLegConvention = ModifiedFollowing # INPUT Swap_fixedLegDayCounter = Actual360() # INPUT # description of the float leg of the swap Swap_iborIndex = Libor("USDLibor", Period(3, Months), settlement_days, USDCurrency(), UnitedStates(), Actual360()) print("Description of the Swap used for the Yield Curve construction") print("Tenor of the fixed leg: ", Swap_fixedLegTenor) print("Index of the floated leg: ", Swap_iborIndex.name) print("Maturity Rate ") swapRates = [] swapRatesTenor = [] # INPUT : all the following data are input : the rate and the corresponding tenor # You could make the choice of more or less data # --> However you have tho choice the instruments with different maturities swapRates = [ 0.005681, 0.006970, 0.009310, 0.012010, 0.014628, 0.016881, 0.018745, 0.020260, 0.021545 ] swapRatesTenor = [Period(i, Years) for i in range(2, 11)] for tenor, rate in zip(swapRatesTenor, swapRates): print(tenor, "\t\t\t", rate) # ++++++++++++++++++++ Creation of the vector of RateHelper (need for the Yield Curve construction) # ++++++++++++++++++++ Libor LiborFamilyName = currency.name + "Libor" instruments = [] for rate, tenor in zip(liborRates, liborRatesTenor): # Index description ___ creation of a Libor index liborIndex = Libor(LiborFamilyName, tenor, settlement_days, currency, calendar, Libor_dayCounter) # Initialize rate helper ___ the DepositRateHelper link the recording rate with the Libor index instruments.append(DepositRateHelper(rate, index=liborIndex)) # +++++++++++++++++++++ Swap SwapFamilyName = currency.name + "swapIndex" for tenor, rate in zip(swapRatesTenor, swapRates): # swap description ___ creation of a swap index. The floating leg is described in the index 'Swap_iborIndex' swapIndex = SwapIndex(SwapFamilyName, tenor, settlement_days, currency, calendar, Swap_fixedLegTenor, Swap_fixedLegConvention, Swap_fixedLegDayCounter, Swap_iborIndex) # Initialize rate helper __ the SwapRateHelper links the swap index width his rate instruments.append(SwapRateHelper.from_index(rate, swapIndex)) # ++++++++++++++++++ Now the creation of the yield curve riskFreeTS = PiecewiseYieldCurve.from_reference_date( BootstrapTrait.ZeroYield, Interpolator.Linear, settlementDate, instruments, dayCounter) # ++++++++++++++++++ build of the underlying process : with a Black-Scholes model print('Creating process') bsProcess = BlackScholesProcess(underlying_priceH, riskFreeTS, flatVolTS) # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++ Description of the option +++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Option_name = "IBM Option" maturity = Date(26, Jan, 2013) strike = 190 option_type = 'call' # Here, as an implementation exemple, we make the test with borth american and european exercise europeanExercise = EuropeanExercise(maturity) # The emericanExercise need also the settlement date, as his right to exerce the buy or call start at the settlement date! #americanExercise = AmericanExercise(settlementDate, maturity) americanExercise = AmericanExercise(maturity, settlementDate) print("**********************************") print("Description of the option: ", Option_name) print("Date of maturity: ", maturity) print("Type of the option: ", option_type) print("Strike of the option: ", strike) # ++++++++++++++++++ Description of the discrete dividends # INPUT You have to determine the frequece and rates of the discrete dividend. Here is a sollution, but she's not the only one. # Last know dividend: dividend = 0.75 #//0.75 next_dividend_date = Date(10, Feb, 2012) # HERE we have make the assumption that the dividend will grow with the quarterly croissance: dividendCroissance = 1.03 dividendfrequence = Period(3, Months) dividendDates = [] dividends = [] d = next_dividend_date while d <= maturity: dividendDates.append(d) dividends.append(dividend) d = d + dividendfrequence dividend *= dividendCroissance print("Discrete dividends ") print("Dates Dividends ") for date, div in zip(dividendDates, dividends): print(date, " ", div) # ++++++++++++++++++ Description of the final payoff payoff = PlainVanillaPayoff(option_type, strike) # ++++++++++++++++++ The OPTIONS : (American and European) with their dividends description: dividendEuropeanOption = DividendVanillaOption(payoff, europeanExercise, dividendDates, dividends) dividendAmericanOption = DividendVanillaOption(payoff, americanExercise, dividendDates, dividends) # just too test europeanOption = VanillaOption(payoff, europeanExercise) americanOption = VanillaOption(payoff, americanExercise) # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++ Description of the pricing +++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # For the european options we have a closed analytic formula: The Black Scholes: dividendEuropeanEngine = AnalyticDividendEuropeanEngine(bsProcess) # For the american option we have make the choice of the finite difference model with the CrankNicolson scheme # this model need to precise the time and space step # More they are greater, more the calul will be precise. americanGirdPoints = 600 americanTimeSteps = 600 dividendAmericanEngine = FDDividendAmericanEngine('CrankNicolson', bsProcess, americanTimeSteps, americanGirdPoints) # just to test europeanEngine = AnalyticEuropeanEngine(bsProcess) americanEngine = FDAmericanEngine('CrankNicolson', bsProcess, americanTimeSteps, americanGirdPoints) # ++++++++++++++++++++ Valorisation ++++++++++++++++++++++++++++++++++++++++ # Link the pricing Engine to the option dividendEuropeanOption.set_pricing_engine(dividendEuropeanEngine) dividendAmericanOption.set_pricing_engine(dividendAmericanEngine) # just to test europeanOption.set_pricing_engine(europeanEngine) americanOption.set_pricing_engine(americanEngine) # Now we make all the needing calcul # ... and final results print( "NPV of the European Option with discrete dividends=0: {:.4f}".format( dividendEuropeanOption.npv)) print("NPV of the European Option without dividend: {:.4f}".format( europeanOption.npv)) print( "NPV of the American Option with discrete dividends=0: {:.4f}".format( dividendAmericanOption.npv)) print("NPV of the American Option without dividend: {:.4f}".format( americanOption.npv)) # just a single test print("ZeroRate with a maturity at ", maturity, ": ", \ riskFreeTS.zero_rate(maturity, dayCounter, Simple))
# bootstrap the yield/dividend/vol curves flat_term_structure = FlatForward(reference_date=settlement_date, forward=risk_free_rate, daycounter=daycounter) flat_dividend_ts = FlatForward(reference_date=settlement_date, forward=dividend_yield, daycounter=daycounter) flat_vol_ts = BlackConstantVol(settlement_date, calendar, volatility, daycounter) black_scholes_merton_process = BlackScholesMertonProcess( underlyingH, flat_dividend_ts, flat_term_structure, flat_vol_ts) payoff = PlainVanillaPayoff(option_type, strike) european_exercise = EuropeanExercise(maturity) european_option = VanillaOption(payoff, european_exercise) method = 'Black-Scholes' analytic_european_engine = AnalyticEuropeanEngine(black_scholes_merton_process) european_option.set_pricing_engine(analytic_european_engine) print('today: %s settlement: %s maturity: %s' % (todays_date, settlement_date, maturity)) print('NPV: %f\n' % european_option.net_present_value) ### EOF #######################################################################
def test_analytic_cont_geo_av_price_greeks(self): tolerance = {} tolerance["delta"] = 1.0e-5 tolerance["gamma"] = 1.0e-5 # tolerance["theta"] = 1.0e-5 tolerance["rho"] = 1.0e-5 tolerance["divRho"] = 1.0e-5 tolerance["vega"] = 1.0e-5 opt_types = [Call, Put] underlyings = [100.0] strikes = [90.0, 100.0, 110.0] q_rates = [0.04, 0.05, 0.06] r_rates = [0.01, 0.05, 0.15] lengths = [1, 2] vols = [0.11, 0.50, 1.20] spot = SimpleQuote(0.0) q_rate = SimpleQuote(0.0) r_rate = SimpleQuote(0.0) vol = SimpleQuote(0.0) q_ts = flat_rate(q_rate, self.daycounter) r_ts = flat_rate(r_rate, self.daycounter) vol_ts = BlackConstantVol(self.today, self.calendar, vol, self.daycounter) process = BlackScholesMertonProcess(spot, q_ts, r_ts, vol_ts) calculated = {} expected = {} for opt_type, strike, length in product(opt_types, strikes, lengths): maturity = EuropeanExercise(self.today + length*Years) payoff = PlainVanillaPayoff(opt_type, strike) engine = AnalyticContinuousGeometricAveragePriceAsianEngine(process) option = ContinuousAveragingAsianOption(Geometric, payoff, maturity) option.set_pricing_engine(engine) for u, m, n, v in product(underlyings, q_rates, r_rates, vols): q = m r = n spot.value = u q_rate.value = q r_rate.value = r vol.value = v value = option.npv calculated["delta"] = option.delta calculated["gamma"] = option.gamma # calculated["theta"] = option.theta calculated["rho"] = option.rho calculated["divRho"] = option.dividend_rho calculated["vega"] = option.vega if (value > spot.value*1.0e-5): # perturb spot and get delta and gamma du = u*1.0e-4 spot.value = u + du value_p = option.npv delta_p = option.delta spot.value = u - du value_m = option.npv delta_m = option.delta spot.value = u expected["delta"] = (value_p - value_m)/(2*du) expected["gamma"] = (delta_p - delta_m)/(2*du) # perturb rates and get rho and dividend rho dr = r*1.0e-4 r_rate.value = r + dr value_p = option.npv r_rate.value = r - dr value_m = option.npv r_rate.value = r expected["rho"] = (value_p - value_m)/(2*dr) dq = q*1.0e-4 q_rate.value = q + dq value_p = option.npv q_rate.value = q - dq value_m = option.npv q_rate.value = q expected["divRho"] = (value_p - value_m)/(2*dq) # perturb volatility and get vega dv = v*1.0e-4 vol.value = v + dv value_p = option.npv vol.value = v - dv value_m = option.npv vol.value = v expected["vega"] = (value_p - value_m)/(2*dv) # perturb date and get theta dt = self.daycounter.year_fraction(self.today - 1, self.today + 1) self.settings.evaluation_date = self.today - 1 value_m = option.npv self.settings.evaluation_date = self.today + 1 value_p = option.npv self.settings.evaluation_date = self.today expected["theta"] = (value_p - value_m)/dt # compare for greek, calcl in calculated.items(): expct = expected[greek] tol = tolerance[greek] error = relative_error(expct, calcl, u) self.assertTrue(error < tol)
def test_bates_det_jump(self): # this looks like a bug in QL: # Bates Det Jump model does not have sigma as parameter, yet # changing sigma changes the result! settlement_date = today() self.settings.evaluation_date = settlement_date daycounter = ActualActual() exercise_date = settlement_date + 6 * Months payoff = PlainVanillaPayoff(Put, 1290) exercise = EuropeanExercise(exercise_date) option = VanillaOption(payoff, exercise) risk_free_ts = flat_rate(0.02, daycounter) dividend_ts = flat_rate(0.04, daycounter) spot = 1290 ival = {'delta': 3.6828677022272715e-06, 'kappa': 19.02581428347027, 'kappaLambda': 1.1209758060939223, 'lambda': 0.06524550732595163, 'nu': -1.8968106563601956, 'rho': -0.7480898462264719, 'sigma': 1.0206363887835108, 'theta': 0.01965384459461113, 'thetaLambda': 0.028915397380738218, 'v0': 0.06566800935242285} process = BatesProcess( risk_free_ts, dividend_ts, SimpleQuote(spot), ival['v0'], ival['kappa'], ival['theta'], ival['sigma'], ival['rho'], ival['lambda'], ival['nu'], ival['delta']) model = BatesDetJumpModel(process, ival['kappaLambda'], ival['thetaLambda']) engine = BatesDetJumpEngine(model, 64) option.set_pricing_engine(engine) calc_1 = option.net_present_value ival['sigma'] = 1.e-6 process = BatesProcess( risk_free_ts, dividend_ts, SimpleQuote(spot), ival['v0'], ival['kappa'], ival['theta'], ival['sigma'], ival['rho'], ival['lambda'], ival['nu'], ival['delta']) model = BatesDetJumpModel(process, ival['kappaLambda'], ival['thetaLambda']) engine = BatesDetJumpEngine(model, 64) option.set_pricing_engine(engine) calc_2 = option.net_present_value if(abs(calc_1-calc_2) > 1.e-5): print('calc 1 %f calc 2 %f' % (calc_1, calc_2)) self.assertNotEqual(calc_1, calc_2)
def test_bsm_hw(self): print("Testing European option pricing for a BSM process" + " with one-factor Hull-White model...") dc = Actual365Fixed() todays_date = today() maturity_date = todays_date + Period(20, Years) settings = Settings() settings.evaluation_date = todays_date spot = SimpleQuote(100) q_ts = flat_rate(todays_date, 0.04, dc) r_ts = flat_rate(todays_date, 0.0525, dc) vol_ts = BlackConstantVol(todays_date, NullCalendar(), 0.25, dc) hullWhiteModel = HullWhite(r_ts, 0.00883, 0.00526) bsm_process = BlackScholesMertonProcess(spot, q_ts, r_ts, vol_ts) exercise = EuropeanExercise(maturity_date) fwd = spot.value * q_ts.discount(maturity_date) / \ r_ts.discount(maturity_date) payoff = PlainVanillaPayoff(Call, fwd) option = VanillaOption(payoff, exercise) tol = 1e-8 corr = [-0.75, -0.25, 0.0, 0.25, 0.75] expectedVol = [ 0.217064577, 0.243995801, 0.256402830, 0.268236596, 0.290461343 ] for c, v in zip(corr, expectedVol): bsm_hw_engine = AnalyticBSMHullWhiteEngine(c, bsm_process, hullWhiteModel) option = VanillaOption(payoff, exercise) option.set_pricing_engine(bsm_hw_engine) npv = option.npv compVolTS = BlackConstantVol(todays_date, NullCalendar(), v, dc) bs_process = BlackScholesMertonProcess(spot, q_ts, r_ts, compVolTS) bsEngine = AnalyticEuropeanEngine(bs_process) comp = VanillaOption(payoff, exercise) comp.set_pricing_engine(bsEngine) impliedVol = comp.implied_volatility(npv, bs_process, 1e-10, 500, min_vol=0.1, max_vol=0.4) if (abs(impliedVol - v) > tol): print("Failed to reproduce implied volatility cor: %f" % c) print("calculated: %f" % impliedVol) print("expected : %f" % v) if abs((comp.npv - npv) / npv) > tol: print("Failed to reproduce NPV") print("calculated: %f" % comp.npv) print("expected : %f" % npv) self.assertAlmostEqual(impliedVol, v, delta=tol) self.assertAlmostEqual(comp.npv / npv, 1, delta=tol)
def test_zanette(self): """ From paper by A. Zanette et al. """ dc = Actual365Fixed() todays_date = today() settings = Settings() settings.evaluation_date = todays_date # constant yield and div curves dates = [todays_date + Period(i, Years) for i in range(3)] rates = [0.04 for i in range(3)] divRates = [0.03 for i in range(3)] r_ts = ZeroCurve(dates, rates, dc) q_ts = ZeroCurve(dates, divRates, dc) s0 = SimpleQuote(100) # Heston model v0 = .1 kappa_v = 2 theta_v = 0.1 sigma_v = 0.3 rho_sv = -0.5 hestonProcess = HestonProcess(risk_free_rate_ts=r_ts, dividend_ts=q_ts, s0=s0, v0=v0, kappa=kappa_v, theta=theta_v, sigma=sigma_v, rho=rho_sv) hestonModel = HestonModel(hestonProcess) # Hull-White kappa_r = 1 sigma_r = .2 hullWhiteProcess = HullWhiteProcess(r_ts, a=kappa_r, sigma=sigma_r) strike = 100 maturity = 1 type = Call maturity_date = todays_date + Period(maturity, Years) exercise = EuropeanExercise(maturity_date) payoff = PlainVanillaPayoff(type, strike) option = VanillaOption(payoff, exercise) def price_cal(rho, tGrid): fd_hestonHwEngine = FdHestonHullWhiteVanillaEngine( hestonModel, hullWhiteProcess, rho, tGrid, 100, 40, 20, 0, True, FdmSchemeDesc.Hundsdorfer()) option.set_pricing_engine(fd_hestonHwEngine) return option.npv calc_price = [] for rho in [-0.5, 0, .5]: for tGrid in [50, 100, 150, 200]: tmp = price_cal(rho, tGrid) print("rho (S,r): %f Ns: %d Price: %f" % (rho, tGrid, tmp)) calc_price.append(tmp) expected_price = [ 11.38, ] * 4 + [ 12.79, ] * 4 + [ 14.06, ] * 4 np.testing.assert_almost_equal(calc_price, expected_price, 2)
def test_compare_BsmHW_HestonHW(self): """ From Quantlib test suite """ print("Comparing European option pricing for a BSM " + "process with one-factor Hull-White model...") dc = Actual365Fixed() todays_date = today() settings = Settings() settings.evaluation_date = todays_date tol = 1.e-2 spot = SimpleQuote(100) dates = [todays_date + Period(i, Years) for i in range(40)] rates = [0.01 + 0.0002 * np.exp(np.sin(i / 4.0)) for i in range(40)] divRates = [0.02 + 0.0001 * np.exp(np.sin(i / 5.0)) for i in range(40)] s0 = SimpleQuote(100) r_ts = ZeroCurve(dates, rates, dc) q_ts = ZeroCurve(dates, divRates, dc) vol = SimpleQuote(0.25) vol_ts = BlackConstantVol(todays_date, NullCalendar(), vol.value, dc) bsm_process = BlackScholesMertonProcess(spot, q_ts, r_ts, vol_ts) variance = vol.value * vol.value hestonProcess = HestonProcess(risk_free_rate_ts=r_ts, dividend_ts=q_ts, s0=s0, v0=variance, kappa=5.0, theta=variance, sigma=1e-4, rho=0.0) hestonModel = HestonModel(hestonProcess) hullWhiteModel = HullWhite(r_ts, a=0.01, sigma=0.01) bsmhwEngine = AnalyticBSMHullWhiteEngine(0.0, bsm_process, hullWhiteModel) hestonHwEngine = AnalyticHestonHullWhiteEngine(hestonModel, hullWhiteModel, 128) tol = 1e-5 strikes = [0.25, 0.5, 0.75, 0.8, 0.9, 1.0, 1.1, 1.2, 1.5, 2.0, 4.0] maturities = [1, 2, 3, 5, 10, 15, 20, 25, 30] types = [Put, Call] for type in types: for strike in strikes: for maturity in maturities: maturity_date = todays_date + Period(maturity, Years) exercise = EuropeanExercise(maturity_date) fwd = strike * s0.value * \ q_ts.discount(maturity_date) / \ r_ts.discount(maturity_date) payoff = PlainVanillaPayoff(type, fwd) option = VanillaOption(payoff, exercise) option.set_pricing_engine(bsmhwEngine) calculated = option.npv option.set_pricing_engine(hestonHwEngine) expected = option.npv if ((np.abs(expected - calculated) > calculated * tol) and (np.abs(expected - calculated) > tol)): cp = PAYOFF_TO_STR[type] print("Failed to reproduce npv") print("strike : %f" % strike) print("maturity : %d" % maturity) print("type : %s" % cp) self.assertAlmostEqual(expected, calculated, delta=tol)
def test_compare_bsm_bsmhw_hestonhw(self): dc = Actual365Fixed() todays_date = today() settings = Settings() settings.evaluation_date = todays_date tol = 1.e-2 spot = SimpleQuote(100) dates = [todays_date + Period(i, Years) for i in range(40)] rates = [0.01 + 0.0002 * np.exp(np.sin(i / 4.0)) for i in range(40)] divRates = [0.02 + 0.0001 * np.exp(np.sin(i / 5.0)) for i in range(40)] s0 = SimpleQuote(100) r_ts = ZeroCurve(dates, rates, dc) q_ts = ZeroCurve(dates, divRates, dc) vol = SimpleQuote(0.25) vol_ts = BlackConstantVol(todays_date, NullCalendar(), vol.value, dc) bsm_process = BlackScholesMertonProcess(spot, q_ts, r_ts, vol_ts) payoff = PlainVanillaPayoff(Call, 100) exercise = EuropeanExercise(dates[1]) option = VanillaOption(payoff, exercise) analytic_european_engine = AnalyticEuropeanEngine(bsm_process) option.set_pricing_engine(analytic_european_engine) npv_bsm = option.npv variance = vol.value * vol.value hestonProcess = HestonProcess(risk_free_rate_ts=r_ts, dividend_ts=q_ts, s0=s0, v0=variance, kappa=5.0, theta=variance, sigma=1e-4, rho=0.0) hestonModel = HestonModel(hestonProcess) hullWhiteModel = HullWhite(r_ts, a=0.01, sigma=0.01) bsmhwEngine = AnalyticBSMHullWhiteEngine(0.0, bsm_process, hullWhiteModel) hestonHwEngine = AnalyticHestonHullWhiteEngine(hestonModel, hullWhiteModel, 128) hestonEngine = AnalyticHestonEngine(hestonModel, 144) option.set_pricing_engine(hestonEngine) npv_heston = option.npv option.set_pricing_engine(bsmhwEngine) npv_bsmhw = option.npv option.set_pricing_engine(hestonHwEngine) npv_hestonhw = option.npv print("calculated with BSM: %f" % npv_bsm) print("BSM-HW: %f" % npv_bsmhw) print("Heston: %f" % npv_heston) print("Heston-HW: %f" % npv_hestonhw) self.assertAlmostEqual(npv_bsm, npv_bsmhw, delta=tol) self.assertAlmostEqual(npv_bsm, npv_hestonhw, delta=tol)
hestonModel = HestonModel(hestonProcess) # Hull-White kappa_r = 1 sigma_r = .2 hullWhiteProcess = HullWhiteProcess(r_ts, a=kappa_r, sigma=sigma_r) strike = 100 maturity = 1 type = Call maturity_date = todays_date + Period(maturity, Years) exercise = EuropeanExercise(maturity_date) payoff = PlainVanillaPayoff(type, strike) option = VanillaOption(payoff, exercise) def price_cal(rho, tGrid): fd_hestonHwEngine = FdHestonHullWhiteVanillaEngine( hestonModel, hullWhiteProcess, rho, tGrid, 100, 40, 20, 0, True, FdmSchemeDesc.Hundsdorfer()) option.set_pricing_engine(fd_hestonHwEngine) return option.npv calc_price = []