Pricing a EURUSD Digital Knockout Barrier Option in QuantLib
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.