I'm trying to use the QuantLib library to price American options that pay discrete dividends.
The call options are priced with good accuracy (generally <0.1% error), however the same inputs for a put option have a large discrepancy (>50% error) compared to the expected values (obtained from Hoadley's excel software), especially for deep out-the-money put options.
The function I am using is defined as so:
import QuantLib as ql
def get_option_price(
valuation_date = [1,7,2021],
expiry_date = [1,7,2022],
strike_price = 70,
underlying_price = 100,
volatility = 0.30,
risk_free_rate = 0.06,
dividends = [],
is_american = True,
is_call = True
):
div_dates = []
div_values = []
for div in dividends:
date = div[0]
value = div[1]
div_dates.append(ql.Date(*date))
div_values.append(value)
# Reformat dates from list into QL date format
valuation_date = ql.Date(*valuation_date)
expiry_date = ql.Date(*expiry_date)
ql.Settings.instance().setEvaluationDate(valuation_date)
day_count = ql.Actual365Fixed()
calendar = ql.Australia()
# Reformat prices and rates from list into QL format
underlying_price = ql.QuoteHandle(ql.SimpleQuote(underlying_price))
risk_free_rate = ql.YieldTermStructureHandle(
ql.FlatForward(valuation_date, risk_free_rate, day_count))
volatility = ql.BlackVolTermStructureHandle(
ql.BlackConstantVol(valuation_date, calendar, volatility, day_count))
# Create option
if is_call:
payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike_price)
else:
payoff = ql.PlainVanillaPayoff(ql.Option.Put, strike_price)
if is_american:
exercise = ql.AmericanExercise(valuation_date, expiry_date)
else:
exercise = ql.EuropeanExercise(expiry_date)
option = ql.DividendVanillaOption(payoff, exercise, div_dates,
div_values)
# Black Scholes process
process = ql.BlackScholesProcess(underlying_price,
risk_free_rate,
volatility)
# Create option's pricing engine
precision_steps = 500
engine = ql.FdBlackScholesVanillaEngine(process, precision_steps, precision_steps - 1)
option.setPricingEngine(engine)
# Price the option
return option.NPV()
Here are some sample inputs/outputs for call options that generally do as I expect:
price = get_option_price(dividends = [[[1,7,2021], 30]], is_call = True)
print(price)
>> 29.999999999999044
# (Expected: 30.0004)
price = get_option_price(dividends = [[[1,1,2022], 30]], is_call = True)
print(price)
>> 32.32516446072868
# (Expected: 32.3034)
price = get_option_price(dividends = [[[30,6,2022], 30]], is_call = True)
print(price)
>> 34.969800332368024
# (Expected: 34.9839)
Here are the sample inputs/outputs for put options that are inaccurate:
price = get_option_price(dividends = [[[1,7,2021], 30]], is_call = False)
print(price)
>> 6.670681910486139
# (Expected: 6.6734)
price = get_option_price(dividends = [[[1,1,2022], 30]], is_call = False)
print(price)
>> 8.262009670450684
# (Expected: 6.2855)
price = get_option_price(dividends = [[[30,6,2022], 30]], is_call = False)
print(price)
>> 8.882930513922709
# (Expected: 5.6245)
The inaccuracy generally worsens as the dividend date approaches expiry, or if the dividend value increases.
- Is there something wrong with my implementation, or is this a limitation of the pricing engine I am using? If it's the latter:
- Are there alternative pricing engines that can handle discrete dividends? (as far as I'm aware QuantLib's BinomialVanillaEngine doesn't support discrete dividend scheduling)