quantlib

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

  • I have encountered the same issues. For one year EUR/USD vanilla call option, it will generate a small difference slightly smaller than 1 basis point by calling the Quantlib and by following the formula from the book Clark.

    After some thorough test, I am able to identify why the difference arises. The Quantlib Black Scholes library does all the calculation with two dates, i.e. evaluation_date(i.e. Today) and delivery_date.

    So, if there are four dates: evaluation_date(i.e. today), SpotDate, expiry_date(i.e. maturity), delivery_date. The Quantlib Black Scholes formula will assume only two dates, the payment on delivery_date, and all the relevant terms are discounted back to evaluation_Date.

    If you have access to the source code, the cpp file, the actual calculation for the d1, d2, is as in the following picture, the highlighted part: enter image description here

    Please notice, the forward above is the forward exchange rate on the delivery_date, it is derived based on the exchange rate on evaluation_date, the discount factor between delivery_date and evaluation_date for the domestic and foreign market. The stdDev is just the volatility * sqrt(T).

    Finally, the value of the option is calculated within the same file blackcalculator.cpp, as follows: enter image description here

    The discount in the above picture is the domestic discount factor between evaluation_date and deivery_date. So that we get a final result on the evaluation_date, denoted as MV_evalDate. This will not match exactly the value on Bloomberg or Murex, since on Murex, it is quoted for spot date. We need to forward the result on the evaluation_date towards to the spot date, then we get the market value on spot date as MV_spotDate = MV_evalDate * exp(domestic_rate * timefrac_between_evalDate_spotDate) = MV_evalDate / domestic_discountfactor_evalDate_spotDate.

    Then we could get the matched result for the market value on the spot date, or the one with settlement adjustment shown in the formula of (2.86), page 32 of Clark's book.