QuantLib does have an FD pricing engine for asian options ql.FdBlackScholesAsianEngine(stochProcess, tGrid=100, xGrid=100, aGrid=50)
, but I've just discovered it only prices Discrete, Arithmetic payoffs!
Moving from Continuous to Discrete (documented here) doesn't change the price of the option much, if you pass in something like asianFixingDates = [ql.TARGET().advance(today, x, ql.Days) for x in range(1,91)]
which samples every day. Of course, this is a bit unrealistic, but it's good that we recover the continuous price from the analytic pricer in that limit (I get 4.187 vs. 4.184 from the original code when I make this change).
Unfortunately, running the FD pricer on this option gives me this error: RuntimeError: Arithmetic averaging supported only
Moving to an arithmetic averaging option does impact pricing significantly. However, in case that is of any use to you, I've included the changes required to your code at the bottom of this answer (changing the averaging to ql.Average.Arithmetic
, and using a discrete option)
As an alternative if you need a numerical solver, you might consider ql.MCDiscreteGeometricAPEngine
(documented here) which uses Monte Carlo instead to price the geometric option. You'll still need to price the discrete averaging option, but the price comes out very close to the analytic solution using something like this:
rng = "lowdiscrepancy" # could use "pseudorandom"
numPaths = 100000
engine = ql.MCDiscreteGeometricAPEngine(stochProcess, rng, requiredSamples=numPaths)
option.setPricingEngine(engine)
price = option.NPV()
print(f"Option price: {price}")
Price a discrete-averaging arithmetic asian using FD in QL:
import QuantLib as ql
today = ql.Settings.instance().evaluationDate
averageType = ql.Average.Arithmetic
option_type = ql.Option.Call
strike = 100.0
exerciseDate = ql.TARGET().advance(today, 90, ql.Days)
pastFixings = 0 # Empty because this is a new contract
asianFixingDates = [ql.TARGET().advance(today, x, ql.Days) for x in range(1,91)]
payoff = ql.PlainVanillaPayoff(option_type, strike)
exercise = ql.EuropeanExercise(exerciseDate)
option = ql.DiscreteAveragingAsianOption(averageType, 0.0, pastFixings, asianFixingDates, payoff, exercise)
initialValue = ql.QuoteHandle(ql.SimpleQuote(100))
sigma = 0.3
riskFreeTS = ql.YieldTermStructureHandle(ql.FlatForward(today, 0.03, ql.Actual365Fixed()))
volTS = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(today, ql.NullCalendar(), sigma, ql.Actual365Fixed()))
stochProcess = ql.BlackScholesProcess(initialValue, riskFreeTS, volTS)
engine = ql.FdBlackScholesAsianEngine(stochProcess, tGrid=100, xGrid=100, aGrid=50)
option.setPricingEngine(engine)
price = option.NPV()
print(f"Option price: {price}")