财富编织:Beancount复式记账指南

转载请注明出处❤️

作者:测试蔡坨坨

原文链接:caituotuo.top/6a84b0ca.html


前言

你好,我是测试蔡坨坨。

在前面两篇文章中,我们探讨了「一年之余,财富何方?」以及「财富梳理:复式记账之道」,旨在回答两个核心问题:“为什么要记账?”和“如何科学记账?”。

实践是检验真理的唯一标准。同样,复式记账也需要通过实践来理解。在开始实践之前,学习很多晦涩难懂的会计学概念没有任何意义。正如上篇文章末尾所述,对于复式记账的理论部分,我们只需记住会计恒等式两句口诀,便能游刃有余。

1
2
3
4
5
会计恒等式:资产+费用=负债+所有者权益+收入

两句口诀:
资产、费用类:借增贷减
负债、权益、收入类:借减贷增

文本记账:Beancount

要开始实践,就要有工具上手,复式记账是方法论,而Beancount则是支持复式记账的工具

在众多选择中,我推荐使用Beancount。虽然第一次见到Beancount时,我的内心是拒绝的,纯文本环境,也没有市面上大多数记账软件方便快捷、界面简单的优势,似乎还需要一点学习成本。

但是,在犹豫片刻之后,我便鼓起勇气用一晚上时间完成了学习和初始化,两天后就决定全面搬迁到Beancount。从2024年初开始,到现在已有三个多月的使用体验,我对它十分满意,希望能将它推广给更多的用户,原因如下:

  • 开源免费:Beancount是一个开源的Python库,可以在本地运行,同时还有活跃的社区支持和持续的更新和改进,适合计算机宝宝
  • 文本格式:账本使用简洁的文本格式进行记账,方便存储和管理,个人拥有全部的数据,还可以使用Git管理,记账时只需打开文本文件,无需等待图形界面慢悠悠的启动
  • 灵活性:账本的语法很规范,也具备灵活性,像编程语言一样可以嵌套引入,配合插件可实现语法高亮和代码检查,用户能够根据自己的需求和偏好自定义账户结构和记账方式
  • 数据可视化:除了完整的命令行工具链,官方还提供了可视化工具Fava,还有基于类SQL的查询和报表生成
  • 适应性:没有预先定义的类别、货币等现实世界的概念,可以轻松实现多币种记账,包括各种点数、虚拟货币等

除了Beancount之外,还有许多其他开源的工具可供选择。可以参考Plain Text Accounting网站上列出的,类似的具有竞争力的还有Ledger和hledger。其中,Ledger是这类工具的开创者。无论是里面介绍的哪个工具,实现理念都是大同小异,即记录账户之间的资金流动

为什么选择文本记账?

Beancount是一个基于文本的复式记账软件,与其说是记账软件,不如说它是复式算账软件,因为它没有提供任何对于记账相关的功能,它提供的是对于某种特定格式的账本的解析功能,实际上的记账人是你,甚至你的账本Beancount不会做任何操作,官方将其定义为“一种复式记账计算机语言”。

复式记账软件不少,就算是开源世界,也有GnuCash,这些软件都有完整的GUI操作,用户在一堆文本框里输入各种数字和文字,软件接收输入后存储到自己的数据库里,又何苦选择文本记账的方式呢?

我给出的理由是:

  • 大多数GUI记账软件的数据都是存储在三方,无法直接看到或操作他们的数据,必须通过软件来操作
  • 文本记账的折腾成本低,比如GnuCash,如果你要给它折腾一个手机、PC的多端记账方案,你就得研究它的数据库和接口,而文本记账,只用打印文本就行了
  • 一旦软件停止更新,用户的数据就危在旦夕,难以导出和复用,很难跨平台或跨设备同步
为什么选择Beancount?

上面提到,文本记账的话,其实还有Ledger和hledger可以选择。Beancount是一个Ledger-like软件,Ledger是这一类复式记账软件的开创者。相比于C++写的Ledger,用Python写的Beancount更轻便,方便增加插件和二次开发,同时也增加了许多功能,如灵活强大的“货币”支持(Beancount其实并不知道什么是货币,它记录的只是通货(commodity)的变化,所有的通货皆由用户自定义,因此Beancount可以用来记录除货币在内的任何东西的变化,比如假期天数、信用卡积分、股票、基金、航空里程等,当然也可以用来数豆子,这也是Beancount名字的来源)。

在这三个软件中,Beancount的生态社区最为活跃。它简化了记账符号的使用,将正负号代替了传统的“借”和“贷”。

此外,Beancount官方提供了一个名为fava的图形化管理工具。通过fava,用户可以在web界面中展示文本账簿,使记账信息一目了然。fava还提供各种报表统计分析功能,让用户能够更加方便地进行财务分析。可以访问官方提供的Demo提前体验一波。

安装Beancount和Fava

由于Beancount和Fava都是使用Python实现的,因此安装过程也非常简单(前提是需要有Python环境)。

  1. 安装Beancount

    1
    pip install beancount

  2. 安装Fava

    1
    pip install fava

  3. 安装完成后,您只需执行fava xxx.bean即可查看任意账本文件(鉴于目前还没开始编写账本,可以先用fava提供的示例账本过把干瘾)

    1
    fava example.bean

  4. 打开浏览器,并访问 http://127.0.0.1:5000,即可开始使用

损益表和资产负债表

运行Fava后,你会看到一个华丽的 Web UI,其中有两张报表值得我们关注,损益表和资产负债表。

记账的目的是为了了解自己的财务状况,换成通俗易懂的话就是为了回答以下三个问题:

  • 我的钱从哪里来?
  • 我的钱在哪?
  • 我的钱去哪了?

一本维护良好的账本能生成很多有用的财务报表,而其中最有用的是损益表资产负债表,前者能回答第一个和第三个问题,后者能回答第二个问题。

损益表(income statement)

损益表,也称为利润表,在这张报表中,我们可以一目了然地看到自己每月有哪些收入、收入来自于哪些地方、有多少支出、支出花在了什么地方。

在复式记账中,支出类借增贷减,上半部分代表支出(正数、流出),收入类借减贷增,下半部分代表收入(负数、流入)。

鼠标悬停在某个月份上,可以看到这个月份的净利润:

右上角可以切换 按年/按季/按月/按周/按日 展示:

还可以切换到收入支出上,只看收入或支出的情况:

默认展示堆叠条形图,鼠标悬停在条形图上,可以看到此项在这个月的变化:

切换到Income或Expenses上,能看到收入或支出的旭日图:

页面右上角有时间、账户、筛选(标签,收款人等等)几个过滤框,日期除了支持具体的年月日,还支持 year/month/day 等关键字,例如 year-2 - year 表示只展示前年到今年的交易:

页面下半部分是具体每个账户的收支情况:

点击某个账户,可以看到这个账户的曲线图和具体的交易记录:

资产负债表(balance sheet)

在资产负债表里,可以一目了然地看到自己有多少资产、资产分别在哪些账户里,有多少负债、是哪里的负债。

上半部分是净资产变化的折线图,下半部分是具体每个账户的情况。

切换到Assets、Liabilities或Equity上,可以看到资产、负债或净资产的饼图:

快速入门

说了这么多,也许你已经安装完毕,磨拳搓掌准备行动。

Beancount的使用很简单,概括为两个步骤:

  1. 使用文本文件(文件扩展名为.bean)按一定格式记账
  2. 命令行执行fava xxx.bean

下面是个完整的示例:

账本文件中包含账本信息设置、账户设置、账户初始化、交易记录等步骤,我们将在下文做详细说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
;【一、账本信息】
option "title" "我的账本" ;账本名称
option "operating_currency" "CNY" ;账本主货币

;【二、账户设置】
;1、开设账户
1990-01-01 open Assets:Card:1234 CNY, USD ;尾号1234的银行卡,支持CNY和USD
1990-01-01 open Expenses:Traffic:Taxi CNY ;打车消费,只支持CNY
1990-01-01 open Liabilities:CreditCard:5678 CNY, USD ;双币信用卡
1990-01-01 open Income:Salary CNY ;工资收入
1990-01-01 open Equity:OpenBalance ;用于账户初始化,支持任意货币

;2、账户初始化
2024-01-11 * "账户余额初始化" "初始化银行卡1234余额为10000元"
Assets:Card:1234 10000.00 CNY
Equity:OpenBalance -10000.00 CNY

;【三、交易记录】
2024-01-01 * "滴滴打车" "打车到公司,银行卡支付"
Expenses:Traffic:Taxi 200.00 CNY
Assets:Card:1234 -200.00 CNY

2024-01-01 * "" "餐饮"
Assets:Card:1234 -20.00 CNY
Liabilities:CreditCard:5678 20.00 CNY

2024-01-10 * "XX公司" "工资收入"
Assets:Card:1234 3000.00 CNY
Income:Salary -3000.00 CNY

执行命令:

1
fava demo.bean

浏览器访问http://127.0.0.1:5000:

到这里,我们已经完成了Beancount的快速入门。

最佳实践

1. 编辑器支持

Beancount的作者是Emacs的用户,因此自己写了Emacs的插件。Vim的用户可以使用第三方提供的nathangrigg/vim-beancount插件。Sublime的用户可以使用sublime-beancount

而我使用的是Visual Studio Code,配合Beancount(by Lencerf)扩展插件,能够实现语法着色、账户自动补全、数字按小数点对齐、错误提示等功能。

选择编辑器和插件的目的在于提高效率,使记账变得更加轻松和愉快。

当然,如果你不嫌麻烦,直接使用最原始的记事本也完全没有问题。

2. 账本结构

在组织账本时,将其拆分成多个文件是一个很好的做法。这样可以更好地管理和维护账本,使其更具可读性和可维护性。通过使用Beancount的include语法,可以将多个账本文件关联起来,形成一个完整的账户体系。

例如,我的账本结构目录如下:

最外层是main.bean主账本文件,用于整合和管理所有其他账本文件,查账时只需执行命令fava main.bean;开户和账户余额初始化相关的信息放在accounts文件夹下,并使用assets、liabilities等关键字命名文件,以便对应相应类型的账户;交易记录按照年份进行分类,在每个年份文件夹中可以进一步细分,例如日常支出、转账、收入、旅游娱乐、投资收益等。

这样的结构设计使得账本信息更加有条理,便于查找和管理。同时,也方便了账本的维护和更新,能够更有效地记录和追踪财务情况。

3. 账本设置

在初始化时,需要配置账本名称、主货币等属性。

1
2
3
4
5
6
7
;【账本设置】
;功能:设置属性
;语法:option 属性名 值
option "title" "Joker's Ledger" ;设置账本名称
option "operating_currency" "CNY" ;账本主货币为人民币

1990-01-01 custom "fava-option" "language" "zh" ;可视化界面使用中文

4. 账户相关

在开始记账之前,必须开通好账户。

在Beancount中,账户是以树形结构组织的,支持多层级,以英文冒号:分隔,如Assets:Bank:CMB:1234,第一层必须是会计恒等式中的五个账户类型之一。

命名时可以采用银行的缩写、尾号、用途等信息,以便于识别和管理。

开通账户

开户命令:

1
日期 open 账户名 货币类型

举栗:

1
2024-01-01 open Assets:Wechat:MiniFund CNY ;微信零钱通
  • 开户日期:格式为yyyy-MM-dd,开户日期需要在该账户第一笔交易之前,可以使用开始复式记账的前一天或者自己的生日。当然还有一些更有创意的选择:
    • Assets 和 Liabilities 账户中的借记卡和信用卡,可以以在银行的开户日期作为 Beancount 中的开户日期
    • Expenses 账户可以使用自己的生日作为开户日期
    • Income 账户下可以按来源分类,如 Income:Salary 以公司入职时间作为开户日期
  • open:开户命令关键字。
  • 账户名:账户可以使用英文分号:分割成多个层级,合理利用层级可以让账户层次更清晰;第一层级只能使用Assets、Liabilities、Equity、Income、Expenses中的一个,因为Beancount需要根据这个字段区分账户的性质,以决定它们该出现在哪张报表中;第二层级必须以大写英文或数字开头,后面的层级就没有做限制了。
  • 货币类型:开户时货币类型不是必须的,但建议加上,记录交易时货币不一致会报错,货币可设置多个,用英文逗号,分隔。
  • 注释:beancount语法使用英文分号;作为注释符号。

日常交易中涉及的账户,一定可以归于其中某一类。

合理开户的核心要素在于你想要追踪哪些东西,比如你想看每个月吃了多少肯德基,你就可以专门开个费用账户叫做Expenses:KFC,如果你完全不在意三餐的区别,开一个吃饭(Expenses:Food)账户就足够了。

账户类型 说明 举栗
资产(Assets) 所有我的 流动资产:银行储蓄卡余额、微信零钱、零钱通、支付宝余额、余额宝等;固定资产:房子、车子等
费用(Expenses) 花出去的 衣、食、住、行、娱乐、旅游等
负债(Liabilities) 问别人借的 信用卡CreditCard、贷款Loan、花呗、京东白条、XX月付、房贷、车贷、借钱Payable等
所有者权益(Equity) 老板投的钱 这个账户比较特殊,在账户初始化、误差处理等少数场合使用
收入(Income) 赚的钱 工资Salary、投资收益Investments、副业收入Sideline、意外收入Gift、二手交易2ndTrade等

开户示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;资产账户
2024-01-01 open Assets:Bank:CMB:1234 CNY ;招商银行
2024-01-01 open Assets:Wechat:MiniFund CNY ;微信零钱通

;负债账户
2024-01-01 open Liabilities:Huabei CNY ;花呗

;费用账户
2024-01-01 open Expenses:Clothing CNY ;衣
2024-01-01 open Expenses:Food CNY ;食
2024-01-01 open Expenses:Housing CNY ;住
2024-01-01 open Expenses:Transportation CNY ;行

;收入账户
2024-01-01 open Income:Salary CNY ;工资
2024-01-01 open Income:Investments CNY ;投资收益
2024-01-01 open Income:Sideline CNY ;副业

; 所有者权益
2024-01-01 open Equity:OpenBalance CNY ;初始化账户余额
账户余额初始化

当我们开始记账时,一般资产和负债都不会是0,所以需要对资产和负债账户进行初始化。

初始化的命令格式和交易记录的命令一致,下文将会介绍交易记录。

举栗:

1
2
3
2024-01-02 * "账户余额初始化" "招商银行卡1234"
Assets:Bank:CMB:1234 300.00 CNY
Equity:OpenBalance -300.00 CNY
注销账户

不要惧怕开户,对于一些短时间用的小账户,也可以开户,因为账户是可以关闭的,关闭后的账户不会出现在报表中,因此也不会触发你的强迫症。

销户命令:

1
日期 close 账户名

举栗:

1
1999-07-01 close Assets:Bank:CMB:1234

5. 交易记录

Beancount的交易记录主要包含日期和相关账户的资金流动(至少两个账户,可以涉及多个账户)。

命令格式:

1
2
3
日期 对账标志位 "交易方" "交易备注"
账户A 金额 货币
账户B 金额 货币

举栗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;借方:费用增加,贷方:资产减少
2024-03-09 * "高德打车" "高德地图打车订单"
Expenses:Transport 84.68 CNY ;借方:交通费用增加
Assets:Bank:CMB:1234 -84.68 CNY ;贷方:资产减少

;借方:费用增加,贷方:资产减少,权益减少
2024-03-01 * "KFC" "10块钱买了一个吮指原味鸡"
Expenses:Food 10.00 CNY
Assets:Bank:CMB:1234 -7.00 CNY
Equity:Tickets:KFC -3.00 CNY ;假设有个3元的优惠券

;借方:资产增加,贷方:收入增加
2024-03-10 * "XXX公司" "2024年2月工资"
Assets:Bank:CMB:1234 3000.00 CNY
Income:Salary -3000.00 CNY

;借方:费用增加,贷方:负债增加
2024-02-04 * "铁路12306" "火车票"
Expenses:Transport 39.00 CNY
Liabilities:Huabei -39.00 CNY

其中:

  • *号表示这笔交易是确定的,没有疑问,若是!则表示存疑,但一般用不上

  • 交易方和交易备注均可省略

  • 货币必须与账本设置的主货币一致,若不一致会报错,如果是不同货币,可以使用@@进行货币转换,比如:

    1
    2
    3
    2024-02-26 * "XXX" "转账备注:微信转账(兑换2500泰铢)"
    Expenses:Travel 2500.00 THB @@ 511.00 CNY
    Assets:Wechat:MiniFund -511.00 CNY
  • 正号表示借方(正号可以省略),负号表示贷方

    1
    2
    3
    2024-03-09 * "高德打车" "高德地图打车订单"
    Expenses:Transport 84.68 CNY ;借方:交通费用增加
    Assets:Bank:CMB:1234 -84.68 CNY ;贷方:资产减少
  • 支持单个账户的自动补全

    1
    2
    3
    4
    2024-03-01 * "KFC" "10块钱买了一个吮指原味鸡"
    Expenses:Food ;自动补全,会转化为 Food账户 +10.00 CNY
    Assets:Bank:CMB:1234 -7.00 CNY
    Equity:Tickets:KFC -3.00 CNY ;假设有个3元的优惠券
  • 打标签

    • 单个标签#后面的内容是标签名称,在Fava上可以筛选标签,查看该标签下各个账户的收支

      1
      2
      3
      2024-03-01 * "XXX" "XXX" #Phuket
      Expenses:Travel 300.00 CNY
      Assets:Bank:CMB:1234 -300.00 CNY

    • 标签堆栈,堆栈中所有交易记录都会被自动打上标签,便于后期检索

      1
      2
      3
      4
      5
      ;语法:pushtag #标签内容
      ; 交易记录1
      ; 交易记录2
      ; .........
      ; poptag #标签内容
      1
      2
      3
      4
      5
      6
      7
      pushtag #Phuket
      ...
      2024-03-01 * "" ""
      Expenses:Travel 300.00 CNY
      Assets:Bank:CMB:1234 -300.00 CNY
      ...
      poptag #Phuket

6. 价格相关

Beancount 可以使用多种通货,但好比不同货币有汇率,这些通货之间也有对应数量关系,即某个东西值多少钱,这就是单位通货和货币的汇率。

1
2
3
;功能:导入某个通货某日的价格
;语法:时间 price 通货 价格
2024-03-21 price GOLD 431.00 CNY

7. bean-query复杂查询

Fava的华丽 Web UI 已经能展现很多有用的财务报表,满足大部分用户的需求,如果用户需要进行一些更复杂的数据统计,比如「我 2023 年吃过的饭店按次数排列」,则可以使用 bean-query 工具用 SQL 语句进行查询,详见 Beancount 作者的文档:Beancount – Query Language

这是一个用来统计光顾麦当劳次数的例子:

8. 事件

在生活中有些事件希望被记录,但这些事情可能与财务无直接关系,Beancount也支持记录,比如旅行、会议、生日、纪念日等。

命令:

1
日期 even "事件分类" "事件详情"

举栗:

1
2024-01-01 event "beancount" "开始使用beancount复式记账啦"

对于大额的转账类收入或支出,如果直接归到收入或支出,会导致统计图的比例被挤压。

这种情况也可以使用事件来解决,比如创建一个Equity:Exchange账户负责转账记录,然后在事件中创建转账条目用以记录。

1
2
3
4
5
2024-02-12 * "XX转账5万元"
Assets:BankCard 50000.00 CNY
Equity:Exchange -50000.00 CNY

2024-02-12 event "转账" "XX转账5万元"

9. 误差处理

金额浮点数误差

在使用一段时间后,发现Beancount在金额计算上因为浮点数非精确计算的性质,会出现0.01误差(在记录金额时建议保留两位小数,比如100.00,可以消除浮点误差)。如果已经有很多的金额没有保留两位小数,出现误差也不要慌,我的解决方案是创建一个Equity:Balance-Error账户,定期对误差进行消除。

1
2
3
2024-03-21 * "消除账户浮点误差"
Equity:Balance-Error -0.01 CNY
Assets:BankCard 0.01 CNY

还有一些时间久了无法回溯的差额、四舍五入的误差,都可以使用Equity账户来处理。

解决方案是设置四个Equity账户用于处理不同场景的误差:

1
2
3
4
Equity:OpenBalance 用于初始化
Equity:HistoryIncome 开始复式记账前的部分收入
Equity:Round 四舍五入操作
Equity:UFO 无法追溯的差额

10. 定期断言

一本维护良好的账本应该定期做断言(assertion)

前面说到复式记账最大的优势是不仅仅记录收入和支出,同时还会记录资产负债,Beancount中可以通过balance命令实现账户核对,标记某个日期某个账户(资产Assets账户或负债账户Liabilities)里还有多少豆子。

1
2
3
4
;资产
2024-02-01 balance Assets:Bank:CMB:1234 100.00 CNY
;负债
2024-02-01 balance Liabilities:Huabei -1000.00 CNY

断言语句告诉 Beancount,某个账户在某个日期凌晨 00:00:00 时间点(也就是前一天深夜 23:59:59 的下一秒),余额为某个数字。如果历史交易记录无误,计算出来的金额就会与预期一致,若不一致,则Beancount就会报错提示。

Beancount 的时间精度是,所以诸如 open, close, balance 等带日期的语句,均发生在当日的第一笔交易之前,你可以想像它们都是在凌晨 00:00:00 时间点发生的,而普通的交易都是发生在白天。因此,要断言一月份的余额,日期应写作 02-01 而不是 01-31。

理想情况下,进行断言的频率应该是每月或每半个月一次,这样可以及时发现并纠正可能存在的错误。

综述

说到这里,我们已经介绍了Beancount的基本使用方法。如果你想了解更多关于Beancount的语法规则和更广泛的应用场景,建议参考官方文档:

虽然我们现在可以开始进行记账了。

但是,问题来了,每次都需要手动录入交易信息,对于我这种懒人来说,想想就很痛苦。

所以,本着“能坐着就不站着,能躺着就不坐着”的原则,自动记账这件事势在必行。

如何自动记账?

我的方案是:

  • 使用Python/Java等编程语言,实现账单(微信/支付宝账单)的自动导入和解析。
  • 对于没有出现在账单中的交易,可以借助机器人(如Telegram、企业微信、钉钉)来实现快速随时记账

通过这种方式,我们可以实现自动或半自动记账。关于账单的自动导入和解析,将在下一篇文章中进行介绍。