开始使用 Beancount

Martin Blais,2014 年 7 月

http://furius.ca/beancount/doc/getting-started

引言

本文档是创建您的第一个 Beancount 文件的入门指南,内容包括初始化一些选项、组织文件的建议,以及如何声明账户并确保其初始余额不会引发错误。如果您使用 Emacs,本文还包含一些相关配置说明。

您可能需要先阅读一些 用户手册,以熟悉语法和可用的指令;或者,如果您已经设置了文件或知道如何操作,可以直接进入 食谱。如果您熟悉 Ledger,建议先阅读 Ledger 与 Beancount 的区别

编辑器支持

Beancount 账本是简单的文本文件。您可以使用任何文本编辑器编写输入文件。然而,一个能够理解 Beancount 语法并提供语法高亮、自动补全和自动缩进等功能的优秀编辑器,将极大提升您编译和维护账本的效率。

Emacs

Emacs 中编辑 Beancount 账本文件的支持传统上随 Beancount 一同发布,现在已作为一个独立项目托管在 此 GitHub 仓库 中。

Vim

Vim 中编辑 Beancount 账本文件的支持由 Nathan Grigg 实现,可在 此 GitHub 仓库 中获取。

Sublime

Sublime 中编辑 Beancount 账本文件的支持由 Martin Andreas Andersen 贡献,可在 此 GitHub 仓库 或作为 Sublime 插件 此处 获取。

VSCode

有多个插件可用于处理 Beancount 文本文件,其中包括 Lencerf 开发的 VSCode-Beancount

创建您的第一个输入文件

开始之前,让我们创建一个包含两个账户和一笔交易的最小输入文件。将以下内容输入或复制到一个文本文件中:

2014-01-01 open Assets:Checking
2014-01-01 open Equity:Opening-Balances

2014-01-02 * "Deposit"
  Assets:Checking           100.00 USD
  Equity:Opening-Balances

语法简要概述

几点说明及 Beancount 语法的极简概览:

  • 货币代码必须全部使用大写字母(允许包含数字及某些特殊字符,如“_”或“-”)。不支持货币符号(如 $ 或 €)。

  • 账户名称不允许包含空格(但可以使用连字符),且必须至少包含两个由冒号分隔的组成部分。每个账户组成部分必须以大写字母或数字开头。

  • 描述字符串必须用引号括起来,例如:"AMEX PMNT"

  • 日期仅支持 ISO8601 格式,即 YYYY-MM-DD。

  • 标签必须以 “#” 开头,链接必须以 “^” 开头。

有关语法的完整说明,请参阅用户手册

验证您的文件

Beancount 的目的是从您的输入文件生成报告(通过控制台或其网页界面)。但您也可以使用一个工具仅加载文件内容并进行一些验证检查,以确保输入中没有错误。Beancount 的校验非常严格;此工具可在您录入数据时使用,以确保输入文件合法。该工具名为“bean-check”,使用方法如下:

bean-check /path/to/your/file.beancount

现在请对上一节中创建的文件尝试运行此命令。它应无任何输出退出。如果存在错误,错误信息将打印在控制台上。错误信息采用 Emacs 默认支持的格式,因此您可以使用 Emacs 的内置函数 next-errorprevious-error 将光标直接跳转到问题位置。

查看网页界面

查看报告的一个便捷方式是使用“bean-web”工具打开您的输入文件。尝试一下:

bean-web /path/to/your/file.beancount

然后,您可以在网页浏览器中访问http://localhost:8080,浏览 Beancount 生成的各种报告。您可以修改输入文件后,刷新浏览器页面——bean-web 会自动重新加载文件内容。

此时,您可能需要阅读一下语言语法文档。

如何组织您的文件

本节提供组织文件的一般性建议。前提是您已阅读过语言语法文档。

输入文件的开头部分

我建议您从单个文件开始1。我的文件开头包含一个告诉 Emacs 如何打开该文件的头部信息,以及一些常用选项:

;; -*- mode: beancount; coding: utf-8; fill-column: 400; -*-
option "title" "My Personal Ledger"
option "operating_currency" "USD"
option "operating_currency" "CAD"

标题选项用于报告中。“运营货币”列表标识了您最常作为“货币”使用的商品,这些商品将在报告中单独显示为专用列(此声明对任何计算行为均无其他影响)。

章节与账户声明

我喜欢将输入文件按每个真实账户划分成多个部分。每个部分通过使用 Open 指令定义与该真实账户相关的所有账户。例如,这是一个支票账户:

2007-02-01 open Assets:US:BofA:Savings              USD
2007-02-01 open Income:US:BofA:Savings:Interest     USD

我喜欢尽可能明确声明货币限制,以避免错误。同时请注意,我为该账户专门声明了一个收入账户。这有助于在报税时细分收入,因为你很可能会收到与该账户收入相关的税务文件(在美国,这通常是银行出具的 1099-INT 表格)。

对于投资账户,其开户账户可能如下所示:

2012-03-01 open Assets:US:Etrade:Main:Cash            USD
2012-03-01 open Assets:US:Etrade:Main:ITOT            ITOT
2012-03-01 open Assets:US:Etrade:Main:IXUS            IXUS
2012-03-01 open Assets:US:Etrade:Main:IEFA            IEFA
2012-03-01 open Income:US:Etrade:Main:Interest        USD
2012-03-01 open Income:US:Etrade:Main:PnL             USD
2012-03-01 open Income:US:Etrade:Main:Dividend        USD
2012-03-01 open Income:US:Etrade:Main:DividendNoTax   USD

关键是,所有这些账户都以某种方式相关联。本手册的各个部分将描述为每个类别建议创建的账户集合。

并非所有部分都必须如此组织。例如,我的结构包含以下部分:

  • 永久账户。 我在文件顶部设置了一个专门部分,用于存放特殊且“永久性”的账户,例如应付款和应收款。

  • 日记账。 我在文件底部设置了一个“日记账”部分,按时间顺序记录所有现金支出。

  • 支出账户。 我的所有支出账户(分类)都在独立的部分中定义。

  • 雇主。 对于每个雇主,我都设置了一个部分,用于记录其直接存款、休假、股票归属及其他与工作相关的交易。

  • 税务。 我为税务设置了一个部分,并按纳税年度进行组织。

你可以按任何你喜欢的方式组织,因为 Beancount 不关心声明的顺序。

关闭账户

如果某个真实账户已关闭,或不再有任何交易发生,你可以使用 Close 指令在特定日期将其标记为“已关闭”:

; Moving to another bank.
2013-06-13 close Assets:US:BofA:Savings

这会告诉 Beancount 不在任何不包含该账户活跃日期的报表中显示该账户。同时,如果之后你尝试向该账户记账,系统会触发错误,从而避免错误发生。

去重

一个经常出现的问题是,一旦你设置了某种代码或流程,用于从下载的文件中自动提取记账条目,你最终会导入提供同一笔交易两个不同部分的条目。例如,通过支票账户转账支付信用卡余额。如果你下载支票账户的交易记录,会提取出类似以下内容:

2014-06-08 * "ONLINE PAYMENT - THANK YOU"
  Assets:CA:BofA:Checking           -923.24 USD

信用卡下载则会得到以下内容:

2014-06-10 * "AMEX EPAYMENT    ACH PMT"
  Liabilities:US:Amex:Platinum       923.24 USD

通常,这些账户的交易需要记入支出账户,但在此情况下,这两笔交易实际上是同一笔转账的两个部分。当你导入其中一笔时,通常会查找另一笔并将其合并:

;2014-06-08 * "ONLINE PAYMENT - THANK YOU"
2014-06-10 * "AMEX EPAYMENT    ACH PMT"
  Liabilities:US:Amex:Platinum       923.24 USD
  Assets:CA:BofA:Checking           -923.24 USD

我通常会将其中一条描述保留在注释中——这只是我的习惯,Beancount 会忽略它。另外请注意,我必须从两个日期中选择一个。我只需选择自己偏好的日期,只要不破坏任何余额断言即可。

如果你忘记合并这两笔导入的交易,不必担心!这就是余额断言的用途。定期在其中一个账户中添加余额断言,例如每次导入时都添加一次,如果你不小心重复录入了交易,系统会给出明确的错误提示。这种情况非常常见,久而久之,你就能迅速识别并修复这类编译器错误。

最后,当我确定只导入了其中一方时,我会手动选择另一方账户,并将我知道稍后会被导入的那笔记账标记为一个标志,以提醒我尚未去重这笔交易:

2014-06-10 * "AMEX EPAYMENT    ACH PMT"
  Liabilities:US:Amex:Platinum       923.24 USD
  ! Assets:CA:BofA:Checking

稍后当我导入支票账户的交易并寻找这笔付款的另一方时,我会发现这个标记,并感到世界正按预期运行。

(如果你对去重和合并交易的更多讨论感兴趣,请参阅此功能提案。此外,你可能对“effective_date”插件这一外部贡献感兴趣,它能将一笔交易拆分为两笔。)

哪一方?

因此,如果你按照我上面建议的方式将账户分段组织,那么这些涉及两个独立账户的“合并”交易应保留在文件的哪个部分呢?这取决于你的选择。例如,对于两个分别拥有独立章节的账户之间的转账,理想情况下你希望将两笔交易都保留在各自章节中,这样在编辑输入文件时,你可以在任一章节中看到它们;但遗憾的是,一笔交易在文档中只能出现一次。你必须做出选择。

我个人对选择哪个章节保留交易并不太在意;有时我会选择其中一个账户的章节,有时则选择另一个账户的章节来存放同一对账户的交易。这从未成为问题,因为我使用 Emacs 并频繁使用 i-search,这让我能轻松在庞大的输入文件中查找内容。如果你希望保持输入文件更整洁有序,可以为自己制定一条规则,例如:“信用卡支付始终保留在付款账户的章节中,而非信用卡账户的章节中”,或者你也可以将交易同时保留在两个章节中,但注释掉其中一条2

填充余额

如果你刚开始使用——而你很可能正是如此,因为你正在阅读本文——那么你将没有任何历史数据。这意味着 Beancount 中你的资产和负债账户余额都将为零。但在定义了一些账户之后,你首先应该做的是建立资产负债表,并将这些账户的余额调整为当前实际金额。

以你的支票账户为例,假设你不久前开设了该账户。你记不清确切的开户日期,因此我们使用一个近似日期:

2000-05-28 open Assets:CA:BofA:Checking  USD

接下来,你需要查询当前余额,并为相应金额添加一条余额断言:

2014-07-01 balance Assets:CA:BofA:Checking    1256.35 USD

仅运行此 Beancount 文件会正确产生错误,因为 Beancount 假设在账户开启时存在一个隐式的“空余额”断言。你必须在账户开启与当前余额之间某个时间点插入一个余额调整,将账户余额调整至今日水平,该调整应与某个权益账户关联——这是一个任意指定的、用于记录“初始余额来源”的位置。通常,这个账户就是“Equity:Opening-Balances”。因此,让我们加入这个调整交易,并回顾一下目前的内容:

2000-05-28 open Assets:CA:BofA:Checking  USD

2000-05-28 * "Initialize account"
  Equity:Opening-Balances                    -1256.35 USD
  Assets:CA:BofA:Checking                     1256.35 USD

2014-07-01 balance Assets:CA:BofA:Checking    1256.35 USD

从这里开始,你将开始添加反映 7 月 1 日之后所有交易的条目。但如果你想要回溯到过去呢?一旦你完成了会计科目表的设置,很自然地希望补全缺失的历史记录,至少回溯到今年年初,这是完全合理的。

假设你在 2014 年 6 月有一笔交易,我们来添加它:

2000-05-28 open Assets:CA:BofA:Checking  USD

2000-05-28 * "Initialize account"
  Equity:Opening-Balances                    -1256.35 USD
  Assets:CA:BofA:Checking                     1256.35 USD

2014-06-28 * "Paid credit card bill"
  Assets:CA:BofA:Checking                     -700.00 USD
  Liabilities:US:Amex:Platinum                 700.00 USD

2014-07-01 balance Assets:CA:BofA:Checking    1256.35 USD

现在余额断言失败了!你需要调整初始化条目来修复这个问题:

2000-05-28 open Assets:CA:BofA:Checking  USD

2000-05-28 * "Initialize account"
  Equity:Opening-Balances                    -1956.35 USD
  Assets:CA:BofA:Checking                     1956.35 USD

2014-06-28 * "Paid credit card bill"
  Assets:CA:BofA:Checking                     -700.00 USD
  Liabilities:US:Amex:Platinum                 700.00 USD

2014-07-01 balance Assets:CA:BofA:Checking    1256.35 USD

现在它正常工作了。基本上,每次你在过去插入一条交易时,都必须调整余额。这很烦人吗?是的,确实如此。

幸运的是,我们可以提供一些帮助:你可以使用 Pad 指令来替代并自动合成余额调整,使其与下一个余额检查匹配,如下所示:

2000-05-28 open Assets:CA:BofA:Checking  USD

2000-05-28 pad Assets:CA:BofA:Checking Equity:Opening-Balances

2014-06-28 * "Paid credit card bill"
  Assets:CA:BofA:Checking                     -700.00 USD
  Liabilities:US:Amex:Platinum                 700.00 USD

2014-07-01 balance Assets:CA:BofA:Checking    1256.35 USD

请注意,这仅适用于资产负债表账户(资产和负债),因为我们并不关心收入和费用账户的初始余额,我们只关心它们的变动值(即在期间内发生的变动)。例如,将“Expenses:Restaurant”账户的余额调整为你自出生以来所有餐饮消费的总和,是没有意义的。

因此,你可能希望为每个资产和负债账户都使用 Open 和 Pad 指令。

接下来是什么?

此时,你可能会继续前往食谱,或者如果你尚未阅读过,可以阅读用户手册


  1. 你可能会想把一个大文件拆分成多个小文件,但尤其是在初期,将所有内容集中在一个地方非常方便。

  2. 有人建议 Beancount 可以通过启发式方法自动检测重复交易并自动忽略(删除)其中一条,但这一功能尚未被尝试。特别是,这种机制非常适合按章节甚至按文件组织交易,即每个文件都包含其所代表账户的所有交易。如果你有兴趣实现此功能,可以轻松将其作为插件开发,而不会影响系统的其他部分。