C++ 語言
↓
物件模型(Object Lifetime / Polymorphism)
↓
金融系統架構(QuantLib)
為什麼一直強調 class / virtual / override
因為你不是在寫小程式,是在寫「會活十年以上的系統」
Instrument (abstract)
|
v
Bond
|
v
FixedRateBond
Instrument <-- uses --> PricingEngine (Strategy)
|
v
DiscountingBondEngine
BlackBondEngine
一個金融商品,永遠不只一種評價方式
Instrument 不知道「怎麼算」
Engine 才是「演算法」
override 是「金融模型的插槽」
| 關鍵字 | 為什麼存在 |
|---|---|
virtual |
支援多態、模型可替換 |
override |
防止寫錯金融邏輯 |
=delete |
強制產品一定要有必要參數 |
explicit |
防止金融物件被誤轉型 |
static |
工具類、共用設定 |
friend |
效能/operator/數學結構 |
const |
保證不改狀態 |
現在的你們:
看得懂 QuantLib
能理解設計動機
不會亂寫繼承
下一步
template / traits
smart pointer 設計
policy-based design
curve bootstrapping
engine / argument / result 的完整模式
…
「你現在不是只會寫 C++,你已經開始『用 C++ 設計金融系統』了。」
呼叫順序(概念)
Bond::NPV()
LazyObject::calculate()
PricingEngine::calculate()
重點不是流程,而是責任切分
避免重複計算
支援 market data 改變
Observer / Observable 的真實用途
實際的問題:
假設你有一個 Bond,它的價格取決於:
yield curve
evaluation date
discount factor
如果其中一個東西改了,那要怎麼辦?「重算」!!??
因為:
同一個價格會被算上萬次
市場資料不一定真的有變
重算成本非常高
LazyObject 的功用:LazyObject = Observer +
Observable + Cache 控制器
LazyObject = Observer +
Observable + Cache 控制器
LazyObject
想解決的不是「延遲計算」,而是為了「正確性 +
效能」。
class LazyObject : public virtual Observable, public virtual Observer
「virtual 繼承」
Instrument 可能同時是 Observable & Observer
PricingEngine 也可能是 Observable & Observer
LazyObject 類別的成員變數
mutable bool calculated_;
calculated_ == true:目前的結果是「可信的」
calculated_ == false:結果已過期,下次要重算
但 LazyObject 自己不會知道資料變了
不是 Bond 去問資料「你有沒有變」,而是資料主動說「我變了」。
calculated_ 設成 false?Observable (YieldCurve, Settings::evaluationDate)
|
| notifyObservers()
v
Observer (LazyObject / Instrument)
inline void LazyObject::update() {
if (calculated_ || alwaysForward_) {
calculated_ = false;
if (!frozen_)
notifyObservers();
}
}
市場資料一變,LazyObject 收到通知,把自己標記為「過期」,不做任何計算。
下一次有人呼叫 NPV() 才重新算
update() 裡面只應該做一件事:標記狀態
觀察者模式角色
Observable:
Observer:
| 角色 | QuantLib 實例 |
|---|---|
| Observable | YieldCurve、Settings::evaluationDate |
| Observer | LazyObject / Instrument |
| 通知 | notifyObservers() |
| 反應 | update() |
關鍵流程
Market Data 變動
↓
notifyObservers()
↓
LazyObject::update()
↓
calculated_ = false
Instrument::NPV() 為什麼只做
calculate()
double Instrument::NPV() {
calculate(); // LazyObject
return result_;
}
NPV() 不負責計算(不把商業邏輯塞在
getter)
NPV()
只負責「確保結果有效」,也就是:你要回傳 result_
前,先確定它不是過期的
NPV() 是「讀取結果」的
API,但它會保證讀到的是最新、可信的結果。
同一個 instrument(例如
bond)可能被很多人、很多模組呼叫
NPV()(風控、報表、交易、監理),你不希望每個人都自己判斷要不要重算,也不希望有人忘了重算。
calculate() 的真實角色:Cache Gate + Validity
Gate
inline void LazyObject::calculate() const {
if (!calculated_ && !frozen_) {
calculated_ = true; // prevent infinite recursion
try {
performCalculations();
} catch (...) {
calculated_ = false;
throw;
}
}
}
例外安全
calculated_Cache Gate(避免重算)
calculated_ == true
代表「我已經算過,而且仍然有效」
同一個價格被呼叫 100 次,也只會真的計算 1 次
Validity Gate(確保正確)
calculated_ == false 代表「你現在看到的 result_
不能信」
所以一定要進 performCalculations() 更新結果
只在必要時算,但只要你要讀結果,我就保證結果是最新的。
performCalculations() 放什麼?為什麼必須在這裡?
performCalculations()
才是「金融計算本體」,例如:
bond:cashflow discounting、accrued、yield、duration
option:model pricing(BS / tree / MC)
swap:legs pricing、NPV split、fair rate
封裝:
分離計算 vs 存取:NPV() 不管怎麼算,只要保證有效
多型:不同 instrument 對應不同演算法
觀察者模式不是要讓你「少算」,而是要讓你「只在真的需要時才算,而且永遠算對」。
Observer Pattern
Observable:會變的東西(市場資料 / evaluation date / curve)
Settings::evaluationDate(評價日變了)
YieldTermStructure(曲線點變了)
Quote(利率、價格、波動率更新)
Observer:在乎結果的人(Instrument / Pricing Engine / LazyObject)
Instrument 通常會繼承 LazyObject(或內部用
LazyObject 機制),同時也是 Observer。所以它會有
update():
Observable 變動 → 呼叫 observer.update()
instrument.update() → 把自己標記成
calculated_ = false
結論
正確性
市場資料 / curve / evaluation date 變 →
notifyObservers()
Instrument(LazyObject)收到 update()
calculated_ = false
下一次 NPV() → 一定會重算
重點:沒有任何地方會用到過期結果
效率性:
如果資料沒變:
update() 不會被呼叫
calculated_ 一直是 true
NPV() 被呼叫 100 次:
performCalculations() 只跑 1 次重點:金融系統「可擴展性」的關鍵