使用 Beancount 和 Fava 的可编写脚本的工作流
Beancount(一种纯文本复式记账工具)和 Fava(其 Web 界面)具有高度的可扩展性和可编写脚本性。它们的设计允许你通过编写 Python 脚本来自动化财务任务、生成自定义报告和设置警报。正如一位用户所说,“我真的很喜欢以如此方便的格式存储我的数据,而且我喜欢我可以随心所欲地自动化事情。没有像磁盘上的文件那样的 API;它很容易集成。” 本指南将引导你创建可编写脚本的工作流——从初学者友好的自动化到高级 Fava 插件。
入门:将 Beancount 作为 Python 脚本运行
在深入了解具体任务之前,请确保已安装 Beancount(例如,通过 pip install beancount)。由于 Beancount 是用 Python 编写的,因此你可以在自己的脚本中将其用作库。一般方法是:
-
加载你的 Beancount 账本:使用 Beancount 的加载器将
.beancount文件解析为 Python 对象。例如:from beancount import loader
entries, errors, options_map = loader.load_file("myledger.beancount")
if errors:
print("Errors:", errors)这为你提供了一个
entries列表(交易、余额等)和一个包含元数据的options_map。现在可以在代码中访问你的所有帐户、交易和余额。 -
利用 Beancount 查询语言 (BQL):你可以运行类似于 SQL 的查询来查询数据,而无需手动迭代。例如,要按月获取总支出,你可以使用查询 API:
from beancount.query import query
q = query.Query(entries, options_map)
result = q.query("SELECT month, sum(position) WHERE account ~ 'Expenses' GROUP BY month")
print(result)这使用 Beancount 的查询系统来聚合数据。(在底层,这类似于
bean-query命令所做的事情,但在这里你在脚本中使用它。) 事实上,Beancount 的作者指出,你可以加载文件并直接通过 Python API 调用run_query(),从而避免了在循环中调用外部命令的需要。 -
设置项目结构:将你的脚本与你的账本一起组织。常见的布局是为 importers(用于获取/解析外部数据)、reports 或 queries(用于分析脚本)和 documents(用于存储下载的报表)创建目录。例如,一位用户保留:
importers/– 自定义 Python 导入脚本(带有测试),queries/– 用于生成报告的脚本(可通过python3 queries/...运行),documents/– 按帐户组织的下载的银行 CSV/PDF。
通过此设置,你可以手动运行脚本(例如 python3 queries/cash_flow.py)或安排它们(通过 cron 或任务运行器)以自动化你的工作流程。
自动化对账任务
对账 意味着确保你的账本与外部记录(银行对账单、信用卡报告等)匹配。Beancount 的纯文本账本和 Python API 使自动化此过程的大部分成为可能。
导入和匹配交易(初学者)
对于初学者,建议的方法是使用 Beancount 的 导入器插件。你编写一个小型的 Python 类,遵循 Beancount 的导入器协议来解析给定的格式(CSV、OFX、PDF 等)并生成交易。然后使用 bean-extract 命令或脚本来应用这些导入器:
- 为你银行的 CSV 格式编写一个导入器(一个 Python 类,其中包含
identify()、extract()等方法)。Beancount 的文档提供了指南和示例。 - 在脚本或 Makefile(如
justfile示例)中使用bean-extract来解析新的报表。例如,一个工作流程在~/Downloads中的所有文件上运行bean-extract并将交易输出到临时文件。 - 手动审查并将交易从临时文件复制到你的主账本中,然后运行
bean-check以确保余额对账。
虽然此过程仍涉及审查步骤,但解析和格式化条目的大部分繁琐工作已自动化。导入器脚本 还可以自动分配类别,甚至设置余额断言(预期余额的声明)以捕获差异。例如,导入后,你可能会有类似 2025-04-30 balance Assets:Bank:Checking 1234.56 USD 的行,该行断言期末余额。当你运行 bean-check 时,Beancount 将 验证所有这些余额断言是否正确,如果缺少或重复交易,则会标记任何错误。这是一个最佳实践:为每个报表期自动生成余额断言,让计算机为你发现未对账的差异。
自定义对账脚本(中级)
为了获得更多控制,你可以编写一个自定义 Python 脚本来比较银行的交易列表(CSV 或通过 API)与你的账本条目:
- 读取外部数据:使用 Python 的
csv模块(或 Pandas)解析银行的 CSV 文件。将数据标准化为交易列表,例如,每个交易都具有日期、金额和描述。 - 加载账本交易:如前所示使用
loader.load_file获取所有账本条目。将此列表过滤到感兴趣的帐户(例如,你的支票帐户)以及报表的日期范围。 - 比较并查找不匹配项:
- 对于每个外部交易,检查账本中是否存在相同的条目(按日期和金额匹配,可能还有描述)。如果未找到,则将其标记为“new”并可能将其输出为 Beancount 格式的交易供你审核。
- 相反,识别该帐户中未出现在外部来源中的任何账本条目——这些可能是数据输入错误或尚未清算的交易。
- 输出结果:打印报告或创建一个新的
.beancount代码段,其中包含缺少的交易。
例如,一个名为 reconcile.py 的社区脚本正是这样做的:给定一个 Beancount 文件和一个输入 CSV,它会打印一个应该导入的新交易列表,以及输入中不存在的任何现有账本过账(可能表示错误分类)。使用这样的脚本,每月对账可以像运行它然后将建议的交易附加到你的账本一样简单。一位 Beancount 用户指出,他们 “每个月都对所有帐户执行对账过程” 并使用不断增长的 Python 代码集合来消除导入和对账数据中的大部分手动工作。
提示: 在对账期间,利用 Beancount 的工具来提高准确性:
- 如前所述,使用余额断言,对帐户余额进行自动检查。
- 如果需要,使用
pad指令,它可以自动插入平衡条目以解决较小的舍入差异(谨慎使用)。 - 为你的导入器或对账逻辑编写单元测试(Beancount 提供测试帮助程序)。例如,一个工作流程涉及获取一个示例 CSV,编写具有预期交易的失败测试,然后实现导入器直到所有测试通过。这可确保你的导入脚本适用于各种情况。
生成自定义报告和摘要
虽然 Fava 提供了许多标准报告(损益表、资产负债表等),但你可以使用脚本创建自定义报告。这些报告的范围可以从简单的控制台输出到格式丰富的格式文件或图表。
查询用于报告的数据(初学者)
在基本层面上,你可以使用 Beancount 查询语言 (BQL) 获取摘要数据并打印或保存它。例如:
-
现金流量摘要: 使用查询来计算净现金流量。“现金流量”可以定义为特定期间内某些帐户的余额变化。使用 BQL,你可以这样做:
SELECT year, month, sum(amount)
WHERE account LIKE 'Income:%' OR account LIKE 'Expenses:%'
GROUP BY year, month这将按月计算所有收入和支出过账的净额。你可以通过
bean-queryCLI 或通过 Python API(如前所示的query.Query)运行它,然后格式化结果。 -
类别支出报告: 查询每个类别的总支出:
SELECT account, round(sum(position), 2)
WHERE account ~ 'Expenses'
GROUP BY account
ORDER BY sum(position) ASC这将生成一个按类别列出的支出表。你可以在脚本中运行多个查询,并将结果输出为文本、CSV 甚至 JSON 以进行进一步处理。
一位用户发现使用 Fava 或使用脚本分析财务数据 “微不足道”,并引用说他们使用 一个 Python 脚本通过查询语言从 Beancount 中提取数据,然后将其放入 Pandas DataFrame 中以准备自定义报告。例如,你可以使用查询获取每月总额,然后使用 Pandas/Matplotlib 绘制随时间变化的现金流量图。BQL 和数据科学库的结合使你可以构建超出 Fava 默认提供的报告。
高级报告(图表、绩效等)
对于更高级的需求,你的脚本可以计算投资业绩等指标或创建可视化输出:
-
投资业绩 (IRR/XIRR):由于你的账本包含所有现金流量(买入、卖出、股息),因此你可以计算投资组合回报率。例如,你可以编写一个脚本来过滤你的投资帐户的交易,然后计算内部收益率。有一些库(或公式)可以根据现金流量数据计算 IRR。一些社区开发的 Fava 扩展(例如 PortfolioSummary 或 fava_investor)正是这样做的,计算投资组合的 IRR 和其他指标。作为脚本,你可以对一系列供款/提款加上期末价值使用 IRR 函数(来自 NumPy 或你自己的)。
-
多期或自定义指标:想要一份每月储蓄率(储蓄与收入之比)的报告吗?一个 Python 脚本可以加载账本,将所有收入帐户和所有支出帐户相加,然后计算储蓄 = 收入 - 支出和百分比。这可以输出一个漂亮的表格,甚至可以生成一个 HTML/Markdown 报告以供你记录。
-
可视化:你可以在 Fava 之外生成图表。例如,在脚本中使用
matplotlib或altair创建一个随时间变化的净值图表 ,使用账本数据。由于账本具有所有历史余额(或者你可以通过迭代条目来累积它们),因此你可以生成时间序列图。将这些图表保存为图像或交互式 HTML。(如果你喜欢应用内视觉效果,请参阅下面的 Fava 扩展部分,了解如何在 Fava 内部 添加图表。)
输出选项: 确定如何交付报告:
- 对于一次性分析,打印到屏幕或保存到 CSV/Excel 文件可能就足够了。
- 对于仪表板,请考虑生成一个包含数据的 HTML 文件(可能使用 Jinja2 等模板库,甚至只是编写 Markdown),你可以在浏览器中打开该文件。
- 你还可以与 Jupyter Notebook 集成以获得交互式报告环境,但这更多是用于探索而不是自动化。
从你的账本触发警报
可编写脚本的工作流的另一个强大用途是根据你的财务数据中的条件设置警报。由于你的账本会定期更新(并且可以包括未来日期的项目,例如即将到来的账单或预算),因此你可以使用脚本扫描它并收到重要事件的通知。
低帐户余额警告
为了避免透支或维持最低余额,你可能希望在任何帐户(例如支票或储蓄)低于阈值时发出警报。以下是如何实现此目的:
-
确定当前余额: 通过加载器加载
entries后,计算感兴趣帐户的最新余额。你可以通过聚合过账或使用查询来做到这一点。例如,使用 BQL 查询特定帐户的余额:SELECT sum(position) WHERE account = 'Assets:Bank:Checking'这将返回该帐户的当前余额(所有过账的总和)。或者,使用 Beancount 的内部函数来构建资产负债表。例如:
from beancount.core import realization
tree = realization.realize(entries, options_map)
acct = realization.get_or_create(tree, "Assets:Bank:Checking")
balance = acct.balance # an Inventory of commodities然后提取数值(例如,
balance.get_currency_units('USD')可能会给出 Decimal)。但是,在大多数情况下,使用查询更简单。 -
检查阈值: 将余额与你预定义的限制进行比较。如果低于, 则触发警报。
-
触发通知: 这可能就像将警告打印到控制台一样简单,但对于真正的警报,你可能会发送电子邮件或推送通知。你可以与电子邮件(通过
smtplib)或 IFTTT 或 Slack 的 webhook API 等服务集成以推送警报。例如:if balance < 1000:
send_email("Low balance alert", f"Account XYZ balance is {balance}")(使用你的电子邮件服务器详细信息实现
send_email。)
通过每天运行此脚本(通过 cron 作业或 Windows 任务计划程序),你将获得主动警告。因为它使用账本,所以它可以考虑 所有 交易,包括你刚刚添加的交易。
即将到来的付款截止日期
如果你使用 Beancount 来跟踪账单或截止日期,你可以标记未来的付款,并让脚本提醒你。在 Beancount 中表示即将到来的义务的两种方式:
-
事件: Beancount 支持用于任意日期注释的
event指令。例如:2025-05-10 event "BillDue" "抵押贷款付款到期"这不会影响余额,但会记录带有标签的日期。脚本可以扫描
entries中Event.type == "BillDue"(或你选择的任何自定义类型)的Event条目,并检查日期是否在今天起的未来 7 天内。如果是,则触发警报(电 子邮件、通知,甚至弹出窗口)。 -
未来交易: 有些人输入未来日期的交易(延期付款),例如预定的付款。在日期过去之前,这些交易不会显示在余额中(除非你将报告运行到未来日期)。脚本可以查找在不久的将来注明日期的交易并列出它们。
使用这些,你可以创建一个“提醒”脚本,该脚本在运行时输出即将到来的任务或账单列表。如果你想在那里自动创建提醒,请与 Google 日历或任务管理器等 API 集成。
异常检测
除了已知的阈值或日期之外,你还可以为异常模式编写自定义警报。例如,如果通常每月发生的费用尚未发生(可能你忘记支付账单),或者如果某个类别的支出在本月异常高,你的脚本可以标记它。这通常涉及查询最近的数据并与历史记录进行比较(这可能是一个高级主题——可能需要使用统计或 ML)。
在实践中,许多用户依靠对帐来捕获异常(意外交易)。如果你收到银行通知(例如每笔交易的电子邮件),你可以使用脚本解析这些通知并自动将其添加到 Beancount,或者至少验证它们是否已记录。一位爱好者甚至配置他们的银行发送交易警报电子邮件,计划解析它们并自动将其附加到账本。这种事件驱动的警报可以确保没有交易未被记录。