講義一

課程目標

  • 了解 C++ 語言的特性,奠定程式語言的基礎。

  • 了解如何運用C++程式語言解決金融計算中的問題和挑戰。

  • 學習如何使用C++進行金融模型的實現與演算法的設計。

  • 金融計算程式運用(一)屬於『初階C++』介紹,並加上更多相關財務案例的討論。

  • 金融計算程式運用(二)將介紹更多關於物件導向、模板設計、C++ 標準程式庫、QuantLib程式庫原始碼的介紹。

  • 透過了解高品質的 C++ 量化金融程式庫(QuantLib),強化實作觀念與能力。

參考書籍

  • 上課內容:

    • 以老師自編的講義為主。
  • 案例分析:

    • Armstrong, J. (2017). C++ for financial mathematics. CRC Press.

    • Pitt-Francis, J., & Whiteley, J. (2017). Guide to scientific computing in C++. Springer.

  • 基礎:

    • Barbara, Stanley B. Lippman Josée Lajoie, and E. Moo. (2013). C++ Primer.
  • 進階:

    • Duffy, Daniel J. Financial instrument pricing using C++. John Wiley & Sons, 2018.

C++ 簡介

  • C++ 是由 AT&T Bell 實驗室的 Bjarne Stroustrup 博士及其同事於80年代在 C 語言的基礎上開發成功而誕生。

  • C++ 語言是一門經典、靈活、功能強大、野心勃勃的計算機程式語言,但也是公認具有一定學習難度的電腦程式語言。

  • 即便是那些不使用 C++ 語言進行開發的職業,很多也要求求職者具備 C++ 程式功力,究其原因,主要就是因為凡是對 C++ 語言開發有良好駕馭能力的人,整體開發實力明顯要比從未接觸過 C++ 語言編程的開發者強出許多。

  • C 語言為 C++ 的子集合,具備 C 的強大功能並擴展了物件導向特性。

  • 兼具低階與高階程式語言特性,適用於系統開發與應用程式開發。

  • 可直接存取記憶體位址,進行位元操作,如位元運算子(bitwise operator)。

  • 可用來開發系統軟體、嵌入式系統、資料庫管理系統、遊戲引擎等。

  • 執行速度快,適合高效能計算與即時處理。

  • 運用廣泛,涵蓋金融、科學計算、人工智慧、區塊鏈、量子計算等領域。

  • 支援多種程式設計範式:過程導向、物件導向、泛型程式設計、函數式程式設計。

  • 與多種外部庫整合良好:如 BoostEigen、OpenMP、CUDA、QuantLib

C++ 起源與特性

  • C++(物件導向)為編譯式語言,其為 C 語言(過程導向)的增強版。

    • 加入「類別(class)」、「虛函數(virtual function)」、「運算子重載(operator overloadding)」、「模版(template)」等特性。
  • 1998年,C++ 標準委員會正式發佈第一個 C++ 標準,此版本被認為「標準 C++」,又稱「C++ 98」(1.0)。

  • 2003年發布 C++ 標準第二版,也叫「C++ 03」。

  • 2011年,新的 C++ 標準正式發佈,稱「C++ 11」(2.0)。

    • C++98(1998) 版本以及之前的版本,一般稱為『傳統 C++』。

    • C++03(2003):小幅修訂,修正 C++98 的問題。

    • C++11 (2011)版本之後,一般稱為『現代 C++』。

    • C++14(2014):增強 C++11,改進泛型與 constexpr

    • C++17(2017):引入 std::optionalif constexpr、結構化綁定。

    • C++20(2020):提供 conceptsrangescoroutines,大幅提升泛型能力。

電腦開發環境建議

Windows

  1. Visual Studio 2022

    • 因需要 Visual Studio C++ 編譯器(Microsoft Visual C++,簡稱 MSVC),故建議安裝。否則需安裝其他 C++編譯器(如 MinGW-w64,乃 Windows 版本的 GNU Compiler Collection(GCC)。其提供一個在Windows上使用的 GNU工具鏈,包括C++編譯器)。

    • Visual Studio 是 Microsoft 官方提供的整合開發環境(IDE),非常適合在Windows電腦上進行 C++ 程式開發。

    • Visual Studio 2022 是最新版本,支援 Windows 11。

    • 提供豐富的功能,包括程式碼編輯、調試、自動完成、視覺化設計和內建的 CMake 支援。

    • Visual Studio 2022 還支援許多擴展,可擴展其功能,且社區版是免費的,適用於大多數開發者。

    • Windows環境下,使用 QuantLib C++ 程式庫時推薦使用。

    • 安裝過程中,記得勾選 C++ 工作負載所需的核心功能,以確保安裝 Visual Studio C++ 編譯器(MSVC)。

  2. Visual Studio Code

    • Visual Studio Code(VS Code)是一個輕量級的代碼編輯器。

    • 可透過安裝 C/C++ 與 CMake 相關延伸模組,可以使 VS Code 成為一個強大的 C++ 開發工具。

    • VS Code 是免費的,且社群與插件生態環境相當活躍。

  3. CMake

    • CMake 是一個用於自動生成跨平台應用程式的建構(Build)工具。

    • 可管理 C++ 或其他程式語言專案的構建過程。

    • 使用 CMake,可在不同的作業系統上產生相對應的構建文件,例如 Makefile(用於 Linux 和 macOS)或 Visual Studio 專案建構文件(用於 Windows)。

    • 使得不同平台上的專案能夠保持一致性且易於管理。

    • 安裝 CMake:

      • 下載 CMake 安裝程式:

      • 執行安裝程式:

        • 雙擊下載的安裝執行檔,接著按照安裝步驟進行操作。

        • 在安裝過程中,可選擇安裝路徑、加入快捷方式等選項。通常,預設選項是最合適的。

      • 設定環境變數以完成安裝:

        • 安裝完成後,確保將 CMake 的安裝路徑添加到系統的 PATH 環境變數中。

        • 如果需要手動添加到 PATH,請按照以下步驟:

          1. 打開「設定」。

          2. 選擇「系統」。

          3. 下拉到畫面最後,選擇「系統資訊」。

          4. 在「系統資訊」畫面尋找「進階系統設定」後並點擊之。

          5. 進入「系統內容」視窗後,點擊「環境變數」按鈕。

          6. 在「系統變數」的區域中,下拉尋找名為 Path 變數。

          7. 在「編輯環境變數」對話框中,按一下「新建」,然後加入 CMake 的安裝路徑(一般都是放在 C:\Program Files\CMake\bin,請確認 CMake 在你電腦的位置)。

          8. 點擊「確定」儲存變更。

        • 打開命令提示列(cmd)或 PowerShell。

        • 在命令提示列輸入下列指令檢查 CMake 是否成功安裝,並顯示版本資訊,若成功安裝,則會看到 cmake 版本:

          cmake --version

Mac

  1. Xcode:

    • 若尚未安裝 Xcode,首先從 Mac App Store 下載和安裝 Xcode。
    • Xcode 是 macOS 上的整合開發環境(IDE),其包括編譯器(含 C++編譯器 - Clang)和許多開發工具,非常適合 C++ 開發。
  2. Homebrew

    • Homebrew 是 macOS 上的一個套件管理器,可以幫助您輕鬆安裝和管理軟件包。您可以使用以下命令安裝 Homebrew:

      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

      • Macbook pro M1 電腦可以執行。其他型號請各位同學試試看。
    • (若安裝失敗,請先看終端機上的錯誤訊息提示並進行操作。)

  3. C++ 編譯器:

    • 使用 Homebrew 安裝最新版本的 GCC 或 Clang(Xcode 內建)。您可以選擇其中一個來編譯 C++ 程式。

      • 安裝 GCC:

        brew install gcc

      • 使用 Xcode 內建的 Clang。

  4. Visual Studio Code

    • 下載 macOS 版本: 點擊下載頁面上的「macOS」按鈕,以下載 macOS 版本的 Visual Studio Code。

    • 安裝 VS Code: 下載完成後,您會在下載目錄中找到一個名為「Visual Studio Code.app」的應用程式檔案。將該檔案拖曳至「應用程式」資料夾中,以完成安裝。

    • 啟動 VS Code: 前往「應用程式」資料夾,找到安裝完成的 Visual Studio Code 應用程式,接著雙擊打開。

    • 安裝插件(擴展): 第一次啟動 VS Code 時,將看到歡迎畫面。可在左側的側邊欄中找到插件圖示(方塊和箭頭)。點擊該圖示,然後在搜索欄中輸入欲安裝的插件名稱。例如,想進行 C++ 開發,需安裝「C/C++」擴展。單擊「安裝」按鈕以安裝所選的擴展。另外,請同時安裝 CMake 插件。

  5. CMake

    • 方法一:使用 Homebrew 安裝 CMake

      • 打開終端機應用程式(Terminal)。若未安裝 Homebrew,可在終端機執行以下命令來安裝:

        /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

      • 安裝 CMake,只需在終端機執行以下命令:

        brew install cmake

      • 可在終端機執行以下命令來驗證 CMake 是否安裝成功:

        cmake --version

      • 若安裝成功,終端機將顯示 CMake 的版本訊息。

    • 方法二:從官方網站下載並安裝 CMake

      • 前往 CMake 官方網站的下載頁面

      • 滾動到 “Binary distributions” 部分,找到 macOS 的選項,通常會有一個 .dmg 檔案的下載連結,點擊它下載最新版本的 CMake。

      • 雙擊下載的 .dmg 檔案,它會在 Finder 中打開一個新的視窗。

      • 在打開的視窗中,將 “CMake” 應用程式圖標拖放到 “Applications” 資料夾中,以完成安裝。

      • 可關閉 .dmg 視窗,並在「應用程式」資料夾中找到 CMake 應用程式。第一個 C++ 程式

      • 可在終端機執行以下命令來驗證 CMake 是否安裝成功:

        cmake --version

      • 若安裝成功,終端機將顯示 CMake 的版本訊息。

CMake 與 Ninja 工具

  • CMake 是一個開源跨平台『自動化專案建構』的工具,其不依賴於特定的編譯器或作業系統。

  • CMake 主要任務是生成適用於不同建構系统的『建構文件檔』。依系統不同有以下格式:

    • Makefiles:Make工具上的建構文件檔

    • Visual Studio 專案的建構文件檔

    • Ninja 的建構文件檔等。

  • CMake 的配置文件稱為 CMakeLists.txt,其用來定義專案的各種屬性、依賴關係和建構的規則等。

  • Ninja 是一個跨平台的軟體專案建構系统,其用於『專案的構建』。最初由 Google 團隊開發使用。

  • 與 Make 或其他建構工具不同,Ninja 的設計目的是快速執行與平行化建構方式。

  • Ninja 以建構文件檔 —— build.ninja 形式使用,而這些文件檔由 CMake 工具所產生。

  • 當使用 CMake 專案時,CMake 工具會根據專案的配置產生適用於 Ninja 的建構文件檔。意指你可以指定使用 Ninja 作為實際建構專案的工具,而非採 Make 或 Visual Studio 等其他建構工具。

CMake指令

  • 在 macOS 上使用 CMake 工具一般不需要額外安裝 Ninja。

  • macOS 內建 make 建構工具,一般情況下足以完成大部分 CMake 專案的構建。

    • 因 macOS 預設安裝 make 工具(GNU Make)。故可在終端機中執行 “make” 命令。

    • 為確認已經正確安裝。請執行下列指令:

      make --version
  • 補充:安裝Ninja(可選):

    • macOS:

      • 可使用套件管理器進行安裝,如 Homebrew。以下是使用 Homebrew 安裝 Ninja 的步驟:

        brew install ninja
      • 驗證安裝:

        ninja --version
    • Windows:

      1. 安裝 Chocolatey(可選)

        • 以『系統管理員身分執行』打開 PowerShell 。

        • 執行以下指令安装 Chocolatey:

          Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
      2. 安裝 Ninja(可選)

        • 當 Chocolatey 安装完成,可使用 Chocolatey 來安裝 Ninja。

        • 同樣以『系統管理員身分執行』打開的 PowerShell 或命令提示列視窗執行以下指令:

          choco install ninja
      3. 驗證安裝:

        • 在命令提示列或 PowerShell 執行以下指令:

          ninja --version
  • 要在 CMake 中配置使用 make 或 ninja 作為建構工具,或是在 Windows 系統下使用 Visual Studio 2022 的生成器,可透過執行 CMake 指令時指定生成器(Generator)來達成。

  • 生成器決定了 CMake 將使用哪種建構工具。

    • 指定使用 Make 建構工具:cmake -S . -B build -G "Unix Makefiles"(Mac 可用)

    • 指定使用 Ninja 建構工具:cmake -S . -B build -G "Ninja"(Mac、Windows 可用,建構速度較快,建議 Mac 使用

    • 指定使用 Visual Studio 2022 建構工具:cmake -S . -B build -G "Visual Studio 17 2022”建議 Windows 使用

    • 上述指令用於配置和生成 CMake 專案的配置文件:

      • cmake:這是 CMake 的命令行工具,用於執行各種 CMake 相關的操作。

      • -S .:這部分指定了 CMake 的來源目錄(Source Directory)。. 表示當前目錄,也就是執行該命令的目錄。這個目錄應包含 CMakeLists.txt 文件,因為它告訴 CMake 哪裡可以找到專案的配置相關設定。

      • -B build:這部分指定 CMake 的建構目錄(Build Directory)。在上述範例中,將建構文件存儲在名為 “build” 的子目錄中。建構目錄是用於儲存產生的中間檔案和建構檔案的位置。您可以選擇不同的目錄名稱,但通常建議將生成文件存儲在專門的子目錄中,以保持專案的整潔與一致性。

      • -G "Unix Makefiles":這部分指定了生成器(Generator)。在上述範例中,若使用的生成器為 “Unix Makefiles”,此生成器用於產生可由 Unix 系統上的 Make 指令使用的建構文件。這表示希望使用 “make” 作為建構工具來編譯和構建你的 C++ 專案。

  • CMake 建構指令:

    cmake --build build
    • 此指令使用 CMake 設定的建構工具來進行建構的命令。

    • CMake 會根據之前指定的生成器來選擇適用的建構工具。

    • 以下為該指令各部分的解釋:

      • cmake:這是 CMake 的命令行工具,用於執行各種 CMake 相關的操作。

      • --build:這是 CMake 的一個子命令,用於執行實際的建構操作。

      • build:這是之前使用 -B 選項指定的生成目錄的名稱。建構目錄是 CMake 用來儲存生成的中間檔案和建構檔案的位置。

    • 當執行 cmake --build build 時,CMake 將使用預設的生成工具(例如 make 或 ninja,取決於之前配置的生成器)來自動編譯和連結 C++ 專案。

    • 該指令會在建構目錄中查找 CMake 已經生成的建構指令,接著執行這些指令以完成建構過程。

執行第一個 C++ 程式

  • 程式語言實際上就是一套規範的集合,主要包含該語言使用的字元集合、直接或間接支援的資料型態集合、運算子集合、關鍵字集合、指令集合、語法規則,以及特定構造的支援,例如:函數的定義,抽象資料型態的定義、繼承、模板、例外處理等。

  • main 函數(主函數)為C++程式的「入口點」。

  • main 函數為使用者運行程式時所執行的函數。

  • C++程式常用的副檔名:

    • .cppcxxcc:原始檔(source file)。

    • .h.hpp:標頭檔(header file)

  • 『函數(function)』可以接收輸入、執行某些指令並回傳結果的程式碼區塊。

// test.cpp,放入src資料夾
// 這是單行註解

/*
 * 這是多行註解
 * 這是多行註解
 * 這是多行註解
 */

#include <iostream>                                       // 包含iostream標頭檔。

int main(int argc, char* argcv[])
{
  std::cout << "Hello Quant World" << std::endl;         // std為C++標準程式庫的命名空間(namesapce)名稱。
  return 0;
}

使用 VS Code

  • 建議『QuantLib範例』專案資料夾,專案名為: MyProj,資料夾內建立子資料夾:

    • src:存放c++原始檔(source file, ex: 副檔名為 .cpp.cxx.cc)。

    • include:存放標頭檔(header file, ex: 副檔名為 .h.hpp)。

    • external:存放外部程式庫原始檔,例如:QuantLib-1.37

    • data:存放外部資料檔,例如 .txt, .json檔案。

    • bin:執行檔放置的位置。

    • CMakeLists.txt

      • 在專案資料夾 MyProj 建立名稱為 CMakeLists.txt 的檔案,用於描述專案的結構和如何構建該專案。

      • 定義原始檔、編譯選項、連結庫、目標(target)和其他構建相關的設置。

      • CMakeLists.txt 使用 CMake 語法,其包含一系列命令和變數設置。

        • 不含 QuantLib C++ Library

          cmake_minimum_required(VERSION 3.10)
          
          project(CppProject
            VERSION 1.0.0
            DESCRIPTION "Study C++"
            LANGUAGES CXX
          )
          
          # 指定 C++ 的版本
          set(CMAKE_CXX_STANDARD 17)
          set(CMAKE_CXX_STANDARD_REQUIRED ON)
          
          
          # 設定專案的目錄結構
          set(SRC_DIR ${CMAKE_SOURCE_DIR}/src)
          set(INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include)
          set(EXTERNAL_DIR ${CMAKE_SOURCE_DIR}/external)
          set(DATA_DIR ${CMAKE_SOURCE_DIR}/data)
          set(BIN_DIR ${CMAKE_SOURCE_DIR}/bin)
          
          
          # 設定執行檔名稱與原始檔名稱 #################################
          # set(SOURCE_FILES ${SRC_DIR}/main.cpp)          #    原始檔名稱
          set(EXECUTABLE_NAME MyApp)                       #    執行檔名稱
          ##############################################################
          
          
          # 使用 GLOB 收集原始檔案
          file(GLOB SOURCE_FILES "${SRC_DIR}/*.cpp")
          
          
          # 設定可執行檔案的輸出目錄
          set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR})
          
          
          # 設定執行檔名稱和源文件
          add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
          # MyApp 是要創建的執行檔名稱(目標target),main.cpp 是該執行檔所依賴的源文件。
          # 這個指令告訴 CMake 創建一個名為 MyApp 的執行檔,該執行檔的源文件是 main.cpp。
          
          
          # 將 include 目錄添加到編譯器的包含路徑中
          target_include_directories(${EXECUTABLE_NAME} PRIVATE ${INCLUDE_DIR})
          
          
          message(STATUS "Binary output directory: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
          
          
          # 最後將測試資料複製一份到輸出目錄(bin資料夾)
          file(COPY ${DATA_DIR} DESTINATION ${BIN_DIR})
        • 包含 QuantLib C++ Library

          cmake_minimum_required(VERSION 3.10)
          
          project(QuantLibExample
            VERSION 1.0.0
            DESCRIPTION "An example project using QuantLib"
            LANGUAGES CXX
          )
          
          # 指定 C++ 的版本
          set(CMAKE_CXX_STANDARD 17)
          set(CMAKE_CXX_STANDARD_REQUIRED ON)
          
          
          # 設定專案的目錄結構
          set(SRC_DIR ${CMAKE_SOURCE_DIR}/src)
          set(INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include)
          # set(EXTERNAL_DIR ${CMAKE_SOURCE_DIR}/external)
          set(DATA_DIR ${CMAKE_SOURCE_DIR}/data)
          set(BIN_DIR ${CMAKE_SOURCE_DIR}/bin)
          
          
          # 設定執行檔名稱與原始檔名稱 #################################
          # set(SOURCE_FILES ${SRC_DIR}/main.cpp)          #    原始檔名稱
          set(EXECUTABLE_NAME MyApp)                       #    執行檔名稱
          ##############################################################
          
          
          # 使用 GLOB 收集原始檔案
          file(GLOB SOURCE_FILES "${SRC_DIR}/*.cpp")
          
          
          # 設定 QuantLib 的原始碼路徑
          set(QUANTLIB_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/QuantLib-1.37)
          # ${CMAKE_CURRENT_SOURCE_DIR} 是 CMake 內建的變數,表示當前 CMakeLists.txt 文件所在的目錄的路徑。
          # 假設 CMakeLists.txt 文件位於您的專案的根目錄下,這條命令將把 QUANTLIB_SOURCE_DIR 設置為 ${專案根目錄}/external/QuantLib-1.37。
          
          
          # 將 QuantLib 的原始碼添加到您的專案中
          add_subdirectory(${QUANTLIB_SOURCE_DIR})
          # 在這個例子中,${QUANTLIB_SOURCE_DIR} 是一個變數,表示 QuantLib 的原始碼所在的目錄路徑,是之前使用 set() 命令設定的。
          # ${QUANTLIB_SOURCE_DIR} 的值應該是 QuantLib 原始碼的根目錄。
          
          # 設定可執行檔案的輸出目錄
          set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR})
          
          
          # 設定執行檔名稱和源文件
          add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
          # MyApp 是要創建的執行檔名稱(目標target),main.cpp 是該執行檔所依賴的源文件。
          # 這個指令告訴 CMake 創建一個名為 MyApp 的執行檔,該執行檔的源文件是 main.cpp。
          
          
          # 連接 QuantLib 程式庫
          target_link_libraries(${EXECUTABLE_NAME} ql_library)
          # 將目標 MyApp 連接(link)到 ql_library 程式庫。
          # 在這裡,使用了 add_subdirectory(${QUANTLIB_SOURCE_DIR}) 命令將 QuantLib 的原始碼目錄添加到專案中,從而生成了 ql_library 程式庫。
          # target_link_libraries() 命令確保了 MyApp 執行檔在編譯時能夠找到 ql_library 程式庫的二進位檔案,並在執行時能夠使用它提供的函數和功能。
          # 在連接程式庫時,CMake 將根據目標平台和編譯器使用適當的連接器(例如 g++、clang++、Visual Studio 等)來建立連接。
          
          
          # 將 QuantLib 的頭文件目錄添加到編譯器的包含路徑
          target_include_directories(${EXECUTABLE_NAME} PRIVATE ${QUANTLIB_SOURCE_DIR})
          # 將 include 目錄添加到編譯器的包含路徑中
          target_include_directories(${EXECUTABLE_NAME} PRIVATE ${INCLUDE_DIR})
          
          
          
          
          message(STATUS "Binary output directory: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
          
          
          # 最後將測試資料複製一份到輸出目錄(bin資料夾)
          file(COPY ${DATA_DIR} DESTINATION ${BIN_DIR})
  • 每個 C++ 程式包含一個或多個函數(function)。其中一個必須命名為 main

  • main 函數為程式的入口點(entry point),作業系統是透過 main 函數來執行 C++ 程式。

  • 一個函數的定義包含四個部分:

    • 回傳型態(return type)

    • 函數名(function name)

    • (形式)參數列表(parameter list,可為空)

    • 函數體(function body)

  • main 函數回傳值必須為 int,即為整數型態;其為一種內建型態(built-in type)。

  • 函數體由左大括弧({)開始,以右大括弧結束(})。

  • #include <iostream> 告訴編譯器我們想要使用 iostream 程式庫。< >內指定標頭檔(header file)名稱。而#include指令與標頭檔名稱必須寫在同一行,且一般出現在所有函數之外(含main函數)。

    • 標準 C 並沒有提供對 I/O 的直接支援,只有定義標準 I/O 的函數接口,故 I/O 工作都是透過函數庫來完成。標準 C++則繼承了 C 的 I/O 函數庫接口,並重新定義自己的物件導向的 I/O 系統。
  • return 語句(statememt)結束函數的執行。

  • 請注意 return 語句末尾的分號(;)。大多數 C++的語句以分號(;)表示該語句的結束。

  • 在大部分系統中,main 函數的回傳值表示『執行狀態』。回傳 0 代表執行成功,非 0 的回傳值則由系統定義,通常代表錯誤的類型。

C++ 程式建構的過程

  • C++程式由資料數據(常數、變數,含物件等)與函數等所組成。

  • 因 C++ 為『編譯程式語言』,故編寫程式之後則需進行『編譯』。

  • 編譯器如何進行編譯端看所使用的作業系統與編譯器:

    • 可使用『整合開發環境(IDE)』(如 VS code 與 Visual Studio 2022 等),其為將編譯器與其他程式創建和分析工具整合在一起。
  • 亦可透過命令提示列進行。

  • 建構過程:

    1. 編輯器(editor):將程式設計的想法轉換為程式碼。

      • 若為語法方面的錯誤,則會在程式編譯過程中出錯,且編譯器會出現錯誤提示。

      • 若為邏輯方面的錯誤(例如程式輸出結果不如預期時),或執行效率低落,則不會有明顯的錯誤提示。

    2. 預處理器(preprocesser)

      1. 編譯器在正式編譯之前,遍歷整個原始碼檔案,先處理 C/C++ 原始檔案中的預處理命令,即以 # 開頭的指令。

      2. 預處理的含義將後面提及的檔案(標頭檔)『複製』至當前原始碼檔案中。並進行下列處理:

        • 刪除所有的 #define 並展開所有 macro

        • 處理所有的預編譯條件,例如 #ifdef , #include (展開引用文件)

        • 刪除所有 註解

        • 增加 行號以及 文件識別名,讓編譯器在編譯失敗時可以顯示錯誤的行號。

        • 常見預處理指令:

          • #include

          • #ifndef … #else …#endif

          • #define

          • #pragma

    3. 編譯器(compiler)

      • 雖然 C++ 標準是唯一的,但各編譯器的實現未必完全一致。

      • 因不同電腦的硬體系統未必相同,故一種編譯器一般是針對一種或某些作業系統所設計。

      • 某種平台下所編譯的程式,一般無法在另一個作業系統上執行。

      • 此階段編譯器會將展開後的程式碼轉換成『組合語言』。

      • 預編譯完成後,編譯器會編譯每個原始檔(.cpp.cxx.cc.c),如果編譯成功,會產生對應的『目標檔(object file)』,Linux平台為 .o 檔,Windows平台下為 .obj 檔。

      • 編譯完成的目標檔還不可執行。還需要『連結器』進一步進行連結處理。

    4. 連結器(linker):連結器將多個目標檔與多個靜態程式庫處理成『執行檔』

      • 執行檔:包含指令、位址與數據(實際上位址也是一種特殊的數據)。
    5. 執行程式:

      1. 連結任務完成後即可產生執行檔。但仍需檢驗程式能否正常運行,並符合設計的要求。

      2. 很少有程式能一次執行成功,總會有一些意想不到的錯誤發生。

      3. 錯誤發生後則需回頭檢查原始碼、重新編譯、連結,試執行等。

補充:在 Windows 電腦安裝 WSL(Linux 系統),並使用 VS code 開發 C++

  1. 安裝 WSL ,此指令會啟用 WSL 並安裝 Linux Ubuntu 作業系統:

    wsl --install
  2. 設定『使用者帳號』與密碼。

  3. 打開 Ubuntu 命令列,並安裝 C++ 編譯器(g++):

    sudo apt update
    sudo apt install g++
  4. 安裝 C++ boost 程式庫:

    sudo apt-get install libboost-all-dev
  5. 安裝 CMake 工具:

    sudo apt install cmake
  6. 安裝 Visual Studio Code:

    sudo apt install code
  7. 在 Ubuntu 路徑上(/home/[你的使用者帳號]/)建立『專案資料夾』,例如命名為 [QuantExamples]:

    cd /home/[你的使用者帳號]
    mkdir QuantExamples
  8. 切換路徑專案資料內,並建立五個子資料夾(bin, data, src, include, external):

    cd QuantExamples
    mkdir bin
    mkdir data
    mkdir src
    mkdir include
    mkdir external
  9. 將 QuantLib 1.37 壓縮檔下載至 external 資料夾,並進行解壓縮:

    cd external
    sudo apt install wget
    wget https://github.com/lballabio/QuantLib/releases/download/v1.37/QuantLib-1.37.tar.gz
    tar xzf QuantLib-1.37.tar.gz
  10. 將路徑切回專案資料夾,並以 Visual Studio Code 打開此專案:

    cd /home/[你的使用者帳號]/QuantExamples
    code .
  11. 安裝 Visual Studio Code 必要的『延伸模組』:如與 cmake相關、WSL 相關延伸模組等。

  12. 建立 CMakeLists.txt 檔案。將講義上的 CMakeLists.txt 內容複製貼上至新建立的 CMakeLists.txt 檔案中。

  13. 在 src 資料夾內建立主程式檔案供測試使用,如 test.cpp:

    /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    
    /*!
     Copyright (C) 2008 Allen Kuo
    
     This file is part of QuantLib, a free-software/open-source library
     for financial quantitative analysts and developers - http://quantlib.org/
    
     QuantLib is free software: you can redistribute it and/or modify it
     under the terms of the QuantLib license.  You should have received a
     copy of the license along with this program; if not, please email
     <quantlib-dev@lists.sf.net>. The license is also available online at
     <http://quantlib.org/license.shtml>.
    
     This program is distributed in the hope that it will be useful, but WITHOUT
     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     FOR A PARTICULAR PURPOSE.  See the license for more details.
     */
    
    /* This example sets up a callable fixed rate bond with a Hull White pricing
       engine and compares to Bloomberg's Hull White price/yield calculations.
    */
    
    #include <ql/qldefines.hpp>
    #if !defined(BOOST_ALL_NO_LIB) && defined(BOOST_MSVC)
    #  include <ql/auto_link.hpp>
    #endif
    #include <ql/experimental/callablebonds/callablebond.hpp>
    #include <ql/experimental/callablebonds/treecallablebondengine.hpp>
    #include <ql/models/shortrate/onefactormodels/hullwhite.hpp>
    #include <ql/termstructures/yield/flatforward.hpp>
    #include <ql/time/calendars/unitedstates.hpp>
    #include <ql/time/daycounters/actualactual.hpp>
    
    #include <vector>
    #include <cmath>
    #include <iomanip>
    #include <iostream>
    
    using namespace std;
    using namespace QuantLib;
    
    ext::shared_ptr<YieldTermStructure>
        flatRate(const Date& today,
                 const ext::shared_ptr<Quote>& forward,
                 const DayCounter& dc,
                 const Compounding& compounding,
                 const Frequency& frequency) {
        return ext::make_shared<FlatForward>(today,
                                             Handle<Quote>(forward),
                                             dc,
                                             compounding,
                                             frequency);
    }
    
    
    ext::shared_ptr<YieldTermStructure>
        flatRate(const Date& today,
                 Rate forward,
                 const DayCounter& dc,
                 const Compounding &compounding,
                 const Frequency &frequency) {
        return flatRate(today,
                ext::make_shared<SimpleQuote>(forward),
                dc,
                compounding,
                frequency);
    }
    
    
    int main(int, char* [])
    {
        try {
    
    
            Date today = Date(16,October,2007);
            Settings::instance().evaluationDate() = today;
    
            cout <<  endl;
            cout << "Pricing a callable fixed rate bond using" << endl;
            cout << "Hull White model w/ reversion parameter = 0.03" << endl;
            cout << "BAC4.65 09/15/12  ISIN: US06060WBJ36" << endl;
            cout << "roughly five year tenor, ";
            cout << "quarterly coupon and call dates" << endl;
            cout << "reference date is : " << today << endl << endl;
    
            /* Bloomberg OAS1: "N" model (Hull White)
               varying volatility parameter
    
               The curve entered into Bloomberg OAS1 is a flat curve,
               at constant yield = 5.5%, semiannual compounding.
               Assume here OAS1 curve uses an ACT/ACT day counter,
               as documented in PFC1 as a "default" in the latter case.
            */
    
            // set up a flat curve corresponding to Bloomberg flat curve
    
            Rate bbCurveRate = 0.055;
            DayCounter bbDayCounter = ActualActual(ActualActual::Bond);
            InterestRate bbIR(bbCurveRate,bbDayCounter,Compounded,Semiannual);
    
            Handle<YieldTermStructure> termStructure(flatRate(today,
                                                              bbIR.rate(),
                                                              bbIR.dayCounter(),
                                                              bbIR.compounding(),
                                                              bbIR.frequency()));
    
            // set up the call schedule
    
            CallabilitySchedule callSchedule;
            Real callPrice = 100.;
            Size numberOfCallDates = 24;
            Date callDate = Date(15,September,2006);
    
            for (Size i=0; i< numberOfCallDates; i++) {
                Calendar nullCalendar = NullCalendar();
    
                Bond::Price myPrice(callPrice, Bond::Price::Clean);
                callSchedule.push_back(
                    ext::make_shared<Callability>(
                                        myPrice,
                                        Callability::Call,
                                        callDate ));
                callDate = nullCalendar.advance(callDate, 3, Months);
            }
    
    
            // set up the callable bond
    
            Date dated = Date(16,September,2004);
            Date issue = dated;
            Date maturity = Date(15,September,2012);
            Natural settlementDays = 3;  // Bloomberg OAS1 settle is Oct 19, 2007
            Calendar bondCalendar = UnitedStates(UnitedStates::GovernmentBond);
            Real coupon = .0465;
            Frequency frequency = Quarterly;
            Real redemption = 100.0;
            Real faceAmount = 100.0;
    
            /* The 30/360 day counter Bloomberg uses for this bond cannot
               reproduce the US Bond/ISMA (constant) cashflows used in PFC1.
               Therefore use ActAct(Bond)
            */
            DayCounter bondDayCounter = ActualActual(ActualActual::Bond);
    
            // PFC1 shows no indication dates are being adjusted
            // for weekends/holidays for vanilla bonds
            BusinessDayConvention accrualConvention = Unadjusted;
            BusinessDayConvention paymentConvention = Unadjusted;
    
            Schedule sch(dated, maturity, Period(frequency), bondCalendar,
                         accrualConvention, accrualConvention,
                         DateGeneration::Backward, false);
    
            Size maxIterations = 1000;
            Real accuracy = 1e-8;
            Integer gridIntervals = 40;
            Real reversionParameter = .03;
    
            // output price/yield results for varying volatility parameter
    
            Real sigma = QL_EPSILON; // core dumps if zero on Cygwin
    
            auto hw0 = ext::make_shared<HullWhite>(termStructure,reversionParameter,sigma);
    
            auto engine0 = ext::make_shared<TreeCallableFixedRateBondEngine>(hw0,gridIntervals);
    
            CallableFixedRateBond callableBond(settlementDays, faceAmount, sch,
                                               vector<Rate>(1, coupon),
                                               bondDayCounter, paymentConvention,
                                               redemption, issue, callSchedule);
            callableBond.setPricingEngine(engine0);
    
            cout << setprecision(2)
                 << showpoint
                 << fixed
                 << "sigma/vol (%) = "
                 << 100.*sigma
                 << endl;
    
            cout << "QuantLib price/yld (%)  ";
            cout << callableBond.cleanPrice() << " / "
                 << 100. * callableBond.yield(bondDayCounter,
                                              Compounded,
                                              frequency,
                                              accuracy,
                                              maxIterations)
                 << endl;
    
            cout << "Bloomberg price/yld (%) ";
            cout << "96.50 / 5.47"
                 << endl
                 << endl;
    
            sigma = .01;
    
            cout << "sigma/vol (%) = " << 100.*sigma << endl;
    
            auto hw1 = ext::make_shared<HullWhite>(termStructure,reversionParameter,sigma);
    
            auto engine1 = ext::make_shared<TreeCallableFixedRateBondEngine>(hw1,gridIntervals);
    
            callableBond.setPricingEngine(engine1);
    
            cout << "QuantLib price/yld (%)  ";
            cout << callableBond.cleanPrice() << " / "
                 << 100.* callableBond.yield(bondDayCounter,
                                             Compounded,
                                             frequency,
                                             accuracy,
                                             maxIterations)
                 << endl;
    
            cout << "Bloomberg price/yld (%) ";
            cout << "95.68 / 5.66"
                 << endl
                 << endl;
    
            ////////////////////
    
            sigma = .03;
    
            auto hw2 = ext::make_shared<HullWhite>(termStructure, reversionParameter, sigma);
    
            auto engine2 = ext::make_shared<TreeCallableFixedRateBondEngine>(hw2,gridIntervals);
    
            callableBond.setPricingEngine(engine2);
    
            cout << "sigma/vol (%) = "
                 << 100.*sigma
                 << endl;
    
            cout << "QuantLib price/yld (%)  ";
            cout << callableBond.cleanPrice() << " / "
                 << 100. * callableBond.yield(bondDayCounter,
                                              Compounded,
                                              frequency,
                                              accuracy,
                                              maxIterations)
                 << endl;
    
            cout << "Bloomberg price/yld (%) ";
            cout << "92.34 / 6.49"
                 << endl
                 << endl;
    
            ////////////////////////////
    
            sigma = .06;
    
            auto hw3 = ext::make_shared<HullWhite>(termStructure, reversionParameter, sigma);
    
            auto engine3 = ext::make_shared<TreeCallableFixedRateBondEngine>(hw3,gridIntervals);
    
            callableBond.setPricingEngine(engine3);
    
            cout << "sigma/vol (%) = "
                 << 100.*sigma
                 << endl;
    
            cout << "QuantLib price/yld (%)  ";
            cout << callableBond.cleanPrice() << " / "
                 << 100. * callableBond.yield(bondDayCounter,
                                              Compounded,
                                              frequency,
                                              accuracy,
                                              maxIterations)
                 << endl;
    
            cout << "Bloomberg price/yld (%) ";
            cout << "87.16 / 7.83"
                 << endl
                 << endl;
    
            /////////////////////////
    
            sigma = .12;
    
            auto hw4 = ext::make_shared<HullWhite>(termStructure, reversionParameter, sigma);
    
            auto engine4 = ext::make_shared<TreeCallableFixedRateBondEngine>(hw4,gridIntervals);
    
            callableBond.setPricingEngine(engine4);
    
            cout << "sigma/vol (%) = "
                 << 100.*sigma
                 << endl;
    
            cout << "QuantLib price/yld (%)  ";
            cout << callableBond.cleanPrice() << " / "
                 << 100.* callableBond.yield(bondDayCounter,
                                             Compounded,
                                             frequency,
                                             accuracy,
                                             maxIterations)
                 << endl;
    
            cout << "Bloomberg price/yld (%) ";
            cout << "77.31 / 10.65"
                 << endl
                 << endl;
    
            return 0;
    
        } catch (std::exception& e) {
            std::cerr << e.what() << std::endl;
            return 1;
        } catch (...) {
            std::cerr << "unknown error" << std::endl;
            return 1;
        }
    }
  14. 完成後,可關閉並重新開啟 Visual Studio Code。CMake 工具即自動建立編譯設定檔,並建立在 build 中。

  15. 即可進行『建置』,測試編譯是否成功。