jeff7522553 commited on
Commit
976a6a9
·
1 Parent(s): 2dacaa6

更新註解

Browse files
Files changed (3) hide show
  1. .gitignore +2 -0
  2. app.py +11 -4
  3. 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 ...: 所有來自UI的模擬參數 (此處為百分比單位)
 
 
 
 
 
 
 
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 模型模擬無風險利率(drf)的路徑。
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
- 在無風險利率的基礎上,模擬有風險利率(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); spread_paths = np.zeros((n_simulations, num_steps + 1)); spread_paths[:, 0] = initial_spread
 
 
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
- 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()
186
- cov_matrix = np.cov(drf_changes, drs_changes); corr_matrix = np.corrcoef(drf_changes, drs_changes)
 
 
 
 
 
 
 
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
- final_prices_rf = price_rf_paths[:, -1]; final_prices_drs = price_drs_paths[:, -1]; final_prices_dr = price_dr_paths[:, -1]
 
 
 
 
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
- if n_simulations <= 1000: # 避免檔案過大
 
 
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
- plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']; plt.rcParams['axes.unicode_minus'] = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  fig_rate_paths = plt.figure(figsize=(12, 10))
219
- ax1 = fig_rate_paths.add_subplot(2, 2, 1)
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
- ax_dist1 = fig_rate_dist.add_subplot(1, 3, 1)
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
- ax_p1 = fig_price_paths.add_subplot(1, 3, 1)
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
- ax_pd1 = fig_price_dist.add_subplot(1, 3, 1)
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