Skip to content
All library documents

Pricing a EURUSD Digital Knockout Barrier Option in QuantLib

Article Quant Q&A · Author: Trent Maetzold

Summary

The document describes an attempt to reproduce a Bloomberg-style EURUSD digital knock-out option price using QuantLib in Python. It specifies a down-and-out put with a cash-or-nothing payoff, market quotes for spot, volatility, and interest rates, and tests several pricing engines. The author reports that the Vanna–Volga barrier engine rejects the digital payoff because it expects a plain-vanilla payoff, while a binomial barrier engine accepts the payoff but produces a price the author considers insufficiently close to the reference.

The author suspects that using flat volatility rather than a calibrated volatility surface may explain some of the discrepancy and asks how to build a smile or reproduce the closed-form Vanna–Volga model. No resolution is provided, and the reported binomial price is only an intermediate result, not evidence of a validated replication. The example is useful as a practical illustration of exotic-option model and payoff compatibility issues, but it leaves market conventions, implementation choices, and the source of the pricing mismatch unsettled.

Key ideas

  • The example targets a EURUSD cash-or-nothing down-and-out put.
  • The Vanna–Volga barrier engine described rejects the digital payoff type used in the attempt.
  • A binomial barrier engine accepts that payoff but does not match the reference price closely enough for the author.
  • Flat volatility may contribute to a mismatch when the reference relies on a volatility smile.
  • The document poses an implementation question and does not establish a successful replication.

Tags

Full text
# Pricing a Digital Barrier Option using QuantLib in Python


# Pricing a Digital Barrier Option using QuantLib in Python












I'm trying to price a EURUSD digital knockout in QuantLib/Python. Ideally would like to get the same output as this stylized Bloomberg OVML model (OVML EURUSD DIKO 1.0000P B0.9500 01/13/23 N1M). I have tried VannaVolgaBarrierEngine, AnalyticDigitalAmericanKOEngine, BinomialBarrierEngine, and MCBarrierEngine, all to no avail. I've only ever priced a vanilla EURUSD option successfully, and this is my first attempt at an exotic. My code is below, but this is really just my best effort at trying different things.

#### Setup

```
import QuantLib as ql

OVML = "OVML EURUSD DIKO 1.0000P B0.9500 01/13/23 N1M"

today = ql.Date(12, ql.October, 2022)
ql.Settings.instance().evaluationDate = today

# option specification
underlying = "EURUSD"
option_type = ql.Option.Put
strike = 1.0
barrier_type = ql.Barrier.DownOut
barrier = 0.95
payoff_amt = 1000000.0
trade_dt = ql.Date(12, 10, 2022)
settle_dt = ql.Date(14, 10, 2022)
expiry_dt = ql.Date(13, 1, 2023)
delivery_dt = ql.Date(17, 1, 2023)

# market data
spot = 0.9703
vol_atm = 12.48
vol_rr = -2.002
vol_bf = 0.400
vol_25d_put = vol_bf - vol_rr / 2 + vol_atm
vol_25d_call = vol_rr / 2 + vol_bf + vol_atm
eur_depo = 0.71764
usd_depo = 3.84558

# simple quotes
spot_quote = ql.SimpleQuote(spot)
vol_atm_quote = ql.SimpleQuote(vol_atm / 100)
vol_25d_put_quote = ql.SimpleQuote(vol_25d_put / 100)
vol_25d_call_quote = ql.SimpleQuote(vol_25d_call / 100)
eur_depo_quote = ql.SimpleQuote(eur_depo / 100)
usd_depo_quote = ql.SimpleQuote(usd_depo / 100)

# delta quotes
atmVol = ql.DeltaVolQuote(
    ql.QuoteHandle(vol_atm_quote),
    ql.DeltaVolQuote.Fwd,
    3.0,
    ql.DeltaVolQuote.AtmFwd,
)
vol25Put = ql.DeltaVolQuote(
    -0.25, ql.QuoteHandle(vol_25d_put_quote), 3.0, ql.DeltaVolQuote.Fwd
)
vol25Call = ql.DeltaVolQuote(
    0.25, ql.QuoteHandle(vol_25d_call_quote), 3.0, ql.DeltaVolQuote.Fwd
)

# term structures
domesticTS = ql.FlatForward(
    0, ql.UnitedStates(), ql.QuoteHandle(eur_depo_quote), ql.Actual360()
)
foreignTS = ql.FlatForward(
    0, ql.UnitedStates(), ql.QuoteHandle(usd_depo_quote), ql.Actual360()
)
volTS = ql.BlackConstantVol(
    0, ql.UnitedStates(), ql.QuoteHandle(vol_atm_quote), ql.ActualActual()
)
expanded_volTS = ql.BlackConstantVol(
    0, ql.UnitedStates(), ql.QuoteHandle(vol_atm_quote), ql.ActualActual()
)
```

#### Vanna-Volga

Bloomberg is using Vanna-Volga, so I'd like to replicate that for a start. I can't get it to price using CashOrNothingPayoff, it seems VannaVolgaBarrierEngine isn't implemented to use anything but PlainVanillaPayoff. I can get it to price using PlainVanillaPayoff, but as you might expect it doesn't approximate OVML. The error is `RuntimeError: non-plain payoff given`

```
def vanna_volga_barrer_option():
    payoff = ql.CashOrNothingPayoff(option_type, strike, payoff_amt)
    exercise = ql.EuropeanExercise(expiry_dt)
    option = ql.BarrierOption(barrier_type, barrier, 0.0, payoff, exercise)
    engine = ql.VannaVolgaBarrierEngine(
        ql.DeltaVolQuoteHandle(atmVol),
        ql.DeltaVolQuoteHandle(vol25Put),
        ql.DeltaVolQuoteHandle(vol25Call),
        ql.QuoteHandle(spot_quote),
        ql.YieldTermStructureHandle(domesticTS),
        ql.YieldTermStructureHandle(foreignTS),
    )
    option.setPricingEngine(engine)
    return option

def main():
    option = vanna_volga_barrer_option()
    print("Premium: ", option.NPV())

if __name__ == "__main__":
    main()
```

#### Binomial

BinomialBarrierEngine will price with CashOrNothingPayoff and gives (sort of) a value in the ballpark of OVML, but is still unacceptable.

```
def binomial_barrier_option():
    payoff = ql.CashOrNothingPayoff(option_type, strike, payoff_amt)
    exercise = ql.EuropeanExercise(expiry_dt)
    option = ql.BarrierOption(barrier_type, barrier, 0.0, payoff, exercise)
    process = ql.GarmanKohlagenProcess(
        ql.QuoteHandle(spot_quote),
        ql.YieldTermStructureHandle(foreignTS),
        ql.YieldTermStructureHandle(domesticTS),
        ql.BlackVolTermStructureHandle(expanded_volTS),
    )
    engine = ql.BinomialBarrierEngine(process, "crr", 200)
    option.setPricingEngine(engine)
    return option

def main():
    option = binomial_barrier_option()
    print("Premium: ", option.NPV())

if __name__ == "__main__":
    main()

[out] Premium:  74744.98133848533
```

I suspect this could be because I'm using flat vols, but I haven't quite figured out how to build the actual vol surface or use a Vanna-Volga derived smile/surface (still working through QuantLib Cookbook).

In any case, what I want to do is replicate the Vanna-Volga closed-form model that Bloomberg is using. If anyone has ideas how to model this in QL, would be very helpful.

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.