3 votos

Quantlib: evaluación diaria del valor de la opción

Estoy utilizando Quantlib en Python para fijar el precio de una opción FX. Estoy comparando el resultado con Bloomberg, para asegurarme de que el código está funcionando correctamente.

Quiero calcular las pérdidas y ganancias de una determinada estrategia de negociación de opciones mediante el uso de la expansión de Taylor de las pérdidas y ganancias (discutido en otro post aquí ) Y también utilizando el VAN de la opción.

Por lo tanto, es importante tener un VAN correcto para cada fecha en que la opción esté viva. La opción que utilizo para probar esto es una opción estilizada de 1 semana.

El problema que se produce es que el VAN coincide con Bloomberg correctamente en la primera, segunda, tercera y última fecha, pero no en las demás.

import QuantLib as ql

Spot = 1.1
Strike = 1.101
Sigma = 10/100
Ccy1Rate = 5/100
Ccy2Rate = 10/100
OptionType = ql.Option.Call

#Option dates in quantlib objects
EvaluationDate = ql.Date(3, 1,2022)
SettlementDate = ql.Date(5, 1, 2022) #Evaluation +2
ExpiryDate = ql.Date(10, 1, 2022) #Evaluation + term which is 1 week
DeliveryDate = ql.Date(12, 1, 2022) #Expiry +2
NumberOfDaysBetween = ExpiryDate - EvaluationDate
#print(NumberOfDaysBetween)

#Generate continuous interest rates
EurRate = Ccy1Rate
UsdRate = Ccy2Rate

#Create QuoteHandle objects. Easily to adapt later on.
#You can only access SimpleQuote objects. When you use setvalue, you can change it.
#These global variables will then be used in pricing the option.
#Everything will be adaptable except for the strike.
SpotGlobal = ql.SimpleQuote(Spot)
SpotHandle = ql.QuoteHandle(SpotGlobal)
VolGlobal = ql.SimpleQuote(Sigma)
VolHandle = ql.QuoteHandle(VolGlobal)
UsdRateGlobal = ql.SimpleQuote(UsdRate)
UsdRateHandle = ql.QuoteHandle(UsdRateGlobal)
EurRateGlobal = ql.SimpleQuote(EurRate)
EurRateHandle = ql.QuoteHandle(EurRateGlobal)

#Settings such as calendar, evaluationdate; daycount
Calendar = ql.UnitedStates()
ql.Settings.instance().evaluationDate = EvaluationDate
DayCountRate = ql.Actual360()
DayCountVolatility = ql.ActualActual()

#Create rate curves, vol surface and GK process
RiskFreeRateEUR = ql.YieldTermStructureHandle(ql.FlatForward(0, Calendar, EurRateHandle, DayCountRate))
RiskFreeRateUSD = ql.YieldTermStructureHandle(ql.FlatForward(0, Calendar, UsdRate, DayCountRate))
Volatility = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(0, Calendar, VolHandle, DayCountVolatility))
GKProcess = ql.GarmanKohlagenProcess(SpotHandle, RiskFreeRateEUR, RiskFreeRateUSD, Volatility)

#Generate option
Payoff = ql.PlainVanillaPayoff(OptionType, Strike)
Exercise = ql.EuropeanExercise(ExpiryDate)
Option = ql.VanillaOption(Payoff, Exercise)
Option.setPricingEngine(ql.AnalyticEuropeanEngine(GKProcess))
BsPrice = Option.NPV()

ql.Settings.instance().includeReferenceDateEvents = True

ql.Settings.instance().evaluationDate = EvaluationDate
print("Premium is:", Option.NPV()*1000000/Spot)

ql.Settings.instance().evaluationDate = EvaluationDate+1
print("Premium is:", Option.NPV()*1000000/Spot)

ql.Settings.instance().evaluationDate = EvaluationDate+2
print("Premium is:", Option.NPV()*1000000/Spot)

ql.Settings.instance().evaluationDate = EvaluationDate+3
print("Premium is:", Option.NPV()*1000000/Spot)

ql.Settings.instance().evaluationDate = EvaluationDate+4
print("Premium is:", Option.NPV()*1000000/Spot)

ql.Settings.instance().evaluationDate = EvaluationDate+7
print("Premium is:", Option.NPV()*1000000/Spot)

Lo que resulta en:

Premium is: 5487.479999102207

Premium is: 5148.552323458257

Premium is: 4774.333578225227

Premium is: 4353.586300232529

Premium is: 3867.4561591587326

Premium is: 909.0909090908089

Sin embargo, según Bloomberg la prima debería ser:

5487.48 (correct)
5148.55 (correct)
4774.33 (correct)
4499.5 
4015.7
909.9 (correct)

El resultado al vencimiento viene dado por la fijación de lo siguiente (la opción vence in-the-money):

ql.Settings.instance().includeReferenceDateEvents = True

¿Puede alguien explicar por qué el VAN de repente no coincide para esas 2 fechas?

Capturas de pantalla de la valoración de opciones en Bloomberg here

2 votos

En OVML hay dos intervalos de tiempo diferentes: a) tiempo hasta el vencimiento = Fecha de vencimiento - Fecha de precio b) tiempo hasta la entrega = Fecha de entrega - Fecha de prima Quantlib parece no calcular esto (como Matlab, por ejemplo, tampoco lo distingue). Puede ver la fecha de la prima en la parte inferior de la pantalla OVML.

9voto

BC. Puntos 9229

El lenguaje que aparece a continuación no es Python, sino Julia porque ya tenía este código. Sin embargo, la sintaxis es lo suficientemente similar a la de Python, por lo que debería ser posible seguir la lógica.

Importar todos los paquetes y definir el cdf.

using Distributions, Dates
N(x) = cdf(Normal(0,1),x)

Definir fechas

price_dt = Date(2022,1,6)
premium_dt = Date(2022,1,10)
expiry_dt = Date(2022,1,10)
delivery_dt = Date(2022,1,12)
days_to_expiry = (expiry_dt - price_dt)
days_to_delivery = (delivery_dt - premium_dt)
println(days_to_expiry)
println(days_to_delivery)
println("Time to expiry = $(days_to_expiry.value/365)")
println("Time to delivery = $(days_to_delivery.value/365)")

Resultado:

enter image description here

Definir las entradas

spot = 1.1
points = 3.06
fwd_scale = 10000
f = spot + points / fwd_scale
k = 1.101
ccy1 = 0.05 # EUR
ccy2 = 0.1 # USD
vol = 0.1
println(f)

Calcular los tipos de interés continuos (ajustados a las diferencias de recuento de días entre los tipos y los vols)

r1_cont = log(1+ccy1*days_to_expiry/360)/(days_to_expiry/365)
r2_cont = log(1+ccy2*days_to_expiry/360)/(days_to_expiry/365)

Definir Garman Kohlhagen con delantero (técnicamente Negro76) => mismo resultado como se puede ver aquí . La entrega es necesaria para el descuento de los valores de put / call.

function GKF(F,K, days_to_expiry, days_to_delivery ,ccy2,)
    d1 = ( log(F/K) +  0.5*^2*days_to_expiry.value/365 ) / (*sqrt(days_to_expiry.value/365))
    d2 = d1 - *sqrt(days_to_expiry.value/365)
    c  = exp(-ccy2*days_to_delivery.value/365)*(F*N(d1) - K*N(d2))
    p  =  exp(-ccy2*days_to_delivery.value/365)*(-F*N(-d1) + K*N(-d2))
  return c, p
end

Calcular el valor de la opción (Julia tiene una indexación basada en 1). La división por spot es necesaria porque el GK estándar está en términos de CCY2 (USD aquí). El nocional está en CCY1, lo que significa que no necesitamos cambiar nada aquí.

Option = GKF(f, k, days_to_expiry, days_to_delivery, r2_cont, vol )
put = Option[2]*1000000/spot

Resultado para el primer día que es diferente, así como el día inicial para comparar esto.

enter image description here

Hay pequeñas diferencias de redondeo con el BBG porque sólo he utilizado los puntos fwd visibles en la captura de pantalla, que carece de la precisión decimal exacta.

En caso de que alguien intente replicar, el código Quantlib en la pregunta debería ser (en lugar de .Call)

OptionType = ql.Option.Put

0 votos

@Wynn, si esto respondió a tu pregunta por favor considera también aceptarlo o comenta lo que falta.

Finanhelp.com

FinanHelp es una comunidad para personas con conocimientos de economía y finanzas, o quiere aprender. Puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X