效能提升400倍丨外匯掉期估值計算優化案例
在金融領域,合約的估值計算是個常見需求。幾百萬個合約,由於到期時間和期限長短不同,計算時往往對應不同的利率。合約匹配利率,最常見的做法是通過迴圈語句對每一個合約做計算。而 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. 程式碼附錄
資料準備指令碼
外匯掉期估值的優化實現指令碼
完整優化過程
- 設計模式之狀態模式
- 如何實現資料庫讀一致性
- 我是怎麼入行做風控的
- C 11精要:部分語言特性
- 吳恩達來信:人工智慧領域的求職小 tips
- EasyCV帶你復現更好更快的自監督演算法-FastConvMAE
- 某車聯網App 通訊協議加密分析(四) Trace Code
- 帶你瞭解CANN的目標檢測與識別一站式方案
- EasyNLP玩轉文字摘要(新聞標題)生成
- PostgreSQL邏輯複製解密
- 基於 CoreDNS 和 K8s 構建雲原生場景下的企業級 DNS
- 迴圈神經網路(RNN)可是在語音識別、自然語言處理等其他領域中引起了變革!
- 技術分享| 分散式系統中服務註冊發現元件的原理及比較
- 利用谷歌地圖採集外貿客戶的電話和手機號碼
- 跟我學Python影象處理丨關於影象金字塔的影象向下取樣和向上取樣
- 帶你掌握如何使用CANN 運算元ST測試工具msopst
- 一招教你如何高效批量匯入與更新資料
- 一步步搞懂MySQL元資料鎖(MDL)
- 你知道如何用 PHP 實現多程序嗎?
- KubeSphere 閘道器的設計與實現(解讀)