Options

為什麼比較定價方法(Python)時美式期權價格存在差異?

  • March 10, 2018

我已經編寫了一個 Python 腳本來使用最小二乘蒙特卡羅為美式期權定價,並在下面添加了一個 QuantLib 實現(分析/二項式/有限差分)進行比較。問題是我的 MCLS 方法似乎略微高估了看漲期權和低估了看跌期權的價格,而且我似乎無法在程式碼中找到錯誤。任何有關此/建議以最佳方式標準化基礎價格的任何幫助將不勝感激,在此先感謝!

   """ AMERICAN OPTION PRICING BY LEAST SQUARES MONTE CARLO, FINITE DIFFERENCE, ANALYTICAL AND BINOMIAL METHODS """

from numpy import zeros, concatenate, sqrt, exp, maximum, polyfit, polyval, shape, where, sum, argsort, random, \
   RankWarning, put, nonzero
from zlib import compress
import matplotlib.pyplot as plt
import os
import sys
from QuantLib import *
from pylab import *
import warnings
warnings.simplefilter('ignore', RankWarning)

plt.style.use('seaborn')

# Define global parameters
S0 = 100                                                       # Underlying price
K = 90                                                         # Strike
valuation_date = Date(1, 1, 2018)                              # Valuation date
expiry_date = Date(1, 1, 2019)                                 # Expiry date
t = ActualActual().yearFraction(valuation_date, expiry_date)   # Year fraction
T = 100                                                        # Time grid
dt = t / T                                                     # Delta time
r = 0.01                                                       # Interest rate
sig = 0.4                                                      # Volatility
sim = 10 ** 5                                                  # Number of MC simulations
DiscountFactor = exp(-r * dt)                                  # Discount factor

""" Least Squares Monte Carlo """


def GBM(underlying, time, simulations, rate, sigma, delta_t):  # Geometric Brownian Motion
   GBM = zeros((time + 1, simulations))
   GBM[0, :] = underlying
   for t in range(1, time + 1):
       brownian = standard_normal(simulations // 2)
       brownian = concatenate((brownian, -brownian))
       GBM[t, :] = (GBM[t - 1, :] * exp((rate - sigma ** 2 / 2.) * delta_t + sigma * brownian * sqrt(delta_t)))
   return GBM


def Payoff(strike, paths, simulations):  # Define option type and respective payoff
   if OptionType == 'call':
       po = maximum(paths - strike, zeros((T + 1, simulations)))
   elif OptionType == 'put':
       po = maximum(strike - paths, zeros((T + 1, simulations)))
   else:
       print('Incorrect input')
       os.execl(sys.executable, sys.executable, *sys.argv)
   return po


def loadingBar(count, total, size):  # MC progress bar
   percent = float(count) / float(total) * 100
   sys.stdout.write("\r" + str(int(count)).rjust(3, '0') + "/" + str(int(total)).rjust(3, '0') + ' [' + '=' * int(
       percent / 10) * size + ' ' * (10 - int(percent / 10)) * size + ']')


# Graph the regression fit and simulations
OptionType = str(input('Price call or put:'))
print('Plotting fitted regression at T...')
GBM = GBM(S0, T, sim, r, sig, dt)
payoff = Payoff(K, GBM, sim)
ValueMatrix = zeros_like(payoff)
ValueMatrix[T, :] = payoff[T, :]
prices = GBM[T, :]
value = ValueMatrix[T, :]
regression = polyfit(prices, value * DiscountFactor, 4)
ContinuationValue = polyval(regression, prices)
sorted_index = argsort(prices)
prices = prices[sorted_index]
ContinuationValue = ContinuationValue[sorted_index]

ValueMatrix[T, :] = where(payoff[T, :] > ContinuationValue, payoff[T, :], ValueMatrix[T, :] * DiscountFactor)
ValueVector = ValueMatrix[T, :] * DiscountFactor
ValueVector = ValueVector[sorted_index]

plt.figure()
f, axes = plt.subplots(2, 1)
axes[0].set_title('American Option')
axes[0].plot(prices, ContinuationValue, label='Fitted Polynomial')
axes[0].plot(prices, ValueVector, label='Inner Value')
axes[0].set_ylabel('Payoff')
axes[0].set_xlabel('Asset Price')
axes[0].legend()
axes[1].set_title('Geometric Brownian Motion')
axes[1].plot(GBM, lw=0.5)
axes[1].set_ylabel('Asset Price')
axes[1].set_xlabel('Time')
f.tight_layout()
plt.show()

# MC results
print('Pricing option...')
for i in range(0, 100):
   loadingBar(i, 100, 2)
   for t in range(T - 1, 0, -1):
       ITM = payoff[t, :] > 0
       ITMS = compress(ITM, GBM[t, :])
       ITMP = compress(ITM, payoff[t + 1, :] * DiscountFactor)
       regression = polyval(polyfit(ITMS, ITMP, 4), ITMS)
       continuation = zeros(sim)
       put(continuation, nonzero(ITM), regression)
       payoff[t, :] = where(payoff[t, :] > continuation, payoff[t, :], payoff[t + 1, :] * DiscountFactor)
       price = sum(payoff[1, :] * DiscountFactor) / sim
print('\nLeast Squares Monte Carlo Price:', price)


""" QuantLib Pricing """

S0 = SimpleQuote(S0)
if OptionType == 'call':
   OptionType = Option.Call
elif OptionType == 'put':
   OptionType = Option.Put
else:
   print('Incorrect input')
   os.execl(sys.executable, sys.executable, *sys.argv)


def Process(valuation_date, r, dividend_rate, sigma, underlying):
   calendar = UnitedStates()
   day_counter = ActualActual()
   Settings.instance().evaluation_date = valuation_date
   interest_curve = FlatForward(valuation_date, r, day_counter)
   dividend_curve = FlatForward(valuation_date, dividend_rate, day_counter)
   volatility_curve = BlackConstantVol(valuation_date, calendar, sigma, day_counter)
   u = QuoteHandle(underlying)
   d = YieldTermStructureHandle(dividend_curve)
   r = YieldTermStructureHandle(interest_curve)
   v = BlackVolTermStructureHandle(volatility_curve)
   return BlackScholesMertonProcess(u, d, r, v)


def FDAmericanOption(valuation_date, expiry_date, OptionType, K, process):  # Finite difference
   exercise = AmericanExercise(valuation_date, expiry_date)
   payoff = PlainVanillaPayoff(OptionType, K)
   option = VanillaOption(payoff, exercise)
   time_steps = 100
   grid_points = 100
   engine = FDAmericanEngine(process, time_steps, grid_points)
   option.setPricingEngine(engine)
   return option


def ANAmericanOption(valuation_date, expiry_date, OptionType, K, process):  # Analytical
   exercise = AmericanExercise(valuation_date, expiry_date)
   payoff = PlainVanillaPayoff(OptionType, K)
   option = VanillaOption(payoff, exercise)
   engine = BaroneAdesiWhaleyEngine(process)
   option.setPricingEngine(engine)
   return option


def BINAmericanOption(valuation_date, expiry_date, OptionType, K, process):  # Binomial
   exercise = AmericanExercise(valuation_date, expiry_date)
   payoff = PlainVanillaPayoff(OptionType, K)
   option = VanillaOption(payoff, exercise)
   timeSteps = 10 ** 3
   engine = BinomialVanillaEngine(process, 'crr', timeSteps)
   option.setPricingEngine(engine)
   return option


def FDAmericanResults(option):
   print('Finite Difference Price: ', option.NPV())
   print('Option Delta: ', option.delta())
   print('Option Gamma: ', option.gamma())


def ANAmericanResults(option):
   print('Barone-Adesi-Whaley Analytical Price: ', option.NPV())


def BINAmericanResults(option):
   print('Binomial CRR Price: ', option.NPV())


# Quantlib results
process = Process(valuation_date, r, 0, sig, S0)
ANoption = ANAmericanOption(valuation_date, expiry_date, OptionType, K, process)
ANAmericanResults(ANoption)
BINoption = BINAmericanOption(valuation_date, expiry_date, OptionType, K, process)
BINAmericanResults(BINoption)
FDoption = FDAmericanOption(valuation_date, expiry_date, OptionType, K, process)
FDAmericanResults(FDoption)

os.system('say "completo"')

我從來沒有處理過 Python,所以我只是想從視覺/邏輯上理解發生了什麼。總體而言,它看起來不錯,除了 LS 建議僅使用實值路徑進行回歸之外,在您的程式碼中看不到這一點,還是我錯過了它?一些旁注,您的變數命名有點不標準,我會更改您的 T -> NT,t -> T。此外,您所說的折扣率實際上通常稱為折扣因子。最後,您嘗試為沒有股息的美國(百慕大)看漲期權定價,該看漲期權的價格應與同等歐洲看漲期權的價格相同。

LS 算法,如果處理得當,無論如何都會低估你的美國人。那是因為通過基函式的連續值近似,就是一個近似值。這意味著算法(其決策基於持續價值)不會總是做出正確的(最佳)決定來行使,這意味著期權價值將略低於您始終以最佳方式行使的期權價值。

現在,正如 Bob Jansen 所說,很可能是您的噪音太大了。我的意思是,現在誰使用 10.000 MC 路徑!我猜 Python 對 MC 來說太慢了。所以,我用這個工具和 8.000 個(Sobol)路徑(用於主模擬和回歸)和 6 個基函式嘗試了你的問題,我得到 C=21.32(半秒內)。然後嘗試使用 131.000 條路徑,我得到 21.02(在 12 秒內)。正確的價格是 21.046。所以,是的,很可能是你的噪音太大,路徑太少,你無法得出任何關於你是否有其他問題的結論。

引用自:https://quant.stackexchange.com/questions/38697