'FX vanilla call price in Quantlib doesn't match Bloomberg

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"

here is the BBG screenshot enter image description here

the domestic/foreign rates (compounding style MMkt): enter image description here

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



Solution 1:[1]

You're correct; at this time, there's no way to account for a different delivery date. As a workaround, you might try correcting the price by the additional discount factor between the expiry and delivery dates (which you'll have to calculate as the ratio of the two corresponding discount factors).

On the other hand, the different timing for the curves (discount to settlement, volatility from trade to expiry) might already be obtained by working on the reference dates of the corresponding curves; something like

FlatForward(in.settlementDate, ...)

for the rates and

BlackConstantVol(in.evaluationDate, ...)

should work for vanilla options using the analytic engine (but not for finite-differences or Monte Carlo engines, which unfortunately require a common time axis).

I'll try the above and report my results when I get some more time.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Luigi Ballabio