复式记账指北(一):What and Why?

各位好久不见,我是KAAAsS。没错,我又在没填完坑的情况下开新坑了!(自豪脸)这次写作的对象是“记账”,是一个听起来既不Geek又不有趣的话题。但是相信我,这一系列文章要介绍的“复式记账”本身其实十分有趣,而且最后的整个记账方案也足够Geek。

另外就是关于为什么会突然想要研究“记账”。最直接的原因是前些天@KevinAxel在群里面提到了复式记账工具Beancount,简单研究了下发现确实十分有趣。更深层次的原因当然还是有记账的需求啦。随着我也逐渐开始折腾各类金融产品,我才意识到清楚了解自身的财务状况开始变得越来越困难。加上近一个月手办集中补款,让我对我的财务状况引起了深深的担忧。所以干脆趁这段不是很忙的时间好好规划下记账这件事情。

写作计划

按照我的思路,这个系列的写作大概分为三到四篇:

  1. 第一篇:介绍复式记账、Beancount基础配置、使用、规划账户
  2. 第二篇:结合我个人的经验,介绍详细做账的方式
  3. 第三篇(重点!):介绍我自己记账的Best Practice、各个Beancount相关服务的部署方式等等

文章目标是由浅入深的介绍大部分Beancount内容,建议各位各取所需的看。如果各位感兴趣的话,我就努力连续周更完成这个系列吧~

单式记账

在了解复式记账之前,我们有必要了解下它的对立面——单式记账的概念。单式记账其实就是我们大多数人对“记账”这一行为的认识:记下每一笔发生的交易,然后累加得到账户的余额。如果用数轴来表示的话,一个初始的账户大致如下:

每当记下一笔帐(用椭圆表示),账户的余额(数轴上的数字)就会发生变化。

只要维持这样的记账方式,我们就能追踪账户的余额和账户的开销变化了。一般在现实情况下,每一个“数轴”都会对应于一个特定的“账户”,比如支付宝。而为了更好的管理账单,我们通常还会给账目打上标签,比如交易对象、交易内容等等。比如在餐厅消费79元就可以这么表示

而这就是大多数记账软件给我们提供的“流水账”。你会发现由于单式记账法的本质就是记录金额的变更,因此如果要区分不同的账目(比如用于食物的开销)就必须给每个账目打上标签。一般的记账软件都预制了不同的标签来帮助你分析财务状况,比如支付宝就会自动给订单打“衣食住行”等的标签。

复式记账

接下来就是复式记账的概念了。和单式记账不同,复式记账还要求开设一个“交易目标账户”。比如对于之前的餐厅消费,我们再开一个餐厅账户。而由于我们的消费对餐厅来讲就是一笔收入,所以我们再给餐厅也记一笔账:

所以为什么要给餐厅也开个账户呢?其实很简单,因为我们想要追踪关于餐厅的帐目。由于对任意一次消费我们都会记两笔帐(一正一负),所以无论通过哪个账户支付(支付宝、微信、银行卡等等),只要是关于餐厅的消费都会在餐厅账户上记上一笔。比如用信用卡买单,我们可以记成:

所以如果我们想知道自己在餐厅上的开销,只需要计算餐厅账户的余额就行了。而这个过程完全不需要在账目上打什么标签!

此外,由于复式记账中的账目总是成对(或者成组)出现的,所以我们可以把这些相关的账目都关联起来,组成一个“交易”。

不难发现,“交易”其实相当于在不同的账户之间进行“转账”。而转账是不会改变总金额数的,于是这就引出了复式记账的基本原则:

一个交易内的账目金额之和一定等于0

无论一个交易有多么复杂,这个原则在复式记账中都是不变的。比如对于“发薪水”,为了计入个人所得税、医保等等包含在工资内的支出,我们可能需要记不止两笔帐:

公司一共支付了2905元薪水,缴纳905元税款后,最后得到2000元净薪水。所以把这个交易的各个账目金额求和:(-2905) + (+905) + (+2000) = 0,依旧符合0和的要求。

不过,虽然它保证了账目之和为0,但却把“信用卡”和“薪水”两个账户的余额记成了负数。乍一看好像会觉得,这是不是做账做出问题了?其实账户余额为负并没有什么问题。可以这么来理解:信用卡的负数意味着我们欠了银行钱;薪水的负数意味着我们从公司的薪水账户中“取走”了这个月的钱 所以公司血亏,你赚了!

至此,我们就可以发现复式记账与单式记账最大的不同之处了。如果把交易中的负数账目归为一类,正数账目归为一类,不难看出:负数账目意味着这笔交易的金额“从哪儿来”,而正数账目意味着金额要“到哪儿去”。换句话说,复式记账还关心我们把钱花到哪儿去了。而在单式记账中,我们只能用一个个标签来追踪钱的去向,显然这是不够灵活的。反观复式记账,你只需要建立对应的账户就能追踪相关的账目了。

不妨举一个例子来看看。试想一下,你和你的朋友A、B出门吃饭,最后你们决定AA制付款,由你代付。你要付69元饭钱,但是支付宝余额竟然只剩1元了 快别骂了,所以剩下钱就只能用花呗付了。那么请听题:单式记账要怎么记呢?好像这已经不是打打标签就能解决的问题了。而如果使用复式记账,只用这样就能表示了:

  • 支付宝:-1
  • 花呗:-68
  • 餐厅:+23
  • 朋友A:+23
  • 朋友B:+23

账户的分类

在上一节中已经介绍了复式记账的基本原理,那这一节就来聊聊账户相关的内容。在上一节的例子中我们已经创建了不少账户:银行卡、信用卡、餐厅、薪水等等。不难发现这些账户都有各自的特性,而有些又很类似(比如餐厅和税务),因此有必要给这些不同的账户进行分类。

账户之间最明显的一个区别是:我们到底想知道一个账户的余额,还是它的差额。比方说对于支付宝余额、信用卡欠款,我们更在意现在钱包还有多少钱,因此余额比起差额来说应该更加重要。而对于餐厅账户来说,比起了解这辈子到底花了多少钱吃饭,这个月到底花了多少才更具指导意义,因此对它来说差额又更加重要。

另一个分类的尺度就是账户余额的符号。一般而言,复式记账中账户的符号总是不变的。比如支付宝余额总是正的 也有可能是0,而信用卡账户、收入账户(比如薪水,原因在上一节提过)总是负的。

因此汇总起来,可以大致将账户分为四类:资产、负债、支出、收入。

余额通常是正的余额通常是负的
更关注余额
(资产表)
资产(Assets)负债(Liabilities)
更关注差额
(损益表)
支出(Expenses)收入(Income)
  • 资产(+):资产记录着我们当前持有的东西。最常见的就是各种账户:支付宝余额、储蓄卡、现金等等。同样,基金、股票也可算作资产,只不过这些账户的“单位”不是货币而已。此外,有形资产(车、房)也可以算作资产。这三种情况我在第二篇中都会给出各自的例子。
  • 负债(-):负债记录我们所欠的东西。比如信用卡、贷款都可以算作负债。当偿还时,我们通常需要从资产账户转账给负债账户来“清零”债务。
  • 支出(+):支出记录着我们所得到的东西。这里的“得到”并不是指财产,而是指通过交易得来的其他物品,比如食物、服务等等。试想下买东西就是一个把转出的钱变成商品然后转入支出账户的过程,这样应该能更好的理解为什么支出的符号是正的了。
  • 收入(-):收入记录着我们为了得到资产的付出。一般情况下就是薪水,可以理解成是我们付出了薪水等值的工作量,然后转账得到了钱,所以才是负的。有时收入也可以记录对应于“支出”的付出,比如对于发工资时的所得税(可以看作工资->税的转账),可以看成是记录了为了得到政府服务而做出的付出。
  • 权益(Equity):权益是很特殊的账户,也不出现在上面那张表格。一般用权益账户来做一些调整,比如设置刚开始记账的初始金额、记录浮点数舍入误差、生成一段时间内的财务报表之类的。

由此我们就可以给一个超复杂的转账记录进行整理了。

Emmmmm,好吧,虽然它现在好像还是很乱。

分析财务状况

有了账户分类,就可以通过一些报表来分析当前的财务情况了。这里仅提及一些财务报表,更详细的分析操作比如分期分析请看引文中的文章。

试算表

将同类账户归类然后计算账户余额,就得到了试算表。

试算表直观的展示了各个账户在某一时刻的余额,一般用来检查记账是否出现了错误。比如,如果你发现试算表里的支付宝余额和真正的支付宝余额不一致,那说明帐肯定哪里记得不对劲。

损益表

如果只取一段时间的支出、收入汇总,就能得到损益表。损益表展示了这一段时间内你个人的收支情况。如果对损益表的所有支出、收入账户的余额求和,就能计算出这一段时间的净利润。这直接意味着你这一段时间是攒下了钱(负),还是失去了财富(正)。

资产负债表

类似损益表,把资产与债务汇总就能得到资产负债表。资产负债表直观地表示某个时间点时,你个人的资产状况。而它们之和就等于净资产,在表格中表示为权益账户余额的负数(此时权益已经通过“清理”操作包含了所有的支出与收入,因为篇幅有限就不展开讲了)。一般而言,个人的净资产都是正数,也就是说权益账户的总值是负数。否则你可能要面对资不抵债的破产危机了 还不一定能破产,得乖乖还钱

会计等式

看到标题你可能也猜到了,没错,看完这一节你就要成为一名预备会计了(才怪啦!)。咳咳,回到正题。由于复式记账由交易组成,而所有交易都满足每条账目之和为0,所以所有账户余额之和也自然为0。如果用公式来表达就是:

A + L + E + X + I = 0

其中,

  • A:资产账户之和
  • L:负债账户之和
  • X:支出账户之和
  • I:收入账户之和
  • E:权益账户之和

如果我们计算净利润 NI = X + I,然后把权益账户调整成 E’ = E + NI = E + X + I,就能得到更常见的一个会计等式:

A + L + E’ = 0

这时候,财会专业同学们的血压可能就上来了。明明书上写的是 资产 = 负债 + 所有者权益 嘛!确实,和一般会计采用的借贷记账法不同,本文介绍的复式记账法是带上正负号的。而实际的借贷记账法中大部分金额都是正的,因此也不会出现负数收入这样的反直觉情况。不过相对的,借贷记账法中的各种计算就需要手动添加符号了,这也就导致了借贷记账法的会计等式不全是加号。所以为了简单起见(所有统计只用加法!),还是委屈委屈自己接受正负号吧~对于一个交易,其实负数的账目就相当于“贷”(账户发出贷款,余额减少),正数的账目就相当于“借”(账户借来金额,余额增加)。

文本记账:Beancount

虽然理论部分的篇幅比我想象中的要长了亿点点,不过倒也不是什么坏事啦。毕竟如果不了解一些原理的话是很难一开始就把帐做漂亮的。而且我也没看到比较好的关于Beancount复式记账原理的中文资料,所以就干脆转写了一部分文档的内容。那现在就让我们来谈谈实际的记账操作吧。

Why?

首先还是为什么选择文本记账的问题啦。复式记账软件很多,就算是开源世界也有GnuCash,这些软件都有完整的图形化操作,又何苦选用文本的方式记账呢?我给出的理由是:高可定制化、第三方开发容易,也就是折腾成本低!比如GnuCash,如果你要给他折腾一个手机、PC的多端记账方案,你就要研究他的数据库、折腾他的接口(不过GnuCash也有手机版啦)。而文本记账,只用打印文本就行啦!

然后是为什么选择Beancount。文本记账的话,其实还有Ledger和Hledger可以选择。我选择的原因也很简单:Beancount生态比较好(偷懒)、不纠结正负号修正。本身它作为Python包就提供了相关API,中文社群也很活跃(反观英文社群其实是三个里面最不活跃的,大概要归功于BYVoid的安利文),而且还简化了记账符号(就像之前文章说的那样,记账时用正负号替代了“借”“贷”)。

安装

说了那么多,其实安装 Beancount 超简单,只需要

pip3 install beancount

一般来说,还建议安装一个Web界面以便使用

pip3 install fava

现在对任意账本文件,只需要执行fava [文件]就可以查看账本啦!不过鉴于现在还没编写账本,可以用示例账本过把干瘾:

bean-example > main.bean
fava main.bean
大概相当于73306元人民币

为了更好的编辑,建议给你最常用的文本编辑器安装相关插件。Emacs的话可以使用beancount-mode,VS Code的话可以使用Beancount。

记账语法

Beancount的语法说来其实很简单,最核心的只有两种语句:开户、记账。此外,基本所有语句开头都是日期。

开户

开户语句只用一行:

; 打开现金账户
2000-01-01 open Assets:Cash
; 可以指定账户的货币,如果不指定默认为默认货币
2016-01-01 open Assets:Digital:Paypal USD
; 也可以指定多种货币
2019-12-01 open Liabilities:CreditCard:MASTER:CMBC CNY, USD

账户的名称用:分开,表示不同的层级。其中,第一级必须是Assets(资产)、Liabilities(负债)、Equity(权益)、Income(收入)、Expenses(支出)中的一个,其余没有规定。不过名称中不能使用汉字等非ASCII字符。

交易

交易(transaction)的第一行是交易的信息,其余的行记录账目(posting)。

; YYYY-MM-dd <标志> "交易对象" "交易描述"
2021-09-29 * "汉堡王" "饭的吃"
  ; <账户> <金额> 
  Assets:DebitCard:BCM        -28.9 CNY
  ; 金额可以省略,将会自动计算。此处就是 28.9 CNY
  Expenses:Food:Dinner:Supper

标志可以是*或者!!意味着交易还需要之后核对,在Fava中会标红显示。交易对象可以省略不写。

余额

余额用来标记账户当前的真实余额。余额本身不会影响账本,只是会触发Beancount对账本内容进行检查。如果Beancount发现账户的余额和标记的不符,就会报错提示。一般情况下这意味着哪儿记账记错了。

; 表示 2021-10-10 这天开始时支付宝余额是 1 CNY,太惨了。太惨了(疯狂暗示右上角)
2021-10-10 balance Assets:Digital:Alipay   1.00 CNY

指令

官方其实并不把这个称为指令,所以只是我那么叫啦。指令主要用于Beancount本身的一些配置。

; 配置参数
option "operating_currency" "CNY"

; 加载插件
plugin "beancount.plugins.tag_pending"

; 包含其他文件
include "accounts/assets.bean"

注释

“只要不是Beancount语法的行都会被看成注释”,虽然文档是这么说的,但是好像有些时候还是会炸裂。因此还是推荐使用;开始注释的内容。此外,由于作者是Emacs使用者,所以你也可以用Org-mode的语法来“语义化”你的帐本(就是层级化啦)。

* 账户
** 资产
*** 数字钱包
2016-01-01 open Assets:Digital:Paypal USD

初始化账本

然后就可以开始考虑记账的事啦。首先就是好好规划一个账本文件结构,毕竟像示例那样全写在一个文件实在是太麻烦了。可以参考这样的结构:

.
├── main.bean                 ; 主账本文件
├── accounts                  ; 存放各类账户
│   ├── assets.bean
│   ├── equity.bean
│   ├── expenses.bean
│   ├── income.bean
│   └── liabilities.bean
└── txs                       ; 存放记账文件:年份.bean
    ├── 2021.bean
    └── init.bean             ; 开户情况

当然最好还是自己规划一个比较顺眼的好啦。如果你不想搞得太复杂,也可以使用一个文件先试试。反正之后再修改也只需要复制粘贴。然后是main.bean,基本上就是包含所有文件:

option "operating_currency" "CNY"

2000-09-20 commodity CNY

* 账户
include "accounts/assets.bean"
include "accounts/expenses.bean"
include "accounts/liabilities.bean"
include "accounts/income.bean"
include "accounts/equity.bean"

* 交易
include "txs/init.bean"
include "txs/2021.bean"

最后在accounts/equity.bean开一个初始金额账户,用来给账户设定初始金额:

1970-01-01 open Equity:Opening-Balances

初始化账户

初始化账户主要有两件事要做,

  1. 打开账户;
  2. 部分账户(比如支付宝)还需要设定初始金额。

以支付宝账户(Assets:Digital:Alipay)为例,首先是打开账户。在accounts/assets.bean加上:

2000-01-01 open Assets:Digital:Alipay

左侧年份随意!可以填生日、1970-01-01、注册账户日期等等。只要比相关交易要早就可以啦。然后是设定初始金额,在txs/init.bean加上:

2021-10-09 pad Assets:Digital:Alipay Equity:Opening-Balances
2021-10-10 balance Assets:Digital:Alipay  1.00 CNY

第二条语句断言2021-10-10这天开始时支付宝账户的余额是1.00 CNY,也就是开户时的余额。为了遵守复式记账的准则,还需要从另一个账户转1元给它。因此我们在balance之前创建一条从Equity:Opening-Balances转账到Assets:Digital:Alipay的交易,这就是填充交易(padding)。第一条语句的意思就是在2021-10-09添加一条填充交易,从Equity:Opening-Balances转账到Assets:Digital:Alipay来满足之后的第一个balance

之后只要按照这个流程继续创建完四大类账户就行啦!一般来说,只有Assets账户需要做第二步,所以其实很快就能搞定。

记账

创建完需要的账户,就可以在txs/2021.bean记账了,好耶!然后打开Fava(fava main.bean),你应该就能看到刚刚记下的帐了。

Fava提供的试算表

如何合理规划账户?

虽然文章已经写了好长了,但我还是想花一些篇幅来谈谈这个话题:如何合理规划账户。毕竟账户开的好,记账起来才爽嘛!其实很简单,合理开户的核心要素就在于弄清楚你到底想追踪哪些东西。比如,如果你想弄清楚自己分别在三餐上花了多少钱,那你就可以给三餐都开个户。但如果你完全不在意三餐的区别,开一个“吃饭”账户就足够了。

接下来就按着这个指导思想,来介绍介绍我个人的账户规划思路吧。

Assets:资产

资产账户主要用来记录你想追踪的资产。换句话说,如果你做了什么变动这个资产的事,你就希望记下这笔账,那就说明它应该开个户了,比如支付宝、银行卡。反之,对于手机话费、校园卡、村口老王的洗剪吹优惠卡、交通卡,我个人认为就没什么开户的必要了。我可懒得每次坐公交都记一笔帐,对我而言,充卡的时候记一笔就得了。把记账搞得太复杂,只会让你最终放弃记账。据此可以提供一条判断准则:只给能取出/变成钱的账户开资产账户

我给资产账户定的格式是:Assets:类别:账户[:币种]。类别我大致取了以下几种:

  • Cash:现金
  • Digital:数字钱包。支付宝、微信
  • DebitCard:储蓄卡
  • FinTech:数字理财产品。余额宝、零钱通
  • Tangibles:有形资产。实体类资产,如游戏卡带
  • Receivables:代付记账。帮其他人垫付钱就相当于你的资产,账户就是垫付对象,建议用人名的首字母拼音
  • Trade:Fund:基金。账户是F{基金号}
  • Trade:Stock:股票

Liabilities:负债

负债的格式和资产一样。

  • CreditCard:信用卡。建议账户用卡种:银行,比如Liabilities:CreditCard:MASTER:ICBC
  • Loan:贷款
  • ConsumptionCredit:消费信贷。如花呗、白条
  • FinalPayment:尾款
  • Payable:借钱、他人垫付

Income:收入

同样格式不变,介绍几种我自己选的类别:

  • Job:工作相关收入。如薪水、医保等等
  • PartTime:零工收入。如外包
  • Gift:意外所得、获赠
  • Home:生活费
  • Award:奖金
  • Interest:利息。银行卡、余额宝
  • Trade:交易PnL。如网店、基金、股票
  • 2ndTrade:二手交易PnL。如闲鱼

关于交易相关的内容,我会在第二篇里面详细讲解。一般而言只需要建立PnL(盈利或亏损,Profit and loss)账户就行,这也是收入账户里少有的几个可能是正的账户(虽然我们希望他是负的,但基金有它自己的想法)。

Expenses:支出

跟个人习惯强相关。一般而言覆盖“衣食住行”就行啦,关键还是:你希望知道自己在哪些上的开销有多少,就建哪些账户。建议先随便写几个,然后在之后的记账中慢慢补全。还可以把自己上个月的账单整理出来,然后分析自己可能常用的账户。

Equity:权益

这个没有什么必要规划,就这一个即可。其它账户要么Beancount会帮你创建,要么下一篇会介绍。

  • Opening-Balances:开始记账记录初始金额用

后记

这篇文章算是补齐了我刚开始使用Beancount时,翻找各种资料却没找到的一些内容。目标就是希望读者只看完这篇文章,就能具备使用Beancount进行简单记账的能力。虽然有点长,但是介绍的内容都是我认为能帮助你更合理的记账、降低”荒废率“的。下一篇文章,我将介绍一般的Beancount记账方法,然后介绍若干个特殊的场景。相信看完后你就更能认识到复式记账法强大的表达能力了。

Reference

  1. The Double-Entry Counting Method(https://beancount.github.io/docs/the_double_entry_counting_method.html
分享到

KAAAsS

喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)

相关日志

评论

  1. IceSpite 2021.10.11 10:02上午

    手动记账如果坚持不下来可以考虑自动记账(手动狗头):https://github.com/dreamncn/Qianji_auto

    • KAAAsS 2021.10.11 11:36上午

      最终的方案其实也是完全自动(或半自动的):

      – 支持导入支付宝、微信、银行卡、金融 APP 账单,并可相互查重
      – 支持 Telegram Bot 手动一句话记账,用来补充账单导入的不足
      – 支持实时获取基金、股票价格数据,计算浮盈浮亏
      – 账本数据版本控制、定期备份

      实测每个月大概花半小时整理即可~话说我是不是应该把这些写进文章里……

  2. KevinAxel 2021.10.16 2:09下午

    看来得让这个KAAAsS多看到点东西帮忙折腾)

  3. Sanpo 2022.01.07 3:28上午

    感谢KAAAsS让我摸到了门

  4. Sanpo 2022.01.07 9:14下午

    请教KAAAsS,本地fava已实现,不知道怎样能从局域网访问fava服务(装了台Ubuntu server虚拟机跑了下fava,想从主机访问fava,输入虚拟机ip:5000未果)

    • KAAAsS 2022.01.07 9:24下午

      Fava 默认监听了 localhost,所以局域网访问需要加上 -H 参数:fava -H 0.0.0.0 main.bean

      • Sanpo 2022.01.07 9:29下午

        谢谢!

在此评论中不能使用 HTML 标签。