Quantlib price for vanilla european EURUSD call option doesn't match bloomberg OVML price.
e.g. for below option Quantlib value =4.60991, BBG value=4.6137, error=0.0038 (while it should be ~1e-6 difference )
As far as I know the time for volatility and time for discounting or drift should be adjusted for exact period and time. For example, the discounting period should be from settlement date until delivery date, and the volatility term should be from trade date until expiry date. Volatility parameters should also be expressed correctly by taking into account the difference in expiry and trade times.
However I don't see an option in Quantlib to account for delivery date different from expiration date. How can I take into account settlement adjustment (e.g. where settlement date is T+2 for EURUSD , i.e. 2 days after spot/trade date, or T+1 for USDCAD) , and delayed delivery adjustment (where delivery date is T+2, i.e. 2 days after expiry), as described in Clark, Iain J. Foreign exchange option pricing: A practitioner's guide. John Wiley & Sons, 2011. p.33, and "Wystup, Uwe. FX options and structured products. John Wiley & Sons, 2015. p.26-29"
the domestic/foreign rates (compounding style MMkt):
and the code
int main (){
QuantLib::Real S = 100;
QuantLib::Real K = 105;
QuantLib::Spread f = 0.05;// Foreign rate (EUR in EURUSD)
QuantLib::Rate r = 0.02; // Domestic rate (USD in EURUSD)
QuantLib::Volatility vol = 0.2;
QuantLib::DayCounter dayCounter = Actual365Fixed();
QuantLib::Date evaluationDate = Date(13, Feb, 2018);
QuantLib::Date settlementDate = evaluationDate + Period(2, Days);//T+2 = Date(15, Feb, 2018);
QuantLib::Date expirationDate = settlementDate + Period(1, Years); //Date(15, May, 2019);
Calendar calendar = UnitedStates(UnitedStates::NYSE);
Exercise::Type exerciseType = Exercise::European;
Real result = 4.6137;
Real tol = 1e-3; // tolerance
Option::Type optionType = Option::Call;
Compounding compounding = Compounded;
Frequency compoundingFrequency = Semiannual;
VanillaOptionData vanillaOptionData = { S, K, f, r, vol, dayCounter, evaluationDate, settlementDate,
expirationDate, calendar,exerciseType, result, tol, optionType, compounding, compoundingFrequency };
calculator_fx_vanilla_black_scholes(vanillaOptionData);
//results
//calculated value=4.60991, expected value=4.6137, error=0.00379258
return 0;
}
void calculator_fx_vanilla_black_scholes(VanillaOptionData in) {
Calendar calendar = TARGET();
Settings::instance().evaluationDate() = in.evaluationDate;
boost::shared_ptr<Exercise> exercise= boost::make_shared<EuropeanExercise>(in.expirationDate);
Handle<Quote>underlyingH(boost::shared_ptr<Quote>(new SimpleQuote(in.S)));
Handle<YieldTermStructure> rTS(boost::shared_ptr<YieldTermStructure>(new FlatForward(in.settlementDate, in.r, in.dayCounter, in.compounding, in.compoundingFrequency)));
Handle<YieldTermStructure> fTS(boost::shared_ptr<YieldTermStructure>(new FlatForward(in.settlementDate, in.f, in.dayCounter, in.compounding, in.compoundingFrequency)));
Handle<BlackVolTermStructure> flatVolTS(boost::shared_ptr<BlackVolTermStructure>(new BlackConstantVol(in.settlementDate, calendar, in.vol, in.dayCounter)));
boost::shared_ptr<StrikedTypePayoff>payoff(new PlainVanillaPayoff(in.optionType, in.K));
boost::shared_ptr<GarmanKohlagenProcess>process(new GarmanKohlagenProcess(underlyingH, fTS, rTS, flatVolTS));
VanillaOption option(payoff, exercise);
boost::shared_ptr<PricingEngine> pe(new AnalyticEuropeanEngine(process));
option.setPricingEngine(pe);
Real calculated = option.NPV();
Real expected = in.result;
Real error = std::fabs(calculated - expected);
cout << "calculated value=" << calculated << ", expected value=" << expected << ", error=" << error << endl;
}
related: https://quant.stackexchange.com/questions/33604/pricing-of-a-foreign-exchange-vanilla-option