Giới Thiệu
Bài viết này trình bày cách sử dụng thư viện QuantLib để định giá và phân tích rủi ro cho một quyền chọn kiểu Châu Âu cơ bản. Qua ví dụ cụ thể, chúng ta sẽ tìm hiểu các thành phần chính trong một quy trình tính toán tài chính và cách kết nối chúng.
Định Giá Quyền Chọn Châu Âu Bằng Mô Hình Black-Scholes
Xét một quyền chọn mua (call option) kiểu Châu Âu với các thông số sau:
- Giá tài sản cơ sở hiện tại (S): 49$
- Giá thực hiện (K): 50$
- Lãi suất phi rủi ro (r): 5% mỗi năm
- Biến động hàng năm (σ): 20%
- Thời gian đến ngày đáo hạn (T): 20 tuần
1. Thiết Lập Các Điều Khoản Hợp Đồng
import QuantLib as ql
import numpy as np
import pandas as pd
# Thiết lập lịch và quy tắc tính ngày
calendar = ql.UnitedStates(ql.UnitedStates.NYSE)
day_count_basis = ql.Actual365Fixed(ql.Actual365Fixed.Standard)
# Ngày định giá và ngày đáo hạn
valuation_date = ql.Date(11, ql.July, 2019)
expiry_date = valuation_date + ql.Period(20, ql.Weeks)
settlement_date = valuation_date
# Tham số định giá
spot_price = 49.0
strike_price = 50.0
interest_rate = 0.05
annual_volatility = 0.2
# Đặt ngày định giá toàn cục
ql.Settings.instance().evaluationDate = valuation_date
2. Tạo Đối Tượng Quyền Chọn
# Điều khoản thực hiện kiểu Châu Âu
exercise_style = ql.EuropeanExercise(expiry_date)
option_kind = ql.Option.Call
payoff_structure = ql.PlainVanillaPayoff(
type=option_kind,
strike=strike_price
)
# Tạo quyền chọn
vanilla_option = ql.VanillaOption(
payoff=payoff_structure,
exercise=exercise_style
)
3. Thiết Lập Công Cụ Tính Giá (Pricing Engine)
# Đối tượng chứa giá tài sản cơ sở, có thể thay đổi động
underlying_quote = ql.SimpleQuote(spot_price)
underlying_handle = ql.QuoteHandle(underlying_quote)
# Đường cong lãi suất phi rủi ro (cấu trúc kỳ hạn)
flat_rate_curve = ql.YieldTermStructureHandle(
ql.FlatForward(
settlement_date,
interest_rate,
day_count_basis
)
)
# Đường cong biến động ngầm định (cấu trúc kỳ hạn)
flat_vol_curve = ql.BlackVolTermStructureHandle(
ql.BlackConstantVol(
settlement_date,
calendar,
annual_volatility,
day_count_basis
)
)
# Mô hình quá trình Black-Scholes
bs_model_process = ql.BlackScholesProcess(
s0=underlying_handle,
dividendTS=flat_rate_curve, # Giả sử không có cổ tức
riskFreeTS=flat_rate_curve,
volTS=flat_vol_curve
)
# Công cụ định giá giải tích cho quyền chọn Châu Âu
analytic_engine = ql.AnalyticEuropeanEngine(bs_model_process)
vanilla_option.setPricingEngine(analytic_engine)
4. Thực Hiện Tính Toán
print("Giá trị quyền chọn (NPV) =", vanilla_option.NPV())
print("Delta =", vanilla_option.delta())
print("Theta =", vanilla_option.theta())
print("Theta hàng ngày =", vanilla_option.thetaPerDay())
print("Gamma =", vanilla_option.gamma())
print("Vega =", vanilla_option.vega())
print("Rho =", vanilla_option.rho())
Kết quả đầu ra dự kiến:
Giá trị quyền chọn (NPV) = 2.395988448539984
Delta = 0.5213970624832108
Theta = -4.309457134907618
Theta hàng ngày = -0.011806731876459226
Gamma = 0.06563585494066533
Vega = 12.089225358769994
Rho = 8.88039853654583
Lưu ý về Quy Tắc Tính Ngày (Day Count Convention)
Kết quả có thể khác biệt nhỏ so với các công cụ tính toán khác do sự khác biệt trong quy tắc chuyển đổi khoảng thời gian thành số năm. QuantLib sử dụng quy tắc Actual/365 (Fixed) mặc định, trong đó 20 tuần được tính là xấp xỉ 0.38356 năm. Các quy tắc khác (ví dụ: Actual/360, Business/252) có thể cho kết quả hơi khác. Sự khác biệt này thường nhỏ với quyền chọn, nhưng rất quan trọng đối với các công cụ thu nhập cố định.
print(day_count_basis.yearFraction(settlement_date, expiry_date))
# Output: 0.3835616438356164
Tính Năng Động của Lớp `Quote`
QuantLib sử dụng mẫu thiết kế Observer. Khi giá trị của một đối tượng Quote thay đổi, tất cả các thành phần phụ thuộc (như quyền chọn, công cụ tính giá) sẽ tự động được thông báo và tính toán lại mà không cần thiết lập lại toàn bộ quy trình. Điều này rất hữu ích cho việc phân tích độ nhạy.
# Phân tích độ nhạy theo giá tài sản cơ sở
price_range = np.arange(start=30.0, stop=70.0, step=0.01)
# Khởi tạo mảng kết quả
npv_results = np.full_like(price_range, np.nan)
delta_results = np.full_like(price_range, np.nan)
theta_results = np.full_like(price_range, np.nan)
gamma_results = np.full_like(price_range, np.nan)
vega_results = np.full_like(price_range, np.nan)
rho_results = np.full_like(price_range, np.nan)
for idx, test_price in enumerate(price_range):
# Cập nhật giá trị động - các tính toán tự động cập nhật
underlying_quote.setValue(test_price)
npv_results[idx] = vanilla_option.NPV()
delta_results[idx] = vanilla_option.delta()
theta_results[idx] = vanilla_option.theta()
gamma_results[idx] = vanilla_option.gamma()
vega_results[idx] = vanilla_option.vega()
rho_results[idx] = vanilla_option.rho()
# Tổng hợp kết quả vào DataFrame
results_df = pd.DataFrame(
data={
'NPV': npv_results,
'Delta': delta_results,
'Theta': theta_results,
'Gamma': gamma_results,
'Vega': vega_results,
'Rho': rho_results
},
index=price_range
)
# Vẽ biểu đồ
results_df.plot(subplots=True, layout=(3, 2), figsize=(10, 12))