jeff7522553 commited on
Commit
2dacaa6
·
1 Parent(s): 5b339f4

初始化

Browse files
Files changed (4) hide show
  1. app.py +175 -0
  2. bond.py +303 -0
  3. bond_db.csv +0 -0
  4. requirements.txt +5 -0
app.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ from datetime import datetime
4
+
5
+ # 從 bond.py 導入重構後的函數和數據
6
+ from bond import create_bond_database, run_simulation
7
+
8
+ # 1. 數據準備
9
+ BOND_DATABASE = create_bond_database()
10
+ BOND_CHOICES = list(BOND_DATABASE.keys())
11
+
12
+ # --- Gradio 介面輔助函數 ---
13
+
14
+ def get_bond_info_html(bond_name):
15
+ """
16
+ 根據下拉選單選擇的債券名稱,生成用於顯示其詳細資訊的 HTML 字串。
17
+
18
+ :param bond_name: str, 使用者從下拉選單選擇的債券名稱。
19
+ :return: str, 一個 HTML 格式的字串,用於在 Gradio 介面中顯示。
20
+ """
21
+ if not bond_name:
22
+ return "<p>請從上方選擇一個債券</p>"
23
+
24
+ bond = BOND_DATABASE[bond_name]
25
+ issue_date_str = bond['issueDate'].strftime('%Y-%m-%d')
26
+ maturity_date_str = bond['maturity'].strftime('%Y-%m-%d')
27
+
28
+ # **重要**: 根據資料庫中的百分比格式,調整顯示方式
29
+ html = f"""
30
+ <div style="border: 1px solid #e0e0e0; padding: 15px; border-radius: 5px;">
31
+ <h4 style="margin-top:0;">債券: {bond_name}</h4>
32
+ <table style="width:100%;">
33
+ <tr><td style="width:50%;"><strong>平均殖利率 (avgYld):</strong> {bond['avgYld']:.3f}%</td><td style="width:50%;"><strong>票面利率 (coupon):</strong> {bond['coupon']:.3f}%</td></tr>
34
+ <tr><td><strong>發行日 (issueDate):</strong> {issue_date_str}</td><td><strong>到期日 (maturity):</strong> {maturity_date_str}</td></tr>
35
+ <tr><td><strong>每年付息 (payPerYr):</strong> {bond['payPerYr']} 次</td><td><strong>每年複利 (compPerYr):</strong> {bond['compPerYr']} 次</td></tr>
36
+ <tr><td><strong>存續期間 (durT):</strong> {bond['durT']}</td><td><strong>修正存續期間 (mdurT):</strong> {bond['mdurT']}</td></tr>
37
+ <tr><td><strong>價格曲度 (convT):</strong> {bond['convT']}</td><td></td></tr>
38
+ </table>
39
+ </div>
40
+ """
41
+ return html
42
+
43
+ def run_monte_carlo(
44
+ bond_name, n_simulations, dt,
45
+ kappa_rf, theta_rf, sigma_rf,
46
+ initial_spread, sigma_spread, correlation,
47
+ mu_r, sigma_r
48
+ ):
49
+ """
50
+ Gradio 的主要執行函數。
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:
61
+ raise gr.Error("請先選擇一個債券!")
62
+
63
+ bond_data = BOND_DATABASE[bond_name]
64
+
65
+ # **重要**: 將來自UI的百分比參數轉換為小數以進行計算
66
+ params = {
67
+ 'kappa_rf': kappa_rf,
68
+ 'theta_rf': theta_rf / 100.0,
69
+ 'sigma_rf': sigma_rf / 100.0,
70
+ 'initial_spread': initial_spread / 100.0,
71
+ 'sigma_spread': sigma_spread / 100.0,
72
+ 'correlation': correlation,
73
+ 'mu_r': mu_r / 100.0,
74
+ 'sigma_r': sigma_r / 100.0,
75
+ "n_simulations":int(n_simulations),
76
+ }
77
+
78
+ rate_stats_df, price_stats_df, fig_rate_paths, fig_rate_dist, fig_price_paths, fig_price_dist, report_filepath = run_simulation(
79
+ bond_data=bond_data, n_simulations=int(n_simulations), dt=dt, params=params
80
+ )
81
+
82
+ return (
83
+ rate_stats_df, price_stats_df,
84
+ fig_rate_paths, fig_rate_dist,
85
+ fig_price_paths, fig_price_dist,
86
+ gr.update(value=report_filepath, visible=True)
87
+ )
88
+
89
+ # --- 建立 Gradio 介面 ---
90
+
91
+ with gr.Blocks(theme=gr.themes.Soft(), title="債券利率蒙地卡羅模擬") as app:
92
+ gr.Markdown("# 債券利率與價格蒙地卡羅模擬")
93
+
94
+ # --- 步驟 1 & 2: 參數設定 ---
95
+ with gr.Row():
96
+ with gr.Column(scale=2):
97
+ gr.Markdown("## 步驟 1: 選擇債券")
98
+ bond_selector = gr.Dropdown(BOND_CHOICES, label="選擇債券", info="從模擬的資料庫中選擇一個債券以載入其初始參數。", value=BOND_CHOICES[0])
99
+ bond_info_display = gr.HTML(get_bond_info_html(BOND_CHOICES[0]))
100
+
101
+ with gr.Column(scale=3):
102
+ gr.Markdown("## 步驟 2: 設定模擬參數")
103
+ with gr.Tabs():
104
+ with gr.Tab("無風險利率 (drf)"):
105
+ kappa_rf = gr.Slider(0.01, 1.0, value=0.3, step=0.01, label="均值回歸速度 (kappa_rf)")
106
+ theta_rf = gr.Slider(1.0, 5.0, value=2.2, step=0.1, label="長期平均利率 (%)")
107
+ sigma_rf = gr.Slider(0.1, 2.0, value=0.5, step=0.1, label="波動率 (%)")
108
+ with gr.Tab("有風險利率 (drs)"):
109
+ initial_spread = gr.Slider(0.1, 3.0, value=0.8, step=0.1, label="初始信用利差 (%)")
110
+ sigma_spread = gr.Slider(0.1, 1.0, value=0.3, step=0.05, label="利差波動率 (%)")
111
+ correlation = gr.Slider(-1.0, 1.0, value=0.6, step=0.05, label="drf與利差的相關係數")
112
+ with gr.Tab("綜合利率 (dr)"):
113
+ mu_r = gr.Slider(0.0, 0.2, value=0.05, step=0.01, label="長期漂移趨勢 (%)")
114
+ sigma_r = gr.Slider(0.1, 2.0, value=0.6, step=0.1, label="年化波動率 (%)")
115
+ n_simulations = gr.Slider(50, 100000, value=100, step=50, label="模擬次數 (n_simulations)")
116
+ dt = gr.Number(value=1/252, label="時間步長 (dt)", info="以年為單位,預設為一個交易日(1/252年)。")
117
+
118
+ # --- 步驟 3: 執行 ---
119
+ run_button = gr.Button("開始模擬", variant="primary")
120
+
121
+ # --- 步驟 4: 結果呈現 ---
122
+ with gr.Tabs():
123
+ with gr.Tab("利率分析"):
124
+ gr.Markdown("## 利率模擬結果")
125
+ with gr.Row():
126
+ with gr.Column(scale=1):
127
+ gr.Markdown("#### 利率統計數據")
128
+ rate_results_table = gr.DataFrame(headers=["指標", "數值"], datatype=["str", "str"])
129
+ with gr.Column(scale=2):
130
+ gr.Markdown("#### 利率路徑與相關性圖")
131
+ plot_rate_paths = gr.Plot()
132
+ gr.Markdown("#### 利率變化分佈圖")
133
+ plot_rate_distribution = gr.Plot()
134
+
135
+ with gr.Tab("債券價格分析"):
136
+ gr.Markdown("## 債券價格模擬結果")
137
+ gr.Markdown("使用修正存續期間與曲度來估算價格變化。初始價格假設為 100。")
138
+ with gr.Row():
139
+ with gr.Column(scale=1):
140
+ gr.Markdown("#### 價格統計數據 (含 VaR)")
141
+ price_results_table = gr.DataFrame()
142
+ with gr.Column(scale=2):
143
+ gr.Markdown("#### 價格模擬路徑")
144
+ plot_price_paths = gr.Plot()
145
+ gr.Markdown("#### 最終價格分佈")
146
+ plot_price_distribution = gr.Plot()
147
+
148
+ # --- 步驟 5: 下載資料 ---
149
+ with gr.Column():
150
+ gr.Markdown("--- \n ## 下載完整報告,(若模擬次數超過1000,則不會包含模擬路徑與價格資料,以避免檔案過大)")
151
+ report_file = gr.File(label="下載完整報告 (Excel)", visible=False)
152
+
153
+ # --- 事件監聽 ---
154
+ bond_selector.change(fn=get_bond_info_html, inputs=bond_selector, outputs=bond_info_display)
155
+
156
+ all_inputs = [
157
+ bond_selector, n_simulations, dt,
158
+ kappa_rf, theta_rf, sigma_rf,
159
+ initial_spread, sigma_spread, correlation,
160
+ mu_r, sigma_r
161
+ ]
162
+
163
+ run_button.click(
164
+ fn=run_monte_carlo,
165
+ inputs=all_inputs,
166
+ outputs=[
167
+ rate_results_table, price_results_table,
168
+ plot_rate_paths, plot_rate_distribution,
169
+ plot_price_paths, plot_price_distribution,
170
+ report_file
171
+ ]
172
+ )
173
+
174
+ if __name__ == "__main__":
175
+ app.launch()
bond.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from datetime import datetime
5
+ import tempfile
6
+ import os
7
+
8
+ THIS_DIR = os.path.dirname(os.path.abspath(__file__))
9
+
10
+
11
+ def create_bond_database():
12
+ """
13
+ 創建一個包含多個債券樣本的字典,作為模擬資料庫。
14
+ 注意:此處的 avgYld 和 coupon 以「百分比」形式儲存 (例如 2.2 代表 2.2%)。
15
+
16
+ :return: dict,鍵為債券名稱,值為包含該債券詳細參數的字典。
17
+
18
+ 例如:
19
+ 平均殖利率 (avgYld): 1.553%
20
+ 票面利率 (coupon): 2.125%
21
+ 發行日 (issueDate): 2011-01-13
22
+ 到期日 (maturity): 2031-01-13
23
+ 每年付息 (payPerYr): 1 次
24
+ 每年複利 (compPerYr): 1 次
25
+ 存續期間 (durT): 5.3548
26
+ 修正存續期間 (mdurT): 5.273
27
+ 價格曲度 (convT): 1762.7325
28
+ """
29
+ bond_table = pd.read_csv(os.path.join(THIS_DIR, "bond_db.csv"))
30
+ bond_table["issueDate"] = bond_table["issueDate"].apply(lambda x: pd.to_datetime(str(x)))
31
+ bond_table["maturity"] = bond_table["maturity"].apply(lambda x: pd.to_datetime(str(x)))
32
+ db = {
33
+ row["name"]: row
34
+ for row in bond_table.to_dict("records")
35
+ }
36
+ return db
37
+
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, 初始利率 (小數形式)。
45
+ :param kappa: float, 均值回歸速度。
46
+ :param theta: float, 長期平均利率。
47
+ :param sigma: float, 波動率。
48
+ :param T: float, 總模擬時長(年)。
49
+ :param dt: float, 每個時間步長(年)。
50
+ :param n_simulations: int, 模擬路徑的數量。
51
+ :param dW_shocks: np.ndarray, 預先生成的隨機衝擊。
52
+ :return: np.ndarray, 模擬的利率路徑,形狀為 (n_simulations, num_steps + 1)。
53
+ """
54
+ num_steps = int(T / dt)
55
+ rates = np.zeros((n_simulations, num_steps + 1)); rates[:, 0] = r0
56
+ for t in range(1, num_steps + 1):
57
+ rates[:, t] = np.maximum(0.001, rates[:, t-1] + kappa * (theta - rates[:, t-1]) * dt + sigma * dW_shocks[:, t-1])
58
+ return rates
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])
80
+ return rf_paths + spread_paths, spread_paths
81
+
82
+ def simulate_gbm(r0, mu, sigma, T, dt, n_simulations, dW_shocks):
83
+ """
84
+ 使用幾何布朗運動(Geometric Brownian Motion)模型模擬綜合利率(dr)的路徑。
85
+ 此模型常用於模擬股價,這裡用來作為一個簡化的利率模型。
86
+
87
+ :param r0: float, 初始利率 (小數形式)。
88
+ :param mu: float, 長期漂移率。
89
+ :param sigma: float, 波動率。
90
+ :param T: float, 總模擬時長(年)。
91
+ :param dt: float, 每個時間步長(年)。
92
+ :param n_simulations: int, 模擬路徑的數量。
93
+ :param dW_shocks: np.ndarray, 預先生成的隨機衝擊。
94
+ :return: np.ndarray, 模擬的利率路徑。
95
+ """
96
+ num_steps = int(T / dt); rates = np.zeros((n_simulations, num_steps + 1)); rates[:, 0] = r0
97
+ for t in range(1, num_steps + 1):
98
+ rates[:, t] = np.maximum(0.001, rates[:, t-1] * np.exp((mu - 0.5 * sigma**2) * dt + sigma * dW_shocks[:, t-1]))
99
+ return rates
100
+
101
+ def calculate_price_paths(rate_paths, mdurT, convT, initial_price=100.0):
102
+ """
103
+ 使用修正存續期間(mdurT)和價格曲度(convT)近似法,根據利率路徑計算債券價格路徑。
104
+
105
+ 重要:
106
+ 此處的 convT 參數假定為您資料庫中的 "1/2價格曲度" (如 288.3137),
107
+ 其金融定義為 (P * C_Mod / 2),且 P 為 initial_price (通常為 100)。
108
+
109
+ :param rate_paths: np.ndarray, 模��的利率路徑,刻度為小數點形式。
110
+ :param mdurT: float, 債券的修正存續期間 (D_Mod)。
111
+ :param convT: float, 債券的 "1/2 價格曲度" (P * C_Mod / 2)。
112
+ :param initial_price: float, 債券的初始價格,預設為100。
113
+ :return: np.ndarray, 模擬的債券價格路徑。
114
+ """
115
+ n_simulations, num_steps = rate_paths.shape[0], rate_paths.shape[1] - 1
116
+ price_paths = np.zeros_like(rate_paths)
117
+ price_paths[:, 0] = initial_price
118
+
119
+ # --- 邏輯修正 ---
120
+ # 根據定義,convT = (initial_price * C_Mod / 2)
121
+ # 我們需要 C_Mod / 2 這一項
122
+ # 因此,C_Mod / 2 = convT / initial_price
123
+ #
124
+ # 這個值在近似法中被假定為常數,不應隨 P(t-1) 變動。
125
+ # 【錯誤的邏輯】: convexity_factor = convT / prev_price
126
+ # 【修正後的邏輯】:
127
+ convexity_factor = convT / initial_price
128
+ # --- 修正結束 ---
129
+
130
+ for t in range(1, num_steps + 1):
131
+ delta_y = rate_paths[:, t] - rate_paths[:, t-1]
132
+
133
+ # 抓取前一期的價格 P(t-1)
134
+ prev_price = price_paths[:, t-1]
135
+
136
+ # 1. 計算價格變動百分比 (dP/P)
137
+ # dP/P = -mdurT * dy + (C_Mod / 2) * (dy^2)
138
+ #
139
+ # 【注意】: 這裡的 convexity_factor 是我們在迴圈外
140
+ # 就已經計算好的常數 (convT / initial_price)
141
+ price_change_factor = -mdurT * delta_y + convexity_factor * (delta_y ** 2)
142
+
143
+ # 2. 計算新價格 P(t) = P(t-1) * (1 + dP/P)
144
+ price_paths[:, t] = prev_price * (1 + price_change_factor)
145
+
146
+ return price_paths
147
+
148
+ # --- 主執行函數 ---
149
+ def run_simulation(bond_data, n_simulations, dt, params):
150
+ """
151
+ 程式核心執行函數。
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}"],
196
+ "基於 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}"],
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
bond_db.csv ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ pandas
3
+ numpy
4
+ matplotlib
5
+ openpyxl