Accounting
Accounting is essentially answering three questions:
- Where does the money come from?
- Where is the money now?
- Where did the money end up?
It sounds simple, but as long as your financial activities are a little more complicated, these three questions will immediately become a “multivariable equation”:
- Multiple accounts: bank cards, Alipay, credit cards, exchanges, on-chain wallets…
- Multiple asset types: savings, loans, investments…
- Multiple currencies: CNY, USD, BTC, various altcoins…
- Multiple time dimensions: installment, advance, loan…
Because of this, I have changed several plans.
2016–2021: NetEase is rich - the needs at that time were very simple: recording RMB income, savings, and daily expenses. It can automatically synchronize bank statements and Alipay and WeChat bills, which is very convenient for domestic scenarios.
2021–2024: MoneyWiz - I need multiple currencies after moving abroad and mainly use a UK bank account. MoneyWiz is very convenient for synchronizing multiple currencies and overseas accounts.
2024: I started recording crypto assets. At this time, the situation became very complicated. I not only wanted to know “how much the total amount has increased”, but also wanted: the details of each purchase/sale, the proportion of different assets, the daily profit and loss, and a more rigorous calculation of the rate of return.
MoneyWiz has very limited support for investment/encrypted assets; I tried Youzhiyouxing again. It can record the total amount before and after the investment and calculate the rate of return, but it cannot record the details of each transaction. Many prices need to be maintained manually, and floating profits and losses cannot be automatically calculated.
Finally I came across double-entry accounting and Beancount, which completely solved my problem.
I realized that many times we find bookkeeping troublesome not because the UI is not beautiful enough and the interaction is not smooth enough, but because the bookkeeping method is not scientific enough. When the method is scientific enough, you don’t even need a UI. A CLI + a few plain text files + a set of clear syntax can describe the financial world very accurately.
Double-entry accounting
Double-entry accounting may seem counterintuitive at first, but once you master it, you’ll find it to be incredibly powerful and flexible, and it will force you to understand your financial activities in a clearer way.
Five buckets (account type)
Double-entry accounting will classify all accounts into five categories (can be thought of as five buckets of beans):
- Assets: cash, bank deposits, securities, on-chain assets…
- Liabilities: credit card debt, mortgage, car loan…
- Income: salary, bonus, interest…
- Expenses: Eat, shop, travel, subscribe…
- Equity: “base” for adjustment/archiving, such as opening balance, error correction, etc.
All financial activities are “transfers between buckets”
- Income → Assets: Pay wages (the income bucket decreases and the asset bucket increases)
- Asset → Spend: Buy something (the asset bucket decreases and the spending bucket increases)
- Liability → Asset: Borrow money (the liability bucket increases, the asset bucket increases)
- Asset → Liability: Repayment (Asset bucket decreases, Liability bucket decreases)
- Spend → Asset: Return (the spending bucket decreases and the asset bucket increases)
- Assets → Income: Investment losses (assets bucket decreases, income bucket decreases)
You will find that according to this sign convention, the balance of the income account is often negative. This is not a bug, but a different accounting perspective.
Think of it this way: Think of “income” as a bucket containing the fruits of your life (past and future). Every time you get a salary, you are actually taking some of it out of this bucket and putting it into your assets. The more you take, the more negative the value in the income bucket becomes.
So negative income doesn’t mean “you owe income,” it’s just that you’re taking assets out of the bucket. This also involves a core rule of double-entry accounting: in a transaction, the sum of all numbers must equal 0. This is also the key to verifying its correctness: if you miss one stroke or write one wrong digit, the system will alarm you on the spot.
Under the common notation conventions of Beancount, you will see “accounting identity” written like this:
(收入 + 负债) + (资产 + 花费) = 0
You can also think of it in a more traditional way: whatever you earn or borrow ultimately either becomes an asset you own or is spent.
Beancount
Beancount is the most comfortable plain text double-entry accounting tool I have ever used and the most suitable for the complex financial world.
It is not the pioneer of double-entry accounting software. The earlier representative is Ledger. But Beancount has made a lot of improvements on the Ledger concept and has stood the test of time (started in 2007).
The most direct way to learn Beancount is to read the official documentation: Getting Started with Beancount, Beancount Language Syntax, or this article is also good: Beancount - Command line double-entry bookkeeping | wzyboy’s blog
In this blog, I will not go into the details of syntax and usage, but will use a few examples to let you quickly feel the power of Beancount and why it is more suitable for the era of Crypto and AI.
Handle complex transactions
Beancount can be used to handle complex transactions gracefully. The following examples involve many-to-many, multi-account + multi-person complex transactions, but they can be described clearly.
2016-02-05 * "饭店" "和室友吃饭"
Assets:Cash:Wallet -300.00 CNY ; 我用现金支付
Assets:Receivables:Bob -200.00 CNY ; 室友帮我付的现金
Expenses:Food:DiningOut +250.00 CNY ; AA 我的一半
Assets:Receivables:Bob +250.00 CNY ; AA 室友的一半
Here’s what happened at the same time:
- You paid part of it in cash
- The roommate also paid part of the money in cash.
- AA share
- You incur/offset receivables against roommates
Finally, you only need to look at the balance of Assets:Receivables:Bob to know how much you owe your roommate, and the balance of Expenses:Food:DiningOut to know how much you spend on food every month.
If you use “single-entry accounting + remarks” to pile up, you will basically be unable to calculate later. However, double-entry accounting can directly write out the structure, and then statistics will become very natural.
Processing multi-currency transactions
Cross-currency transactions often involve exchange rate issues, and Beancount can also handle them gracefully.
2016-02-10 * "商店" "买东西"
Assets:Cash -200.00 USD
Liabilities:CMB:CreditCards -650.00 CNY @@ 100.00 USD
Expenses:Clothing:Pants +150.00 USD
Expenses:Clothing:Shoes +150.00 USD
This kind of scenario where “the credit card is denominated in RMB, but the actual consumption is denominated in US dollars” is very common in cross-border life. The expression of Beancount is very straightforward: use a record to bind the relationship between the currencies on both sides and the current exchange rate, and subsequent queries/reports can continue to be derived based on it.
More suitable for Crypto transactions
Beancount does not limit currency units: USD, CNY, BTC, USDT, or even any custom token you can use. Coupled with the SQL-like query tool bean-query, it is very convenient to use for crypto asset investment analysis.
Let’s take an example that looks complicated but is very concisely recorded:
2025-07-23 price BTC 1 USD
2025-07-24 * "OKX" "买BTC"
Assets:Crypto:USD -150 USD
Assets:Crypto:BTC 149 BTC {1 USD}
Expenses:TradingFee:Crypto 1 USD
2025-07-24 * "OKX" "买BTC"
Assets:Crypto:USD -60 USD
Assets:Crypto:BTC 19 BTC {3 USD}
Expenses:TradingFee:Crypto 3 USD
2025-07-24 * "OKX" "卖BTC"
Assets:Crypto:BTC -198 BTC {} @ 2 USD
Assets:Crypto:USD 298 USD
Income:Investment
2025-07-25 price BTC 4 USD
This example includes:
- Multiple purchases with different costs
- handling fee
- Automatically match costs when selling (
{}let Beancount find it yourself) - Market price (
price) changes over time
Based only on these records, Beancount can automatically calculate:
- Total revenue/realized revenue/unrealized revenue
- Various types of rates of return (such as capital-weighted rate of return Modified Dietz, time-weighted rate of return TWRR, or even any income calculation you customize)
In other words: you are only responsible for writing the facts clearly, and any complex calculations can be handed over to Beancount.
My Beancount practice
Treat the ledger as code management.
Editor
I use VSCode, with Beancount, Beancount Formatter and the following configurations to achieve syntax highlighting, auto-completion, error checking, formatting, etc.
{
"beancount.mainBeanFile": "main.bean",
"beancount.runFavaOnActivate": false,
"beancount.completePayeeNarration": true,
"beancount.fixedCJKWidth": true,
"beancount.inputMethods": ["pinyin"],
"beancount.instantAlignment": false,
"editor.formatOnSave": true
}

Project structure
Beancount naturally supports modularity: different types of transactions are split into different files and then include enter the main entrance, so the maintenance cost is very low.
.
├── books
│ ├── 2025
│ │ ├── 2025.bean
│ │ ├── cn.bean
│ │ ├── crypto.bean
│ │ ├── daily.bean
│ │ ├── periodic.bean
│ │ ├── prices.bean
│ └── books.bean
├── scripts
│ ├── importer-dbs-credit.js
│ ├── importer-dbs-debit.js
│ ├── importer-okx.js
├── accounts.bean
├── commodity.bean
├── config.pbtxt
├── dashboards.yaml
├── main.bean
main.bean is the entry file, which contains configuration, plug-ins and include:
option "title" "DIYgod's Book"
option "operating_currency" "USD"
option "booking_method" "LIFO"
option "account_rounding" "Equity:Rounding-Error"
plugin "beancount_periodic.recur"
plugin "beancount_periodic.split"
2024-01-01 custom "fava-option" "auto-reload" "true"
2024-01-01 custom "fava-option" "default_page" "income_statement/?conversion=units&interval=day"
2024-01-01 custom "fava-extension" "fava_portfolio_returns" "{
'beangrow_config': 'config.pbtxt',
}"
2024-01-01 custom "fava-extension" "fava_dashboards" "{
'config': 'dashboards.yaml'
}"
include "accounts.bean"
include "books/books.bean"
include "commodity.bean"
accounts.bean Define all accounts (omitted):
; Expenses 费用 —— 外出就餐、购物、旅行等
1995-06-09 open Expenses:Food:Restaurant
; ... 省略若干行 ...
; Income 收入 —— 工资、奖金等
2020-04-01 open Income:Salary
; ... 省略若干行 ...
; Assets 资产 —— 现金、银行存款、有价证券等
2024-01-01 open Assets:Bank:DBS SGD
2024-01-01 open Assets:Crypto:OKX:BTC BTC
; ... 省略若干行 ...
; Liabilities 负债 —— 信用卡、房贷、车贷等
2024-01-01 open Liabilities:Bank:DBS SGD
; ... 省略若干行 ...
; Equity 权益 —— 用于「存放」某个时间段开始前已有的豆子
2024-01-01 open Equity:Opening-Balances
2024-01-01 open Equity:Rounding-Error
commodity.bean Define the currency/asset and use price to specify the price source (automatic updates will be discussed later):
2025-01-01 commodity USDT
name: "Tether"
2025-01-01 commodity BTC
name: "Bitcoin"
price: "USD:coinbase/BTC-USD"
2025-01-01 commodity USD
name: "United States Dollar"
2025-01-01 commodity QQQ
name: "NASDAQ-100 Index"
price: "USD:alphavantage/price:QQQ:USD"
books/2025/prices.bean is used to record daily prices (automatically generated) so that the profit and loss of positions can be calculated:
2025-01-01 price BTC 93346.48 USD
2025-01-02 price BTC 94384.76 USD
; ... 省略若干行 ...
Several common Crypto transactions
The following examples come from my actual practice: transactions are not filled in by hand, but automatically generated from OKX transaction records via scripts/importer-okx.js.
Stablecoin Spot Trading
- Buy: Use USDT to buy BTC + handling fee
2025-11-14 * "OKX" "Buy BTC with USDT"
Assets:Crypto:OKX:USDT -3022.493 USDT
Assets:Crypto:OKX:BTC 0.0302113288 BTC {100000 USDT}
Expenses:TradingFee:Crypto:OKX
Here I leave the handling fee blank and let Beancount automatically fill it in (using the “each transaction adds up to 0” rule).
But if your BTC may come from different stablecoins such as USDT and USDC, the subsequent statistical income will involve exchange rate and currency conversion. In order to make the analysis more unified, I often use a transition account to unify the costs into USD:
2025-11-14 * "OKX" "Buy BTC with USDT"
Assets:Crypto:OKX:USDT -3022.493 USDT
Equity:Exchange 3022.493 USDT
Equity:Exchange -3022.493 USDT @ 1 USD
Assets:Crypto:OKX:BTC 0.0302113288 BTC {100000 USD}
Expenses:TradingFee:Crypto:OKX
Equity:Exchange functions like a “unified conversion layer”: it converts different stablecoin transactions into USD, and subsequent reports will be very comfortable.
- Sell: Sell BTC to get USDC + automatic profit calculation
2025-09-18 * "OKX" "Sell BTC for USDC"
Assets:Crypto:OKX:BTC -0.100011 BTC {} @ 117600 USD
Equity:Exchange 11756.00101788 USD
Equity:Exchange -11756.00101788 USD @ 1 USDC
Assets:Crypto:OKX:USDC 11756.00101788 USDC
Expenses:TradingFee:Crypto:OKX -5.29258212 USD
Income:Investment
{} @ 117600 USD: The cost price is automatically matched from historical records by Beancount, and the selling price is 117600 USD- The extra part goes into
Income:Investment: the income comes naturally in this way
If you don’t need unified conversion, it can also be simplified to:
2025-09-18 * "OKX" "Sell BTC for USDC"
Assets:Crypto:OKX:BTC -0.100011 BTC {} @ 117600 USDC
Assets:Crypto:OKX:USDC 11756.00101788 USDC
Expenses:TradingFee:Crypto:OKX -5.29258212 USDC
Income:Investment
Coin spot trading
Buy ETH with BTC
2025-08-22 * "OKX" "Buy ETH with BTC"
Assets:Crypto:OKX:BTC -0.025828016279999998 BTC {} @ 112480.295 USD
Equity:Exchange 2,905.1428904392 USD
Equity:Exchange -2,905.1428904392 USD
Assets:Crypto:OKX:ETH 0.6820274505 ETH {4224.16 USD}
Income:Investment
Expenses:TradingFee:Crypto:OKX 0.0001705495 ETH {4224.16 USD}
The cost/benefit of cryptocurrency transactions is more likely to be confusing, but after unifying the process to the USD caliber, it will become very clear: ETH exchanged with BTC has a clear USD cost; handling fees and subsequent earnings can also be correctly included.
U-based contract
The U-based contract does not actually sell BTC within the exchange, but in order to reflect the profit and loss on asset changes, I use a “borrowed currency sell/buy back” approximate model to express:
- Opening a short position: equivalent to “borrow BTC, sell, and exchange for USDT”
- Close position: Equivalent to “use USDT to buy back BTC and return it”
- Profit and loss will naturally be reflected in the final extra (or missing) part
2025-12-01 * "OKX" "合约 BTC-USDT 做空开仓"
Assets:Crypto:OKX:Futures:BTC -10 BTC @ 100000 USD
Equity:Exchange 1000000 USD
Equity:Exchange -1000000 USD @ 1 USDT
Assets:Crypto:OKX:Futures:USDT 1000000 USDT
2025-12-10 * "OKX" "合约 BTC-USDT 做空平仓"
Assets:Crypto:OKX:Futures:USDT 1000000 USDT
Equity:Exchange 1000000 USDT
Equity:Exchange -1000000 USDT @ 1 USD
Assets:Crypto:OKX:Futures:BTC -10 BTC {80000 USD}
Income:Investment
Real contracts will also involve margin, funding fees, forced liquidation, settlement currency differences, etc. A more rigorous approach can continue to split the account model, but this writing method can already record and calculate the results clearly in most scenarios.
Coin-margined contract
Coin-margined contracts are more complex: the position is typically “USD-denominated BTC price exposure” rather than a fixed amount of BTC. It will be very complicated to calculate floating profits and losses based on daily prices, which requires a “double-account model” (factual account + valuation account).
I currently choose to simplify it to this: manually calculate the profit in BTC when closing the position, and only record the final BTC change, but this will sacrifice the data of the unrealized profit of the current position:
2025-11-21 * "OKX" "合约 BTC-USD 1.0"
Income:Investment
Assets:Crypto:OKX:BTC 0.007433571994392 BTC {85400 USD}
Automation script + AI accounting
Through automated scripts and AI, the accounting workload can be greatly simplified, turning “accounting” into “reconciliation.”
Automatically generate transaction records
Beancount is plain text, which means it is naturally suited to automating record generation and updating asset prices.
There are many import tools in the community, such as: beangulp, smart_importer.
But the ones I use most are self-written scripts (such as OKX’s scripts/importer-okx.js, DBS’s scripts/importer-dbs.js). The principles are similar: obtain transaction data from the interface or web page → convert it into Beancount text.
For some accounts that do not have interfaces or even export bills (such as the Trust I am using now), it is also very hassle-free to directly take screenshots and send them to AI for recognition:

Automatically update prices
Beancount itself is an offline system and will not automatically pull prices from the Internet, so I will write the latest currency price/stock price into books/2025/prices.bean every day, so that the report can calculate the current day’s valuation and unrealized profits.
The official provides beanprice out of the box, which supports Coinbase, Coinmarketcap, Alphavantage and other sources. It can be updated with one command:
bean-price -i --update-rate daily --no-cache main.bean >> books/2025/prices.bean
AI Analysis and Visualization
Many people stop at “writing down” their accounting and end it, but what is truly valuable and easily forgotten is how to analyze it after recording.
Beancount is very exaggerated in its analysis and visualization capabilities:
- Web interface that can install plug-ins: Fava
- SQL-like query language: BQL (Beancount Query Language)
Let’s take a look at some of the effects I commonly use:
Price chart of investment products (included with Fava):

Yield curves of different investment products, as well as various weighted returns (plug-in Fava Portfolio Returns):

Change curve, distribution proportion, list of specific assets (plug-in Fava Dashboards + custom BQL):

What’s even better is: now with vibe coding, times have changed. Now you don’t even need to learn how to write BQL first – you can just let AI write it.
For example, I would make a request like this:
Using the Fava Dashboards plug-in, write BQL in dashboards.yaml to implement a graph showing the changes in all my cryptocurrency positions over time.
Personalized financial analysis that once took a long time can now be just one sentence.
Conclusion
I have been using Beancount for a year, and almost all my needs have been met. I am very satisfied and have shared it within the company. This blog is to organize the shared content into text, hoping to recommend it to more people.
I also admit that the threshold for Beancount is not low for most people - it is more like a “programming language for writing ledgers” rather than a little app. But I also believe that with the development of AI, these barriers will become lower and lower. In the future, everyone can control this powerful tool at a lower cost and achieve full control of their own finances.
If you are also experiencing:
- Multi-currency life
- More and more investment products
- The more the accounts are kept, the more confusing they become. -Analyses are becoming increasingly complex
Then I suggest you spend at least half a day trying Beancount. It may make you feel for the first time: It turns out that I can finally explain money clearly.