效能提升400倍丨外匯掉期估值計算優化案例

語言: CN / TW / HK

在金融領域,合約的估值計算是個常見需求。幾百萬個合約,由於到期時間和期限長短不同,計算時往往對應不同的利率。合約匹配利率,最常見的做法是通過迴圈語句對每一個合約做計算。而 DolphinDB 中,可以通過強大的表連線函式,將需要的資訊關聯後,再通過 SQL向量程式設計 進行計算。該優化方法相比迴圈的方法, 效能提升超 400 倍

本文包含的主要內容有: 外匯掉期估值的計算邏輯及資料準備、開發環境配置、外匯掉期估值的普通實現和優化實現 等。

本文中的指令碼在 DolphinDB 1.30.18/2.00.6 及以上版本的環境中測試通過。

1. 外匯掉期估值的計算邏輯及資料準備

1.1 外匯掉期估值的計算邏輯

本次的計算整體邏輯如下圖:

本次外匯掉期估值計算的邏輯,參考​ ​中國外匯交易中心​ ​2019年4月2日在官網推出的外匯掉期估值服務中的估值演算法,即通過掉期曲線計算當日的遠端全價匯率折算,並與約定交易的全價匯率生成的現金流軋差得到淨值,再用當日的 Shibor3M(Shanghai Interbank Offered Rate) 利率互換收盤曲線進行貼現,最後得到合約淨額的貼現值即為合約估值。

1.2 資料準備

外匯掉期估值的計算需要用到掉期曲線利率以及利率互換曲線兩張表。以上兩張表的資料直接採集於2021年12月14日中國外匯掉期​ ​USD.CNY外匯掉期曲線​ ​​及​ ​Shibor3M利率互換行情曲線​ ​。

資料結構分別為:

  • USD.CNY外匯掉期曲線:

name

typeString

comment

Date

DATE

日期

timeminute

MINUTE

時間

currencyPair

STRING

貨幣對

termToMaturity

STRING

期限品種

swapRate_Pips

DOUBLE

掉期點

exchangeRate

DOUBLE

全價匯率

maturityDate

DATE

遠端起息日

  • Shibor3M利率互換行情曲線:

name

typeString

comment

Maturity

STRING

標準期限

buying_rate

DOUBLE

報買(%)

average_rate

DOUBLE

均值(%)

selling_rate

DOUBLE

報賣(%)

本教程用到的合約資料為自行模擬的100萬個不同到期期限的合約資料,資料結構為:

name

typeString

comment

contract_no

INT

合約編號

contract_date

DATE

合約日

trade_date

DATE

近端起息日

maturity_date

DATE

遠端起息日

buy_sell

STRING

買賣方向

period

STRING

期限

near_leg_currency

STRING

近端交易貨幣

near_leg_amount

INT

交易量

near_leg_spot_rate

DOUBLE

近端掉期全價匯率

far_leg_currency

STRING

遠端清算貨幣

far_leg_spot_rate

DOUBLE

遠端掉期全價匯率

上述利率資料以及模擬合約資料的指令碼請參閱​ ​資料準備指令碼​ ​。

2. 開發環境配置

2.1 DolphinDB server伺服器環境

  • CPU 型別:Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz
  • 邏輯 CPU 總數:8
  • 記憶體:64GB
  • OS:64位 CentOS Linux 7 (Core)

2.2 DolphinDB server部署

  • server版本:1.30.18 或 2.00.6
  • server部署模式:單節點
  • 配置檔案:dolphindb.cfg
localSite=localhost:8848:local8848
mode=single
maxMemSize=64
maxConnections=512
workerNum=8
localExecutors=7
newValuePartitionPolicy=add
webWorkerNum=2
dataSync=1

單節點部署教程:​ ​單節點部署​

3. 外匯掉期估值的普通實現

由於每一個合約有不同的起息日和合約期限,因此會對應不同的互換利率以及全價匯率。普通的實現方法是自定義一個包含估值計算邏輯的函式,通過迴圈語句,將每個合約與對應利率進行計算。

包含三表的估值函式定義如下:

def valuationDtmRate(today, swap_rate, IR_swap, maturity_date,far_leg_spot_rate, near_leg_amount){
  target_swap_rate_next = select * from swap_rate where Date=today, currencyPair = `USD.CNY, maturityDate >=maturity_date limit 1
  target_swap_rate_prev = select * from swap_rate where Date=today, currencyPair = `USD.CNY, maturityDate <=maturity_date order by maturityDate desc limit 1
  tartget_IR_swap = select * from IR_swap where Maturity in (target_swap_rate_prev.termToMaturity[0],target_swap_rate_next.termToMaturity[0])
  fex = target_swap_rate_prev.exchangeRate +(maturity_date - target_swap_rate_prev.maturityDate)\ (target_swap_rate_next.maturityDate-target_swap_rate_prev.maturityDate) * (target_swap_rate_next.exchangeRate - target_swap_rate_prev.exchangeRate)
  sir = tartget_IR_swap.selling_rate[0] + (maturity_date - target_swap_rate_prev.maturityDate)\ (target_swap_rate_next.maturityDate-target_swap_rate_prev.maturityDate) * ( tartget_IR_swap.selling_rate[1] -  tartget_IR_swap.selling_rate[0])
  dicount_factor = exp(-sir \ 100 * (maturity_date-today)\365)
  valuation = (fex - far_leg_spot_rate) * dicount_factor * near_leg_amount
  return valuation
}

由於涉及到多表間的資訊提取,函式看起來會相對複雜。做完這一步之後,就可以對所有的合約進行迴圈計算。100萬個合約在測試環境下做計算耗時約3分20秒。

for迴圈寫法的程式碼示例如下:

today = 2021.12.14
k = array(DOUBLE, 0)
for (x in fx_contract){
  k.append!(valuationDtmRate(today, swap_rate, IR_swap,x.maturity_date, x.far_leg_spot_rate, x.near_leg_amount))
}
result = fx_contract join table(k as valuation)

或用loop函式的寫法示例如下:

k = loop(valuationDtmRate{today, swap_rate, IR_swap},fx_contract.maturity_date, fx_contract.far_leg_spot_rate, fx_contract.near_leg_amount).flatten()
result = fx_contract join table(k as valuation)

利用多執行緒平行計算,可以在同樣的計算邏輯上做進一步優化,將原本的單執行緒迴圈改成多執行緒平行計算,最後將結果進行拼接。DolphinDB 提供了 ​ ​ploop​ ​​/​ ​peach​ ​ 函式,可以很方便的把這類迴圈語句記憶體計算轉換成多執行緒任務。

此例用了8個並行度做計算,最後耗時大約20秒:

k = ploop(valuationDtmRate{today, swap_rate, IR_swap},fx_contract.maturity_date, fx_contract.far_leg_spot_rate, fx_contract.near_leg_amount).flatten()
result = fx_contract join table(k as valuation)

注意:​ ​ploop​ ​​/​ ​peach​ ​​ 的並行度由配置檔案中的 localExecutors 控制,預設值是 CPU 核心數減1。在批流一體的因子實現中,不建議使用 ​ ​ploop​ ​​/​ ​peach​ ​​ 並行加速。具體並行策略可以參考因子最佳實踐中的​ ​平行計算章節​ ​。

4. 外匯掉期估值的優化實現

DolphinDB 中提供了許多表關聯函式,故原本的兩張小表 swap_rate 和 IR_rate 以及合約大表,可以先通過表關聯函式將有效資訊提取出來,繼而直接通過 SQL 做計算即可。如此一來,避免了迴圈語句重複消耗資源,也減少了程式碼的開發量。

做外匯掉期估值計算時,可以用 ​ ​equal join​ ​​ , ​ ​asof join​ ​​ 及 ​ ​window join​ ​ 將需要的遠端利率等資訊通過三表關聯得到,程式碼如下:

today = 2021.12.14
tmp = select * from ej(swap_rate,IR_swap,`termToMaturity, `Maturity) where Date = today
res_aj=aj(fx_contract,tmp, `maturity_date, `maturityDate)
res_wj=wj(res_aj,tmp, 0d:366d,<[first(termToMaturity) as next_termToMaturity, first(exchangeRate) as next_exchangeRate, first(maturityDate) as next_maturityDate,first(buying_rate) as next_buying_rate, first(average_rate) as next_average_rate, first(selling_rate) as next_selling_rate]>, `maturity_date, `maturityDate)

由於關聯後的資訊可以在一張表( res_wj )中得到,故計算邏輯也可以得到簡化,可以直觀地翻譯原公式邏輯進行向量化計算:

def valuationSQL(exchangeRate,maturity_date, maturityDate, next_maturityDate, next_exchangeRate, far_leg_spot_rate, selling_rate, next_selling_rate,today, near_leg_amount){
  return (exchangeRate + (maturity_date - maturityDate)\ (next_maturityDate-maturityDate) * (next_exchangeRate - exchangeRate) - far_leg_spot_rate) * exp(-(selling_rate +  (maturity_date - maturityDate)\ (next_maturityDate-maturityDate) * (next_selling_rate - selling_rate))\100 * (maturity_date-today)\365) * near_leg_amount
}

用優化後的表關聯邏輯做外匯掉期估值計算,整體耗時只需0.5秒左右,效能相比最初提升了超400倍。

timer{
  today = 2021.12.14
  tmp = select * from ej(swap_rate,IR_swap,`termToMaturity, `Maturity) where Date = today
  res_aj=aj(fx_contract,tmp, `maturity_date, `maturityDate)
  res_wj=wj(res_aj,tmp, 0d:366d,<[first(termToMaturity) as next_termToMaturity, first(exchangeRate) as next_exchangeRate, first(maturityDate) as next_maturityDate,first(buying_rate) as next_buying_rate, first(average_rate) as next_average_rate, first(selling_rate) as next_selling_rate]>, `maturity_date, `maturityDate)
  
  result = select contract_no,contract_date,trade_date,maturity_date,buy_sell, period, near_leg_currency, near_leg_amount, near_leg_spot_rate, far_leg_currency, far_leg_spot_rate, valuationSQL(exchangeRate,maturity_date, maturityDate, next_maturityDate, next_exchangeRate, far_leg_spot_rate, selling_rate, next_selling_rate,today, near_leg_amount) as valuation from res_wj
}

5. 總結

對於需要用到多表資料計算的任務,用迴圈語句的方式效能較差。建議先用 DolphinDB 優化的 ​ ​asof join​ ​ 等 表關聯函式將需要的資訊提取 。​ ​asof join​ ​ 等表關聯函式亦支援分散式關聯,對於儲存於資料庫中的分散式表也可以多執行緒並行關聯,效能卓越。計算部分 建議使用 SQL 向量化程式設計的方式 做計算,不僅可以減少程式碼開發量(基本可以直觀地翻譯計算公式),也避免了迴圈語句重複消耗 CPU 資源。

6. 程式碼附錄

​資料準備指令碼​

​外匯掉期估值的優化實現指令碼​

​完整優化過程​