Aurelio 储蓄目标所需回报率:从公式、二分求解到 17.5% 的实战解读

发布时间:2026/11/2 1:22:00
Aurelio 储蓄目标所需回报率:从公式、二分求解到 17.5% 的实战解读

【免费下载链接】aurelio-finance

Open-source personal finance app with an AI financial advisor: track your net worth, investments, ETFs, cash and debts on your own computer.

项目地址:https://gitcode.com/gh_mirrors/au/aurelio-finance
点击查看免费下载

本文围绕 Aurelio(一款开源的个人财务应用,可自托管并内置 AI 财务顾问)中「储蓄目标需要多少年化回报率」这一功能展开:先给出目标的终值公式与数值求解方式,再用 demo 家庭的「三年买 18,000 欧元汽车」算例逐档验证,最后说明backend/app/planning.py的源码实现、REST 接口与前端实时提示的对应关系。读完后你应能独立推演任意目标所需回报率,并准确理解 Aurelio 给出 0%、4%、8%、12% 这些读数的含义与边界。

摘要:储蓄目标所需回报率,就是把「现有本金 + 每月追加」在到期日推到目标金额所需的年化增长率。4,000 欧元起步、每月 250 欧元、三年要凑到 18,000 欧元,需要17.5%/年——Aurelio 会把这条判读为「不现实」。若只靠储蓄就能达成,答案是 0%;若只有不切实际的回报才能达成,正确的动作是改计划(每月多存、推后日期、下调目标),而不是期待那个回报。

问题定义:目标的终值公式

设C为当前已有金额,M为每月追加金额,i为月回报率,n为月数,则到期价值为:

FV = C × (1 + i)^n + M × ((1 + i)^n − 1) / i

这一公式同时出现在文档与源码的模块级文档字符串中,见 backend/app/planning.py。

  • 当M = 0(不再追加)时方程可直接解出:i = (target / C)^(1/n) − 1,这是闭式解;
  • 当M > 0时没有闭式解,只能数值求解。Aurelio 用**二分法(bisection)**求月利率,再换算成年利率:(1 + i)^12 − 1。

注意这里的年化不是简单乘 12,而是按月复利折算,因此「月利率 0.5%」对应的年化是 6.17% 而不是 6%。这一点在复现计算时最容易出错。

算例:demo 家庭三年买一辆车

Aurelio 的 demo 数据里有一个具体目标(见 backend/app/demo.py):目标 18,000 欧元、三年后到期、现有 4,000 欧元、每月追加 250 欧元。你可以在 自托管快速上手 里用./start.sh --demo直接把这套数据跑起来。

不作任何投资增长时:4,000 + 36 × 250 = 13,000,距目标差 5,000 欧元。填补这个缺口的回报率是17.5%/年,Aurelio 的判读是「Unrealistic: consider extending the horizon, raising contributions, or lowering the target.」

单改某一项会怎样:

方案所需回报率Aurelio 的判读
按原记录:每月 250,3 年17.5%Unrealistic(不现实)
每月 30010.7%Aggressive(激进)
每月 3504.5%Moderate(温和)
每月 3890%Already on track without investment growth
改为 4 年4.8%Moderate(温和)
改为 5 年0%Already on track without investment growth
目标降为 16,000 的汽车11.0%Aggressive(激进)

表里的数字可以用仓库里的函数直接复核(下面的命令在backend/目录、已安装项目依赖的环境下执行):

python3 -c " from app.planning import required_annual_return as r, assess for monthly in (250, 300, 350, 389): rate = r(4000, monthly, 18000, 3) print(monthly, round(rate * 100, 2), assess(rate)) print('4y', round(r(4000, 250, 18000, 4) * 100, 2)) print('16000', round(r(4000, 250, 16000, 3) * 100, 2)) "

输出为17.54 / 10.7 / 4.47 / 0.0 / 4.8 / 11.02。注意「每月 389」恰好是 0%:因为4,000 + 36 × 389 = 18,004 ≥ 18,000,在required_annual_return里直接命中if fv(0.0) >= target: return 0.0这个前置判断(backend/app/planning.py),根本不会进入二分循环。也就是说,0% 不是一个算出来的小数字,而是一个「无需增长即可达成」的语义结论。

对三年期的目标来说,月供金额和到期日对结果的影响,远大于任何你可以合理期待的回报率。三年时间太短,一个长期看应当获得温和回报的组合,也可能正好在你需要用钱的时候处于浮亏。

更长的目标:增长承担更多工作

同样的算术放到更长的期限上,复利的分量就显现出来了:

目标所需回报率
25 年后 300,000,从零开始,每月 5005.2%
同上,但从 10,000 起步4.4%
5 年后 50,000,从 10,000 起步,每月 6002.8%
10 年后 60,000,从 50,000 起步,不再追加1.8%
5 年后 200,000,从 20,000 起步,不再追加58.5%

最后一行的「无追加」情形走的是闭式解分支,5 年即n = 60,(200000/20000)^(1/60) − 1得到月利率约 3.9%,年化即 58.5%。这也是为什么短期限 + 高倍数的目标天然无解,而不是「风险高一点就能做到」。

二分法在源码里是怎么实现的

required_annual_return(current, monthly, target, years)的返回语义有三种:0.0表示无需增长;None表示在这些投入下任何利率都到不了;其余为年化小数(0.175即 17.5%)。核心逻辑见 backend/app/planning.py,可以拆成四步:

  1. 期限校验:years <= 0直接返回None,月数n = years * 12.0。
  2. 零利率快检:fv(0)用current + monthly * n计算(避免了除零),若已达标返回0.0。
  3. 可达性判定:current <= 0 and monthly <= 0时返回None——「无中生有」在任何利率下都不成立;再取上界hi = 1.0(每月 100%,一个夸张的上界),若fv(1.0)仍小于目标,也返回None。
  4. 二分收敛:lo = 0.0、hi = 1.0,固定迭代100 次取中点,最后(lo + hi) / 2作为月利率,并换算为(1 + monthly_rate) ** 12 - 1。

两个容易被忽略的实现细节:

  • 溢出保护。(1 + 1.0) ** n在n ≥ 1024个月(约 85 年)时超出浮点上限。源码在 backend/app/planning.py 里捕获OverflowError并返回float("inf"),因为只要current或monthly有一个大于 0,fv就随i单调递增,inf在读作「已超过目标」时是正确的方向。这一处修的是真实缺陷:把溢出当达标,会为「本金为零、月供为零」的目标找出一个利率;因此第 3 步的current <= 0 and monthly <= 0判断必须放在上界检查之前。对应回归测试见 backend/tests/test_planning.py 与 backend/tests/test_a_goal_a_century_away.py。
  • 精度自证。测试不是断言「约等于 7.2%」,而是把解出的年利率反解回月利率、重算终值,要求相对误差小于 0.1%,见 backend/tests/test_planning.py。这是验证数值解法最稳妥的方式。

判读档位:0%、4%、8%、12% 从哪来

assess(r)把结果映射成一句结论(backend/app/planning.py)。这些是读计划的经验法则,不是对任何市场回报的预测:

所需回报率判读源码条件
0% 或更低Already on track without investment growthr <= 0
不超过 4%Conservative: feasible with a low-risk allocationr <= 0.04
不超过 8%Moderate: in line with a diversified long-term portfolior <= 0.08
不超过 12%Aggressive: high risk, not guaranteedr <= 0.12
高于 12%Unrealistic: consider extending the horizon, raising contributions, or lowering the target其余
任何利率都到不了Target effectively unreachable with these contributions. Raise contributions or extend the horizon.r is None

由于是<=逐级判断,边界值归入较宽松的一档(正好 8% 是 Moderate)。注意None与「高于 12%」是两种不同结论:前者表示这批投入在数学上无解,后者表示有解但代价荒谬。

计算中有两样东西不包含在内:

  • 通货膨胀。整个计算是名义金额。以今天的价格写下的目标,十年后达成时购买力更低——要么用未来货币写目标,要么把所需回报读作实际回报并在其上叠加通胀。
  • 税费与成本。基金费率与收益税从回报里扣,所以你需要的税前、费前回报要略高于这个数。

接口、前端与 AI 的三种读法

直接调用计算接口

POST /api/planning/required-return是不依赖数据库的纯计算端点(backend/app/routers/goals.py),请求体由RequiredReturnRequest约束(backend/app/schemas.py:current_amount与monthly_contribution默认 0 且须>= 0,target_amount必填且须> 0,target_date为日期):

curl -s -X POST http://127.0.0.1:8000/api/planning/required-return \ -H 'Content-Type: application/json' \ -d '{"current_amount": 4000, "monthly_contribution": 250, "target_amount": 18000, "target_date": "2029-10-12"}'

响应是{ "years": 2.95, "required_annual_return": 0.1754..., "assessment": "Unrealistic: ..." }。years由(target_date - today).days / 365.25得出并四舍五入到两位(backend/app/planning.py):用 365.25 是为了让闰年不改变「年」的含义。若日期已过,返回required_annual_return: null且判读为The target date is in the past.。

表单里即时出现的彩色提示

在 Profile → Goals 里选「Target amount by a date」,填上目标、现有金额、每月追加与日期,页面立刻给出结论,例如「Over ~3 years this requires a return of17.5%/yr.」frontend/src/components/Goals.tsx 的实现要点:

  • useEffect监听type / targetAmount / targetDate / currentAmount / monthly五个字段,任一变化就以空值兜底(Number(x) || 0)重新请求;
  • 带cancelled标志,避免快速输入时旧响应覆盖新结果;请求失败则把提示置空,不让表单因提示报错而中断;
  • 颜色阈值在RequiredReturnNote中:null或> 0.12为红色(bg-down-tint),> 0.08为琥珀色(bg-warn-tint),其余为绿色(bg-up-tint);
  • 展示时用(r * 100).toFixed(1)保留一位小数,null显示为unreachable。相应请求封装在 frontend/src/api/goals.ts 的computeRequiredReturn。

因为「年数」是服务端按当天算的,同一份表单过几天再看,years与回报率会有轻微变化——这是预期的,目标日期在逼近。

聊天与 AI 顾问看到什么

聊天上下文(以及 MCP 服务器暴露的同一份「财务全貌」)会为每个带金额和日期的目标输出一行,含所需回报与判读,构建逻辑见 backend/app/advisor.py:target 18000 EUR by ... (current 4000, +250/mo, ~3.0y) -> requires 17.5%/yr (Unrealistic: ...)。这让模型不必自己重算,避免同一批记录算出两个答案。

更重要的是,sizing.py会把「一笔钱能站在什么基础上」一次性算清楚(backend/app/sizing.py):每个目标的months_left、gap、needs_a_month_with_no_growth(缺口除以月数,零增长)、lacks_a_month(月需减去已记录的月供,不小于 0)以及required_return。demo 的汽车对应:缺口 14,000、14,000 / 36 ≈ €388.80的月度需求、已记录€250,于是还缺€138.80——正是文档给出的数字。这里的关键设计是:已记录的月供同时被计入「每月承诺」,在计算每月还能投多少时先被扣掉,而「目标还需要多少」只并列展示、绝不从可用额度里再减一次,从而避免重复扣减。

常见问题

多少回报率算现实?没人知道市场未来会给出什么回报。Aurelio 的档位把年化 4% 以内读作保守、8% 以内读作与分散化的长期组合相符、12% 以上读作不现实;它们是读计划的尺子,不是承诺。

怎么降低目标所需的回报?每月多存、把日期推后、或下调目标。对 demo 的汽车,每月多 100 欧元就能把 17.5% 降到 4.5%;多给一年则降到 4.8%。

这跟储蓄计算器是一回事吗?方向相反:储蓄计算器问「给定回报,你将有多少」;这里问「你的计划需要什么回报」。

零回报和「完全无解」怎么区分?前者是0.0(现有本金加月供已够),后者是null(本金与月供同时为 0,或连每月 100% 的利率都到不了目标)。前端把null显示为unreachable,与红色但仍有具体数字的「不现实」是两种不同处境。

相关源码与延伸阅读

  • 公式、二分求解与档位判读:backend/app/planning.py
  • 每个目标的月需、缺口与所需回报:backend/app/sizing.py
  • 计算接口与请求/响应模型:backend/app/routers/goals.py、backend/app/schemas.py
  • 前端即时提示与配色:frontend/src/components/Goals.tsx、frontend/src/api/goals.ts
  • 数学测试与超长期限回归:backend/tests/test_planning.py、backend/tests/test_a_goal_a_century_away.py
  • 同一批「金额站在什么基础上」的测试:backend/tests/test_how_much_stands_on_the_records.py
  • 目标与规划的系列导读:wiki/goals-and-planning.md
  • 应急基金需要几个月:wiki/how-many-months-of-emergency-fund-do-you-need.md

教育与信息性质内容,不构成受监管的财务建议。

【免费下载链接】aurelio-finance

Open-source personal finance app with an AI financial advisor: track your net worth, investments, ETFs, cash and debts on your own computer.

项目地址:https://gitcode.com/gh_mirrors/au/aurelio-finance
点击查看免费下载

相关推荐

上一篇:ShopXO多门店系统部署教程:连锁品牌线上线下一体化方案
下一篇:PhpRedis 6.x新特性详解:ACL支持与性能提升

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考