这是一本关于 Beancount 语言的用户手册,它是命令行复式记账系统。Beancount 定义了一种计算机语言,允许你在文本文件中输入财务交易,并从中提取各种报告。它是一种通用的计数工具,可用于多种货币、按成本持有的商品(如股票),甚至允许你跟踪不寻常的东西,如假期时间、航空里程和奖励积分,以及其他任何你可能想要计算的东西,甚至是豆子。
本文介绍了 Beancount 的语法和一些必要的技术细节,以便人们了解它是如何进行计算的。本文件不提供复式记账法的介绍,也不提供在输入文件中输入交易的例子和指南,更不提供如何运行工具。这些主题都有自己的专门文件,建议你在进入本用户手册之前,先看一下这些文件。本手册涵盖了使用 Beancount 的技术细节。
Beancount 是一种声明性语言。输入由一个文本文件组成,主要包含一系列指令或条目(我们在代码和文档中可交替地使用这些术语) ; 还有用于定义各种选项的语法。每个指令都以一个关联的日期开始,该日期确定指令将应用的时间点,以及它的类型,该类型定义该指令代表的事件类型。所有的指令都以这样的语法开头:
YYYY-MM-DD <type> ...
其中 YYYY 为年份,MM 为数字月份,DD 为数字日期。所有数字都是必需的,例如,2007年5月7日应该是“2007-05-07”,包括它的零。Beancount 支持使用破折号(如“2014-02-03”)或斜杠相同顺序(如“2014/02/03”)的国际 iso8601 标准格式。
这里有一些指令的例子,只是为了给你一个美学的概念:
2014-02-03 open Assets:US:BofA:Checking
2014-04-10 note Assets:US:BofA:Checking "Called to confirm wire transfer."
2014-05-02 balance Assets:US:BofA:Checking 154.20 USD
经过解析的输入文件的最终结果是数据结构中这些条目的简单列表。在 Beancount 的所有操作都是在这些条目上进行的。
每个特定的指令类型都在下面的章节中有记录。
指令的声明顺序并不重要。实际上,在解析之后和处理之前,会按时间顺序对条目进行重新排序。这是该语言的一个重要特性,因为它使您可以按照自己喜欢的方式组织输入文件,而不必担心会影响指令的含义。
除了交易之外,每个指令都假定发生在每天的开始。例如,你可以声明一个账户在第一次交易的同一天开立:
2014-02-03 open Assets:US:BofA:Checking
2014-02-03 * "Initial deposit"
Assets:US:BofA:Checking 100 USD
Assets:Cash -100 USD
然而,如果你假设立即关闭该账户,你不能在同一天声明关闭,你必须将日期延后,在 02-04 声明关闭。
2014-02-04 close Assets:US:BofA:Checking
这也解释了为什么在同一日期发生的任何交易之前要验证余额断言。这是为了保持一致性。
Beancount 将商品积累在账户中。这些账户的名称在文件中使用前不需要声明,仅凭其语法就可以被识别为 “账户”。账户名称是一个用冒号分隔的大写单词列表,以字母开头,其第一个单词必须是五个账户类型中的一个: ( 请注意 :存在一个”Open”指令,用于提供每个帐户的开始日期。它可以位于文件中的任何位置,因此在使用帐户名之前,它不必出现在文件的某个地方。你可以马上开始在交易中使用帐户名,尽管所有接收到他们的帐户名最终都必须有一个相应的 Open 指令,该指令的日期在所有交易之前。)
Assets
Liabilities
Equity
Income
Expenses
账户名称的每个组成部分都以大写字母或数字开头,后面是字母、数字或破折号(-)字符。所有其他字符是不允许的。
下面是一些现实的账户名称例子:
Assets:US:BofA:Checking
Liabilities:CA:RBC:CreditCard
Equity:Retained-Earnings
Income:US:Acme:Salary
Expenses:Food:Groceries
在一个输入文件中看到的所有账户名称的集合隐含地定义了一个账户的层次结构(有时称为账户图表),类似于文件系统中的文件组织方式。例如,以下账户名称:
Assets:US:BofA:Checking
Assets:US:BofA:Savings
Assets:US:Vanguard:Cash
Assets:US:Vanguard:RGAGX
Assets:Receivables
隐式地声明了一个账户树,看起来像这样:
`-- Assets |-- Receivables `-- US |-- BofA | |-- Checking | `-- Savings `-- Vanguard |-- Cash `-- RGAGX
我们会说,”Assets:US:BofA “是 “Assets:US:BofA:Checking “的父账户,而后者是前者的子账户。
账户包含货币,我们有时也称其为商品(这两个词我们可以互换使用)。与账户名称一样,货币名称也是通过其语法来识别的,不过,与账户名称不同的是,它们在使用前不需要声明。货币的语法是一个全部用大写字母表示的词,如以下所示:
USD
CAD
EUR
MSFT
IBM
AIRMILE
(从技术上讲,一个货币名称可以长达24个字符,必须以大写字母开头,必须以大写字母或数字结尾,其他字符必须只是大写字母、数字或限于这些字符的标点符号: '._-
(单引号,句号,下划线,破折号)
前三种可能会让你想起真实世界的货币(美元、加元、欧元),接下来的两种可能是股票代码(微软和 IBM),最后一项: 奖励积分(航空里程)。Beancount 不知道这样的事情,从它的角度来看,所有这些工具都被类似的对待。不存在任何先前存在的货币的内在概念。这些货币名称只是“物品”的名称,可以放在账户中,并在与这些账户相关的库存中积累。
没有“特殊”的货币单位,所有商品都被同等对待,这一点很优雅:Beancount 本身就是一个多货币系统。如果您像我们中的许多人一样,是一名外籍人士,您的生活被划分在两个或三个大洲之间,您会很欣赏这一点,您可以毫无问题地处理国际账本。
而且你对货币的使用可以变得很有创意:例如,你可以为你的家创建一种货币(如MYLOFT),一种计算累积假期时间的货币(VACHR),或者一种计算每年允许向你的退休账户缴纳的潜在款项的货币(IRAUSD)。你实际上可以通过这种方式解决很多问题。这本食谱描述了许多这样的具体例子。
Beancount不支持美元符号语法,例如,”$120.00”。你应该在你的输入文件中始终使用货币名称。这使输入更有规律,是一种设计选择。对于货币单位,我建议你使用标准的ISO 4217货币代码作为准则;这些代码很快就会变得熟悉。然而,如上所述,您可以在货币名称中包含一些其他字符,如下划线(_)、破折号(-)、句号(.)或单引号(’),但不能有空格。
最后,你会注意到,存在一个 commodity
指令,可以用来声明货币。它是完全可选的:货币在你使用它们时就会出现,该指令的目的只是为了给它附加元数据。
每当我们需要插入一些文本作为条目的一部分时,应该用双引号将其包围。这主要适用于收款人和叙述字段;基本上任何不是日期、数字、货币、账户名的东西。
字符串可以被分割成多行。(有多行的字符串将包括它们的换行字符,在渲染时需要相应地处理这些字符)。
Beancount输入文件并不打算只包含你的指令:你可以在其中自由地放置注释和标题来组织你的文件。任何在字符“;”之后的文本都会被忽略,像这样的文本。
; I paid and left the taxi, forgot to take change, it was cold.
2015-01-01 * "Taxi home from concert in Brooklyn"
Assets:Cash -20 USD ; inline comment
Expenses:Taxi
如果你愿意,你可以使用一个或多个“;”字符。如果你想输入更大的注释文本,请在所有行上预置。如果你希望注释文本被解析并呈现在你的期刊中,请参见本文档中其他地方的注释指令。
任何不是以有效的 Beancount 语法指令开始的行(例如:以日期开始)都会被默默地忽略。这样,你就可以插入标记来组织你的文件,以适应各种大纲模式,例如 Emacs 中的 org 模式。例如,你可以像这样按机构组织你的输入文件,并独立地折叠和展开每个部分:
\* Banking
\** Bank of America
2003-01-05 open Assets:US:BofA:Checking
2003-01-05 open Assets:US:BofA:Savings
;; Transactions follow …
\** TD Bank
2006-03-15 open Assets:US:TD:Cash
;; More transactions follow …
不匹配的行被简单地忽略了。
请访问 Ledger
的用户注意。在 Ledger
中,“;”既可用于标记评论,也可用于在帖子中附加 “Ledger标签”(Beancount元数据),而在Beancount中则不是这样。在Beancount中,注释永远只是注释,元数据有它自己独立的语法。
关于指令语法的快速参考和概述,请查阅语法小抄。
所有的账户都需要被声明为 open
,以便接受向其存入的金额。要做到这一点,你要写一个类似这样的指令:
2014-05-01 open Liabilities:CreditCard:CapitalOne USD
open
指令的一般格式是:
YYYY-MM-DD open Account [ConstraintCurrency,...] ["BookingMethod"]
逗号分隔的约束货币列表,强制要求该账户的所有变化都以声明的货币之一为单位。建议指定一个货币约束:您为Beancount提供的约束越多,您就越不可能犯数据输入错误,因为如果您犯了错误,它就会警告您。
每个账户都应该在一个特定的日期被声明为 open
,这个日期要早于(或与)第一个向该账户入账的交易日期相同。明确一点: open
指令不一定要出现在文件中的交易之前,而是 open
指令的日期必须在该账户的入账日期之前。文件中声明的顺序并不重要。因此,举例来说,这是一个合法的输入文件:
2014-05-05 * "Using my new credit card"
Liabilities:CreditCard:CapitalOne -37.45 USD
Expenses:Restaurant
2014-05-01 open Liabilities:CreditCard:CapitalOne USD
1990-01-01 open Expenses:Restaurant
开户的另一个可选的声明是 “预订方法”,这是在减少手数导致库存中的匹配手数选择不明确(0、2或更多手数匹配)时,将调用的算法。目前它可能采取的数值是:
- STRICT:批次规格必须与一个批次完全匹配。这是默认的方法。如果调用这个预订方法,它将直接引发一个错误。这确保你的输入文件明确地选择了所有匹配的批次。
- NONE:不进行批量匹配。任何价格的手数都可以接受。允许同一货币的手数有正有负。(类似于Ledger处理匹配的方式......它忽略匹配)。
与 open
指令类似,有一个 close
指令可以用来告诉 Beancount 某个帐户已变为非活动状态,例如:
; Closing credit card after fraud was detected.
2016-11-28 close Liabilities:CreditCard:CapitalOne
close 指令的一般格式是:
YYYY-MM-DD close Account
这个指令有几种用法:
- 如果您在该帐户的截止日期之后发布数额(这是一个完整性检查) ,则会引发错误消息。这有助于避免数据输入错误。
- 它可以帮助报告代码找出哪些账户仍在活动,并过滤掉报告期以外的已关闭账户。当你的分类账随着时间的推移积累了很多数据时,这一点尤其有用,因为会有一些账户停止存在,而你不想在其关闭后的几年内看到报告。
请注意,关闭指令目前并不产生一个隐含的零余额检查。你可能想在关闭日期前添加一个,以确保账户被正确关闭,内容为空。 目前,一旦一个账户被关闭,你就不能在该日期后重新打开它。(当然,你可以删除或注释掉关闭它的指令)。最后,代码中还有一些实用功能,允许你确定哪些账户在某个特定日期是开放的。我强烈建议你在账户实际关闭时关闭它们,这将使你的账本更加整洁。
有一个 commodity
指令,可以用来申报货币、金融工具、商品(在Beancount中对同一事物的不同称呼)。
1867-07-01 commodity CAD
commodity
指令的一般格式是:
YYYY-MM-DD commodity Currency
这个指令姗姗来迟,而且完全是可有可无的:你可以使用货币,而不必真的这样声明它们。这个指令的目的是在其上附加货币特定的元数据字段,以便以后可以由插件收集。例如,你可能想为每个货币提供一个长的描述性名称,比如“瑞士法郎”代表货币 “CHF”,或者 “Hooli Corporation Class C Shares ” 代表 “HOOL”,像这样:
1867-07-01 commodity CAD
name: "Canadian Dollar"
asset-class: "cash"
2012-01-01 commodity HOOL
name: "Hooli Corporation Class C Shares"
asset-class: "stock"
例如,一个插件可以收集元数据属性名称,并按资产类别进行一些汇总。 你可以对一个货币使用任何日期......但一个相关的日期是它被创造或引入的日期,例如,加元是在1867年首次引入的,ILS(新以色列谢克尔)是自1986-01-01开始使用的。对于一个公司来说,公司成立和股份创建的日期可能是一个好的日期。由于本指令的主要目的是收集每个货币的信息,你选择的特定日期并不重要。 同一商品申报两次是一个错误。
交易是分类账中最常见的指令类型。它们与其他指令略有不同,因为它们后面可以有一个过账清单。下面是一个例子:
2014-05-05 txn "Cafe Mogador" "Lamb tagine with wine"
Liabilities:CreditCard:CapitalOne -37.45 USD
Expenses:Restaurant
与其他所有指令一样,交易指令以 YYY-MM-DD
格式的日期开始,后面是指令名称,在这里是 “txn”。 然而,由于交易是我们复式系统存在的理由,因此是迄今为止最常见的指令类型,应该出现在Beancount输入文件中,我们做了一个特例,允许用户省略 “txn “关键字,只使用一个标志来代替它:
2014-05-05 * "Cafe Mogador" "Lamb tagine with wine"
Liabilities:CreditCard:CapitalOne -37.45 USD
Expenses:Restaurant
标志是用来表示交易的状态,标志的具体含义由你来定义。我们建议对其使用以下解释:
- *: 已完成的交易,已知金额,”这看起来是正确的”。
- !: 不完整的交易,需要确认或修改,”这看起来不正确”。
在第一个例子中,使用 txn
使交易不被标记,默认的标志 *
将被设置在交易对象上。(我几乎总是使用 *
的变体,从不使用关键字,这主要是为了与所有其他指令格式保持一致)。
如果你想特别标记交易的一个分支,你也可以在发布内容本身附加标志:
2014-05-05 * "Transfer from Savings account"
Assets:MyBank:Checking -400.00 USD
! Assets:MyBank:Savings
这在去掉交易的中间阶段是很有用的(详见《入门指南》)。
交易指令的一般格式是:
YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Flag] Account Amount [{Cost}] [@ Price] [Flag] Account Amount [{Cost}] [@ Price] ...
第一行之后的几行是 “过账”。你可以在一个交易中附加任意多的过帐。例如,一个工资条目可能是这样的:
2014-03-19 * "Acme Corp" "Bi-monthly salary payment"
Assets:MyBank:Checking 3062.68 USD ; Direct deposit
Income:AcmeCorp:Salary -4615.38 USD ; Gross salary
Expenses:Taxes:TY2014:Federal 920.53 USD ; Federal taxes
Expenses:Taxes:TY2014:SocSec 286.15 USD ; Social security
Expenses:Taxes:TY2014:Medicare 66.92 USD ; Medicare
Expenses:Taxes:TY2014:StateNY 277.90 USD ; New York taxes
Expenses:Taxes:TY2014:SDI 1.20 USD ; Disability insurance
“过帐”中的金额也可以是一个算术表达式,使用 ( ) * / - +
。比如:
2014-10-05 * "Costco" "Shopping for birthday"
Liabilities:CreditCard:CapitalOne -45.00 USD
Assets:AccountsReceivable:John ((40.00/3) + 5) USD
Assets:AccountsReceivable:Michael 40.00/3 USD
Expenses:Shopping
对过账的关键和唯一的限制是其余额之和必须为零。这一点将在下文中详细解释。
也可以将元数据附加到交易和(或)其任何过帐上,所以完全通用的格式是:
YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... ...
请参阅下面关于元数据的专门章节。
一项交易可以有一个可选的“收款人”和(或)“叙述”。在上面的第一个例子中,收款人是 “Cafe Mogador”,叙述是 “Lamb tagine with wine”。 收款人是一个字符串,代表参与交易的一个外部实体。收款人有时在交易中很有用,它将金额记入支出账户,账户中累积了来自多个企业的费用类别。一个很好的例子是 “Expenses:Restaurant”,它将包括一个人可能访问的各种餐厅的所有过账。 叙述是你写的关于交易的描述。它可以是对背景的评论,陪同你的人,关于你买的产品的一些说明......任何你想要的东西。你可以插入任何你喜欢的东西,我喜欢在对账时插入备注,这很便捷,而且我可以在以后参考我的备注,例如回答 “去年冬天我和安德烈亚斯在西区参观的那个很棒的小寿司店叫什么名字?” 如果你在交易行上放置一个单一的字符串,它就会成为其叙述:
2014-05-05 * "Lamb tagine with wine"
…
如果你只想设置一个收款人,放一个空的旁白字符串:
2014-05-05 * "Cafe Mogador" ""
…
出于传统的原因,在这些字符串之间可以接受一个管道符号(”|”)(但这将在未来的某个时候被删除)。
2014-05-05 * "Cafe Mogador" | ""
…
您也可以省略以下任一选项(但必须提供一个标志) :
2014-05-05 *
…
Ledger 用户注意事项 Ledger 没有单独的叙述和收款人字段,它只有一个字段,由“收款人”元数据标记引用,叙述最终以保存的注释(“持久性注释”)结束。在 Beancount 中,一个交易对象只有两个字段: 收款人和叙述,其中收款人在很多时候恰好是一个空值。 或更深入地讨论如何以及何时使用受款人或不使用受款人,请参阅 Payees, Subaccounts, and Assets.
过账是指从一个账户中存入或提取的单一金额。最简单的过账类型只包括其金额:
2012-11-03 * "Transfer to pay credit card"
Assets:MyBank:Checking -400.00 USD
Liabilities:CreditCard 400.00 USD
如果你从另一种货币转换金额,你必须提供一个转换率来平衡交易(见下一节)。这是通过在过帐时附加一个“价格”来完成的,这个价格就是转换率:
2012-11-03 * "Transfer to account in Canada"
Assets:MyBank:Checking -400.00 USD @ 1.09 CAD
Assets:FR:SocGen:Checking 436.01 CAD
您还可以使用 @@
语法来指定总成本:
2012-11-03 * "Transfer to account in Canada"
Assets:MyBank:Checking -400.00 USD @@ 436.01 CAD
Assets:FR:SocGen:Checking 436.01 CAD
Beancount 会自动计算出每单位的价格,即1.090025加元(注意,最后两个小数的精度会有所不同)。 在交易之后,我们对跟踪存入账户的美元单位的汇率不感兴趣:这些“美元”单位只是被存入。从某种意义上说,它们的转换率已经被遗忘。
然而,你存入账户的一些商品必须 “按成本持有”。当你想跟踪这些商品的成本基础时,就会发生这种情况。典型的使用情况是投资,例如,当你在一个账户中存入股票时。在这种情况下,你想要的是将你存入的商品的收购成本附在这些单位上,这样当你以后移除这些单位时(当你卖出时),你应该能够通过成本来确定移除哪些单位,以控制税收的影响(并避免错误)。你可以想象,对于账户中的每个单位,都有一个附加的成本基础。这将使我们以后能够自动计算资本收益。
为了指定一个账户的过账要以特定的成本持有,将成本放在大括号里。
2014-02-11 * "Bought shares of S&P 500"
Assets:ETrade:IVV 10 IVV {183.07 USD}
Assets:ETrade:Cash -1830.70 USD
这是一个更深入讨论的主题。请参考“库存如何运作”和“使用Beancount进行交易”文件,以深入讨论这些主题。
最后,你可以在过帐上同时包括成本和价格。
2014-07-11 * "Sold shares of S&P 500"
Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD
Assets:ETrade:Cash 1979.90 USD
Income:ETrade:CapitalGains
该价格只用于在价格数据库中插入一个价格条目(见下面的价格部分)。在本文件的“平衡发布”部分将详细讨论这个问题。
重要提示: 指定为每份或总价格或成本的金额总是无符号的。使用负号或负成本是一个错误,如果您试图这样做,Beancount将引发一个错误。
复式记账法的一个关键方面是确保所有货币的记账金额之和等于零。这是产生会计等式的核心、不可协商的条件,并使其有可能过滤任何交易的子集,得出平衡为零的资产负债表。 但是随着不同类型的单位、先前引入的价格转换和 “按成本持有 “的单位,这一切意味着什么呢? 这很简单:我们设计了一个简单明了的规则,从每条过帐中获得一个数额和一种货币,用于平衡它们。我们称其为过帐的“权重”或平衡金额。下面是一个从使用四种可能的成本/价格组合的过账中获得权重的简短例子:
YYYY-MM-DD Account 10.00 USD -> 10.00 USD Account 10.00 CAD @ 1.01 USD -> 10.10 USD Account 10 SOME {2.02 USD} -> 20.20 USD Account 10 SOME {2.02 USD} @ 2.50 USD -> 20.20 USD