I'm using Quantlib in Python to price an FX option. I'm comparing the result to Bloomberg, to make sure the code is working correct.
I want to calculate the P&L of a certain option trading strategy by using Taylor expansion of P&L (discussed in other post here) And also by using the NPV of the option.
Therefore, it's important to have a correct NPV for every date that the option is alive. The option I use to test this is a 1-week stylized option.
The problem that occurs is that the NPV matches Bloomberg correctly on the first, second, third and last date, but not on the other dates.
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)
Which results in:
Premium is: 5487.479999102207
Premium is: 5148.552323458257
Premium is: 4774.333578225227
Premium is: 4353.586300232529
Premium is: 3867.4561591587326
Premium is: 909.0909090908089
However, according to Bloomberg the premium should be:
5487.48 (correct)
5148.55 (correct)
4774.33 (correct)
4499.5
4015.7
909.9 (correct)
The result on expiration is given by setting the following (the option expires in-the-money):
ql.Settings.instance().includeReferenceDateEvents = True
Can someone explain why the NPV suddenly doesn't match for those 2 dates?