市場數據

FX Vol Surface 從非均勻打擊與中音網格的插值

  • September 30, 2020

TL;博士

我正在嘗試將 vol 表面擬合到市場 FX 期權報價,以便建立一個本地 vol 模型來定價。與通常具有漂亮的行使價和期限的矩形網格的上市期權不同,外匯期權傾向於在場外交易,並且可用的報價不提供統一的網格。

在非均勻網格上進行 2D 插值的明智方法是什麼?我的想法是:

  • 創建一個更精細的點網格並為這些點插入值(例如,使用scipy.interpolate.griddata如下所示),並為此建構 vol 表面(儘管這似乎很浪費)
  • 對期權罷工應用一些變換以將它們均勻地展開(延伸較早的期限多於後來的期限),然後使用標準的 2D 網格插值器

最終我想在QuantLibusing中建立一個模型ql.BlackVarianceSurface,目前需要一個矩形網格。

我很想听聽人們採取了哪些方法,包括任何 2D 插值危險和外推問題。

問題的更多細節

以下是市場引用的 FX vol 表面範例:

市場外匯交易量表面

一旦將其轉換為(罷工,男高音,卷)三倍罷工看起來像這樣:

打擊面

這為我們提供了一個不均勻的 vol 網格,繪製在 2D 表面上,它們看起來像這樣(在 tte 和根 tte 中):

Tenor-Strike 選項網格

scipy.interpolate.griddata使用和雙插值投射到方形網格:

通過 griddata 投射到網格

幾週前,我在 Quantlib python 中嘗試了一些類似的東西。與您的方法相比,我認為稍微簡單一些:

  1. 從外匯交易量的標準增量報價慣例開始(10D 看跌期權、25D 看跌期權、ATM、25D 看漲期權、10D 看漲期權)
  2. 計算期權的貨幣性以獲得行使價集(這將是一個大的行使價集,因為每個期權到期日將具有與原始來源的貨幣性報價相對應的獨特行使價)
  3. 為每個成熟度的全套罷工插入缺失的 vols - 我使用 Quantlib 中的 BlackVarianceSurface 函式進行了此操作。因此,我有一個完整的到期/罷工網格
  4. 我終於拿到了這些數據並嘗試了 Heston 校準並將輸出插入 HestonBlackVolSurface 函式

結果不是很好,因為 Heston 隱含的 vols 並沒有真正準確地再現我的輸入源 vols,但這可能更多與我糟糕的校準和我使用的虛擬輸入源值有關。儘管如此,這是一次值得的練習。

如果可能有幫助,我的 Quantlib 程式碼如下:

def deltavolquotes(ccypair,fxcurve):

from market import curveinfo

sheetname = ccypair + '_fx_volcurve'
df = pd.read_excel('~/iCloud/python_stuff/finance/marketdata.xlsx', sheet_name=sheetname)
curveinfo = curveinfo(ccypair, 'fxvols')
calendar = curveinfo.loc['calendar', 'fxvols']
daycount = curveinfo.loc['curve_daycount', 'fxvols']
settlement = curveinfo.loc['curve_sett', 'fxvols']
flat_vol = ql.SimpleQuote(curveinfo.loc['flat_vol', 'fxvols'])
flat_vol_shift = ql.SimpleQuote(0)
used_flat_vol = ql.CompositeQuote(ql.QuoteHandle(flat_vol_shift), ql.QuoteHandle(flat_vol), f)
vol_shift = ql.SimpleQuote(0)
calculation_date = fxcurve.referenceDate()
settdate = calendar.advance(calculation_date, settlement, ql.Days)

date_periods = df[ccypair].tolist()
atm = [ql.CompositeQuote(ql.QuoteHandle(vol_shift), ql.QuoteHandle(ql.SimpleQuote(i)), f) for i in
      df['ATM'].tolist()]
C25 = [ql.CompositeQuote(ql.QuoteHandle(vol_shift), ql.QuoteHandle(ql.SimpleQuote(i)), f) for i in
      df['25C'].tolist()]
P25 = [ql.CompositeQuote(ql.QuoteHandle(vol_shift), ql.QuoteHandle(ql.SimpleQuote(i)), f) for i in
      df['25P'].tolist()]
C10 = [ql.CompositeQuote(ql.QuoteHandle(vol_shift), ql.QuoteHandle(ql.SimpleQuote(i)), f) for i in
      df['10C'].tolist()]
P10 = [ql.CompositeQuote(ql.QuoteHandle(vol_shift), ql.QuoteHandle(ql.SimpleQuote(i)), f) for i in
      df['10P'].tolist()]
dates = [calendar.advance(settdate, ql.Period(i)) for i in date_periods]
yearfracs = [daycount.yearFraction(settdate, i) for i in dates]
dvq_C25 = [ql.DeltaVolQuote(0.25, ql.QuoteHandle(i), j, 0) for i, j in zip(C25, yearfracs)]
dvq_P25 = [ql.DeltaVolQuote(-0.25, ql.QuoteHandle(i), j, 0) for i, j in zip(P25, yearfracs)]
dvq_C10 = [ql.DeltaVolQuote(0.10, ql.QuoteHandle(i), j, 0) for i, j in zip(C10, yearfracs)]
dvq_P10 = [ql.DeltaVolQuote(-0.10, ql.QuoteHandle(i), j, 0) for i, j in zip(P10, yearfracs)]

info=[settdate,calendar,daycount,df,used_flat_vol,vol_shift,flat_vol_shift,date_periods]


return atm,dvq_C25,dvq_P25,dvq_C10,dvq_P10,dates,yearfracs,info

def fxvolsurface(ccypair,FX,fxcurve,curve):

atm,dvq_C25,dvq_P25,dvq_C10,dvq_P10,dates,yearfracs,info = deltavolquotes(ccypair,fxcurve)
settdate = info[0]
calendar=info[1]
daycount=info[2]
df=info[3]
used_flat_vol=info[4]
vol_shift=info[5]
flat_vol_shift=info[6]
date_periods=info[7]

blackdc_C25=[ql.BlackDeltaCalculator(ql.Option.Call,j.Spot,FX.value(),
                                  fxcurve.discount(i)/fxcurve.discount(settdate),
                                  curve.discount(i)/curve.discount(settdate),
                                  j.value()*(k**0.5))
                                  for i,j,k in zip(dates,dvq_C25,yearfracs)]
blackdc_C10=[ql.BlackDeltaCalculator(ql.Option.Call,j.Spot,FX.value(),
                                  fxcurve.discount(i)/fxcurve.discount(settdate),
                                  curve.discount(i)/curve.discount(settdate),
                                  j.value()*(k**0.5))
                                  for i,j,k in zip(dates,dvq_C10,yearfracs)]
blackdc_P25=[ql.BlackDeltaCalculator(ql.Option.Put,j.Spot,FX.value(),
                                  fxcurve.discount(i)/fxcurve.discount(settdate),
                                  curve.discount(i)/curve.discount(settdate),
                                  j.value()*(k**0.5))
                                  for i,j,k in zip(dates,dvq_P25,yearfracs)]
blackdc_P10=[ql.BlackDeltaCalculator(ql.Option.Put,j.Spot,FX.value(),
                                  fxcurve.discount(i)/fxcurve.discount(settdate),
                                  curve.discount(i)/curve.discount(settdate),
                                  j.value()*(k**0.5))
                                  for i,j,k in zip(dates,dvq_P10,yearfracs)]
C25_strikes=[i.strikeFromDelta(0.25) for i in blackdc_C25]
C10_strikes=[i.strikeFromDelta(0.10) for i in blackdc_C10]
P25_strikes=[i.strikeFromDelta(-0.25) for i in blackdc_P25]
P10_strikes=[i.strikeFromDelta(-0.10) for i in blackdc_P10]
ATM_strikes=[i.atmStrike(j.AtmFwd) for i,j in zip(blackdc_C25,dvq_C25)]
strikeset=ATM_strikes+C25_strikes+C10_strikes+P25_strikes+P10_strikes
strikeset.sort()
hestonstrikes=[P10_strikes,P25_strikes,ATM_strikes,C25_strikes,C10_strikes]
hestonvoldata=[df['10P'].tolist(),df['25P'].tolist(),df['ATM'].tolist(),df['25C'].tolist(),df['10C'].tolist()]

volmatrix=[]
for i in range(0,len(atm)):
   volsurface=ql.BlackVolTermStructureHandle(ql.BlackVarianceSurface(settdate,calendar,[dates[i]],
                               [P10_strikes[i],P25_strikes[i],ATM_strikes[i],C25_strikes[i],C10_strikes[i]],
                               [[dvq_P10[i].value()],[dvq_P25[i].value()],[atm[i].value()],[dvq_C25[i].value()],
                                [dvq_C10[i].value()]],
                               daycount))
   volmatrix.append([volsurface.blackVol(dates[i],j,True) for j in strikeset])
volarray=np.array(volmatrix).transpose()
matrix = []
for i in range(0, volarray.shape[0]):
   matrix.append(volarray[i].tolist())
fxvolsurface=ql.BlackVolTermStructureHandle(
   ql.BlackVarianceSurface(settdate,calendar,dates,strikeset,matrix,daycount))

'''
process = ql.HestonProcess(fxcurve, curve, ql.QuoteHandle(FX), 0.01, 0.5, 0.01, 0.1, 0)
model = ql.HestonModel(process)
engine = ql.AnalyticHestonEngine(model)
print(model.params())
hmh = []
for i in range(0,len(date_periods)):
   for j in range(0,len(hestonstrikes)):
       helper=ql.HestonModelHelper(ql.Period(date_periods[i]), calendar, FX.value(),hestonstrikes[j][i],
                                   ql.QuoteHandle(ql.SimpleQuote(hestonvoldata[j][i])),fxcurve,curve)
       helper.setPricingEngine(engine)
       hmh.append(helper)
lm = ql.LevenbergMarquardt()
model.calibrate(hmh, lm,ql.EndCriteria(500, 10, 1.0e-8, 1.0e-8, 1.0e-8))
vs = ql.BlackVolTermStructureHandle(ql.HestonBlackVolSurface(ql.HestonModelHandle(model)))
vs.enableExtrapolation()'''

flatfxvolsurface = ql.BlackVolTermStructureHandle(
   ql.BlackConstantVol(settdate, calendar, ql.QuoteHandle(used_flat_vol), daycount))

fxvoldata=pd.DataFrame({'10P strike':P10_strikes,'25P strike':P25_strikes,'ATM strike':ATM_strikes,
                       '25C strike':C25_strikes,'10C strike':C10_strikes,'10P vol':df['10P'].tolist(),
                       '25P vol':df['25P'].tolist(),'ATM vol':df['ATM'].tolist(),
                       '25C vol':df['25C'].tolist(),'10C vol':df['10C'].tolist()})
fxvoldata.index=date_periods

fxvolsdf=pd.DataFrame({'fxvolsurface':[fxvolsurface,flatfxvolsurface],'fxvoldata':[fxvoldata,None]})
fxvolsdf.index=['surface','flat']
fxvolshiftsdf=pd.DataFrame({'fxvolshifts':[vol_shift,flat_vol_shift]})
fxvolshiftsdf.index=['surface','flat']

return fxvolshiftsdf,fxvolsdf

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