import matplotlib.pyplot as plt
import numpy as np
# 設定中文字型與風格 (使用內建字型以確保顯示)
plt.rcParams['font.sans-serif'] = ['Taipei Sans TC Beta', 'Microsoft JhengHei', 'SimHei', 'Arial']
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('seaborn-v0_8-whitegrid')
# 模擬參數
np.random.seed(101) # 固定種子以求結果一致
n_simulations = 5000
mu = 0.075 # 7.5%
sigma = 0.16 # 16%
initial_principal = 100
contribution_amount = 80
def run_simulation(years, contributions):
sim_paths = np.zeros((n_simulations, years + 1))
sim_paths[:, 0] = initial_principal
cost_line = np.zeros(years + 1)
cost_line[0] = initial_principal
for t in range(1, years + 1):
shock = np.random.normal(0, 1, n_simulations)
returns = np.exp((mu - 0.5 * sigma**2) + sigma * shock)
sim_paths[:, t] = sim_paths[:, t-1] * returns
cost_line[t] = cost_line[t-1]
if t in contributions:
sim_paths[:, t] += contribution_amount
cost_line[t] += contribution_amount
return sim_paths, cost_line
def plot_chart(years, contributions, title, filename):
sim_paths, cost_line = run_simulation(years, contributions)
p10 = np.percentile(sim_paths, 10, axis=0)
p50 = np.percentile(sim_paths, 50, axis=0)
p90 = np.percentile(sim_paths, 90, axis=0)
# 設定高解析度畫布
fig, ax = plt.subplots(figsize=(12, 7), dpi=300)
x = np.arange(0, years + 1)
# 繪製區域
ax.fill_between(x, p10, p90, color='#87CEEB', alpha=0.4, label='80% 機率分佈 (P10-P90)')
# 繪製線條
ax.plot(x, p50, color='#00008B', linewidth=3, label='中位數 (P50) - 最可能的結果')
ax.step(x, cost_line, color='#DC143C', linestyle='--', linewidth=2.5, where='post', label='投入本金 (成本)')
# 標示數值
ax.text(years, p90[-1], f' P90 (樂觀): {int(p90[-1])}', fontsize=12, color='#00008B', fontweight='bold', va='center')
ax.text(years, p50[-1], f' P50 (中位): {int(p50[-1])}', fontsize=12, color='#00008B', fontweight='bold', va='center')
ax.text(years, p10[-1], f' P10 (悲觀): {int(p10[-1])}', fontsize=12, color='#00008B', fontweight='bold', va='top')
ax.text(years, cost_line[-1], f' 總成本: {int(cost_line[-1])}', fontsize=12, color='#DC143C', fontweight='bold', va='top')
# 標題與標籤
ax.set_title(title, fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('年份 (Year)', fontsize=14)
ax.set_ylabel('資產金額 (Value)', fontsize=14)
ax.set_xlim(0, years + 1)
ax.grid(True, linestyle='--', alpha=0.6)
ax.legend(loc='upper left', fontsize=12, frameon=True, facecolor='white', framealpha=0.9)
plt.tight_layout()
plt.show()
# 執行繪圖
plot_chart(10, [1, 4, 7], "情境一:10年期 (第1,4,7年加碼)", "chart1")
plot_chart(13, [1, 4, 7, 10], "情境二:13年期 (第1,4,7,10年加碼)", "chart2")
plot_chart(16, [1, 4, 7, 10, 13], "情境三:16年期 (第1,4,7,10,13年加碼)", "chart3")
aW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdAppbXBvcnQgbnVtcHkgYXMgbnAKCiMg6Kit5a6a5Lit5paH5a2X5Z6L6IiH6aKo5qC8ICjkvb/nlKjlhaflu7rlrZflnovku6Xnorrkv53poa/npLopCnBsdC5yY1BhcmFtc1snZm9udC5zYW5zLXNlcmlmJ10gPSBbJ1RhaXBlaSBTYW5zIFRDIEJldGEnLCAnTWljcm9zb2Z0IEpoZW5nSGVpJywgJ1NpbUhlaScsICdBcmlhbCddCnBsdC5yY1BhcmFtc1snYXhlcy51bmljb2RlX21pbnVzJ10gPSBGYWxzZQpwbHQuc3R5bGUudXNlKCdzZWFib3JuLXYwXzgtd2hpdGVncmlkJykKCiMg5qih5pOs5Y+D5pW4Cm5wLnJhbmRvbS5zZWVkKDEwMSkgIyDlm7rlrprnqK7lrZDku6XmsYLntZDmnpzkuIDoh7QKbl9zaW11bGF0aW9ucyA9IDUwMDAgCm11ID0gMC4wNzUgICAjIDcuNSUKc2lnbWEgPSAwLjE2ICMgMTYlCmluaXRpYWxfcHJpbmNpcGFsID0gMTAwCmNvbnRyaWJ1dGlvbl9hbW91bnQgPSA4MAoKZGVmIHJ1bl9zaW11bGF0aW9uKHllYXJzLCBjb250cmlidXRpb25zKToKICAgIHNpbV9wYXRocyA9IG5wLnplcm9zKChuX3NpbXVsYXRpb25zLCB5ZWFycyArIDEpKQogICAgc2ltX3BhdGhzWzosIDBdID0gaW5pdGlhbF9wcmluY2lwYWwKICAgIGNvc3RfbGluZSA9IG5wLnplcm9zKHllYXJzICsgMSkKICAgIGNvc3RfbGluZVswXSA9IGluaXRpYWxfcHJpbmNpcGFsCiAgICAKICAgIGZvciB0IGluIHJhbmdlKDEsIHllYXJzICsgMSk6CiAgICAgICAgc2hvY2sgPSBucC5yYW5kb20ubm9ybWFsKDAsIDEsIG5fc2ltdWxhdGlvbnMpCiAgICAgICAgcmV0dXJucyA9IG5wLmV4cCgobXUgLSAwLjUgKiBzaWdtYSoqMikgKyBzaWdtYSAqIHNob2NrKQogICAgICAgIHNpbV9wYXRoc1s6LCB0XSA9IHNpbV9wYXRoc1s6LCB0LTFdICogcmV0dXJucwogICAgICAgIGNvc3RfbGluZVt0XSA9IGNvc3RfbGluZVt0LTFdCiAgICAgICAgCiAgICAgICAgaWYgdCBpbiBjb250cmlidXRpb25zOgogICAgICAgICAgICAgc2ltX3BhdGhzWzosIHRdICs9IGNvbnRyaWJ1dGlvbl9hbW91bnQKICAgICAgICAgICAgIGNvc3RfbGluZVt0XSArPSBjb250cmlidXRpb25fYW1vdW50CiAgICByZXR1cm4gc2ltX3BhdGhzLCBjb3N0X2xpbmUKCmRlZiBwbG90X2NoYXJ0KHllYXJzLCBjb250cmlidXRpb25zLCB0aXRsZSwgZmlsZW5hbWUpOgogICAgc2ltX3BhdGhzLCBjb3N0X2xpbmUgPSBydW5fc2ltdWxhdGlvbih5ZWFycywgY29udHJpYnV0aW9ucykKICAgIAogICAgcDEwID0gbnAucGVyY2VudGlsZShzaW1fcGF0aHMsIDEwLCBheGlzPTApCiAgICBwNTAgPSBucC5wZXJjZW50aWxlKHNpbV9wYXRocywgNTAsIGF4aXM9MCkKICAgIHA5MCA9IG5wLnBlcmNlbnRpbGUoc2ltX3BhdGhzLCA5MCwgYXhpcz0wKQogICAgCiAgICAjIOioreWumumrmOino+aekOW6pueVq+W4gwogICAgZmlnLCBheCA9IHBsdC5zdWJwbG90cyhmaWdzaXplPSgxMiwgNyksIGRwaT0zMDApCiAgICAKICAgIHggPSBucC5hcmFuZ2UoMCwgeWVhcnMgKyAxKQogICAgCiAgICAjIOe5quijveWNgOWfnwogICAgYXguZmlsbF9iZXR3ZWVuKHgsIHAxMCwgcDkwLCBjb2xvcj0nIzg3Q0VFQicsIGFscGhhPTAuNCwgbGFiZWw9JzgwJSDmqZ/njofliIbkvYggKFAxMC1QOTApJykKICAgIAogICAgIyDnuaroo73nt5rmop0KICAgIGF4LnBsb3QoeCwgcDUwLCBjb2xvcj0nIzAwMDA4QicsIGxpbmV3aWR0aD0zLCBsYWJlbD0n5Lit5L2N5pW4IChQNTApIC0g5pyA5Y+v6IO955qE57WQ5p6cJykKICAgIGF4LnN0ZXAoeCwgY29zdF9saW5lLCBjb2xvcj0nI0RDMTQzQycsIGxpbmVzdHlsZT0nLS0nLCBsaW5ld2lkdGg9Mi41LCB3aGVyZT0ncG9zdCcsIGxhYmVsPSfmipXlhaXmnKzph5EgKOaIkOacrCknKQogICAgCiAgICAjIOaomeekuuaVuOWAvAogICAgYXgudGV4dCh5ZWFycywgcDkwWy0xXSwgZicgUDkwICjmqILop4ApOiB7aW50KHA5MFstMV0pfScsIGZvbnRzaXplPTEyLCBjb2xvcj0nIzAwMDA4QicsIGZvbnR3ZWlnaHQ9J2JvbGQnLCB2YT0nY2VudGVyJykKICAgIGF4LnRleHQoeWVhcnMsIHA1MFstMV0sIGYnIFA1MCAo5Lit5L2NKToge2ludChwNTBbLTFdKX0nLCBmb250c2l6ZT0xMiwgY29sb3I9JyMwMDAwOEInLCBmb250d2VpZ2h0PSdib2xkJywgdmE9J2NlbnRlcicpCiAgICBheC50ZXh0KHllYXJzLCBwMTBbLTFdLCBmJyBQMTAgKOaCsuingCk6IHtpbnQocDEwWy0xXSl9JywgZm9udHNpemU9MTIsIGNvbG9yPScjMDAwMDhCJywgZm9udHdlaWdodD0nYm9sZCcsIHZhPSd0b3AnKQogICAgYXgudGV4dCh5ZWFycywgY29zdF9saW5lWy0xXSwgZicg57i95oiQ5pysOiB7aW50KGNvc3RfbGluZVstMV0pfScsIGZvbnRzaXplPTEyLCBjb2xvcj0nI0RDMTQzQycsIGZvbnR3ZWlnaHQ9J2JvbGQnLCB2YT0ndG9wJykKCiAgICAjIOaomemhjOiIh+aomeexpAogICAgYXguc2V0X3RpdGxlKHRpdGxlLCBmb250c2l6ZT0xOCwgZm9udHdlaWdodD0nYm9sZCcsIHBhZD0yMCkKICAgIGF4LnNldF94bGFiZWwoJ+W5tOS7vSAoWWVhciknLCBmb250c2l6ZT0xNCkKICAgIGF4LnNldF95bGFiZWwoJ+izh+eUoumHkemhjSAoVmFsdWUpJywgZm9udHNpemU9MTQpCiAgICBheC5zZXRfeGxpbSgwLCB5ZWFycyArIDEpCiAgICBheC5ncmlkKFRydWUsIGxpbmVzdHlsZT0nLS0nLCBhbHBoYT0wLjYpCiAgICBheC5sZWdlbmQobG9jPSd1cHBlciBsZWZ0JywgZm9udHNpemU9MTIsIGZyYW1lb249VHJ1ZSwgZmFjZWNvbG9yPSd3aGl0ZScsIGZyYW1lYWxwaGE9MC45KQogICAgCiAgICBwbHQudGlnaHRfbGF5b3V0KCkKICAgIHBsdC5zaG93KCkKCiMg5Z+36KGM57mq5ZyWCnBsb3RfY2hhcnQoMTAsIFsxLCA0LCA3XSwgIuaDheWig+S4gO+8mjEw5bm05pyfICjnrKwxLDQsN+W5tOWKoOeivCkiLCAiY2hhcnQxIikKcGxvdF9jaGFydCgxMywgWzEsIDQsIDcsIDEwXSwgIuaDheWig+S6jO+8mjEz5bm05pyfICjnrKwxLDQsNywxMOW5tOWKoOeivCkiLCAiY2hhcnQyIikKcGxvdF9jaGFydCgxNiwgWzEsIDQsIDcsIDEwLCAxM10sICLmg4XlooPkuInvvJoxNuW5tOacnyAo56ysMSw0LDcsMTAsMTPlubTliqDnorwpIiwgImNoYXJ0MyIpCg==