性能提升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. 代碼附錄

​數據準備腳本​

​外匯掉期估值的優化實現腳本​

​完整優化過程​