Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pricing options with holidays #2035

Open
bgladwyn-pm opened this issue Jul 25, 2024 · 3 comments
Open

Pricing options with holidays #2035

bgladwyn-pm opened this issue Jul 25, 2024 · 3 comments

Comments

@bgladwyn-pm
Copy link

bgladwyn-pm commented Jul 25, 2024

Hi, I'm trying to use Quant lib to price FX options. For some dates, when the settlement date is 2 days after the evaluation, and delivery two days after the expiry I get perfect agreement between bloomberg OVML and quant lib. However, if there is a weekened/holiday between expiry/delivery or evaluation/settlement then there is no longer agreement. How do I get this behavior in quantlib so I can accurately price options?

import QuantLib as ql
import numpy as np

evaluationDate = ql.Date(13, 2, 2018)
settlementDate = evaluationDate + ql.Period(2, ql.Days)  # T+2 = Date(15, Feb, 2018)
expirationDate = ql.Date(13, 2, 2019)  # Date(15, Feb, 2019)
deliveryDate = expirationDate + ql.Period(2, ql.Days)  # Date(19, Feb, 2019)
numberofdays=expirationDate-settlementDate
print(numberofdays)

# Parameters
S = 100
K = 105
f = 0.05  # Foreign rate (EUR in EURUSD)
r = 0.02  # Domestic rate (USD in EURUSD)
vol = 0.2


calendar = ql.UnitedStates(ql.UnitedStates.NYSE)
dayCounter = ql.Actual365Fixed()
exerciseType = ql.Exercise.European
result = 4.6205
tol = 1e-3  # tolerance
optionType = ql.Option.Call
compounding = ql.Compounded
compoundingFrequency = ql.Annual

# Set the evaluation date
ql.Settings.instance().evaluationDate = evaluationDate

# Option data
exercise = ql.EuropeanExercise(expirationDate)
underlyingH = ql.QuoteHandle(ql.SimpleQuote(S))

rTS = ql.YieldTermStructureHandle(ql.FlatForward(evaluationDate, r*365/360, dayCounter, compounding, compoundingFrequency))
fTS = ql.YieldTermStructureHandle(ql.FlatForward(evaluationDate, f*365/360, dayCounter, compounding, compoundingFrequency))
flatVolTS = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(evaluationDate, calendar, vol, dayCounter))


print(f'Fwd matching bloomberg {1.30*(1+r*365/360)/(1+f*365/360)}')
print(f'Forward rate {1.30*(1+r)/(1+f)}')

payoff = ql.PlainVanillaPayoff(optionType, K)
process = ql.GarmanKohlagenProcess(underlyingH, fTS, rTS, flatVolTS)

option = ql.VanillaOption(payoff, exercise)
engine = ql.AnalyticEuropeanEngine(process)
option.setPricingEngine(engine)

# Calculate option price
calculated = option.NPV()

# Print results
expected = 4.613072
error=(calculated-expected)/expected
print(f"Calculated value = {calculated:.5f}, Expected value = {expected:.5f}, Error = {error*100:.8f}%")

The output matches Bloomberg:

363
Fwd matching bloomberg 1.2623661599471252
Forward rate 1.262857142857143
Calculated value = 4.61307, Expected value = 4.61307, Error = 0.00000739%

But shifting the expiry to 02/15/19 gives:

365
Fwd matching bloomberg 1.2623661599471252
Forward rate 1.262857142857143
Calculated value = 4.62657, Expected value = 4.62016, Error = 0.13883435%

which no longer matches.

Is there a way to consider the correct dates in this calculation?
Thanks for the help! :)

image
image

Copy link

boring-cyborg bot commented Jul 25, 2024

Thanks for posting! It might take a while before we look at your issue, so don't worry if there seems to be no feedback. We'll get to it.

Copy link
Contributor

This issue was automatically marked as stale because it has been open 60 days with no activity. Remove stale label or comment, or this will be closed in two weeks.

@github-actions github-actions bot added the stale label Sep 24, 2024
@lballabio lballabio removed the stale label Sep 24, 2024
@lballabio
Copy link
Owner

Hmm—the option is not using settlementDate or deliveryDate for pricing, so there should probably be some correction for that. The option discounts to the evaluation date, and I guess we should discount to the settlement date instead, so something like

calculated /= rTS.discount(settlementDate)

should account for that. In the same way, we're discounting the payoff from the expiration date, not the delivery date, so we should also add

calculated *= rTS.discount(deliveryDate)/rTS.discount(expirationDate)

However, doing that makes the result only slightly better. There might be some other difference—for instance, I see in the second screenshot that r is no longer exactly 5%; is it possible that using that updated rate helps?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants