Beancount 语言语法

Martin Blais,更新于:2016 年 4 月

http://furius.ca/beancount/doc/syntax

引言

语法概览

指令

指令的顺序

账户

商品/货币

字符串

注释

指令

开户

销户

商品

交易

元数据

交易对手与说明

成本与价格

平衡规则 - 记录的“权重”

减少持仓

金额插值

标签

标签堆栈

链接

余额断言

多种商品

大量数据被聚合

父账户检查

关闭前

填充

未使用的 Pad 指令

商品

成本基础

多重填充

备注

文档

来自目录的文档

价格

来自分录的价格

同一天的价格

事件

查询

自定义

元数据

选项

操作货币

插件

包含文件

接下来做什么?

引言

这是 Beancount 的用户手册,Beancount 是一个命令行复式记账系统。Beancount 定义了一种计算机语言,允许您在文本文件中输入财务交易,并从中提取各种报表。它是一个通用的计数工具,支持多种货币、按成本计价的商品(例如股票),甚至允许您追踪一些不寻常的项目,如休假时数、航空里程、奖励积分,以及您可能想记录的任何其他事物,甚至包括豆子。

本文档介绍了 Beancount 的语法以及理解其计算方式所需的一些技术细节。本文档提供复式记账方法的入门动机说明,也不提供输入文件中交易录入的示例和指南,以及如何运行工具。这些主题均有其独立的专用文档,建议您在深入本用户手册之前先查阅这些文档。本手册涵盖的是使用 Beancount 的技术细节。

语法概览

指令

Beancount 是一种声明式语言。输入由一个文本文件组成,其中主要包含一系列指令(或条目;在代码和文档中这两个术语可互换使用);此外还有定义各种选项的语法。每个指令都以一个关联的日期开头,该日期决定了该指令生效的时间点,以及其类型,该类型定义了此指令所代表的事件种类。所有指令均以如下格式开头:

YYYY-MM-DD <type> …

其中 YYYY 为年份,MM 为数字月份,DD 为数字日期。所有数字均为必需,例如 2007 年 5 月 7 日应写作“2007-05-07”,包括其中的零。Beancount 支持国际 ISO 8601 标准的日期格式,使用连字符(例如“2014-02-03”)或相同顺序的斜杠(例如“2014/02/03”)。

以下是一些指令示例,供您了解其风格:

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

然而,如果你假设立即关闭了该账户,你不能在同一天声明其已关闭,而必须将日期往后推,声明关闭日期为 2 月 4 日:

2014-02-04 close Assets:US:BofA:Checking

这也解释了为什么余额断言会在同一天发生的任何交易之前进行验证。这是为了保持一致性。

账户

Beancount 会累积账户中的商品。这些账户的名称无需在文件中预先声明,仅凭其语法即可被识别为“账户”1。账户名称是由冒号分隔的、以字母开头的大写单词组成的列表,且第一个单词必须是以下五种账户类型之一:

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

您可以根据需要使用一个或多个 “;” 字符。如果您想输入较长的注释,可以在每行前都加上 “;”。如果您希望注释内容被解析并在您的账本中显示,请参阅本文档其他地方的 Note 指令。

任何不以有效 Beancount 语法指令开头的行(例如不以日期开头)都会被静默忽略。这样,您就可以插入标记来组织文件,以适应各种大纲模式,例如 Emacs 中的 org-mode。例如,您可以按机构组织输入文件,如下所示,并独立折叠和展开每个部分:

* 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 指令的日期必须早于该账户的记账日期。文件中声明的顺序并不重要。例如,以下是一个合法的输入文件:

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

此指令有以下几种用途:

  • 如果在账户关闭日期之后向该账户记入金额,系统将报错(这是一种合理性检查)。这有助于避免录入错误。

  • 它有助于报告代码识别哪些账户仍处于活跃状态,并在报告期之外过滤掉已关闭的账户。当您的账本随着时间积累大量数据时,这一点尤其有用,因为有些账户会停止使用,而您不希望在它们关闭多年后的报告中继续看到它们。

请注意,目前 Close 指令不会自动生成隐式的零余额检查。您可能需要在关闭日期之前手动添加一条检查,以确保账户在关闭时余额为空。

目前,一旦账户被关闭,就无法在该日期之后重新开启。(当然,您可以删除或注释掉关闭该账户的指令。)最后,代码中提供了实用函数,可用于确定特定日期下哪些账户处于开启状态。我强烈建议您在账户实际关闭时立即执行关闭操作,这将使您的账本更加整洁。

商品

存在一个“Commodity”指令,可用于声明货币、金融工具或商品(在 Beancount 中这些是同一事物的不同名称):

1867-07-01 commodity CAD

Commodity 指令的一般格式为:

YYYY-MM-DD commodity Currency

此指令是后期加入的,完全可选:您可以在不显式声明的情况下直接使用商品。该指令的目的是为商品附加特定的元数据字段,以便后续插件可以收集这些信息。例如,您可能希望为每个商品提供一个详细的描述名称,如将“CHF”命名为“瑞士法郎”,或将“HOOL”命名为“Hooli 公司 C 类股票”,如下所示:

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 年 1 月 1 日起投入使用。对于公司而言,公司成立和股票发行的日期可能是一个合适的日期。由于此指令的主要目的是收集每种商品的信息,因此您选择的具体日期并不太重要。

声明同一商品两次是错误的。

交易

交易是账本中最常见的指令类型。它们与其他指令略有不同,因为可以跟随一系列分录。以下是一个示例:

2014-05-05 txn "Cafe Mogador" "Lamb tagine with wine"
  Liabilities:CreditCard:CapitalOne         -37.45 USD
  Expenses:Restaurant

与其他所有指令一样,交易指令以YYYY-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”,说明为“配葡萄酒的羊肉塔吉锅”。

交易对手是一个表示参与交易的外部实体的字符串。在将金额记入费用账户的交易中,交易对手有时很有用,因为账户会汇总来自多个商家的同一类费用。一个很好的例子是“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 中,交易对象仅有两个字段:收款方和说明,其中收款方字段很多时候为空。

有关何时以及如何使用收款方的深入讨论,请参阅 收款方、子账户与资产

成本与价格

分录表示一笔金额存入或支取某个账户。最简单的分录仅包含其金额:

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.00 USD
  Income:ETrade:CapitalGains  -148.30 USD

价格仅用于在价格数据库中插入一条价格记录(详见下方“价格”部分)。有关此内容的更多细节,请参阅本文档的“分录平衡”部分。

重要提示。 所有指定为每股价格或总价格/成本的金额均为无符号。使用负号或负成本是错误的,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

以下是其计算方式的说明:

  1. 如果记录仅包含金额而无成本,则平衡金额即为该记录上的金额和货币。使用上一节的第一个示例,金额为 -400.00 美元,这与第二条记录的 400.00 美元相平衡。

  2. 如果记录仅包含价格,则将价格乘以单位数量,并使用价格的货币。在上一节的第二个示例中,即 -400.00 美元 × 1.09 加元(/美元)= -436.00 加元,这与另一条记录的 436.00 加元2相平衡。

  3. 如果记录包含成本,则将成本乘以单位数量,并使用成本的货币。在上一节的第三个示例中,即 10 股 IVV × 183.08 美元(/IVV)= 1830.70 美元。这与现金记录的 -1830.70 美元相平衡,因此一切正常。

  4. 最后,如果一条记录同时包含成本和价格,我们直接忽略价格。这个可选的价格稍后用于在内存价格数据库中生成条目,但在平衡过程中完全不使用。

通过这一规则,您应该能够轻松平衡所有交易。此外,这一规则还使 Beancount 能够自动为您计算资本收益(详情请参阅使用 Beancount 进行交易)。

减少头寸

当您对账户中的头寸进行减少时,该减少必须始终与一个现有的批次匹配。例如,如果一个账户持有 3200 美元,而一笔交易向该账户记入 -1200 美元的变动,则这 1200 美元将与现有的 3200 美元匹配,结果为一个 2000 美元的头寸。此规则同样适用于负值。例如,如果一个账户的余额为 -1300 美元,而您记入 +2000 美元的变动,则最终余额为 700 美元。

对任何类型的账户进行记账,都可能导致余额为正或为负;对于简单商品数量(即无成本的商品)的余额没有限制。例如,虽然资产账户通常为正余额,而负债账户通常为负余额,但你仍可合法地贷记资产账户使其变为负余额,或借记负债账户使其变为正余额。这是因为现实世界中确实会发生此类情况:你可能开出过多支票,从而暂时从银行支票账户获得透支信用(通常还会伴随一笔高昂的‘透支费’),或因误操作而两次支付了信用卡余额。

对于按成本计价的商品,记账时指定的成本必须与交易前库存中已有的某一批次成本相匹配。系统会收集所有批次,并与记账条目中{...}部分指定的条件进行匹配。例如,如果你指定了成本,则只有成本完全匹配的批次才会保留;如果你指定了日期,则只有日期匹配的批次才会保留;你也可以使用标签进行筛选。若同时指定了成本和日期,则两者将共同作为筛选条件,用于缩小候选批次的范围。这本质上是对批次列表的过滤操作。

如果过滤后的列表仅剩一个批次,则选择该批次进行减少;如果过滤后剩多个批次,但需减少的总数量恰好等于这些批次的总数量,则所有这些批次都将被相应减少。

例如,如果你过去曾有如下交易:

2014-02-11 * "Bought shares of S&P 500"
  Assets:ETrade:IVV                20 IVV {183.07 USD, "ref-001"}
  …

2014-03-22 * "Bought shares of S&P 500"
  Assets:ETrade:IVV                15 IVV {187.12 USD}
  …

以下每种减少操作都是明确无歧义的:

2014-05-01 * "Sold shares of S&P 500"
  Assets:ETrade:IVV               -20 IVV {183.07 USD}
  …

2014-05-01 * "Sold shares of S&P 500"
  Assets:ETrade:IVV               -20 IVV {2014-02-11}
  …

2014-05-01 * "Sold shares of S&P 500"
  Assets:ETrade:IVV               -20 IVV {"ref-001"}
  …

2014-05-01 * "Sold shares of S&P 500"
  Assets:ETrade:IVV               -35 IVV {}
  …

然而,以下情况则会产生歧义:

2014-05-01 * "Sold shares of S&P 500"
  Assets:ETrade:IVV               -20 IVV {}
  …

如果多个批次与减少记账匹配,但其数量不等于总批次数,则会出现模糊匹配的情况。此时,系统将调用该账户的记账方式。虽然有多种记账方式,但默认情况下所有账户均设置为使用“STRICT”(严格)方式。该方式在出现模糊情况时会直接报错。

你可以将账户的记账方式设置为“FIFO”,以指示 Beancount 选择最旧的批次;或设置为“LIFO”,以选择最新的(最年轻)批次。这样系统将自动选择所有必要的匹配批次来完成减少操作。

请注意! 未来将更合理地处理日期匹配的要求。有关此即将变更的详细信息,请参阅库存记账方式改进提案

对于此类记账,导致单位数量为负通常是不可能的。目前 Beancount 不允许持有按成本计价商品的负数量。例如,仅包含以下交易的输入将失败:

2014-05-23 *
  Assets:Investments:MSFT        -10 MSFT {43.40 USD}
  Assets:Investments:Cash     434.00 USD

如果允许这种情况,将导致 MSFT 的余额为 -10 单位。另一方面,如果该账户在 5 月 23 日持有 12 单位 MSFT,成本为 43.40 美元,则该交易将正常记账,将原有 12 单位减少至 2 单位。大多数情况下,出现的错误是账户持有 10 或更多单位的 MSFT,但其成本不同,而用户却错误地指定了成本值。例如,若账户持有 20 单位 MSFT {42.10 美元} 的正余额,则上述交易仍会失败,因为账户中不存在成本为 43.40 美元的 10 或更多单位 MSFT 可供扣除。

这一约束的设定基于以下几个原因:

  • 录入股票单位时的错误并不少见,它们会对您的账本准确性产生重要负面影响——金额通常较大——并且通常会触发此约束的错误。因此,这种错误检查是检测此类错误的有用方法。

  • 以成本计的负单位数量相当罕见,您很可能根本不需要它们。例外情况包括:股票卖空、期货合约的价差持仓,以及根据您的会计方式,外汇交易中的空头头寸。

这就是为什么此检查默认启用的原因。

请注意! 在 Beancount 的未来版本中,我们将适度放宽此约束。我们将允许账户持有某种商品的负单位数量,但前提是该账户中没有其他该商品的单位;或者,我们将允许您将账户标记为完全不受此类约束限制。其目的是允许商品空头头寸账户的存在。唯一阻碍因素就是此约束。

有关库存记账算法的更多详细信息,请参阅库存工作原理文档。

金额插值

Beancount 能够自动填充交易的部分细节。目前,您可以在一笔交易中省略最多一个分录的金额:

2012-11-03 * "Transfer to pay credit card"
  Assets:MyBank:Checking            -400.00 USD
  Liabilities:CreditCard

在上述示例中,信用卡分录的金额已被省略。Beancount 会自动计算为 400.00 美元,以平衡交易。此功能同样适用于多笔分录和带成本的分录:

2014-07-11 * "Sold shares of S&P 500"
  Assets:ETrade:IVV                 -10 IVV {183.07 USD}
  Assets:ETrade:Cash            1979.00 USD
  Income:ETrade:CapitalGains

在此情况下,IVV 的单位以高于买入价($183.07)的价格($197.90)售出。现金第一笔分录的权重为 -10 × 183.07 = -1830.70,第二笔分录则为直接的 $1979.00。最后一笔分录将被赋予差额,即 -148.30 USD,也就是 $148.30 的收益

在计算需平衡的金额时,将使用与检查交易是否平衡为零相同的平衡金额来填充缺失的金额。例如,以下情况不会触发错误:

2014-07-11 * "Sold shares of S&P 500"
  Assets:ETrade:IVV                 -10 IVV {183.07 USD} @ 197.90 USD
  Assets:ETrade:Cash

现金账户将自动收到 1830.70 美元,因为 IVV 分录的余额为 -1830.70 美元(如果分录同时包含成本和价格,则始终使用成本基础,可选的价格将被忽略)。虽然从平衡角度看这是可接受且正确的,但从会计角度看却是不完整的:销售的资本收益需要单独核算;此外,如果您按上述方式操作,存入现金账户的金额将低于实际存款(1979.00 美元),而后续对现金账户的余额断言很可能会通过触发错误来揭示这一疏漏。

最后,当余额包含多种商品时,此功能同样适用:

2014-07-12 * "Uncle Bob gave me his foreign currency collection!"
  Income:Gifts                 -117.00 ILS
  Income:Gifts                -3000.00 INR
  Income:Gifts                 -800.00 JPY
  Assets:ForeignCash

系统将插入多个分录(每种需平衡的商品一个),以替代被省略的分录。

标签

交易可以附加任意字符串标签:

2014-04-23 * "Flight to Berlin" #berlin-trip-2014
  Expenses:Flights              -1230.27 USD
  Liabilities:CreditCard

这类似于 Twitter 等平台流行的“标签”概念。这些标签本质上允许您标记一组交易,然后可将其用作筛选条件,仅针对该子集生成报告。它们有诸多用途,我喜欢用它们来标记我的所有旅行。

也可以指定多个标签:

2014-04-23 * "Flight to Berlin" #berlin-trip-2014 #germany
  Expenses:Flights              -1230.27 USD
  Liabilities:CreditCard

(如果您想在指令上存储键值对,请参见下面的元数据部分。)

标签栈

通常,与单个标签相关的多个交易会连续输入到文件中。为方便起见,解析器可以自动为一段文本中的所有交易打上标签。其工作原理很简单:解析器维护一个当前标签的“栈”,在逐条读取交易时,会将这些标签应用于所有交易。您可以像这样将标签压入或弹出该栈:

pushtag #berlin-trip-2014

2014-04-23 * "Flight to Berlin"
  Expenses:Flights              -1230.27 USD
  Liabilities:CreditCard

poptag #berlin-trip-2014

这样,您无需逐个输入,即可将多个标签批量应用于一长串连续交易。

交易之间也可以相互链接。您可以将链接视为一种特殊类型的标签,用于在时间维度上将一组财务相关的交易归为一类。例如,您可以使用链接将与特定发票相关的所有交易归组。这有助于跟踪与该发票相关的付款(或核销):

2014-02-05 * "Invoice for January" ^invoice-pepe-studios-jan14
  Income:Clients:PepeStudios           -8450.00 USD
  Assets:AccountsReceivable

2014-02-20 * "Check deposit - payment from Pepe" ^invoice-pepe-studios-jan14
  Assets:BofA:Checking                  8450.00 USD
  Assets:AccountsReceivable

或跟踪与单一不良目的相关的多次转账:

2014-02-05 * "Moving money to Isle of Man" ^transfers-offshore-17
  Assets:WellsFargo:Savings          -40000.00 USD
  Assets:WellsFargo:Checking          40000.00 USD

2014-02-09 * "Wire to FX broker" ^transfers-offshore-17
  Assets:WellsFargo:Checking         -40025.00 USD
  Expenses:Fees:WireTransfers            25.00 USD
  Assets:OANDA:USDollar               40000.00

2014-03-16 * "Conversion to offshore beans" ^transfers-offshore-17
  Assets:OANDA:USDollar          -40000.00 USD
  Assets:OANDA:GBPounds           23391.81 GBP @ 1.71 USD

2014-03-16 * "God save the Queen (and taxes)" ^transfers-offshore-17
  Assets:OANDA:GBPounds             -23391.81 GBP
  Expenses:Fees:WireTransfers           15.00 GBP
  Assets:Brittania:PrivateBanking    23376.81 GBP

无论当前视图或过滤的交易集合如何,Web 界面都能在独立的日记账中渲染这些已链接的交易(链接列表是一个全局页面)。

余额核对

余额核对是一种将您的对账单余额输入交易流的方式。它告诉 Beancount 在某个时间点,某个账户中特定商品的单位数量应等于预期值。例如,

2014-12-26 balance Liabilities:US:CreditCard   -3492.02 USD

表示“检查 2014 年 12 月 26 日清晨,账户“Liabilities:US:CreditCard”中 USD 的单位数量是否为 -3492.02 USD”。在处理交易列表时,如果 Beancount 发现 USD 的余额与此不符,将报告错误。

如果没有报告错误,您就可以有相当的信心认为,该账户中在此之前的所有交易很可能都是正确的。这在实际中非常有用,因为在许多情况下,某些交易可能从其各笔分录的账户中单独导入(参见去重问题)。这可能导致您无意中重复记账,而定期插入余额核对可以每次都捕捉到此类问题。

余额指令的一般格式为:

YYYY-MM-DD balance Account Amount

请注意,与所有其他非交易指令一样,余额核对应用于其日期的开始时刻(即当天午夜)。可以想象,余额检查发生在该日午夜刚过的一瞬间。

余额核对仅适用于资产负债表账户(资产和负债)。因为收入和费用账户的分录仅因其临时性价值而有意义——即我们关注的是这些账户在一段时间内的变动总和(而非绝对值),所以在损益表账户上使用余额核对意义不大。

此外,请注意,每个账户在通过 Open 指令打开时,都会隐式地有一个余额核对,即账户在开户日应为空。您无需显式声明开户时余额为零。

多种商品

一个 Beancount 账户可以包含多种商品(尽管在实际使用中这种情况并不常见,但建议为每种商品创建独立的子账户,例如持有股票投资组合)。余额断言仅适用于该断言中指定的商品;其他商品的余额不会被检查。如需检查多种商品,请使用多个余额断言,如下所示:

; Check cash balances from wallet
2014-08-09 balance Assets:Cash     562.00 USD
2014-08-09 balance Assets:Cash     210.00 CAD
2014-08-09 balance Assets:Cash      60.00 EUR

目前尚无方法可以彻底检查账户中所有商品的完整列表(相关提案正在进行中)。

请注意,如果在此示例中你确实需要彻底检查,可以通过为现金账户创建子账户来分别隔离每种商品,例如 Assets:Cash:USDAssets:Cash:CAD

批次被聚合

余额断言适用于特定商品的单位总数,而不考虑其成本。例如,如果你在账户中持有三种相同商品的批次,例如 5 HOOL {500 USD} 和 6 HOOL {510 USD},以下余额检查应能通过:

2014-08-09 balance Assets:Investing:HOOL     11 HOOL

所有批次会被合并,你可以验证其总单位数量。

父账户检查

余额断言可以应用于父账户,并将包含其自身及其子账户的余额:

2014-01-01 open Assets:Investing
2014-01-01 open Assets:Investing:Apple       AAPL
2014-01-01 open Assets:Investing:Amazon      AMZN
2014-01-01 open Assets:Investing:Microsoft   MSFT
2014-01-01 open Equity:Opening-Balances

2014-06-01 *
  Assets:Investing:Apple       5 AAPL {578.23 USD}
  Assets:Investing:Amazon      5 AMZN {346.20 USD}
  Assets:Investing:Microsoft   5 MSFT {42.09 USD}
  Equity:Opening-Balances

2014-07-13 balance Assets:Investing 5 AAPL
2014-07-13 balance Assets:Investing 5 AMZN
2014-07-13 balance Assets:Investing 5 MSFT

请注意,要合法地在余额断言指令中使用父账户,必须先声明该父账户为已开启状态。

关闭前

在关闭账户之前插入一个单位为 0 的余额断言很有用,以确保账户在关闭时为空。关闭指令不会自动为你插入此断言(我们未来可能会为此开发插件)。

本地容差

有时需要针对特定余额断言调整容差值以放宽检查标准,这可以通过在余额金额基础上指定本地容差值来实现,如下所示:

2013-09-20 balance Assets:Investing:Funds     319.020 ~ 0.002 RGAGX

填充

填充指令会自动插入一笔交易,以确保后续的余额断言成功(如需)。它会插入满足该余额断言所需的差额。(在 LaTeX 中,“橡胶空间”是何作用,填充指令在 Beancount 中对余额即起此作用。)

请注意,此处的“后续”是指按日期顺序,而非文件中声明的顺序。这在概念上等同于一笔自动扩展或收缩以填补两个余额断言之间时间差的交易。其形式如下:

2014-06-01 pad Assets:BofA:Checking Equity:Opening-Balances

填充指令的一般格式为:

YYYY-MM-DD pad Account AccountPad

第一个账户是用于贷记自动计算金额的账户。该账户后应紧跟一个余额断言(如果该账户没有余额断言,则填充条目无害且不产生任何效果)。

第二个账户用于交易的另一方,它是资金的来源,几乎总是某种权益账户。这是因为此指令通常用于初始化新账户的余额,从而避免我们手动插入此类指令,或输入将账户余额调整至当前值所需的全部历史交易记录。

以下是一个实际的使用场景示例:

; Account was opened way back in the past.
2002-01-17 open Assets:US:BofA:Checking

2002-01-17 pad Assets:US:BofA:Checking Equity:Opening-Balances

2014-07-09 balance Assets:US:BofA:Checking  987.34 USD

这将在垫付指令之后、同一日期插入如下交易:

2002-01-17 P "(Padding inserted for balance of 987.34 USD)"
  Assets:US:BofA:Checking        987.34 USD
  Equity:Opening-Balances       -987.34 USD

这是一笔正常的交易——您会在渲染后的日记账中看到它与其他交易一同显示。(注意特殊的“P”标记,脚本可据此查找这些交易。)

请注意,如果没有余额断言,垫付指令将毫无意义。因此,与余额断言一样,它们通常仅用于资产负债表账户(资产和负债)。

您也可以在余额断言之间插入垫付条目,同样有效。例如:

2014-07-09 balance Assets:US:BofA:Checking  987.34 USD
… more transactions… 
2014-08-08 pad Assets:US:BofA:Checking Equity:Opening-Balances

2014-08-09 balance Assets:US:BofA:Checking  1137.23 USD

如果没有中间交易,这将插入以下垫付交易:

2014-08-08 P "(Padding inserted for balance of 1137.23 USD)"
  Assets:US:BofA:Checking        149.89 USD
  Equity:Opening-Balances       -149.89 USD

如果这一点不明显,149.89 美元是 1137.23 美元与 987.34 美元之间的差额。如果在支票账户中存在更多中间交易,则金额将自动调整,以确保第二次断言通过。

未使用的垫付指令

您当前不能在输入文件中保留未使用的垫付指令。它们将触发错误:

2014-01-01 open Assets:US:BofA:Checking

2014-02-01 pad Assets:US:BofA:Checking Equity:Opening-Balances

2014-06-01 * "Initializing account"
  Assets:US:BofA:Checking                   212.00 USD
  Equity:Opening-Balances

2014-07-09 balance Assets:US:BofA:Checking  212.00 USD

(这种严格性仍有争议,或许最终可以移至可选插件中。)

商品

请注意,垫付指令完全未指定任何商品。所有在账户中存在相应余额断言的商品都会受到影响。例如,以下代码将使垫付指令为 USD 和 CAD 分别插入一条交易:

2002-01-17 open Assets:Cash

2002-01-17 pad Assets:Cash Equity:Opening-Balances

2014-07-09 balance Assets:Cash    987.34 USD
2014-07-09 balance Assets:Cash    236.24 CAD

如果账户中包含其他未进行余额断言的商品,则不会为这些商品插入任何条目。

成本基础

目前,垫付指令不适用于持有成本计价头寸的账户。该指令主要仅对现金账户有用。(这主要是因为余额断言尚不支持指定成本基础。未来我们可能会支持断言总成本基础,届时或许会考虑支持基于成本基础的垫付。)

多个垫付

您当前不能为同一账户和商品插入多个垫付条目:

2002-01-17 open Assets:Cash

2002-02-01 pad Assets:Cash Equity:Opening-Balances

2002-03-01 pad Assets:Cash Equity:Opening-Balances

2014-04-19 balance Assets:Cash    987.34 USD

(目前有一个提案正在审议中,旨在允许此功能,并将垫付金额平均分配到所有中间的垫付指令之间,但尚未实现。)

备注

备注指令仅用于为特定账户的日记账附加一个带日期的注释,如下所示:

2013-11-03 note Liabilities:CreditCard "Called about fraudulent card."

当您渲染日记账时,该备注将被上下文化显示。这可用于记录与财务事件相关的事实和声明。我经常用它来记录那些无法通过交易条目体现的信息片段。

备注指令的一般格式为:

YYYY-MM-DD note Account Description

描述字符串可以拆分到多行中。

文档

文档指令可用于将外部文件附加到账户的日记账中:

2013-11-03 document Liabilities:CreditCard "/home/joe/stmts/apr-2014.pdf"

文件名将在网络界面对应账户的日记账中显示为浏览器链接,您应该能够点击它来查看文件本身的内容。这有助于将账户对账单和其他下载文件整合到账户流程中,使其只需几次点击即可轻松访问。您还可以编写脚本,根据账户名称获取这些文档列表并进行相应处理。

文档指令的一般格式为:

YYYY-MM-DD document Account PathToDocument

来自目录的文档

创建这些条目的更便捷方式是使用一个特殊选项,指定包含与会计科目表结构一致的子目录层次的目录。例如,上一节中所示的文档指令,可以通过如下目录结构自动生成:

stmts
`-- Liabilities
    `-- CreditCard
        `-- 2014-04-27.apr-2014.pdf

只需将根目录作为选项指定即可(注意末尾没有斜杠):

option "documents" "/home/joe/stmts"

将被识别的文件是那些以日期开头、格式为 YYYY-MM-DD 的文件。如果您拥有多个此类文档归档,可以多次指定此选项。过去我曾为每一年设置一个独立目录(如果您扫描了所有文档,目录可能会变得非常大,因为扫描的文档通常体积较大)。

使用您账本账户的层次结构来组织电子文档对账单和扫描文件,是一种绝佳的整理方式,能为日后需要时快速定位这些文档提供清晰且无歧义的路径。

价格

Beancount 有时会为每种商品创建一个内存中的价格数据存储,用于多种用途。特别是,它用于报告账户持仓的未实现收益。价格指令可用于为该数据库提供数据点。价格指令定义了一种商品(基础货币)与另一种商品(报价货币)之间的兑换率:

2014-07-09 price HOOL  579.18 USD

此指令表示:“2014 年 7 月 9 日,1 单位 HOOL 的价格为 579.18 美元。”货币汇率的价格条目也以相同方式处理:

2014-07-09 price USD  1.08 CAD

价格指令的一般格式为:

YYYY-MM-DD price Commodity Price

请记住,Beancount 并不了解 HOOL、USD 或 CAD 的具体含义,它们只是不透明的“实体”。您赋予它们意义。因此,如果您以这种方式核算未使用的休假时间,为您的休假时间设定每小时的价格也是完全有效且有用的:

2014-07-09 price VACHR  38.46 USD  ; Equiv. $80,000 year

来自记账项的价格

如果您使用 beancount.plugins.implicit_prices 插件,每当出现带有成本或可选价格的记账项时,插件都会自动根据该成本或价格生成一条价格指令。例如,这笔交易:

2014-05-23 *
  Assets:Investments:MSFT        -10 MSFT {43.40 USD}
  Assets:Investments:Cash     434.00 USD

在解析后会自动变为:

2014-05-23 *
  Assets:Investments:MSFT        -10 MSFT {43.40 USD}
  Assets:Investments:Cash     434.00 USD

2014-05-23 price MSFT  43.40 USD

这非常方便。如果您启用此功能,您的账本价格数据库中的大多数价格数据点都将来源于此。您可以打印账本中解析出的价格表(它也是一种报表类型)。

同一天的价格

请注意,这里没有时间的概念;Beancount 并非为日内交易者设计,尽管它当然能够处理一天内的多笔交易。它只是按天存储价格。(通常,如果你需要许多依赖日内时间的特性,最好使用其他系统,这超出了会计系统的范畴。)

当同一日期存在多个价格指令时,文件中最后出现的那个将被选中并纳入价格数据库。

事件

事件指令3用于跟踪你选择的某个变量随时间的变化。例如,你的位置:

2014-07-09 event "location" "Paris, France"

上述指令的意思是:“从 2014 年 7 月 9 日起,将‘location’事件的值设为‘巴黎,法国’。”每种事件类型每天只有一个值。

事件指令的一般格式为:

YYYY-MM-DD event Name Value

事件的名称字符串无需提前声明,首次使用该指令时它便会自动创建。字符串可以是任意内容,没有预设结构。

如何使用这些指令,最好通过示例说明:

  • 位置:你可以使用事件来跟踪你所在的城市或国家。这在账簿中是合理的用法,因为你的支出性质与你的所在地高度相关。使用起来也很方便:如果你经常使用 Beancount,只需花极少的精力就能在文件中添加这些信息。我通常会设置一个“现金日记”部分,用于记录杂项现金支出,这些指令就放在那里。

  • 地址:如果你经常搬家,记录你过去的家庭住址会很有用。这在移民相关的政府表格中有时会被要求提供。例如,美国的绿卡申请流程中,

  • 雇主:你可以用这种方式记录每份工作的入职和离职日期,然后统计你在该单位工作的天数。

  • 交易窗口:如果你是上市公司的员工,可以记录你被允许交易该公司股票的日期。这可用于确保你在交易窗口关闭期间没有进行交易。

事件常用于统计天数。例如,在加拿大魁北克省,如果你“在日历年中停留 183 天或以上(不包括 21 天或更短的旅行)”,即可享受免费医疗保障。如果你经常出国旅行,可以轻松编写脚本,提醒你剩余的可停留天数,以避免失去保障。许多海外侨民都面临类似情况。

同样地,美国国税局(IRS)将满足实际居住测试的美国移民视为“居民外国人”——因此需要报税,太好了!——该测试定义为:“在当前年度至少实际居住 31 天,且在包括当前年度及前两年的三年期间内累计居住 183 天,计算方式为:当前年度全部天数 + 前一年度天数的三分之一 + 再前一年度天数的六分之一。” 哎呀……头都大了。这变得很复杂。但如果你能记录下自己随时间变化的所在地,就可以自动完成这些计算。

事件还可用于过滤语言中,以指定非连续的时间段。例如,如果您使用事件指令跟踪位置,可以生成仅在您处于特定位置时发生的交易报告,例如:"显示我所有前往德国的支出",或"在我身处蒙特利尔时,列出所有餐厅交易的收款方。"

请注意! 过滤功能尚未实现。此外,Beancount 2.0 中尚未重新实现事件报告。这些功能以及过滤功能将很快重新引入。

值得注意的是,价格和事件指令是唯一不与任何账户关联的指令。

查询

将 SQL 查询嵌入 Beancount 文件中以便自动运行这些查询,可能会非常方便。这仍是一个早期开发/实验性指令。无论如何,您可以像这样在交易流中插入查询:

2014-07-09 query "france-balances" "
  SELECT account, sum(position) WHERE ‘trip-france-2014’ in tags"

语法为:

YYYY-MM-DD query Name SqlContents

每个查询都有一个名称,该名称可能用于将其作为报告类型调用执行。此外,查询的日期旨在表示该查询应针对的日期,即其后的交易应被忽略。如果您熟悉 SQL 语法,这相当于一个隐式的 CLOSE。

自定义

Beancount 的长期计划是允许插件和外部客户端定义自己的指令类型,由 Beancount 输入语言解析器进行声明和验证。在此期间,提供了一个通用指令,供客户端原型化新功能,例如预算管理。

2014-07-09 custom "budget" "..." TRUE 45.30 USD

该指令的语法具有灵活性:

YYYY-MM-DD custom TypeName Value1 ...

第一个参数是一个字符串,旨在唯一标识您的指令。可将其视为您指令的类型。在此之后,您可以添加任意数量的字符串、日期、布尔值、金额和数字。请注意,目前没有验证机制来确保 TypeName 后参数的数量和类型对特定类型保持一致。

(有关此功能的起源,请参阅此线程)。

元数据

您可以为每个条目和分录附加任意数据,其语法如下:

2013-03-14 open Assets:BTrade:HOOLI
  category: "taxable"

2013-08-26 * "Buying some shares of Hooli"
  statement: "confirmation-826453.pdf"
  Assets:BTrade:HOOLI      10 HOOL @ {498.45 USD}
    decision: "scheduled"
  Assets:BTrade:Cash

在此示例中,为账户的 Open 指令附加了 "category" 属性,为交易指令附加了 "statement" 属性(其值为表示文件名的字符串),并为交易的第一个分录附加了 "decision" 属性(分录的额外缩进并非严格必需,但有助于提高可读性)。

元数据可附加到任何指令类型。键必须以小写字母 a-z 开头,可包含(大写或小写)字母、数字、连字符和下划线。此外,值可以是以下任意数据类型:

  • 字符串

  • 账户

  • 货币

  • 日期(datetime.date)

  • 标签

  • 数字(Decimal)

  • 金额(beancount.core.amount.Amount)

这些数据有两种使用方式:

  1. Beancount 自带的查询工具(如 bean-query)将允许您使用元数据值进行过滤和聚合。

  2. 您可以在自定义脚本中访问和使用元数据。元数据值可通过所有指令上的 ".meta" 属性访问,该属性是一个 Python 字典。

特定属性没有特殊含义,这些属性供用户自行定义。然而,所有指令都保证包含一个‘filename’(字符串)和一个‘lineno’(整数)属性,用于反映其创建位置。

最后,没有值的属性将被解析为值 'None'。如果某个属性被重复多次,仅会解析并保留第一个值,后续的值将被忽略。

选项

Beancount 输入文件的绝大部分内容由指令组成,如前一节所示。然而,也可以通过使用一个特殊的无日期“option”指令,在输入文件中设置一些全局选项:

option "title" "Ed’s Personal Ledger"

选项指令的一般格式为:

option Name Value

其中 NameValue 均为字符串。

请注意,根据选项的不同,其效果可能是设置单个值,或向现有值列表中添加内容。换句话说,某些选项是列表。

有三种方式可以查看选项列表:

  • 本文档中,我会定期更新。

  • 要查看您安装版本所支持的完整选项列表,请使用以下命令:bean-doctor list-options

  • 此外,您也可以查看源代码

运营货币

一个显著的选项是“operating_currency”。默认情况下,Beancount 不会对任何商品区别对待,特别是它不知道最常用的商品——货币——有何特殊之处。例如,如果您生活在新西兰,您的交易中将出现大量 NZD 货币。

但有用的报表会尝试将所有非货币商品汇总到主要使用的货币之一。此外,将货币单位单独分列也很有用。这在导出时尤其有用,可避免为该列指定单位,并便于导入到电子表格中进行数值处理。

因此,您可以使用选项声明您最常使用的货币:

option "operating_currency" "USD"

您可以声明多个货币。

无论如何,此选项仅由报表代码使用,不会改变 Beancount 处理或语义的行为。

插件

要加载插件 Python 模块,请使用专用的“plugin”指令:

plugin "beancount.plugins.module_name"

插件名称应为 PYTHONPATH 中的 Python 模块名。这些模块将由 Beancount 加载器导入,并按顺序作用于已解析的条目列表,以转换条目或输出错误。这使您能够将自定义代码集成到 Beancount 中,对条目进行任意转换。详情请参见脚本与插件

插件还可选择性地接受一些配置参数。这些参数可通过一个可选的最终字符串参数提供,如下所示:

plugin "beancount.plugins.module_name" "configuration data"

选项指令的一般格式为:

plugin ModuleName StringConfig

配置数据的格式取决于插件本身。目前,插件会接收一个任意字符串。请参阅各插件的文档,了解其支持的具体内容。

另请参阅“插件处理模式”选项,该选项会影响内置插件的运行列表。

包含

支持包含指令。这允许您将大型输入文件拆分为多个文件。语法如下:

include "path/to/include/file.beancount"

通用格式为

include Filename

指定的路径可以是绝对路径或相对路径。如果使用相对路径,则相对于包含该指令的文件所在目录。这使得将相对包含指令置于目录层次结构中变得非常方便,此类结构可纳入源代码控制并随时检出。

然而,包含指令并非严格处理(例如不像 C 语言那样)。Beancount 解析器会收集所有包含指令,并由加载器单独处理。这是可行的,因为 Beancount 输入文件中声明的顺序无关紧要。

但目前,选项是按文件解析的。用于解析后处理的选项映射是顶级文件返回的选项映射。这一点未来可能会重新评估。

接下来是什么?

本文档描述了 Beancount 语言的所有可能语法。如果您尚未编写过任何 Beancount 输入文件,可以前往入门指南,或浏览命令行记账手册中的实用用例列表。


  1. 请注意,存在一个“Open”指令,用于指定每个账户的起始日期。该指令可位于文件的任意位置,无需在使用账户名称之前出现。您可以立即在交易中使用账户名称,但所有接收过账的账户最终都必须有一个对应的 Open 指令,且其日期必须早于该账户在输入文件中所有交易的日期。

  2. 请注意,无论价格是使用 @ 语法指定为单位价格,还是使用 @@ 语法指定为总价,此规则均适用。

  3. 我非常不喜欢将此指令命名为“event”。我一直在尝试寻找更好的替代名称,但至今尚未成功。“register”可能更合适,因为它类似于处理器寄存器,但可能与账户登记报告混淆。“variable”或许可行,但听起来过于计算机科学化且脱离语境。如果您有更好的建议,请提出,我将认真考虑全面重命名(同时保留对“event”的向后兼容支持)。