# -*- 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)