Spaces:
Sleeping
Sleeping
File size: 21,042 Bytes
2dacaa6 976a6a9 2dacaa6 42f8189 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 42f8189 2dacaa6 42f8189 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 42f8189 2dacaa6 976a6a9 42f8189 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 42f8189 2dacaa6 976a6a9 2dacaa6 42f8189 2dacaa6 976a6a9 42f8189 976a6a9 2dacaa6 42f8189 2dacaa6 976a6a9 42f8189 976a6a9 2dacaa6 42f8189 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 976a6a9 2dacaa6 42f8189 2dacaa6 42f8189 976a6a9 424ed87 976a6a9 a3a5e98 2dacaa6 a3a5e98 42f8189 a3a5e98 42f8189 a3a5e98 42f8189 a3a5e98 42f8189 a3a5e98 42f8189 2dacaa6 976a6a9 42f8189 a3a5e98 42f8189 2dacaa6 976a6a9 42f8189 a3a5e98 42f8189 a3a5e98 42f8189 a3a5e98 42f8189 a3a5e98 42f8189 a3a5e98 42f8189 2dacaa6 976a6a9 42f8189 a3a5e98 42f8189 2dacaa6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
import tempfile
import os
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
def create_bond_database():
"""
創建一個包含多個債券樣本的字典,作為模擬資料庫。
注意:此處的 avgYld 和 coupon 以「百分比」形式儲存 (例如 2.2 代表 2.2%)。
:return: dict,鍵為債券名稱,值為包含該債券詳細參數的字典。
例如:
平均殖利率 (avgYld): 1.553%
票面利率 (coupon): 2.125%
發行日 (issueDate): 2011-01-13
到期日 (maturity): 2031-01-13
每年付息 (payPerYr): 1 次
每年複利 (compPerYr): 1 次
存續期間 (durT): 5.3548
修正存續期間 (mdurT): 5.273
價格曲度 (convT): 1762.7325
"""
bond_table = pd.read_csv(os.path.join(THIS_DIR, "bond_db.csv"))
bond_table["issueDate"] = bond_table["issueDate"].apply(lambda x: pd.to_datetime(str(x)))
bond_table["maturity"] = bond_table["maturity"].apply(lambda x: pd.to_datetime(str(x)))
db = {
row["name"]: row
for row in bond_table.to_dict("records")
}
return db
# --- 模擬函數 ---
def simulate_vasicek(r0, kappa, theta, sigma, T, dt, n_simulations, dW_shocks):
"""
使用 Vasicek 模型模擬債券利率(drf)的路徑。
此模型包含均值回歸特性。
:param r0: float, 初始利率 (小數形式)。
:param kappa: float, 均值回歸速度。
:param theta: float, 長期平均利率。
:param sigma: float, 波動率。
:param T: float, 總模擬時長(年)。
:param dt: float, 每個時間步長(年)。
:param n_simulations: int, 模擬路徑的數量。
:param dW_shocks: np.ndarray, 預先生成的隨機衝擊。
:return: np.ndarray, 模擬的利率路徑,形狀為 (n_simulations, num_steps + 1)。
"""
num_steps = int(T / dt)
rates = np.zeros((n_simulations, num_steps + 1)); rates[:, 0] = r0
for t in range(1, num_steps + 1):
rates[:, t] = np.maximum(0.001, rates[:, t-1] + kappa * (theta - rates[:, t-1]) * dt + sigma * dW_shocks[:, t-1])
return rates
def simulate_cir(r0, kappa, theta, sigma, T, dt, n_simulations, dW_shocks):
"""
使用 Cox-Ingersoll-Ross (CIR) 模型模擬利率路徑。
此模型確保利率永遠為正 (若 2*kappa*theta > sigma**2)。
:param r0: float, 初始利率 (小數形式)。
:param kappa: float, 均值回歸速度。
:param theta: float, 長期平均利率。
:param sigma: float, 波動率。
:param T: float, 總模擬時長(年)。
:param dt: float, 每個時間步長(年)。
:param n_simulations: int, 模擬路徑的數量。
:param dW_shocks: np.ndarray, 預先生成的隨機衝擊。
:return: np.ndarray, 模擬的利率路徑,形狀為 (n_simulations, num_steps + 1)。
"""
num_steps = int(T / dt)
rates = np.zeros((n_simulations, num_steps + 1))
rates[:, 0] = r0
for t in range(1, num_steps + 1):
# 使用 np.maximum 確保根號內的值非負,避免數值計算問題
sqrt_r = np.sqrt(np.maximum(0, rates[:, t-1]))
rates[:, t] = rates[:, t-1] + kappa * (theta - rates[:, t-1]) * dt + sigma * sqrt_r * dW_shocks[:, t-1]
# 再次確保利率不會變為負數
rates[:, t] = np.maximum(0.0001, rates[:, t])
return rates
def simulate_risky_rate(rf_paths, dW_rf, initial_spread, sigma_spread, correlation, T, dt, n_simulations, dW_independent):
"""
在先前已模擬的利率的基礎上,模擬有風險利率(drs)的路徑。
主要模擬與債券利率相關的信用利差(credit spread)的變化。
:param rf_paths: np.ndarray, 已模擬好的債券利率路徑。
:param dW_rf: np.ndarray, 生成債券利率路徑所使用的隨機衝擊。
:param initial_spread: float, 初始信用利差。
:param sigma_spread: float, 信用利差的波動率。
:param correlation: float, 債券利率與信用利差變動的相關係數。
:param T: float, 總模擬時長(年)。
:param dt: float, 每個時間步長(年)。
:param n_simulations: int, 模擬路徑的數量。
:param dW_independent: np.ndarray, 獨立的隨機衝擊,用於生成利差的非系統性風險部分。
:return: tuple (np.ndarray, np.ndarray), 分別為模擬的有風險利率路徑和信用利差路徑。
"""
num_steps = int(T / dt);
spread_paths = np.zeros((n_simulations, num_steps + 1));
spread_paths[:, 0] = initial_spread
dW_spread = correlation * dW_rf + np.sqrt(1 - correlation**2) * dW_independent
for t in range(1, num_steps + 1):
spread_paths[:, t] = np.maximum(0.001, spread_paths[:, t-1] + sigma_spread * dW_spread[:, t-1])
return rf_paths + spread_paths, spread_paths
def simulate_gbm(r0, mu, sigma, T, dt, n_simulations, dW_shocks):
"""
使用幾何布朗運動(Geometric Brownian Motion)模型模擬**綜合利率 (dr)** 的路徑。
公式: dr_t = μr_t dt + σr_t dW_t
此模型常用於模擬股價,這裡用來作為一個簡化的利率模型。
:param r0: float, 初始利率 (小數形式)。
:param mu: float, 長期漂移趨勢 (μ, 在此對應 mu_r)。
:param sigma: float, 年化波動率 (σ, 在此對應 sigma_r)。
:param T: float, 總模擬時長(年)。
:param dt: float, 每個時間步長(年)。
:param n_simulations: int, 模擬路徑的數量。
:param dW_shocks: np.ndarray, 預先生成的隨機衝擊。
:return: np.ndarray, 模擬的利率路徑。
"""
num_steps = int(T / dt); rates = np.zeros((n_simulations, num_steps + 1)); rates[:, 0] = r0
for t in range(1, num_steps + 1):
rates[:, t] = np.maximum(0.001, rates[:, t-1] * np.exp((mu - 0.5 * sigma**2) * dt + sigma * dW_shocks[:, t-1]))
return rates
def calculate_price_paths(rate_paths, mdurT, convT, initial_price=100.0):
"""
使用修正存續期間(mdurT)和價格曲度(convT)近似法,根據利率路徑計算債券價格路徑。
重要:
此處的 convT 參數假定為您資料庫中的 "1/2價格曲度" (如 288.3137),
其金融定義為 (P * C_Mod / 2),且 P 為 initial_price (通常為 100)。
:param rate_paths: np.ndarray, 模擬的利率路徑,刻度為小數點形式。
:param mdurT: float, 債券的修正存續期間 (D_Mod)。
:param convT: float, 債券的 "1/2 價格曲度" (P * C_Mod / 2)。
:param initial_price: float, 債券的初始價格,預設為100。
:return: np.ndarray, 模擬的債券價格路徑。
"""
n_simulations, num_steps = rate_paths.shape[0], rate_paths.shape[1] - 1
price_paths = np.zeros_like(rate_paths)
price_paths[:, 0] = initial_price
# --- 邏輯修正 ---
# 根據定義,convT = (initial_price * C_Mod / 2)
# 我們需要 C_Mod / 2 這一項
# 因此,C_Mod / 2 = convT / initial_price
#
# 這個值在近似法中被假定為常數,不應隨 P(t-1) 變動。
# 【錯誤的邏輯】: convexity_factor = convT / prev_price
# 【修正後的邏輯】:
convexity_factor = convT / initial_price
# --- 修正結束 ---
for t in range(1, num_steps + 1):
delta_y = rate_paths[:, t] - rate_paths[:, t-1]
# 抓取前一期的價格 P(t-1)
prev_price = price_paths[:, t-1]
# 1. 計算價格變動百分比 (dP/P)
# dP/P = -mdurT * dy + (C_Mod / 2) * (dy^2)
#
# 【注意】: 這裡的 convexity_factor 是我們在迴圈外
# 就已經計算好的常數 (convT / initial_price)
price_change_factor = -mdurT * delta_y + convexity_factor * (delta_y ** 2)
# 2. 計算新價格 P(t) = P(t-1) * (1 + dP/P)
price_paths[:, t] = prev_price * (1 + price_change_factor)
return price_paths
# --- 主執行函數 ---
def run_simulation(bond_data, n_simulations, dt, params):
"""
程式核心執行函數。
整合所有模擬、計算、繪圖與報告生成步驟。
:param bond_data: dict, 選定債券的詳細資料。
:param n_simulations: int, 模擬總次數。 e.g., 100
:param dt: float, 每個時間步長(年)。 e.g., 1/252
:param params: dict, 所有模擬模型的參數。
:return: tuple, 包含所有結果的元組 (數據幀, 圖表, 報告路徑)。
"""
# 設定隨機種子以確保每次模擬結果可重現
np.random.seed(42)
# --- 1. 參數準備 ---
# 將來自資料庫的百分比格式 (如 2.2%) 轉換為小數格式 (0.022) 以進行計算
initial_yield = bond_data['avgYld'] / 100.0
# 計算債券的剩餘到期時間(年),作為模擬的總時長 T
time_to_maturity = (bond_data['maturity'] - datetime.now()).days / 365.25
T = max(time_to_maturity, 0.1) # 確保至少模擬一小段時間
num_steps = int(T / dt)
time_points = np.linspace(0, T, num_steps + 1) # 模擬的時間點數列
# --- 2. 生成隨機衝擊 ---
# 為每個模型預先生成符合標準常態分佈的隨機衝擊 (Wiener Process increments)
# dW ~ N(0, dt)
dW_rf = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
dW_spread_independent = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
dW_dr = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
dW_cir = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
# --- 3. 利率路徑模擬 ---
# 模擬三種不同模型下的利率路徑
# drf: 債券風險利率 (Vasicek 模型)
rf_paths = simulate_vasicek(initial_yield, params['kappa_rf'], params['theta_rf'], params['sigma_rf'], T, dt, n_simulations, dW_rf)
# drs: 有風險利率 (Vasicek + 相關信用利差)
drs_paths, _ = simulate_risky_rate(rf_paths, dW_rf, params['initial_spread'], params['sigma_spread'], params['correlation'], T, dt, n_simulations, dW_spread_independent)
# dr: 綜合利率 (幾何布朗運動模型)
dr_paths = simulate_gbm(initial_yield, params['mu_r'], params['sigma_r'], T, dt, n_simulations, dW_dr)
# dr_cir: CIR 模型
cir_paths = simulate_cir(initial_yield, params['kappa_cir'], params['theta_cir'], params['sigma_cir'], T, dt, n_simulations, dW_cir)
# --- 4. 債券價格路徑模擬 ---
# 根據每條利率路徑,使用存續期間和曲度來估算債券價格的變化
price_rf_paths = calculate_price_paths(rf_paths, bond_data['mdurT'], bond_data['convT'])
price_drs_paths = calculate_price_paths(drs_paths, bond_data['mdurT'], bond_data['convT'])
price_dr_paths = calculate_price_paths(dr_paths, bond_data['mdurT'], bond_data['convT'])
price_cir_paths = calculate_price_paths(cir_paths, bond_data['mdurT'], bond_data['convT'])
# --- 5. 統計數據分析 ---
# 計算利率每日變化的基本統計量
drf_changes = np.diff(rf_paths, axis=1).flatten()
drs_changes = np.diff(drs_paths, axis=1).flatten()
dr_changes = np.diff(dr_paths, axis=1).flatten()
cir_changes = np.diff(cir_paths, axis=1).flatten()
# 計算 drf 和 drs 變化之間的共變異數和相關係數
cov_matrix = np.cov(drf_changes, drs_changes)
corr_matrix = np.corrcoef(drf_changes, drs_changes)
rate_stats_df = pd.DataFrame({
"指標": ["drf 變化量平均", "drf 變化量標準差", "drs 變化量平均", "drs 變化量標準差", "dr 變化量平均", "dr 變化量標準差", "dr_cir 變化量平均", "dr_cir 變化量標準差", "drf-drs 共變異數", "drf-drs 相關係數"],
"數值": [f"{np.mean(drf_changes):.8f}", f"{np.std(drf_changes):.8f}", f"{np.mean(drs_changes):.8f}", f"{np.std(drs_changes):.8f}", f"{np.mean(dr_changes):.8f}", f"{np.std(dr_changes):.8f}", f"{np.mean(cir_changes):.8f}", f"{np.std(cir_changes):.8f}", f"{cov_matrix[0, 1]:.8f}", f"{corr_matrix[0, 1]:.6f}"]
})
# 計算最終價格的統計數據,包括風險價值 (VaR)
final_prices_rf = price_rf_paths[:, -1]
final_prices_drs = price_drs_paths[:, -1]
final_prices_dr = price_dr_paths[:, -1]
final_prices_cir = price_cir_paths[:, -1]
price_stats_df = pd.DataFrame({
"統計量": ["平均價格", "價格標準差", "最大價格", "最小價格", "95%分位數 (Q95)", "5%分位數 (Q5)", "95% VaR (價值損失)"],
"基於 drf": [f"{np.mean(final_prices_rf):.4f}", f"{np.std(final_prices_rf):.4f}", f"{np.max(final_prices_rf):.4f}", f"{np.min(final_prices_rf):.4f}", f"{np.percentile(final_prices_rf, 95):.4f}", f"{np.percentile(final_prices_rf, 5):.4f}", f"{100 - np.percentile(final_prices_rf, 5):.4f}"],
"基於 drs": [f"{np.mean(final_prices_drs):.4f}", f"{np.std(final_prices_drs):.4f}", f"{np.max(final_prices_drs):.4f}", f"{np.min(final_prices_drs):.4f}", f"{np.percentile(final_prices_drs, 95):.4f}", f"{np.percentile(final_prices_drs, 5):.4f}", f"{100 - np.percentile(final_prices_drs, 5):.4f}"],
"基於 dr": [f"{np.mean(final_prices_dr):.4f}", f"{np.std(final_prices_dr):.4f}", f"{np.max(final_prices_dr):.4f}", f"{np.min(final_prices_dr):.4f}", f"{np.percentile(final_prices_dr, 95):.4f}", f"{np.percentile(final_prices_dr, 5):.4f}", f"{100 - np.percentile(final_prices_dr, 5):.4f}"],
"基於 dr_cir": [f"{np.mean(final_prices_cir):.4f}", f"{np.std(final_prices_cir):.4f}", f"{np.max(final_prices_cir):.4f}", f"{np.min(final_prices_cir):.4f}", f"{np.percentile(final_prices_cir, 95):.4f}", f"{np.percentile(final_prices_cir, 5):.4f}", f"{100 - np.percentile(final_prices_cir, 5):.4f}"]
})
# --- 6. 生成 Excel 報告 ---
# 創建一個暫存的 Excel 檔案來儲存所有詳細結果
fd, report_filepath = tempfile.mkstemp(suffix=".xlsx")
os.close(fd)
os.makedirs("excel_reports", exist_ok=True)
report_filepath = os.path.join("excel_reports", f"bond_simulation_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx")
with pd.ExcelWriter(report_filepath) as writer:
# 寫入債券基本資訊
pd.DataFrame(list(bond_data.items()), columns=['項目', '數值']).applymap(lambda x: x.strftime('%Y-%m-%d') if isinstance(x, datetime) else x).to_excel(writer, sheet_name='bond_info', index=False)
# 寫入本次模擬使用的參數
pd.DataFrame(list(params.items()), columns=['參數', '設定值']).to_excel(writer, sheet_name='simulation_parameters', index=False)
# 寫入利率和價格的統計結果
rate_stats_df.to_excel(writer, sheet_name='rate_statistics', index=False)
price_stats_df.to_excel(writer, sheet_name='price_statistics', index=False)
# 如果模擬次數不多,則將詳細的模擬路徑寫入 Excel
if n_simulations <= 1000:
pd.DataFrame(rf_paths.T, index=time_points).to_excel(writer, sheet_name='drf_rate_paths')
pd.DataFrame(drs_paths.T, index=time_points).to_excel(writer, sheet_name='drs_rate_paths')
pd.DataFrame(dr_paths.T, index=time_points).to_excel(writer, sheet_name='dr_rate_paths')
pd.DataFrame(cir_paths.T, index=time_points).to_excel(writer, sheet_name='dr_cir_rate_paths')
pd.DataFrame(price_rf_paths.T, index=time_points).to_excel(writer, sheet_name='drf_price_paths')
pd.DataFrame(price_drs_paths.T, index=time_points).to_excel(writer, sheet_name='drs_price_paths')
pd.DataFrame(price_dr_paths.T, index=time_points).to_excel(writer, sheet_name='dr_price_paths')
pd.DataFrame(price_cir_paths.T, index=time_points).to_excel(writer, sheet_name='dr_cir_price_paths')
else:
# 如果模擬次數過多,為避免 Excel 檔案過大,改為生成多個 CSV 檔案
# print(f"模擬次數過多 ({n_simulations}),僅生成 CSV 格式的詳細路徑報告以節省空間。")
# report_dir = os.path.dirname(report_filepath)
# base_name = os.path.splitext(os.path.basename(report_filepath))[0]
# pd.DataFrame(rf_paths.T, index=time_points).to_csv(os.path.join(report_dir, f'{base_name}_drf_rate_paths.csv'))
# pd.DataFrame(drs_paths.T, index=time_points).to_csv(os.path.join(report_dir, f'{base_name}_drs_rate_paths.csv'))
# pd.DataFrame(dr_paths.T, index=time_points).to_csv(os.path.join(report_dir, f'{base_name}_dr_rate_paths.csv'))
# pd.DataFrame(cir_paths.T, index=time_points).to_csv(os.path.join(report_dir, f'{base_name}_dr_cir_rate_paths.csv'))
# pd.DataFrame(price_rf_paths.T, index=time_points).to_csv(os.path.join(report_dir, f'{base_name}_drf_price_paths.csv'))
# pd.DataFrame(price_drs_paths.T, index=time_points).to_csv(os.path.join(report_dir, f'{base_name}_drs_price_paths.csv'))
# pd.DataFrame(price_dr_paths.T, index=time_points).to_csv(os.path.join(report_dir, f'{base_name}_dr_price_paths.csv'))
# pd.DataFrame(price_cir_paths.T, index=time_points).to_csv(os.path.join(report_dir, f'{base_name}_dr_cir_price_paths.csv'))
print(f"模擬次數過多 ({n_simulations}),未將詳細路徑寫入 Excel。")
print("report_filepath:", report_filepath)
# --- 7. 繪圖 ---
# 設定 Matplotlib 以正確顯示中文和負號
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False
# 繪製利率模擬路徑圖
fig_rate_paths = plt.figure(figsize=(12, 10))
plt.suptitle('Simulated Interest Rate Paths (First 50)', fontsize=16)
ax1 = fig_rate_paths.add_subplot(2, 2, 1)
ax1.plot(time_points, rf_paths[:50, :].T * 100, lw=0.5)
ax1.set_title('Risk-Free Rate (drf) - Vasicek')
ax1.set_ylabel('Interest Rate (%)')
ax2 = fig_rate_paths.add_subplot(2, 2, 2)
ax2.plot(time_points, drs_paths[:50, :].T * 100, lw=0.5)
ax2.set_title('Risky Rate (drs) - Vasicek + Spread')
ax2.set_ylabel('Interest Rate (%)')
ax3 = fig_rate_paths.add_subplot(2, 2, 3)
ax3.plot(time_points, dr_paths[:50, :].T * 100, lw=0.5)
ax3.set_title('Composite Rate (dr) - GBM')
ax3.set_xlabel('Time (Years)')
ax3.set_ylabel('Interest Rate (%)')
ax4 = fig_rate_paths.add_subplot(2, 2, 4)
ax4.plot(time_points, cir_paths[:50, :].T * 100, lw=0.5)
ax4.set_title('CIR Rate (dr_cir) - Cox-Ingersoll-Ross')
ax4.set_xlabel('Time (Years)')
ax4.set_ylabel('Interest Rate (%)')
fig_rate_paths.tight_layout(rect=[0, 0, 1, 0.96])
# 繪製利率變化分佈圖
fig_rate_dist = plt.figure(figsize=(12, 5))
plt.hist(drf_changes * 100, bins=100, alpha=0.6, label='drf (Vasicek)', density=True)
plt.hist(drs_changes * 100, bins=100, alpha=0.6, label='drs (Risky)', density=True)
plt.hist(dr_changes * 100, bins=100, alpha=0.6, label='dr (GBM)', density=True)
plt.hist(cir_changes * 100, bins=100, alpha=0.6, label='dr_cir (CIR)', density=True)
plt.title('Distribution of Daily Rate Changes')
plt.xlabel('Rate Change (%)')
plt.ylabel('Probability Density')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.6)
# 繪製價格模擬路徑圖
fig_price_paths = plt.figure(figsize=(12, 10))
plt.suptitle('Simulated Bond Price Paths (First 50)', fontsize=16)
ax_p1 = fig_price_paths.add_subplot(2, 2, 1)
ax_p1.plot(time_points, price_rf_paths[:50, :].T, lw=0.5)
ax_p1.set_title('Prices based on drf (Vasicek)')
ax_p1.set_ylabel('Price')
ax_p2 = fig_price_paths.add_subplot(2, 2, 2)
ax_p2.plot(time_points, price_drs_paths[:50, :].T, lw=0.5)
ax_p2.set_title('Prices based on drs (Risky)')
ax_p2.set_ylabel('Price')
ax_p3 = fig_price_paths.add_subplot(2, 2, 3)
ax_p3.plot(time_points, price_dr_paths[:50, :].T, lw=0.5)
ax_p3.set_title('Prices based on dr (GBM)')
ax_p3.set_xlabel('Time (Years)')
ax_p3.set_ylabel('Price')
ax_p4 = fig_price_paths.add_subplot(2, 2, 4)
ax_p4.plot(time_points, price_cir_paths[:50, :].T, lw=0.5)
ax_p4.set_title('Prices based on dr_cir (CIR)')
ax_p4.set_xlabel('Time (Years)')
ax_p4.set_ylabel('Price')
fig_price_paths.tight_layout(rect=[0, 0, 1, 0.96])
# 繪製最終價格分佈圖
fig_price_dist = plt.figure(figsize=(12, 5))
plt.hist(final_prices_rf, bins=100, alpha=0.6, label='Based on drf (Vasicek)', density=True)
plt.hist(final_prices_drs, bins=100, alpha=0.6, label='Based on drs (Risky)', density=True)
plt.hist(final_prices_dr, bins=100, alpha=0.6, label='Based on dr (GBM)', density=True)
plt.hist(final_prices_cir, bins=100, alpha=0.6, label='Based on dr_cir (CIR)', density=True)
plt.title('Distribution of Final Bond Prices')
plt.xlabel('Price')
plt.ylabel('Probability Density')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.6)
return rate_stats_df, price_stats_df, fig_rate_paths, fig_rate_dist, fig_price_paths, fig_price_dist, report_filepath
|