課程總結

你這學期到底教了什麼?

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++ 設計金融系統』了。」

NPV() 背後真正發生的事(對應 Part A)

  • 呼叫順序(概念)

  • Bond::NPV()

  • LazyObject::calculate()

  • PricingEngine::calculate()

  • 重點不是流程,而是責任切分

為什麼要有 LazyObject?

  • 避免重複計算

  • 支援 market data 改變

  • Observer / Observable 的真實用途

觀察者模式(Observer Pattern)

  • 實際的問題:

    • 假設你有一個 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

      • LazyObject 不知道「什麼資料變了」,它只知道「我不可信了」。
  • 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;
            }
        }
    }
    • 例外安全

      • 計算算失敗 → rollback 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 次
        • 重點:金融系統「可擴展性」的關鍵