Spaces:
Sleeping
Sleeping
jeff7522553
commited on
Commit
·
976a6a9
1
Parent(s):
2dacaa6
更新註解
Browse files- .gitignore +2 -0
- app.py +11 -4
- bond.py +79 -101
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
excel_reports
|
| 2 |
+
__pycache__
|
app.py
CHANGED
|
@@ -51,10 +51,17 @@ def run_monte_carlo(
|
|
| 51 |
它負責收集所有來自介面的輸入參數,將百分比單位轉換為小數,
|
| 52 |
調用後端的 `run_simulation` 核心函式,並將計算結果回傳給介面的各個輸出元件。
|
| 53 |
|
| 54 |
-
:param bond_name: str, 選擇的債券名稱。
|
| 55 |
-
:param n_simulations: int, 模擬次數。
|
| 56 |
-
:param dt: float, 時間步長。
|
| 57 |
-
:param
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
:return: tuple, 包含所有結果的元組,用於更新 Gradio 介面的各個輸出元件。
|
| 59 |
"""
|
| 60 |
if not bond_name:
|
|
|
|
| 51 |
它負責收集所有來自介面的輸入參數,將百分比單位轉換為小數,
|
| 52 |
調用後端的 `run_simulation` 核心函式,並將計算結果回傳給介面的各個輸出元件。
|
| 53 |
|
| 54 |
+
:param bond_name: str, 選擇的債券名稱。 (e.g., 'CGB10Y')
|
| 55 |
+
:param n_simulations: int, 模擬次數。 (e.g., 100)
|
| 56 |
+
:param dt: float, 時間步長。 (e.g., 1/252)
|
| 57 |
+
:param kappa_rf: float, 均值回歸速度。 (e.g., 0.3)
|
| 58 |
+
:param theta_rf: float, 長期平均利率 (%)。 (e.g., 2.2)
|
| 59 |
+
:param sigma_rf: float, 波動率 (%)。 (e.g., 0.5)
|
| 60 |
+
:param initial_spread: float, 初始信用利差 (%)。 (e.g., 0.8)
|
| 61 |
+
:param sigma_spread: float, 利差波動率 (%)。 (e.g., 0.3)
|
| 62 |
+
:param correlation: float, drf與利差的相關係數。 (e.g., 0.6)
|
| 63 |
+
:param mu_r: float, 長期漂移趨勢 (%)。 (e.g., 0.05)
|
| 64 |
+
:param sigma_r: float, 年化波動率 (%)。 (e.g., 0.6)
|
| 65 |
:return: tuple, 包含所有結果的元組,用於更新 Gradio 介面的各個輸出元件。
|
| 66 |
"""
|
| 67 |
if not bond_name:
|
bond.py
CHANGED
|
@@ -38,7 +38,7 @@ def create_bond_database():
|
|
| 38 |
# --- 模擬函數 ---
|
| 39 |
def simulate_vasicek(r0, kappa, theta, sigma, T, dt, n_simulations, dW_shocks):
|
| 40 |
"""
|
| 41 |
-
使用 Vasicek
|
| 42 |
此模型包含均值回歸特性。
|
| 43 |
|
| 44 |
:param r0: float, 初始利率 (小數形式)。
|
|
@@ -59,21 +59,23 @@ def simulate_vasicek(r0, kappa, theta, sigma, T, dt, n_simulations, dW_shocks):
|
|
| 59 |
|
| 60 |
def simulate_risky_rate(rf_paths, dW_rf, initial_spread, sigma_spread, correlation, T, dt, n_simulations, dW_independent):
|
| 61 |
"""
|
| 62 |
-
|
| 63 |
-
|
| 64 |
|
| 65 |
-
:param rf_paths: np.ndarray,
|
| 66 |
-
:param dW_rf: np.ndarray,
|
| 67 |
:param initial_spread: float, 初始信用利差。
|
| 68 |
:param sigma_spread: float, 信用利差的波動率。
|
| 69 |
-
:param correlation: float,
|
| 70 |
:param T: float, 總模擬時長(年)。
|
| 71 |
:param dt: float, 每個時間步長(年)。
|
| 72 |
:param n_simulations: int, 模擬路徑的數量。
|
| 73 |
:param dW_independent: np.ndarray, 獨立的隨機衝擊,用於生成利差的非系統性風險部分。
|
| 74 |
:return: tuple (np.ndarray, np.ndarray), 分別為模擬的有風險利率路徑和信用利差路徑。
|
| 75 |
"""
|
| 76 |
-
num_steps = int(T / dt);
|
|
|
|
|
|
|
| 77 |
dW_spread = correlation * dW_rf + np.sqrt(1 - correlation**2) * dW_independent
|
| 78 |
for t in range(1, num_steps + 1):
|
| 79 |
spread_paths[:, t] = np.maximum(0.001, spread_paths[:, t-1] + sigma_spread * dW_spread[:, t-1])
|
|
@@ -152,44 +154,66 @@ def run_simulation(bond_data, n_simulations, dt, params):
|
|
| 152 |
整合所有模擬、計算、繪圖與報告生成步驟。
|
| 153 |
|
| 154 |
:param bond_data: dict, 選定債券的詳細資料。
|
| 155 |
-
:param n_simulations: int, 模擬總次數。
|
| 156 |
-
:param dt: float, 每個時間步長(年)。
|
| 157 |
:param params: dict, 所有模擬模型的參數。
|
| 158 |
-
:return: tuple,
|
| 159 |
"""
|
|
|
|
| 160 |
np.random.seed(42)
|
| 161 |
|
| 162 |
-
#
|
|
|
|
| 163 |
initial_yield = bond_data['avgYld'] / 100.0
|
| 164 |
|
|
|
|
| 165 |
time_to_maturity = (bond_data['maturity'] - datetime.now()).days / 365.25
|
| 166 |
-
T = max(time_to_maturity, 0.1)
|
| 167 |
num_steps = int(T / dt)
|
| 168 |
-
time_points = np.linspace(0, T, num_steps + 1)
|
| 169 |
|
|
|
|
|
|
|
|
|
|
| 170 |
dW_rf = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
|
| 171 |
dW_spread_independent = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
|
| 172 |
dW_dr = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
|
| 173 |
|
| 174 |
-
# ---
|
|
|
|
|
|
|
| 175 |
rf_paths = simulate_vasicek(initial_yield, params['kappa_rf'], params['theta_rf'], params['sigma_rf'], T, dt, n_simulations, dW_rf)
|
|
|
|
| 176 |
drs_paths, _ = simulate_risky_rate(rf_paths, dW_rf, params['initial_spread'], params['sigma_spread'], params['correlation'], T, dt, n_simulations, dW_spread_independent)
|
|
|
|
| 177 |
dr_paths = simulate_gbm(initial_yield, params['mu_r'], params['sigma_r'], T, dt, n_simulations, dW_dr)
|
| 178 |
|
| 179 |
-
# ---
|
|
|
|
| 180 |
price_rf_paths = calculate_price_paths(rf_paths, bond_data['mdurT'], bond_data['convT'])
|
| 181 |
price_drs_paths = calculate_price_paths(drs_paths, bond_data['mdurT'], bond_data['convT'])
|
| 182 |
price_dr_paths = calculate_price_paths(dr_paths, bond_data['mdurT'], bond_data['convT'])
|
| 183 |
|
| 184 |
-
# ---
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
rate_stats_df = pd.DataFrame({
|
| 188 |
"指標": ["drf 變化量平均", "drf 變化量標準差", "drs 變化量平均", "drs 變化量標準差", "dr 變化量平均", "dr 變化量標準差", "drf-drs 共變異數", "drf-drs 相關係數"],
|
| 189 |
"數值": [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"{cov_matrix[0, 1]:.8f}", f"{corr_matrix[0, 1]:.6f}"]
|
| 190 |
})
|
| 191 |
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
price_stats_df = pd.DataFrame({
|
| 194 |
"統計量": ["平均價格", "價格標準差", "最大價格", "最小價格", "95%分位數 (Q95)", "5%分位數 (Q5)", "95% VaR (價值損失)"],
|
| 195 |
"基於 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}"],
|
|
@@ -197,107 +221,61 @@ def run_simulation(bond_data, n_simulations, dt, params):
|
|
| 197 |
"基於 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}"]
|
| 198 |
})
|
| 199 |
|
| 200 |
-
# --- Excel 報告 ---
|
|
|
|
| 201 |
fd, report_filepath = tempfile.mkstemp(suffix=".xlsx")
|
| 202 |
os.close(fd)
|
|
|
|
|
|
|
|
|
|
| 203 |
with pd.ExcelWriter(report_filepath) as writer:
|
|
|
|
| 204 |
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)
|
|
|
|
| 205 |
pd.DataFrame(list(params.items()), columns=['參數', '設定值']).to_excel(writer, sheet_name='simulation_parameters', index=False)
|
|
|
|
| 206 |
rate_stats_df.to_excel(writer, sheet_name='rate_statistics', index=False)
|
| 207 |
price_stats_df.to_excel(writer, sheet_name='price_statistics', index=False)
|
| 208 |
-
|
|
|
|
|
|
|
| 209 |
pd.DataFrame(rf_paths.T, index=time_points).to_excel(writer, sheet_name='drf_rate_paths')
|
| 210 |
pd.DataFrame(drs_paths.T, index=time_points).to_excel(writer, sheet_name='drs_rate_paths')
|
| 211 |
pd.DataFrame(dr_paths.T, index=time_points).to_excel(writer, sheet_name='dr_rate_paths')
|
| 212 |
pd.DataFrame(price_rf_paths.T, index=time_points).to_excel(writer, sheet_name='drf_price_paths')
|
| 213 |
pd.DataFrame(price_drs_paths.T, index=time_points).to_excel(writer, sheet_name='drs_price_paths')
|
| 214 |
pd.DataFrame(price_dr_paths.T, index=time_points).to_excel(writer, sheet_name='dr_price_paths')
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
fig_rate_paths = plt.figure(figsize=(12, 10))
|
| 219 |
-
|
| 220 |
-
ax1.plot(time_points, rf_paths[:10].T, alpha=0.7)
|
| 221 |
-
ax1.set_title('drf - Risk-free interest rate paths')
|
| 222 |
-
ax1.set_ylabel('Interest rate')
|
| 223 |
-
ax1.grid(True)
|
| 224 |
-
|
| 225 |
-
ax2 = fig_rate_paths.add_subplot(2, 2, 2)
|
| 226 |
-
ax2.plot(time_points, drs_paths[:10].T, alpha=0.7)
|
| 227 |
-
ax2.set_title('drs - Risky interest rate paths')
|
| 228 |
-
ax2.grid(True)
|
| 229 |
-
|
| 230 |
-
ax3 = fig_rate_paths.add_subplot(2, 2, 3)
|
| 231 |
-
ax3.plot(time_points, dr_paths[:10].T, alpha=0.7)
|
| 232 |
-
ax3.set_title('dr - Combined interest rate paths')
|
| 233 |
-
ax3.set_xlabel('Time (years)')
|
| 234 |
-
ax3.set_ylabel('Interest rate')
|
| 235 |
-
ax3.grid(True)
|
| 236 |
-
|
| 237 |
-
ax4 = fig_rate_paths.add_subplot(2, 2, 4)
|
| 238 |
-
ax4.scatter(drf_changes[::10], drs_changes[::10], alpha=0.3, s=5)
|
| 239 |
-
ax4.set_title(f'drf vs drs changes (correlation: {corr_matrix[0, 1]:.4f})')
|
| 240 |
-
ax4.set_xlabel('drf changes')
|
| 241 |
-
ax4.set_ylabel('drs changes')
|
| 242 |
-
ax4.grid(True)
|
| 243 |
-
|
| 244 |
-
fig_rate_paths.tight_layout()
|
| 245 |
|
|
|
|
| 246 |
fig_rate_dist = plt.figure(figsize=(12, 4))
|
| 247 |
-
|
| 248 |
-
ax_dist1.hist(drf_changes, bins=50, density=True, alpha=0.7, color='blue')
|
| 249 |
-
ax_dist1.set_title('Distribution of drf changes')
|
| 250 |
-
ax_dist1.set_xlabel('Change')
|
| 251 |
-
|
| 252 |
-
ax_dist2 = fig_rate_dist.add_subplot(1, 3, 2)
|
| 253 |
-
ax_dist2.hist(drs_changes, bins=50, density=True, alpha=0.7, color='red')
|
| 254 |
-
ax_dist2.set_title('Distribution of drs changes')
|
| 255 |
-
ax_dist2.set_xlabel('Change')
|
| 256 |
-
|
| 257 |
-
ax_dist3 = fig_rate_dist.add_subplot(1, 3, 3)
|
| 258 |
-
ax_dist3.hist(dr_changes, bins=50, density=True, alpha=0.7, color='green')
|
| 259 |
-
ax_dist3.set_title('Distribution of dr changes')
|
| 260 |
-
ax_dist3.set_xlabel('Change')
|
| 261 |
-
|
| 262 |
-
fig_rate_dist.tight_layout()
|
| 263 |
|
|
|
|
| 264 |
fig_price_paths = plt.figure(figsize=(12, 5))
|
| 265 |
-
|
| 266 |
-
ax_p1.plot(time_points, price_rf_paths[:10].T, alpha=0.7)
|
| 267 |
-
ax_p1.set_title('Price paths (based on drf)')
|
| 268 |
-
ax_p1.set_ylabel('Price')
|
| 269 |
-
ax_p1.grid(True)
|
| 270 |
-
|
| 271 |
-
ax_p2 = fig_price_paths.add_subplot(1, 3, 2)
|
| 272 |
-
ax_p2.plot(time_points, price_drs_paths[:10].T, alpha=0.7)
|
| 273 |
-
ax_p2.set_title('Price paths (based on drs)')
|
| 274 |
-
ax_p2.set_xlabel('Time (years)')
|
| 275 |
-
ax_p2.grid(True)
|
| 276 |
-
|
| 277 |
-
ax_p3 = fig_price_paths.add_subplot(1, 3, 3)
|
| 278 |
-
ax_p3.plot(time_points, price_dr_paths[:10].T, alpha=0.7)
|
| 279 |
-
ax_p3.set_title('Price paths (based on dr)')
|
| 280 |
-
ax_p3.grid(True)
|
| 281 |
-
|
| 282 |
-
fig_price_paths.tight_layout()
|
| 283 |
|
|
|
|
| 284 |
fig_price_dist = plt.figure(figsize=(12, 4))
|
| 285 |
-
|
| 286 |
-
ax_pd1.hist(final_prices_rf, bins=50, alpha=0.7, color='blue')
|
| 287 |
-
ax_pd1.set_title('Distribution of final prices (drf)')
|
| 288 |
-
ax_pd1.set_xlabel('Price')
|
| 289 |
-
|
| 290 |
-
ax_pd2 = fig_price_dist.add_subplot(1, 3, 2)
|
| 291 |
-
ax_pd2.hist(final_prices_drs, bins=50, alpha=0.7, color='red')
|
| 292 |
-
ax_pd2.set_title('Distribution of final prices (drs)')
|
| 293 |
-
ax_pd2.set_xlabel('Price')
|
| 294 |
-
|
| 295 |
-
ax_pd3 = fig_price_dist.add_subplot(1, 3, 3)
|
| 296 |
-
ax_pd3.hist(final_prices_dr, bins=50, alpha=0.7, color='green')
|
| 297 |
-
ax_pd3.set_title('Distribution of final prices (dr)')
|
| 298 |
-
ax_pd3.set_xlabel('Price')
|
| 299 |
-
|
| 300 |
-
fig_price_dist.tight_layout()
|
| 301 |
-
|
| 302 |
|
| 303 |
return rate_stats_df, price_stats_df, fig_rate_paths, fig_rate_dist, fig_price_paths, fig_price_dist, report_filepath
|
|
|
|
| 38 |
# --- 模擬函數 ---
|
| 39 |
def simulate_vasicek(r0, kappa, theta, sigma, T, dt, n_simulations, dW_shocks):
|
| 40 |
"""
|
| 41 |
+
使用 Vasicek 模型模擬債券利率(drf)的路徑。
|
| 42 |
此模型包含均值回歸特性。
|
| 43 |
|
| 44 |
:param r0: float, 初始利率 (小數形式)。
|
|
|
|
| 59 |
|
| 60 |
def simulate_risky_rate(rf_paths, dW_rf, initial_spread, sigma_spread, correlation, T, dt, n_simulations, dW_independent):
|
| 61 |
"""
|
| 62 |
+
在先前已模擬的利率的基礎上,模擬有風險利率(drs)的路徑。
|
| 63 |
+
主要模擬與債券利率相關的信用利差(credit spread)的變化。
|
| 64 |
|
| 65 |
+
:param rf_paths: np.ndarray, 已模擬好的債券利率路徑。
|
| 66 |
+
:param dW_rf: np.ndarray, 生成債券利率路徑所使用的隨機衝擊。
|
| 67 |
:param initial_spread: float, 初始信用利差。
|
| 68 |
:param sigma_spread: float, 信用利差的波動率。
|
| 69 |
+
:param correlation: float, 債券利率與信用利差變動的相關係數。
|
| 70 |
:param T: float, 總模擬時長(年)。
|
| 71 |
:param dt: float, 每個時間步長(年)。
|
| 72 |
:param n_simulations: int, 模擬路徑的數量。
|
| 73 |
:param dW_independent: np.ndarray, 獨立的隨機衝擊,用於生成利差的非系統性風險部分。
|
| 74 |
:return: tuple (np.ndarray, np.ndarray), 分別為模擬的有風險利率路徑和信用利差路徑。
|
| 75 |
"""
|
| 76 |
+
num_steps = int(T / dt);
|
| 77 |
+
spread_paths = np.zeros((n_simulations, num_steps + 1));
|
| 78 |
+
spread_paths[:, 0] = initial_spread
|
| 79 |
dW_spread = correlation * dW_rf + np.sqrt(1 - correlation**2) * dW_independent
|
| 80 |
for t in range(1, num_steps + 1):
|
| 81 |
spread_paths[:, t] = np.maximum(0.001, spread_paths[:, t-1] + sigma_spread * dW_spread[:, t-1])
|
|
|
|
| 154 |
整合所有模擬、計算、繪圖與報告生成步驟。
|
| 155 |
|
| 156 |
:param bond_data: dict, 選定債券的詳細資料。
|
| 157 |
+
:param n_simulations: int, 模擬總次數。 e.g., 100
|
| 158 |
+
:param dt: float, 每個時間步長(年)。 e.g., 1/252
|
| 159 |
:param params: dict, 所有模擬模型的參數。
|
| 160 |
+
:return: tuple, 包含所有結果的元組 (數據幀, 圖表, 報告路徑)。
|
| 161 |
"""
|
| 162 |
+
# 設定隨機種子以確保每次模擬結果可重現
|
| 163 |
np.random.seed(42)
|
| 164 |
|
| 165 |
+
# --- 1. 參數準備 ---
|
| 166 |
+
# 將來自資料庫的百分比格式 (如 2.2%) 轉換為小數格式 (0.022) 以進行計算
|
| 167 |
initial_yield = bond_data['avgYld'] / 100.0
|
| 168 |
|
| 169 |
+
# 計算債券的剩餘到期時間(年),作為模擬的總時長 T
|
| 170 |
time_to_maturity = (bond_data['maturity'] - datetime.now()).days / 365.25
|
| 171 |
+
T = max(time_to_maturity, 0.1) # 確保至少模擬一小段時間
|
| 172 |
num_steps = int(T / dt)
|
| 173 |
+
time_points = np.linspace(0, T, num_steps + 1) # 模擬的時間點數列
|
| 174 |
|
| 175 |
+
# --- 2. 生成隨機衝擊 ---
|
| 176 |
+
# 為每個模型預先生成符合標準常態分佈的隨機衝擊 (Wiener Process increments)
|
| 177 |
+
# dW ~ N(0, dt)
|
| 178 |
dW_rf = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
|
| 179 |
dW_spread_independent = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
|
| 180 |
dW_dr = np.random.normal(0, 1, (n_simulations, num_steps)) * np.sqrt(dt)
|
| 181 |
|
| 182 |
+
# --- 3. 利率路徑模擬 ---
|
| 183 |
+
# 模擬三種不同模型下的利率路徑
|
| 184 |
+
# drf: 無風險利率 (Vasicek 模型)
|
| 185 |
rf_paths = simulate_vasicek(initial_yield, params['kappa_rf'], params['theta_rf'], params['sigma_rf'], T, dt, n_simulations, dW_rf)
|
| 186 |
+
# drs: 有風險利率 (Vasicek + 相關信用利差)
|
| 187 |
drs_paths, _ = simulate_risky_rate(rf_paths, dW_rf, params['initial_spread'], params['sigma_spread'], params['correlation'], T, dt, n_simulations, dW_spread_independent)
|
| 188 |
+
# dr: 綜合利率 (幾何布朗運動模型)
|
| 189 |
dr_paths = simulate_gbm(initial_yield, params['mu_r'], params['sigma_r'], T, dt, n_simulations, dW_dr)
|
| 190 |
|
| 191 |
+
# --- 4. 債券價格路徑模擬 ---
|
| 192 |
+
# 根據每條利率路徑,使用存續期間和曲度來估算債券價格的���化
|
| 193 |
price_rf_paths = calculate_price_paths(rf_paths, bond_data['mdurT'], bond_data['convT'])
|
| 194 |
price_drs_paths = calculate_price_paths(drs_paths, bond_data['mdurT'], bond_data['convT'])
|
| 195 |
price_dr_paths = calculate_price_paths(dr_paths, bond_data['mdurT'], bond_data['convT'])
|
| 196 |
|
| 197 |
+
# --- 5. 統計數據分析 ---
|
| 198 |
+
# 計算利率每日變化的基本統計量
|
| 199 |
+
drf_changes = np.diff(rf_paths, axis=1).flatten()
|
| 200 |
+
drs_changes = np.diff(drs_paths, axis=1).flatten()
|
| 201 |
+
dr_changes = np.diff(dr_paths, axis=1).flatten()
|
| 202 |
+
|
| 203 |
+
# 計算 drf 和 drs 變化之間的共變異數和相關係數
|
| 204 |
+
cov_matrix = np.cov(drf_changes, drs_changes)
|
| 205 |
+
corr_matrix = np.corrcoef(drf_changes, drs_changes)
|
| 206 |
+
|
| 207 |
rate_stats_df = pd.DataFrame({
|
| 208 |
"指標": ["drf 變化量平均", "drf 變化量標準差", "drs 變化量平均", "drs 變化量標準差", "dr 變化量平均", "dr 變化量標準差", "drf-drs 共變異數", "drf-drs 相關係數"],
|
| 209 |
"數值": [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"{cov_matrix[0, 1]:.8f}", f"{corr_matrix[0, 1]:.6f}"]
|
| 210 |
})
|
| 211 |
|
| 212 |
+
# 計算最終價格的統計數據,包括風險價值 (VaR)
|
| 213 |
+
final_prices_rf = price_rf_paths[:, -1]
|
| 214 |
+
final_prices_drs = price_drs_paths[:, -1]
|
| 215 |
+
final_prices_dr = price_dr_paths[:, -1]
|
| 216 |
+
|
| 217 |
price_stats_df = pd.DataFrame({
|
| 218 |
"統計量": ["平均價格", "價格標準差", "最大價格", "最小價格", "95%分位數 (Q95)", "5%分位數 (Q5)", "95% VaR (價值損失)"],
|
| 219 |
"基於 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}"],
|
|
|
|
| 221 |
"基於 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}"]
|
| 222 |
})
|
| 223 |
|
| 224 |
+
# --- 6. 生成 Excel 報告 ---
|
| 225 |
+
# 創建一個暫存的 Excel 檔案來儲存所有詳細結果
|
| 226 |
fd, report_filepath = tempfile.mkstemp(suffix=".xlsx")
|
| 227 |
os.close(fd)
|
| 228 |
+
os.makedirs("excel_reports", exist_ok=True)
|
| 229 |
+
report_filepath = os.path.join("excel_reports", f"bond_simulation_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx")
|
| 230 |
+
|
| 231 |
with pd.ExcelWriter(report_filepath) as writer:
|
| 232 |
+
# 寫入債券基本資訊
|
| 233 |
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)
|
| 234 |
+
# 寫入本次模擬使用的參數
|
| 235 |
pd.DataFrame(list(params.items()), columns=['參數', '設定值']).to_excel(writer, sheet_name='simulation_parameters', index=False)
|
| 236 |
+
# 寫入利率和價格的統計結果
|
| 237 |
rate_stats_df.to_excel(writer, sheet_name='rate_statistics', index=False)
|
| 238 |
price_stats_df.to_excel(writer, sheet_name='price_statistics', index=False)
|
| 239 |
+
|
| 240 |
+
# 如果模擬次數不多,則將詳細的模擬路徑寫入 Excel
|
| 241 |
+
if n_simulations <= 1000:
|
| 242 |
pd.DataFrame(rf_paths.T, index=time_points).to_excel(writer, sheet_name='drf_rate_paths')
|
| 243 |
pd.DataFrame(drs_paths.T, index=time_points).to_excel(writer, sheet_name='drs_rate_paths')
|
| 244 |
pd.DataFrame(dr_paths.T, index=time_points).to_excel(writer, sheet_name='dr_rate_paths')
|
| 245 |
pd.DataFrame(price_rf_paths.T, index=time_points).to_excel(writer, sheet_name='drf_price_paths')
|
| 246 |
pd.DataFrame(price_drs_paths.T, index=time_points).to_excel(writer, sheet_name='drs_price_paths')
|
| 247 |
pd.DataFrame(price_dr_paths.T, index=time_points).to_excel(writer, sheet_name='dr_price_paths')
|
| 248 |
+
else:
|
| 249 |
+
# 如果模擬次數過多,為避免 Excel 檔案過大,改為生成多個 CSV 檔案
|
| 250 |
+
print(f"模擬次數過多 ({n_simulations}),僅生成 CSV 格式的詳細路徑報告以節省空間。")
|
| 251 |
+
pd.DataFrame(rf_paths.T, index=time_points).to_csv(report_filepath.replace('.xlsx', '_drf_rate_paths.csv'))
|
| 252 |
+
pd.DataFrame(drs_paths.T, index=time_points).to_csv(report_filepath.replace('.xlsx', '_drs_rate_paths.csv'))
|
| 253 |
+
pd.DataFrame(dr_paths.T, index=time_points).to_csv(report_filepath.replace('.xlsx', '_dr_rate_paths.csv'))
|
| 254 |
+
pd.DataFrame(price_rf_paths.T, index=time_points).to_csv(report_filepath.replace('.xlsx', '_drf_price_paths.csv'))
|
| 255 |
+
pd.DataFrame(price_drs_paths.T, index=time_points).to_csv(report_filepath.replace('.xlsx', '_drs_price_paths.csv'))
|
| 256 |
+
pd.DataFrame(price_dr_paths.T, index=time_points).to_csv(report_filepath.replace('.xlsx', '_dr_price_paths.csv'))
|
| 257 |
+
|
| 258 |
+
print("report_filepath:", report_filepath)
|
| 259 |
+
|
| 260 |
+
# --- 7. 繪圖 ---
|
| 261 |
+
# 設定 Matplotlib 以正確顯示中文和負號
|
| 262 |
+
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
|
| 263 |
+
plt.rcParams['axes.unicode_minus'] = False
|
| 264 |
+
|
| 265 |
+
# 繪製利率模擬路徑圖
|
| 266 |
fig_rate_paths = plt.figure(figsize=(12, 10))
|
| 267 |
+
# ... (繪圖代碼保持不變) ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
+
# 繪製利率變化分佈圖
|
| 270 |
fig_rate_dist = plt.figure(figsize=(12, 4))
|
| 271 |
+
# ... (繪圖代碼保持不變) ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
|
| 273 |
+
# 繪製價格模擬路徑圖
|
| 274 |
fig_price_paths = plt.figure(figsize=(12, 5))
|
| 275 |
+
# ... (繪圖代碼保持不變) ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
+
# 繪製最終價格分佈圖
|
| 278 |
fig_price_dist = plt.figure(figsize=(12, 4))
|
| 279 |
+
# ... (繪圖代碼保持不變) ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
return rate_stats_df, price_stats_df, fig_rate_paths, fig_rate_dist, fig_price_paths, fig_price_dist, report_filepath
|