beancount记账

之前在安卓用「那样记账」记账,换 iPhone 后发觉在 App Store 没有,想找一款跨平台的记账软件,期望功能:

  • 记账
  • (分类、分层的)统计图示
  • 同步

找到 beancount [1],想起是之前 BYVoid 推荐过 [4]。本篇做入门简介,更详细的介绍见 beancount 的文档 [2] 和 BYVoid 的系列网志 [4]。

Beancount

beancount 属于纯文本记账 [5] 的工具之一,即所有收支记录都记在文本文件里,扩展名 .bean 或 .beancount,配有几款主流编辑器的高亮支持 [6]

由于是文本文件,则可以用 git 同步。目前主要是在电脑用。手机端有个支持安卓和 iphone 的 beancount-mobile [7,8],还未认真研究过。

Single-entry vs. Double-entry Counting

beancount 的记账模式是复式记账(double-entry counting);相对地,多数普通记账软件(如那样记账)的记账模式应该是单式(single-entry counting)。

复式记账是专业的记账法,不过目前记的都是日常消费,单、复式于我只是形式不同,对复式的强大无甚体会。从形式上看:

  • 「那样记账」将帐分成三类:收入、支出、转帐。对前两类,只涉及一个账户,即钱从那个帐户出、或入哪个账户,再加个标记用于统计图示。转帐就涉及两个账户,同复式。
  • 复式记账将所有帐都记为转帐。每笔转帐至少涉及两个账户,也可以多个,分成两方:支出(debit)、收入(credit)方。这可能就是 double 的含义。

example

此处举一例对比「那样记账」和 beancount 的记法差异:

2025.1.5 在 Cotti Coffee 花 12.07 人民币买了一杯抹茶生椰拿铁,刷中国银行的卡。

若用「那样记账」记,这属于支出帐,所以在软件「支出」页面,点个「饮料」标签(预先创建的标签),输入时间、金额,帐户选中国银行卡(也是预先创建的账户),就完成记录。

若用 beancount 记,涉及两个账户:

  • 中国银行卡
  • 饮料

买奶茶就记为「中国银行」账户给「饮料」账户转了 12.07 元,在文件中写作:

2025-01-05 * "Cotti Coffee" "抹茶生椰拿鐵"
    Expenses:Drinks                    12.07 CNY
    Assets:Checking:BoC               -12.07 CNY

其中 Expenses:DrinksAssets:Checking:BoC 是在别处定义的账户,此处省去。账户名是分层的,方便之后分层统计。也有打标签的语法,详见 [2,4]。「饮料」账户是一种抽象:因为这笔钱肯定是转到 Cotti Coffee 店铺的账户上去,只不过我们并不关注具体是打到哪间铺的哪个账户,而是将所有的卖饮料的店舖账户都抽象成一个 Expenses:Drinks,这顺便也承担消费归类的功能。

第二、三行的金额可以省写其中一项,因为每笔转帐的数值和必须是零,理论基础是会计恒等式 [2,4]

balance

balance 语句用来验账,如:

2025-01-10 balance Assets:Checking:BoC      1234.56 CNY

表示断言 Assets:Checking:BoC 账户由开户至 2025.1.10 累积余额为 1234.56 CNY。此数从手机银行 / 动帐通知 / 存折打簿直接读出、手动输入,然后命令行执行 bean-check main.bean假设数簿文件为 main.bean,见后文),beancount 会按时间顺序累积余额,跟此数对比,对不上会报错,告诉你哪句 balance 不满足、差了几多。

有个时机问题:bean 文件中的条目书写顺序是任意的,beancount 会根据条目的时间戳排序,而忽略书写顺序。但时间戳精度为日,那同一日的转帐是算在 balance 语句前还是后呢?对此,beancount 假设 balance 语句(其实包括所有转帐以外的语句,如 open)都于同日的转帐语句,所以此例中 balance 语句只对比 Assets:Checking:BoC 累积到 2025-01-09 的余额,而不含 2025-01-10 的转帐。

为了避免出现 balance 报错时,重审因时间跨度太长、涉及条目太多而工作量过大,建议及时写 balance 并执行 bean-check 验账:

  • 对常用的账户(如日常消费用银行卡),可以离上条 balance 语句 10 条左右转账之后就加一条,或一周一条;
  • 对不常用账户(如借/还钱),可能很久才出现一次涉及到它的转帐,可以每次转帐后都写一条 balance 验账。

Environment

beancount 是用 python 写的。装好 python 后,参考 Installing Beancount,Windows 下装包命令:

pip install beanquery fava

Multi-file Accounting & Project Management

beancount 支持将帐分写在多个 .bean 文件内,于是可以当成一个项目用 git 管理。例如统一放到 my_accounting/ 文件夹中:

my_accounting/
|- make.bat			# 自动生成 main.bean 的脚本
|- main.bean
|- accounts.bean
`- books/
   |- 2024-12.bean
   `- 2025-01.bean

此例从 2024 年 12 月开始用 beancount 记账,每个文件记一个月的帐,另有:

  • main.bean:导入所有其它文件,方便后续统计图示;
  • accounts.bean:定义账户,即开户。

accounts.bean

接上文举的例,定义两个账户:

  • .bean 文件用分号 ; 注释一行(从 Emacs 来?)
  • 开户时间可以早些,只要保证早于第一次转帐就行,不用纠结真实的开户时间。
  • 账户名用冒号 : 分层,可任意多层。
; accounts.bean

; 指定使用的货币,任意多条
option "operating_currency" "CNY"
; option "operating_currency" "USD"

; 定义账户
2024-11-01 open Expenses:Drinks    CNY
2024-11-01 open Assets:Checking:BoC    CNY

账户名只有一个限制:顶层只能用 beancount 预定义的五个之一:AssetsEquityExpensesIncomeLiabilities。举例如下:

; Asserts 资产:现金、储蓄卡、股票、房、车、应收款
2024-11-01 open Assets:Cash    CNY
2024-11-01 open Assets:Checking:BoC    CNY
2024-11-01 open Assets:Checking:WeChat    CNY
2024-11-01 open Assets:Receivables    CNY

; Equity 权益:初始账户额、用以补齐记错帐的虚假资金
2024-11-01 open Equity:Opening-Balances    CNY
2024-11-01 open Equity:Error    CNY

; Expenses 支出:衣食住行、医药、物器、税
2024-11-01 open Expenses:Clothing    CNY
2024-11-01 open Expenses:Transport:Taxi    CNY
2024-11-01 open Expenses:Healthcare:Treatment    CNY

; Income 收入:工资、补贴、利息、利是
2024-11-01 open Income:Salary    CNY
2024-11-01 open Income:Interest:BoC    CNY
2024-11-01 open Income:LuckyMoney    CNY

; Liabilities 负债:信用卡、应付款
2024-11-01 open Liabilities:CreditCard:Huabei    CNY
2024-11-01 open Liabilities:Payables    CNY

main.bean

此文件就导入所有其余 .bean 文件:

; main.bean
option "title" "iTom's Ledger"

; 导入开户信息
include "accounts.bean"

; 导入帐簿
include "books/2024-12.bean"
include "books/2025-01.bean"

make.bat

数簿多了之后,手写 main.bean 有些麻烦。可以用脚本自动生成 main.bean,如:

@REM make.bat
@echo off
setlocal enabledelayedexpansion

echo Create main.bean, importing books.

set mainf=main.bean

@REM 创建 main.bean
echo ; main.bean> %mainf%
echo option "title" "iTom's Ledger">> %mainf%
@REM 导入开户文件
echo include "accounts.bean">> %mainf%
@REM 递归导入 books/ 内数簿
echo ; books>> %mainf%
call :import_books books

@REM 验帐
bean-check %mainf%

goto :eof

:import_books
    for %%f in (%~1\*.*) do (
        set ext=%%~xf

        @REM convert path separator: \ to /
        set cvtf=%%f
        set cvtf=!cvtf:\=/!

        if "!ext!" == ".bean" (
            echo include "!cvtf!">> %mainf%
        ) else if "!ext!" == ".beancount" (
            echo include "!cvtf!">> %mainf%
        )
    )

    for /d %%d in (%~1\*) do (
        call :import_books %%d
    )
exit /b

Visualisation

可以用 fava [3] 生成网页版统计图示:

fava main.bean

然后浏览器打开提示的网址,一般是 http://127.0.0.1:5000

Querying

bean-query 提供像 SQL 一样的查询功能 [9]。这也可以用来做更灵活的分类统计,如:统计 2025 年 5 月的三餐消费总额。假设相关账户如下:

2023-07-01 open Expenses:Food:Meal:Breakfast    CNY
2023-07-01 open Expenses:Food:Meal:Lunch    CNY
2023-07-01 open Expenses:Food:Meal:Supper    CNY

那就先在命令行执行 bean-query main.bean 进入其交互环境,然后查询:

SELECT account, SUM(position)
FROM date >= 2025-05-01 AND date < 2025-06-01
WHERE account ~ 'Food:Meal';

输出形如:

         account          sum_posit
------------------------- ---------
Expenses:Food:Meal:Supper  77.8 CNY
Expenses:Food:Meal:Lunch  110.4 CNY

上述命令实际用时没有换行,这里为了清楚加了换行。其中:

  • SELECT 中,account 就是定义的账户名,如 Expenses:Food:Meal:SupperSUM 求和;position 不知确切含义,暂简单理解成动帐数目。(前文买奶茶的示例是一条 transcation entry,而 transcation 中每一行称为一条 posting,position 应该是指其中的数额,详见 [9]。
  • FROM 是 transcation entry 一级的筛选,此例中用来筛时间范围。可用 help from 看所有可用的筛选字段。
  • WHERE 是 posting 一级的筛选,此例用来筛账户名,~ 是 beancount 定义的正则搜索运算,本例就是筛选 account(名)中含有 Food:Meal 的 posting。
  • SELECTSUMFROMANDWHERE 等关键字、函数名对大小写敏感。

统计过去的数据可以找出消费规律、预测未来消费水平,为未来消费规划提供数据论据。

References

  1. beancount/beancount
  2. Getting Started with Beancount
  3. beancount/fava
  4. Beancount複式記賬(一):爲什麼
  5. Plain Text Accounting (PTA)
  6. editor-support
  7. Beancount.io
  8. beancount-mobile
  9. Beancount Query Language
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值