用 Beancount 复式记账,Double-Entry!
目录
🤔动机
已经有很多次记账的尝试,包括各种 App 如圈子账本(现已倒闭下架)等。但有几个痛点还没法解决:
- 大多数都是简单的记载收支,这对手握多个银行账户、信用卡等的我来说,显得不够用
- 数据安全问题,数据在服务提供者手上,既有隐私担忧,又受制于人,谁知道什么时候就删库跑路了
- 数据报表,大多数 App 的统计都比较垃,甚至需要 VIP 才能看
等我回过神来,大学期间的所有数据全都丢失了,毕竟好几年没用了,这通知后来才看到
于是,推特上搜了一下,beancount 映入眼帘,其具有的优势为:复式记账、开源、文本式存储、生态较活跃
Plain Text Accounting portal - plaintextaccounting.org
https://github.com/siddhantgoel/awesome-beancount
📝概念
复式记账
又叫“借贷记账法”,是目前世界大部分国家(包括中国)的通用记账方法,是会计的入门基本功。“有借必有贷,借贷必相等”,这里的“借”、“贷”,纯粹就是个记账符号,跟它的汉语意思没一点关系。
是把每笔交易都记录到偶数个账户(double-entry)中的记账方法,如资产账户和费用账户,提供了可观测性。
例如:刷卡买了一部手机,花了2000元——①在“银行存款”账本上记上:减少2000元;②在“存货”账本上记上:手机,增加2000元。
Beancount 中有个恒等式:(Income + Liabilities) + (Assets + Expenses) + Equity = 0 ,表示你赚的钱(Income),加上你借来的钱(Liabilities),最终要么变成你自己的钱(Assets),要么就是花掉了(Expenses),最终得到的是个零。Equity 的作用是存放已有的「权益」。
Asset 资产(+) | 有形或者无形的资产,比如 现金, 银行账户, 有价证券, 车子,房子 等等 |
---|---|
Liabilities 负债(-) | 各种形式的负债,房贷,车贷,信用卡债,花呗 |
Income 收入(-) | 工资,奖金,彩票中奖,出租屋收入,股息,存款利息,借贷利息 等等 |
Expenses 支出(+) | 各种花费,手续费,购物,旅行,支付的贷款利息等等 |
Equity 权益(-) | 记录账本初始化之前的净收入(income - expenses) |
Beancount 是一个 Ledger-like 软件。Ledger 是这一类复式簿记软件的开创者。他们共有的特点是:
- 采用改进的复式簿记方案(使用正负号而不是「借」和「贷」来表示账户之间的变化);
- 使用纯文本文件作为账本,用户用文本编辑器即可记账;
- 账本既是用户输入的文件,同时也是软件的「数据库」;
- 软件读取账本并生成报表,账本本身也可供人类直接阅读。
Beancount 可以用来记录包括货币在内任何东西的变化,比如年假天数、股票、航空里程、信用卡积分,当然了,还可以用来数豆子。这也是 Beancount 名字的来源。
🤗实践
开启一个 beancount 项目
# 安装
pip3 install beancount fava
# 生成模板
bean-example > example.beancount
# 运行 Web UI
fava example.beancount
- 在资产负债表(balance sheet)里,看有多少资产、资产分别在哪些账户里、有多少负债、是对哪些银行的负债。
- 在损益表(income statement)里,看每月有哪些收入、收入来自于哪些地方、有多少支出、支出花在了什么地方。
我们后面会使用 VSCode 来记账,可简单模仿例子来建立我们自己的项目。
下面是语法速记表:
Beancount - Syntax Cheat Sheet.pdf
VSCode 插件:Beancount
该插件要求最好按照其推荐的方式组织文件:
.bean
的文件后缀,所有账户的open
和close
放在main.bean
中- 在
main.bean
中include
其他文件 - 用 VSCode 打开所在文件夹,并设置
beancount.mainBeanFile
的全路径
BeanFolder
├── .vscode
│ └── settings.json
├── main.bean
├── before2017.bean
├── 2023-01.bean
└── 2023-02.bean
{
"beancount.mainBeanFile": "/xxx/xxx/xxx/main.bean"
}
如果对指令的格式存在疑问,可查询语法手册:
Beancount Language Syntax - Beancount Documentation
一般而言,怎么记账,可用一张图(源自:记账及fava自建服务器指南)解释:
指令学习
commodity
YYYY-MM-DD commodity Currency
所有的货币、金融工具、商品等用 commodity
指令定义,主要是为了定义一些元数据以供其他插件使用。
1867-07-01 commodity CAD
name: "加元"
asset-class: "cash"
2012-01-01 commodity HOOL
name: "Hooli Corporation Class C Shares"
asset-class: "stock"
open
YYYY-MM-DD open Account [ConstraintCurrency,...] ["BookingMethod"]
1970-11-05 open Equity:Opening-Balances
1970-11-05 open Assets:Bank:CN:ICBC:8043 CNY
1970-11-05 open Assets:Bank:CN:BOC:8882 CNY
1970-11-05 open Assets:Bank:CN:CMB:6783 CNY
所有的账户都得 open
指令来开。
pad
YYYY-MM-DD pad Account AccountPad
2023-01-06 pad Assets:Bank:CMB:2622 Equity:Opening-Balances
2023-01-06 balance Assets:Bank:CMB:2622 1234567.0 CNY
主要用于初始化账户,使之与 balance
断言对齐。
balance
YYYY-MM-DD balance Account Amount
2014-08-09 balance Assets:Cash 562.00 USD
断言,看某一天该账户的资产是否符合预期,如果不满足就会在 Beancount 分析时报错。
price
YYYY-MM-DD price Commodity Price
2014-07-09 price HOOL 579.18 USD
顾名思义,商品的价格都由 price
指令定义。如果开启 beancount.plugins.implicit_prices
插件,就会自动在买入时推测价格。这里没有时间概念,仅可存每日价格。
关于插件,请看:https://beancount.github.io/docs/beancount_scripting_plugins.html
txn
YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Flag] Account Amount [{Cost}] [@ Price] [Flag] Account Amount [{Cost}] [@ Price] ...
2014-05-05 txn "Cafe Mogador" "Lamb tagine with wine"
Liabilities:CreditCard:CapitalOne -37.45 USD
Expenses:Restaurant
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
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
交易指令,最常用的了,完成的交易可简写为*
,而未完成需要后续修订或确认的则简写为!
。
- 可添加标签:例如
#berlin-trip-2014
记录证券投资
什么是成本(cost)?当你买入一支股票的时候,每股所花的钱即是成本。
什么是价格(price)?某支股票的市场价(market price),可用 price
指令定义。
你的盈亏(PnL),就是卖出-买入。
在 Beancount 中,表示 cost 的语法是 {成本, 日期, 备注}
,表示 price 的语法是 @
和 @@
。例如,买入 10 股成本为 1000 USD 的 AMZN 记为:
2017-07-14 * "Buy 10 AMZN"
Assets:Trade:Cash -10000.00 USD
Assets:Trade:Positions 10 AMZN {1000.00 USD}
以 2000 USD 每股的价格出售 3 股 AMZN 记为:
; 单价
2018-08-30 * "Sell 3 AMZN"
Assets:Trade:Positions -3 AMZN {1000.00 USD} @ 2000.00 USD
Assets:Trade:Cash 6000.00 USD
Income:Trade:PnL -3000.00 USD
或
; 总价
2018-08-30 * "Sell 3 AMZN"
Assets:Trade:Positions -3 AMZN {1000.00 USD} @@ 6000.00 USD
Assets:Trade:Cash 6000.00 USD
Income:Trade:PnL -3000.00 USD
Beancount 提供了 5 种簿记方法:
- STRICT:每次取豆子时,必须要明确匹配到某个 lot,不允许模糊匹配(例外是
{}
,代表全部取出)。这也是默认的模式。 - FIFO:即先进先出(First-In, First-Out),每次取豆子时自动从最老的豆子开始取,直到取光。
- LIFO:即后进先出(Last-In, First-Out),与 FIFO 相反,最新的豆子优先被取出。
- AVERAGE:每次重新计算 cost basis。该方法暂未实现。
- NONE:完全不合并,允许不同符号的 lot 同时存在。
Beancount 中可简写省略掉一些具体数值,因为 Beancount 会根据已有条件自动算出来。
周期性记账
现在越来越多的产品趋向于会员订阅制,以及可能存在的分期付款等,需要用到周期性记账的功能。
该功能在 Beancount 中需开启 beancount.plugins.forecast
插件,详见forecast doc。
引入插件之后,咱们看看语法,很容易阅读和理解:
2021-05-17 # "House Rent [MONTHLY]"
Expenses:House:Rent 3000 CNY
Assets:DebitCard:CMB
2014-03-08 # "Electricity bill [MONTHLY UNTIL 2019-12-31]"
Expenses:Electricity 50.10 USD
Assets:Checking -50.10 USD
2014-03-08 # "Electricity bill [MONTHLY REPEAT 10 TIMES]"
Expenses:Electricity 50.10 USD
Assets:Checking -50.10 USD
2014-03-08 # "Electricity bill [WEEKLY SKIP 1 TIME REPEAT 10 TIMES]"
Expenses:Electricity 50.10 USD
Assets:Checking -50.10 USD
2014-03-08 # "Electricity bill [DAILY SKIP 3 TIMES REPEAT 1 TIME]"
Expenses:Electricity 50.10 USD
Assets:Checking -50.10 USD
2022-02-08 # "Electricity bill [MONTHLY UNTIL 2022-12-31]"
Expenses:Electricity 50.10 USD
Assets:Checking -50.10 USD
- 周期有: YEARLY、MONTHLY、WEEKLY、DAILY
- 后可接:UNTIL 2022-12-31
- 循环有:REPEAT {integer} TIMES、REPEAT 1 TIME、SKIP {integer} TIME(S) REPEAT {integer} TIMES
GitHub Actions
施工中……
参考:使用 Beancount 管理家庭财务 · 构建我的被动收入 (bmpi.dev)
脚本导入
施工中……
参考:https://github.com/zsxsoft/my-beancount-scripts
参考文章
Beancount – 命令行复式簿记 | wzyboy’s blog