Estoy tratando de usar la biblioteca QuantLib para determinar el precio de opciones americanas que pagan dividendos discretos.
Las opciones de compra tienen un precio con buena precisión (generalmente, un error <0,1%), sin embargo, los mismos datos de entrada para una opción de venta presentan una gran discrepancia (>50% de error) en comparación con los valores esperados (obtenidos del software de Excel de Hoadley), especialmente para opciones de venta muy fuera del dinero.
La función que estoy utilizando está definida de la siguiente manera:
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)
# Precio de la opción
return option.NPV()
Aquí tienes algunos ejemplos de datos de entrada/salida para opciones de compra que generalmente se comportan como espero:
price = get_option_price(dividends = [[[1,7,2021], 30]], is_call = True)
print(price)
>> 29.999999999999044
# (Esperado: 30.0004)
price = get_option_price(dividends = [[[1,1,2022], 30]], is_call = True)
print(price)
>> 32.32516446072868
# (Esperado: 32.3034)
price = get_option_price(dividends = [[[30,6,2022], 30]], is_call = True)
print(price)
>> 34.969800332368024
# (Esperado: 34.9839)
Aquí tienes ejemplos de datos de entrada/salida para opciones de venta que no son precisos:
price = get_option_price(dividends = [[[1,7,2021], 30]], is_call = False)
print(price)
>> 6.670681910486139
# (Esperado: 6.6734)
price = get_option_price(dividends = [[[1,1,2022], 30]], is_call = False)
print(price)
>> 8.262009670450684
# (Esperado: 6.2855)
price = get_option_price(dividends = [[[30,6,2022], 30]], is_call = False)
print(price)
>> 8.882930513922709
# (Esperado: 5.6245)
La falta de precisión generalmente empeora a medida que la fecha de dividendo se acerca al vencimiento, o si el valor del dividendo aumenta.
- ¿Hay algo mal en mi implementación, o es una limitación del motor de fijación de precios que estoy utilizando? En caso de que sea lo último:
- ¿Existen otros motores de fijación de precios alternativos que puedan manejar dividendos discretos? (hasta donde sé, el BinomialVanillaEngine de QuantLib no admite programación de dividendos discretos)