137 lines
6.5 KiB
Python
137 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
||||
|
|
"""生成可调参数的 FIRE(Barista 半退休) 测算表。改黄色参数格,结果用 Excel 公式自动重算。"""
|
|||
|
|
from openpyxl import Workbook
|
|||
|
|
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
|||
|
|
from openpyxl.utils import get_column_letter
|
|||
|
|
|
|||
|
|
wb = Workbook()
|
|||
|
|
ws = wb.active
|
|||
|
|
ws.title = "FIRE测算"
|
|||
|
|
|
|||
|
|
# ---- 样式 ----
|
|||
|
|
title_font = Font(name="微软雅黑", size=14, bold=True, color="FFFFFF")
|
|||
|
|
title_fill = PatternFill("solid", fgColor="2F5597")
|
|||
|
|
head_font = Font(name="微软雅黑", size=11, bold=True, color="FFFFFF")
|
|||
|
|
head_fill = PatternFill("solid", fgColor="4472C4")
|
|||
|
|
sect_font = Font(name="微软雅黑", size=11, bold=True, color="2F5597")
|
|||
|
|
input_fill = PatternFill("solid", fgColor="FFF2CC") # 黄色=可改
|
|||
|
|
input_font = Font(name="微软雅黑", size=11, bold=True, color="C00000")
|
|||
|
|
calc_fill = PatternFill("solid", fgColor="E2EFDA") # 绿色=自动算
|
|||
|
|
normal_font = Font(name="微软雅黑", size=11)
|
|||
|
|
ok_fill = PatternFill("solid", fgColor="C6E0B4")
|
|||
|
|
thin = Side(style="thin", color="BFBFBF")
|
|||
|
|
border = Border(left=thin, right=thin, top=thin, bottom=thin)
|
|||
|
|
center = Alignment(horizontal="center", vertical="center")
|
|||
|
|
left = Alignment(horizontal="left", vertical="center")
|
|||
|
|
|
|||
|
|
def put(cell, value, font=normal_font, fill=None, align=left, fmt=None, bd=True):
|
|||
|
|
c = ws[cell]
|
|||
|
|
c.value = value
|
|||
|
|
c.font = font
|
|||
|
|
if fill: c.fill = fill
|
|||
|
|
c.alignment = align
|
|||
|
|
if fmt: c.number_format = fmt
|
|||
|
|
if bd: c.border = border
|
|||
|
|
return c
|
|||
|
|
|
|||
|
|
# ---- 标题 ----
|
|||
|
|
ws.merge_cells("A1:F1")
|
|||
|
|
put("A1", "FIRE 半退休 (Barista) 可调参数测算表", title_font, title_fill, center, bd=False)
|
|||
|
|
ws.row_dimensions[1].height = 28
|
|||
|
|
ws.merge_cells("A2:F2")
|
|||
|
|
put("A2", "用法:只改【黄色格子】里的数字,右侧/下方结果会自动重算。", Font(name="微软雅黑", size=9, italic=True, color="808080"), align=left, bd=False)
|
|||
|
|
|
|||
|
|
# ---- 参数区 ----
|
|||
|
|
put("A3", "① 可修改参数(改这里)", sect_font, bd=False)
|
|||
|
|
params = [
|
|||
|
|
("起始本金(元)", 70000, "#,##0"),
|
|||
|
|
("每月定投(元)", 1500, "#,##0"),
|
|||
|
|
("年开支(元)", 65000, "#,##0"),
|
|||
|
|
("兼职年收入(元)", 40000, "#,##0"),
|
|||
|
|
("安全提取率", 0.035, "0.0%"),
|
|||
|
|
("收益率-保守", 0.04, "0.0%"),
|
|||
|
|
("收益率-中性", 0.06, "0.0%"),
|
|||
|
|
("收益率-乐观", 0.08, "0.0%"),
|
|||
|
|
]
|
|||
|
|
r = 4
|
|||
|
|
for name, val, fmt in params:
|
|||
|
|
put(f"A{r}", name, normal_font, input_fill, left)
|
|||
|
|
put(f"B{r}", val, input_font, input_fill, center, fmt)
|
|||
|
|
r += 1
|
|||
|
|
# 记下行号
|
|||
|
|
ROW = {"本金":4, "月投":5, "开支":6, "兼职":7, "提取率":8, "保守":9, "中性":10, "乐观":11}
|
|||
|
|
|
|||
|
|
# ---- 自动计算:目标本金 ----
|
|||
|
|
put("A13", "② 自动计算结果", sect_font, bd=False)
|
|||
|
|
put("A14", "年缺口(开支-兼职,元)", normal_font, calc_fill)
|
|||
|
|
put("B14", f"=B{ROW['开支']}-B{ROW['兼职']}", normal_font, calc_fill, center, "#,##0")
|
|||
|
|
put("A15", "目标本金(元)", Font(name="微软雅黑", bold=True), calc_fill)
|
|||
|
|
put("B15", f"=B14/B{ROW['提取率']}", Font(name="微软雅黑", bold=True, color="C00000"), calc_fill, center, "#,##0")
|
|||
|
|
put("A16", "目标本金(万元)", Font(name="微软雅黑", bold=True), calc_fill)
|
|||
|
|
put("B16", "=B15/10000", Font(name="微软雅黑", bold=True, color="C00000"), calc_fill, center, "0.0")
|
|||
|
|
|
|||
|
|
# ---- 逐年测算表 ----
|
|||
|
|
put("A18", "③ 逐年资产测算(含分红复投;第一个出现 ✅ 的年份≈达成 Barista FIRE)", sect_font, bd=False)
|
|||
|
|
headers = ["年数", "保守(元)", "中性(元)", "乐观(元)", "中性(万)", "是否达标(中性)"]
|
|||
|
|
hr = 19
|
|||
|
|
for i, h in enumerate(headers):
|
|||
|
|
put(f"{get_column_letter(i+1)}{hr}", h, head_font, head_fill, center)
|
|||
|
|
|
|||
|
|
start = hr + 1
|
|||
|
|
for y in range(1, 31):
|
|||
|
|
rr = start + (y - 1)
|
|||
|
|
put(f"A{rr}", y, normal_font, None, center, "0")
|
|||
|
|
# FV(年化/12, 年数*12, -月投, -起始本金) -> 正数
|
|||
|
|
put(f"B{rr}", f"=FV($B${ROW['保守']}/12, A{rr}*12, -$B${ROW['月投']}, -$B${ROW['本金']})", normal_font, None, center, "#,##0")
|
|||
|
|
put(f"C{rr}", f"=FV($B${ROW['中性']}/12, A{rr}*12, -$B${ROW['月投']}, -$B${ROW['本金']})", normal_font, None, center, "#,##0")
|
|||
|
|
put(f"D{rr}", f"=FV($B${ROW['乐观']}/12, A{rr}*12, -$B${ROW['月投']}, -$B${ROW['本金']})", normal_font, None, center, "#,##0")
|
|||
|
|
put(f"E{rr}", f"=C{rr}/10000", normal_font, None, center, "0.0")
|
|||
|
|
put(f"F{rr}", f'=IF(C{rr}>=$B$15,"✅达标","")', Font(name="微软雅黑", bold=True, color="548235"), None, center)
|
|||
|
|
|
|||
|
|
# ---- 列宽 ----
|
|||
|
|
widths = [22, 14, 14, 14, 12, 16]
|
|||
|
|
for i, w in enumerate(widths):
|
|||
|
|
ws.column_dimensions[get_column_letter(i+1)].width = w
|
|||
|
|
|
|||
|
|
# ---- 说明页 ----
|
|||
|
|
ws2 = wb.create_sheet("说明")
|
|||
|
|
notes = [
|
|||
|
|
"【这张表怎么用】",
|
|||
|
|
"1. 在『FIRE测算』页,只改黄色格子里的数字(本金、月投、开支、兼职收入、提取率、收益率)。",
|
|||
|
|
"2. 绿色格子和下方表格会自动重算,不用动公式。",
|
|||
|
|
"",
|
|||
|
|
"【目标本金怎么来的】",
|
|||
|
|
"目标本金 =(年开支 − 兼职年收入)÷ 安全提取率",
|
|||
|
|
"例:开支6.5万、兼职4万、提取率3.5% → (65000-40000)/3.5% = 约71万。",
|
|||
|
|
"兼职每多赚1万,目标本金大约少29万。",
|
|||
|
|
"",
|
|||
|
|
"【提取率为什么用 3.5%】",
|
|||
|
|
"美国常用4%,但中国低利率、市场波动大、社保有空窗,打折到3%~3.5%更稳健。",
|
|||
|
|
"想更保守可把提取率改成 0.03。",
|
|||
|
|
"",
|
|||
|
|
"【收益率怎么理解】",
|
|||
|
|
"已包含分红再投资(总回报口径)。参考组合:红利ETF/工行50-60% + 宽基指数30% + 货基/国债10-15%。",
|
|||
|
|
"保守4% / 中性6% / 乐观8% 为假设值,不是承诺。",
|
|||
|
|
"",
|
|||
|
|
"【达成年限怎么看】",
|
|||
|
|
"看『是否达标(中性)』列,第一个出现 ✅ 的年份,就是按中性收益大约达成的年数。",
|
|||
|
|
"想看保守/乐观,可对比 保守(元)、乐观(元) 两列与目标本金B15的大小。",
|
|||
|
|
"",
|
|||
|
|
"【加速的关键】",
|
|||
|
|
"把『每月定投』从1500改成3000试试,会看到达成年限大幅缩短——提高储蓄率比纠结收益率更有效。",
|
|||
|
|
"",
|
|||
|
|
"⚠️ 本表为个人理性测算与学习参考,不构成投资建议,收益率为假设值、不保证实现。",
|
|||
|
|
]
|
|||
|
|
for i, line in enumerate(notes, start=1):
|
|||
|
|
c = ws2[f"A{i}"]
|
|||
|
|
c.value = line
|
|||
|
|
if line.startswith("【") or line.startswith("⚠️"):
|
|||
|
|
c.font = Font(name="微软雅黑", bold=True, color="2F5597")
|
|||
|
|
else:
|
|||
|
|
c.font = Font(name="微软雅黑", size=10)
|
|||
|
|
ws2.column_dimensions["A"].width = 95
|
|||
|
|
|
|||
|
|
out = "/mnt/d/opc_project/fire_goal/FIRE可调测算表.xlsx"
|
|||
|
|
wb.save(out)
|
|||
|
|
print("saved:", out)
|