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