市場數據
FX Vol Surface 從非均勻打擊與中音網格的插值
TL;博士
我正在嘗試將 vol 表面擬合到市場 FX 期權報價,以便建立一個本地 vol 模型來定價。與通常具有漂亮的行使價和期限的矩形網格的上市期權不同,外匯期權傾向於在場外交易,並且可用的報價不提供統一的網格。
在非均勻網格上進行 2D 插值的明智方法是什麼?我的想法是:
- 創建一個更精細的點網格並為這些點插入值(例如,使用
scipy.interpolate.griddata
如下所示),並為此建構 vol 表面(儘管這似乎很浪費)- 對期權罷工應用一些變換以將它們均勻地展開(延伸較早的期限多於後來的期限),然後使用標準的 2D 網格插值器
最終我想在
QuantLib
using中建立一個模型ql.BlackVarianceSurface
,目前需要一個矩形網格。我很想听聽人們採取了哪些方法,包括任何 2D 插值危險和外推問題。
問題的更多細節
以下是市場引用的 FX vol 表面範例:
一旦將其轉換為(罷工,男高音,卷)三倍罷工看起來像這樣:
這為我們提供了一個不均勻的 vol 網格,繪製在 2D 表面上,它們看起來像這樣(在 tte 和根 tte 中):
scipy.interpolate.griddata
使用和雙插值投射到方形網格:
幾週前,我在 Quantlib python 中嘗試了一些類似的東西。與您的方法相比,我認為稍微簡單一些:
- 從外匯交易量的標準增量報價慣例開始(10D 看跌期權、25D 看跌期權、ATM、25D 看漲期權、10D 看漲期權)
- 計算期權的貨幣性以獲得行使價集(這將是一個大的行使價集,因為每個期權到期日將具有與原始來源的貨幣性報價相對應的獨特行使價)
- 為每個成熟度的全套罷工插入缺失的 vols - 我使用 Quantlib 中的 BlackVarianceSurface 函式進行了此操作。因此,我有一個完整的到期/罷工網格
- 我終於拿到了這些數據並嘗試了 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