Utilizo el modelo Black Scholes Merton (BSM) de QuantLib para calcular el precio de las opciones Call y sus griegas analíticas. También calculo manualmente sus griegas numéricas ( Theta, Vega ), pero los resultados no coinciden. ¿Alguien sabe qué ha fallado en mi aplicación? Gracias.
import numpy as np
import pandas as pd
import QuantLib as ql
def bsm_quantlib(numerical=False):
spot = 100 # spot price
strike = 120 # strike price
rf_rate = 0.035 # risk-free annual interest rate
vol = 0.16 # annual volatility or sigma
div = 0.01 # annual dividend rate
eval_date = ql.Date(7, 1, 2023)
expiry_date = ql.Date(7, 1, 2024)
ql.Settings.instance().evaluationDate = eval_date
calendar = ql.UnitedStates(ql.UnitedStates.NYSE)
day_counter = ql.Actual365Fixed()
payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike)
spot_quote = ql.SimpleQuote(spot)
rf_quote = ql.SimpleQuote(rf_rate)
vol_quote = ql.SimpleQuote(vol)
spot_handle = ql.QuoteHandle(spot_quote)
vol_handle = ql.QuoteHandle(vol_quote)
rf_handle = ql.QuoteHandle(rf_quote)
div_handle = ql.QuoteHandle(ql.SimpleQuote(div))
dividend_yield = ql.YieldTermStructureHandle(
ql.FlatForward(0, calendar, div_handle , day_counter))
risk_free_curve = ql.YieldTermStructureHandle(
ql.FlatForward(0, calendar, rf_handle, day_counter))
volatility = ql.BlackVolTermStructureHandle(
ql.BlackConstantVol(0, calendar, vol_handle, day_counter))
engine = ql.AnalyticEuropeanEngine(
ql.BlackScholesMertonProcess(
spot_handle, dividend_yield, risk_free_curve, volatility))
exercise = ql.EuropeanExercise(expiry_date)
option = ql.VanillaOption(payoff, exercise)
option.setPricingEngine(engine)
greeks = (
numerical_greeks(option, spot_quote, vol_quote, eval_date)
if numerical
else analytical_greeks(option))
return greeks | dict(
call_price=option.NPV(),
spot=spot,
strike=strike,
tau=(expiry_date - eval_date) / 365.0,
riskfree_rate=rf_rate,
volatility=vol,
dividend=div)
donde analytical_greeks()
y numerical_greeks()
son
def analytical_greeks(option):
return dict(
Greeks='QuantLib Analytical',
delta=option.delta(),
theta=option.thetaPerDay(),
vega=option.vega()/100)
def numerical_greeks(option, spot_quote, vol_quote, eval_date):
# delta
p0 = option.NPV()
s0 = spot_quote.value()
v0 = vol_quote.value()
h = 0.01
spot_quote.setValue(s0 + h)
pplus = option.NPV()
spot_quote.setValue(s0 - h)
pminus = option.NPV()
spot_quote.setValue(s0)
delta = (pplus - pminus) / (2*h)
# vega
vol_quote.setValue(v0 + h)
pplus = option.NPV()
vol_quote.setValue(v0)
vega = (pplus - p0) / h
# theta
ql.Settings.instance().evaluationDate = eval_date + 365
pplus = option.NPV()
ql.Settings.instance().evaluationDate = eval_date
theta = (pplus - p0)
return dict(
Greeks='QuantLib Numerical',
delta=delta,
theta=theta/365,
vega=vega/100)
Para comparar, también incluyo el cálculo utilizando el py_vollib
paquete. Para su información, tuve que modificar ligeramente la fecha para que todos los tau=1.0
import numpy as np
import pandas as pd
import datetime as dt
from py_vollib.black_scholes_merton import black_scholes_merton as bsm
from py_vollib.black_scholes_merton.greeks.numerical import (
delta as delta_bsm_n,
theta as theta_bsm_n,
vega as vega_bsm_n)
from py_vollib.black_scholes_merton.greeks.analytical import (
delta as delta_bsm_a,
theta as theta_bsm_a,
vega as vega_bsm_a)
def bsm_vollib(numerical=False):
flag = 'c' # call options
spot = 100 # spot price
strike = 120 # strike price
rf_rate = 0.035 # risk-free annual interest rate
vol = 0.16 # annual volatility or sigma
div = 0.01 # annual dividend rate
eval_date = dt.datetime(2023, 7, 2)
expiry_date = dt.datetime(2024, 7, 1)
tau = (expiry_date - eval_date).days / 365.0
price = bsm(flag, spot, strike, tau, rf_rate, vol, div)
if numerical:
greeks = dict(
Greeks='Vollib Analytical',
delta=delta_bsm_n(flag, spot, strike, tau, rf_rate, vol, div),
theta=theta_bsm_n(flag, spot, strike, tau, rf_rate, vol, div),
vega=vega_bsm_n(flag, spot, strike, tau, rf_rate, vol, div))
else:
greeks = dict(
Greeks='Vollib Numerical',
delta=delta_bsm_a(flag, spot, strike, tau, rf_rate, vol, div),
theta=theta_bsm_a(flag, spot, strike, tau, rf_rate, vol, div),
vega=vega_bsm_a(flag, spot, strike, tau, rf_rate, vol, div))
return greeks | dict(
call_price=price,
spot=spot,
strike=strike,
tau=tau,
riskfree_rate=rf_rate,
volatility=vol,
dividend=div)
He aquí el resultado. El theta
y vega
de py_vollib
El griego numérico y el griego analítico son casi idénticos. Pero con QuantLib, se desvían bastante. ¿Alguna idea de por qué?
pd.DataFrame([
bsm_quantlib(numerical=False),
bsm_quantlib(numerical=True),
bsm_vollib(numerical=False),
bsm_vollib(numerical=True),
]).round(4)