QuantLib 开发指南
面向后续开发者的实践指南
本文档面向需要:
- 在 QuantLib 基础上开发新功能
- 扩展现有模块(新工具、新引擎、新模型)
- 理解库设计规范以保持代码一致性
- 优化现有实现并贡献到上游
1. 快速入门
1.1 环境搭建
# 1. 克隆仓库
git clone <repo-url> QuantLib
cd QuantLib
# 2. 配置构建(推荐 CMake + Ninja)
cmake --preset linux-gcc-ninja-release
# 或 Debug 模式
cmake --preset linux-gcc-debug
# 3. 编译
cmake --build build/linux-gcc-ninja-release -j$(nproc)
# 4. 运行测试
ctest --test-dir build/linux-gcc-ninja-release
# 或运行特定测试
./build/linux-gcc-ninja-release/test-suite/quantlib-test-suite --run_test=EuropeanOption
# 5. 运行基准测试
./build/linux-gcc-ninja-release/test-suite/quantlib-benchmark
1.2 最小应用示例
#include <ql/quantlib.hpp>
#include <iostream>
using namespace QuantLib;
int main() {
// 1. 设置全局评估日期
Settings::instance().evaluationDate() = Date(15, June, 2024);
// 2. 构造市场数据
Handle<Quote> spot(ext::make_shared<SimpleQuote>(100.0));
Handle<YieldTermStructure> rTS(flatRate(0.05, Actual365Fixed()));
Handle<YieldTermStructure> qTS(flatRate(0.02, Actual365Fixed()));
Handle<BlackVolTermStructure> volTS(flatVol(0.20, Actual365Fixed()));
// 3. 构造过程
auto process = ext::make_shared<BlackScholesMertonProcess>(
spot, qTS, rTS, volTS);
// 4. 构造工具
auto payoff = ext::make_shared<PlainVanillaPayoff>(Option::Call, 100.0);
auto exercise = ext::make_shared<EuropeanExercise>(Date(20, December, 2024));
VanillaOption option(payoff, exercise);
// 5. 设置引擎并定价
option.setPricingEngine(
ext::make_shared<AnalyticEuropeanEngine>(process));
std::cout << "NPV: " << option.NPV() << std::endl;
std::cout << "Delta: " << option.delta() << std::endl;
return 0;
}
2. 核心设计模式与如何扩展
2.0 开发新功能流程总览
graph TD
Start["Determine Requirement"] --> Step1{"Need new Instrument?"}
Step1 -->|Yes| NewInst["Create Instrument subclass"]
Step1 -->|No| Step2{"Need new pricing method?"}
NewInst --> Args["Define arguments/results"]
Args --> EngineTypedef["Define engine typedef"]
EngineTypedef --> Step2
Step2 -->|Yes| NewEngine["Create PricingEngine subclass"]
Step2 -->|No| Step3{"Need new model/process?"}
NewEngine --> RegisterWith["registerWith Handles in ctor"]
RegisterWith --> CalcImpl["Implement calculate()"]
CalcImpl --> Step3
Step3 -->|Yes| NewModel["Create Model/Process subclass"]
Step3 -->|No| Step4{"Need new market data?"}
NewModel --> Calibrate["Implement calibrate"]
Calibrate --> Step4
Step4 -->|Yes| NewTS["Create TermStructure or Index"]
Step4 -->|No| Done["Register files in CMakeLists.txt"]
NewTS --> ImplMethod["Implement Impl methods"]
ImplMethod --> Done
Done --> Test["Write tests"]
Test --> Format["Run clang-format"]
Format --> Build["Build and test pass"]
2.1 Observer/Observable 模式
何时使用: 任何需要根据输入数据变化自动重计算的对象。
实现规范:
// === 定义新的 Observable ===
class MyObservable : public virtual Observable {
public:
void modify() {
// ... 修改内部状态 ...
notifyObservers(); // 通知所有注册的 Observer
}
};
// === 定义新的 Observer(同时是 Observable)===
class MyObserver : public virtual Observer, public virtual Observable {
public:
MyObserver(const Handle<SomeObservable>& h) {
registerWith(h); // 注册为 h 的观察者
}
void update() override {
// 当被观察对象变化时调用
// 标记自己为 dirty
notifyObservers(); // 传播通知给自己的观察者
}
};
sequenceDiagram
participant Q as SimpleQuote
participant H as Handle
participant PEC as YieldCurve
participant Idx as Index
participant Cpn as FltCoupon
participant Swap as VanillaSwap
Q->>Q: setValue(newRate)
Q->>Q: notifyObservers()
Q->>H: update()
H->>PEC: update()
PEC->>PEC: LazyObject update
PEC->>PEC: calculated = false
PEC->>Idx: notifyObservers()
Idx->>Cpn: update()
Cpn->>Cpn: calculated = false
Cpn->>Swap: notifyObservers()
Swap->>Swap: calculated = false
Note over Swap: Auto-recalculate on next NPV()
关键规则:
registerWith()必须在构造时调用,确保对象在销毁时自动解注册notifyObservers()在状态变化后调用- 不要在
update()中做重量级计算 — 只标记 dirty,计算延迟到访问时
2.2 LazyObject 模式
何时使用: 任何需要按需计算并缓存结果的对象。
stateDiagram-v2
[*] --> Dirty : construct
Dirty --> Calculating : calculate
Calculating --> Clean : success
Calculating --> Failed : throws exception
Clean --> Dirty : update
Failed --> Dirty : update
Dirty --> Frozen : freeze
Clean --> Frozen : freeze
Frozen --> Clean : unfreeze
实现规范:
class MyLazyCalculator : public LazyObject {
public:
Real result() const {
calculate(); // 触发 LazyObject 框架
return cachedResult_; // 返回缓存结果
}
protected:
void performCalculations() const override {
// 从依赖数据(通过 Handle 访问)计算结果
cachedResult_ = expensiveComputation(inputHandle_->value());
// 注:cachedResult_ 声明为 mutable
}
private:
Handle<InputData> inputHandle_;
mutable Real cachedResult_;
};
关键规则:
calculate()必须在任何访问计算结果的方法中首先调用performCalculations()必须声明为const(结果成员声明为mutable)- 如果计算可能抛出异常,
calculate()会自动管理failed_标志 - 子类不直接调用
performCalculations()— 总是通过calculate()
2.3 添加新的 Instrument
sequenceDiagram
participant Client
participant Inst as Instrument
participant LO as LazyObject
participant Eng as PricingEngine
Client->>Inst: NPV()
Inst->>LO: calculate()
alt expired
Inst->>Inst: setupExpired
else not expired
Inst->>Inst: performCalculations
Inst->>Eng: reset and setupArguments
Eng->>Eng: validate
Eng->>Eng: calculate
Note over Eng: Step1 query Handles for market data
Note over Eng: Step2 run numerical method
Note over Eng: Step3 write results
Inst->>Eng: fetchResults
end
Inst-->>Client: NPV
// === 1. 定义工具类 ===
class MyInstrument : public Instrument {
public:
// 嵌套类型 — 引擎接口
class arguments;
class results;
class engine;
MyInstrument(/* 工具参数 */);
bool isExpired() const override { /* ... */ }
protected:
void setupArguments(PricingEngine::arguments*) const override;
void fetchResults(const PricingEngine::results*) const override;
void setupExpired() const override { NPV_ = 0.0; }
};
// === 2. 定义 arguments 和 results ===
class MyInstrument::arguments : public virtual PricingEngine::arguments {
public:
// 工具参数(引擎需要的数据)
Real strike;
Date maturity;
void validate() const override { /* 检查参数有效性 */ }
};
class MyInstrument::results : public Instrument::results {
public:
// 引擎输出
Real delta, gamma;
void reset() override {
Instrument::results::reset();
delta = gamma = Null<Real>();
}
};
// === 3. 定义 engine typedef ===
class MyInstrument::engine
: public GenericEngine<MyInstrument::arguments, MyInstrument::results> {};
2.4 添加新的 PricingEngine
// 解析引擎示例
class MyAnalyticEngine : public MyInstrument::engine {
public:
MyAnalyticEngine(const Handle<YieldTermStructure>& curve)
: curve_(curve) {
registerWith(curve_); // 关键:注册为 Handle 的观察者
}
void calculate() const override {
// 1. 从 arguments_ 读取工具参数
Real strike = arguments_.strike;
// 2. 从 Handle 查询市场数据
DiscountFactor df = curve_->discount(arguments_.maturity);
// 3. 执行计算并写入 results_
results_.value = computeNPV(strike, df);
results_.delta = computeDelta(strike, df);
}
private:
Handle<YieldTermStructure> curve_;
};
// FD 引擎示例
class MyFdEngine : public MyInstrument::engine {
public:
void calculate() const override {
// 构造 FdmSolverDesc...
// 创建 FdmBackwardSolver + 时间离散...
// 调用 solver.rollback()...
}
};
// MC 引擎示例
class MyMcEngine : public MyInstrument::engine,
public McSimulation<SingleVariate, PseudoRandom, Statistics> {
public:
MyMcEngine(const ext::shared_ptr<StochasticProcess>& process)
: process_(process) {
registerWith(process_);
}
void calculate() const override {
McSimulation::calculate(1e-4, 1000000); // MC 循环
results_.value = mcModel_->sampleAccumulator().mean();
}
protected:
// McSimulation 要求的纯虚函数
ext::shared_ptr<PathPricer<Path>> pathPricer() const override;
ext::shared_ptr<PathGenerator> pathGenerator() const override;
TimeGrid timeGrid() const override;
};
引擎开发检查清单:
- [ ] 构造函数中
registerWith()所有 Handle 成员 - [ ]
calculate()声明为const,写入mutable results_ - [ ] 从
arguments_读取,向results_写入 - [ ] 错误处理:异常会自动被 Instrument 捕获并设置
failed_
2.5 添加新的 TermStructure
// === 1. 直接插值曲线(最简单)===
class MyInterpolatedCurve : public InterpolatedDiscountCurve<LogLinear> {
public:
MyInterpolatedCurve(const std::vector<Date>& dates,
const std::vector<DiscountFactor>& dfs,
const DayCounter& dc)
: InterpolatedDiscountCurve<LogLinear>(dates, dfs, dc) {}
};
// === 2. 自举曲线(通过 BootstrapHelper)===
class MyPiecewiseCurve
: public PiecewiseYieldCurve<Discount, LogLinear, IterativeBootstrap> {
public:
MyPiecewiseCurve(const Date& refDate,
const std::vector<ext::shared_ptr<RateHelper>>& helpers,
const DayCounter& dc)
: PiecewiseYieldCurve<Discount, LogLinear, IterativeBootstrap>(
refDate, helpers, dc) {}
};
// === 3. 装饰器(包装已有曲线并修改行为)===
class MySpreadedCurve : public YieldTermStructure {
public:
MySpreadedCurve(const Handle<YieldTermStructure>& base,
const Handle<Quote>& spread)
: base_(base), spread_(spread) {
registerWith(base_);
registerWith(spread_);
}
protected:
DiscountFactor discountImpl(Time t) const override {
return base_->discount(t) * std::exp(-spread_->value() * t);
}
private:
Handle<YieldTermStructure> base_;
Handle<Quote> spread_;
};
2.6 添加新的 StochasticProcess
class MyProcess : public StochasticProcess1D {
public:
MyProcess(Real x0, Real speed, Real level, Real sigma)
: x0_(x0), speed_(speed), level_(level), sigma_(sigma) {}
// 必须实现的纯虚函数
Real x0() const override { return x0_; }
Real drift(Time t, Real x) const override {
return speed_ * (level_ - x); // OU 漂移
}
Real diffusion(Time t, Real x) const override {
return sigma_; // 常数扩散
}
// 可选:提供解析矩(绕过离散化)
Real expectation(Time t0, Real x0, Time dt) const override {
return level_ + (x0 - level_) * std::exp(-speed_ * dt);
}
Real variance(Time t0, Real x0, Time dt) const override {
return sigma_ * sigma_ / (2 * speed_) * (1 - std::exp(-2 * speed_ * dt));
}
private:
Real x0_, speed_, level_, sigma_;
};
2.7 添加新的 CalibratedModel
class MyModel : public CalibratedModel {
public:
MyModel(Real a, Real sigma)
: CalibratedModel(2) { // 2 个参数
arguments_[0] = ConstantParameter(a, PositiveConstraint());
arguments_[1] = ConstantParameter(sigma, PositiveConstraint());
generateArguments();
}
// 暴露参数
Real a() const { return arguments_[0](0.0); }
Real sigma() const { return arguments_[1](0.0); }
// 如果需要,实现校准辅助函数
void generateArguments() override {
// 当参数更新时,更新模型内部状态
}
};
// === 校准 Helper ===
class MyModelHelper : public BlackCalibrationHelper {
public:
MyModelHelper(const Handle<Quote>& volatility,
/* 工具特定参数 */)
: BlackCalibrationHelper(volatility, /* ... */) {
// 可能需要在此构造工具并设置引擎
}
Real modelValue() const override {
calculate(); // LazyObject
return engineValue_;
}
void performCalculations() const override {
// 从模型参数计算校准值
engineValue_ = /* ... */;
}
private:
mutable Real engineValue_;
};
3. 开发规范
3.1 命名约定
| 约定 | 示例 | 说明 |
|---|---|---|
| 类名 PascalCase | BlackScholesProcess, AnalyticEuropeanEngine |
各单词首字母大写 |
| 方法名 camelCase | netPresentValue(), setPricingEngine() |
首字母小写 |
私有成员后缀 _ |
calculated_, frozen_, rate_ |
尾部下划线 |
| 命名空间 | QuantLib:: |
所有公开 API |
| 实现细节 | QuantLib::detail:: |
内部实现不公开 |
| 文件名小写 | blackscholesprocess.hpp |
与类名对应,全小写 |
| 纯虚函数后缀 | Impl 或 *Impl() |
如 discountImpl(), performCalculations() |
| getter/setter | value() / setValue(x) |
使用前置 set 而非重载 |
3.2 类型使用
// 始终使用 QuantLib 类型别名,而非原始类型
Real price = 100.5; // double
Integer count = 42; // int
Size length = vec.size(); // std::size_t
Time t = 0.5; // Real (年)
Rate r = 0.05; // Real
Volatility vol = 0.20; // Real
DiscountFactor df = 0.95; // Real
Spread sp = 0.001; // Real
// 智能指针:始终使用 ext::shared_ptr
ext::shared_ptr<YieldTermStructure> curve(new FlatForward(...));
auto curve2 = ext::make_shared<FlatForward>(...);
// 空值检查
Handle<YieldTermStructure> handle;
if (handle.empty()) { /* ... */ } // Handle 的空值检查
// 不要直接比较 shared_ptr 为 nullptr
// 空值哨兵
if (value == Null<Real>()) { /* ... */ }
// 不要使用 NaN 或 -DBL_MAX
3.3 错误处理
// 前置条件:使用 QL_REQUIRE
QL_REQUIRE(volatility > 0.0, "volatility must be positive: " << volatility);
QL_REQUIRE(dates.size() == rates.size(),
"dates and rates must have same size");
// 后置条件:使用 QL_ENSURE
QL_ENSURE(NPV_ != Null<Real>(), "NPV not calculated");
// 不可恢复错误:使用 QL_FAIL
QL_FAIL("unsupported exercise type");
3.4 Handle 使用规范
// === 类内部持有 Handle ===
class MyClass {
public:
// 通过 Handle 接收,注册为 Observer
MyClass(const Handle<YieldTermStructure>& curve)
: curve_(curve) {
registerWith(curve_); // 必须!否则不会收到通知
}
private:
Handle<YieldTermStructure> curve_; // 值成员(不是引用)
};
// === 创建和链接 Handle ===
// 方式1:创建后立即链接
RelinkableHandle<YieldTermStructure> handle;
auto curve = ext::make_shared<FlatForward>(...);
handle.linkTo(curve);
// 方式2:通过工厂函数
Handle<YieldTermStructure> h = flatRate(0.05, Actual365Fixed());
// === Handle 解引用 ===
DiscountFactor df = curve_->discount(date); // operator->()
// 如果 Handle 为空,解引用会抛出异常
3.5 构造时注册 Observer
3.6 CMake 集成
新文件必须注册到 ql/CMakeLists.txt(和可选的 ql/Makefile.am)。
# ql/CMakeLists.txt 中
set(QL_SOURCES
# ... 已有文件 ...
mymodule/myclass.cpp
)
set(QL_HEADERS
# ... 已有文件 ...
mymodule/myclass.hpp
mymodule/all.hpp
)
CI 工作流 filelists.yml 会自动验证文件是否已注册。
5. 性能优化指南
5.1 通知图优化
// 对于具有大量现金流的 Swap 和 Bond:
// 使用 simplifyNotificationGraph 减少通知传播开销
#include <ql/instruments/simplifynotificationgraph.hpp>
Swap swap(/* ... */);
swap.setPricingEngine(/* ... */);
simplifyNotificationGraph(swap); // 优化通知图
5.2 LazyObject 通知控制
// 默认:第一个通知后不再转发(QL_FASTER_LAZY_OBJECTS 时默认启用)
LazyObject::Defaults::instance().forwardFirstNotificationOnly();
// 批量更新时禁用通知
ObservableSettings::instance().disableUpdates(true);
// ... 执行多个修改 ...
ObservableSettings::instance().enableUpdates(); // 一次性触发所有通知
5.3 编译优化
# 使用 interprocedural optimization(IPO / LTO)
cmake -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON
# 启用 native 指令优化
cmake -DCMAKE_CXX_FLAGS="-march=native -mtune=native"
# 使用 Ninja 加速构建
cmake -G Ninja
# 禁用不需要的组件
cmake -DQL_BUILD_TEST_SUITE=OFF
-DQL_BUILD_EXAMPLES=OFF
-DQL_BUILD_BENCHMARK=OFF
5.4 运行时优化技巧
-
重用工具和引擎: 不要在循环中反复创建 — 使用
Handle<Quote>更新参数后引擎自动重计算// 不好:每次循环创建新引擎 for (auto price : prices) { VanillaOption option(payoff, exercise); option.setPricingEngine(ext::make_shared<AnalyticEuropeanEngine>(process)); std::cout << option.NPV() << std::endl; } // 更好:复用,通过 Quote 更新 auto spotQuote = ext::make_shared<SimpleQuote>(100.0); Handle<Quote> spot(spotQuote); auto process = ext::make_shared<BlackScholesProcess>(spot, ...); VanillaOption option(payoff, exercise); option.setPricingEngine(ext::make_shared<AnalyticEuropeanEngine>(process)); for (auto price : prices) { spotQuote->setValue(price); // 自动触发重计算 std::cout << option.NPV() << std::endl; } -
使用
freeze()/unfreeze()冻结计算option.freeze(); // 阻止重计算 // ... 进行多个不影响结果的修改 ... option.unfreeze(); // 恢复通知 -
选择合适的日期表示: 除非需要亚日精度,不要开启
QL_HIGH_RESOLUTION_DATE -
选择合适的插值方法: 构建大量曲线时,
LogLinear(分段常数远期利率)比Cubic更快
6. 测试开发
6.1 添加测试
// test-suite/mymoduletests.cpp
#include <ql/qldefines.hpp>
#include "utilities.hpp"
#include <ql/module/myclass.hpp>
using namespace QuantLib;
BOOST_AUTO_TEST_SUITE(TestMyModule)
BOOST_AUTO_TEST_CASE(testMyFeature) {
BOOST_TEST_MESSAGE("Testing my feature...");
// Setup
Date refDate(15, June, 2024);
Settings::instance().evaluationDate() = refDate;
// Execute
MyClass obj(/* 参数 */);
Real result = obj.compute();
// Verify
Real expected = 0.05;
Real tolerance = 1e-10;
if (std::fabs(result - expected) > tolerance)
BOOST_ERROR("Failed: result=" << result << ", expected=" << expected);
}
BOOST_AUTO_TEST_SUITE_END()
6.2 测试工具
测试辅助函数在 test-suite/utilities.hpp 中:
// 检查表达式是否抛出异常
BOOST_CHECK_EXCEPTION(expr, Error, ExpectedErrorMessage("msg"));
// 浮点数容差检查
BOOST_CHECK_CLOSE(actual, expected, tolerance);
// 使用测试夹具
struct CommonVars {
CommonVars() {
// 设置共享测试数据
}
// 共享数据成员
};
BOOST_FIXTURE_TEST_CASE(testName, CommonVars) {
// 使用共享数据
}
7. 常见陷阱
7.1 忘记 registerWith()
// 错误:忘记注册 Handle,不会收到通知
class BadEngine : public VanillaOption::engine {
Handle<YieldTermStructure> curve_;
public:
BadEngine(const Handle<YieldTermStructure>& c) : curve_(c) {
// 缺少: registerWith(curve_);
}
};
// 正确
class GoodEngine : public VanillaOption::engine {
Handle<YieldTermStructure> curve_;
public:
GoodEngine(const Handle<YieldTermStructure>& c) : curve_(c) {
registerWith(curve_); // 必须!
}
};
7.2 Handle 的循环引用
// 错误模式:A 持有 B 的 Handle,B 持有 A 的 Handle
// 会导致 shared_ptr 循环引用,内存泄漏
// 解决方案:使用弱引用或确保至少一个方向不持有 Handle
// 通常 TermStructure → BootstrapHelper 方向不需要 Handle
7.3 虚继承遗忘
// 当多个基类都继承 Observable 时:
// 错误:非虚继承导致菱形问题
class MyClass : public Observable, public Observer { }; // 错误的
// 正确:在基类链中使用虚继承
class MyClass : public virtual Observable, public virtual Observer { };
// LazyObject 已使用虚继承,直接继承 LazyObject 即可
7.4 日期与 Time 混淆
// 错误:混淆 Date(日历年月日)和 Time(年分数)
Date d(15, June, 2024);
Real t = 0.5; // Time = 0.5 年
// TermStructure 提供转换
Time t = curve->timeFromReference(d);
Date d2 = curve->referenceDate() + Integer(t * 365.25); // 近似反向
7.5 析构顺序问题
// 构造时 Observer 注册的逆序析构是安全的:
// 析构时 Observer::unregisterWithAll() 会自动解注册。
// 但如果手动管理生命周期,确保:
// 1. Observable 在 Observer 之后销毁
// 2. 使用 shared_ptr 管理 Observable
8. 贡献流程
- 在
ql/中的适当模块子目录添加.hpp/.cpp文件 - 在对应模块中更新
all.hpp(自动生成或手动编辑) - 在
ql/CMakeLists.txt中注册新文件 - 在
test-suite/中添加测试 - 运行 clang-format:
.clang-format在项目根目录 - 编译并运行完整测试套件
- 确保所有测试通过且没有新增警告