Skip to main content

Beancount Budget

A command-line envelope budgeter.

  • No custom transaction types needed: your budget lives in CSV files, and balances are computed from Beancount data.
  • Set goals using quotas: budget for regular expenses, future large purchases, and more.

Getting started

Requirements

Executables:

  • Python 3.11+
  • fzf

Libraries:

  • beancount
  • click
  • prettytable
  • python-dateutil
  • pyyaml

Installation

pip install beancount-budget

Configuration

budget needs to know some things:

  • regexes for classifying your Beancount accounts; and
  • the paths to your Beancount, budget, and quota files.

Running budget configure will write a configuration file to the current directory, or the directory you specify in budget -c /path/to/dir configure.

Untested use cases

If you

  • handle more than one currency in your daily finances,
  • have ever had to substantially refactor your Beancount accounts,
  • have a stance on investments other than "once the money's in the brokerage, it's left my budget", or
  • are anything other than a U.S. W-2 laborer,

you are encouraged to try this software and email your experiences to the author.

Example

First, I need some example Beancount data and a configuration for the budget:

$ bean-example --seed 0 --date-birth 2022-01-01 --date-end 2024-01-01 > main.beancount
$ budget configure
Example configuration written to `.bcbudget.toml`.
Use your preferred editor to finish configuration.
$ vi .bcbudget.toml

This is what the config file looks like after editing:

currencies = ["USD", "VACHR"]

[regexes]
cash = "^Assets:US:(BofA:Checking|Hoogle:Vacation)"
deductions = "^Expenses:(Taxes:|Health:.*:Insurance$)"
expenses = "^Expenses:"
income = "^Income:"
transfers = "^$"  # explicitly disable unused regexes
credit = "Liabilities:Credit:"
loans = "^$"
invest = "^(Assets|Income):US:ETrade:"
open = "^Equity:Opening-Balances$"

[paths]
beancount = "main.beancount"
budgets = "budgets"
quotas = "quotas"

And here is an empty budget.

$ budget show 2022-01
Category                   Budgeted  Expenses  Balances  Deviations
Expenses:Financial:Fees                  4.00     -4.00       -4.00
Expenses:Food:Groceries                219.35   -219.35     -219.35
Expenses:Food:Restaurant               329.62   -329.62     -329.62
Expenses:Home:Electricity               65.00    -65.00     -130.00
Expenses:Home:Internet                  80.14    -80.14     -160.14
Expenses:Home:Phone                     68.36    -68.36      -68.36
Expenses:Home:Rent                    2400.00  -2400.00    -2400.00
Expenses:Transport:Tram                120.00   -120.00     -240.00
Total                                 3286.47  -3286.47    -3551.47
Available                                       6649.63
Net income                                      6649.63

In this example, all budget commands that require a month will use 2022-01. In daily usage you will likely use commands like budget show unqualified.

Figuring income

The example data's first month contains an opening balance for a checking account ($3948.43), and two paychecks whose net balances are almost evenly split between the checking account and a 401k account ($1350.60 and $1200.00 respectively).

The budgeter's focus is on everyday spending, so 401k postings aren't counted as income. That leaves 3948.43 + (1350.60 * 2) = 6649.63.

Filling the budget

budget fill allocates money to categories until each has enough balance for the month's expenses and quotas. It tries to eliminate negative deviations.

$ budget fill 2022-01 > /dev/null
$ xsv select 'category,"2022-01"' budgets/USD.csv
category,2022-01
Expenses:Financial:Fees,4.00
Expenses:Food:Groceries,219.35
Expenses:Food:Restaurant,329.62
Expenses:Home:Electricity,65.00
Expenses:Home:Internet,80.14
Expenses:Home:Phone,68.36
Expenses:Home:Rent,2400.00
Expenses:Transport:Tram,120.00
$ budget show 2022-01
Category                   Budgeted  Expenses  Balances  Deviations
Expenses:Financial:Fees        4.00      4.00
Expenses:Food:Groceries      219.35    219.35
Expenses:Food:Restaurant     329.62    329.62
Expenses:Home:Electricity     65.00     65.00
Expenses:Home:Internet        80.14     80.14
Expenses:Home:Phone           68.36     68.36
Expenses:Home:Rent          2400.00   2400.00
Expenses:Transport:Tram      120.00    120.00
Total                       3286.47   3286.47
Available                                       3363.16
Net income                                      6649.63

budget trim does the opposite: it removes money from overbudgeted categories, in order to eliminate positive deviations.

$ budget add Expenses:Transport:Tram 100 2022-01
Expenses:Transport:Tram  (Balance now)    100.00
                         (Balance added)  100.00
                         [Available]      100.00
$ budget trim 2022-01
[Available]  (Balance now)            3363.16
             (Balance added)           100.00
             Expenses:Transport:Tram   100.00

Adding quotas

For more on the concept, see "Quotas" below.

Some of these categories cost a fixed amount per month, so it makes sense to start planning for them. To start, create quotas/$YOUR_CURRENCY.yaml. (Unlike budgets, quotas are entirely manually set up.)

Expenses:Home:Electricity:
  this:
    2022-01: set -a 65
Expenses:Home:Internet:
  self:
    2022-01: set -a 80
Expenses:Transport:Tram
  wow:
    2022-01: set -a 120

The quota names were chosen to reflect their arbitrary nature. I use this in whole-category quotas.

$ budget fill 2022-01
Expenses:Home:Electricity  (Balance now)     65.00
                           (Balance added)   65.00
                           [Available]       65.00

Expenses:Home:Internet     (Balance now)     80.00
                           (Balance added)   80.00
                           [Available]       80.00

Expenses:Transport:Tram    (Balance now)    120.00
                           (Balance added)  120.00
                           [Available]      120.00
$ budget show 2022-01
Category                   Budgeted  Expenses  Balances  Deviations
Expenses:Financial:Fees        4.00      4.00
Expenses:Food:Groceries      219.35    219.35
Expenses:Food:Restaurant     329.62    329.62
Expenses:Home:Electricity    130.00     65.00     65.00
Expenses:Home:Internet       160.14     80.14     80.00
Expenses:Home:Phone           68.36     68.36
Expenses:Home:Rent          2400.00   2400.00
Expenses:Transport:Tram      240.00    120.00    120.00
Total                       3551.47   3286.47    265.00
Available                                       3098.16
Net income                                      6649.63

Quotas

Quotas are amounts you intend to budget each month. For example, in United States dollars:

  • A goal quota: "I want to save $1200 for a vacation six months from now."
  • Another goal quota: "I've already saved $10000 for a car, but I'm still looking for the right one."
  • A monthly quota: "My groceries cost $200 per month, give or take."
  • Another monthly quota: "I started going to a gym last week, and it'll cost $70 per month."
  • A group of monthly quotas: "I give to several NPOs monthly, in these amounts: $20, $10, another $10."
  • A fixed quota: "I will budget at least $3600 per month for candles, regardless of spending."
  • Another fixed quota: "My rent costs $1300."
  • A yearly quota: "My PO box costs $24 per month; I pay $288 each June."

The numbers are strictly illustrative and I will brook no complaints about them.

Each of these quotas are assigned to a Beancount account, such as Expenses:Gifts:NPO. Multiple quotas may be assigned to the same account. If none are assigned to an account, that account has a default quota of zero (which conceptually reduces to "no overspending").

A goal or repeating quota expects the balance to be fulfilled during the month before the stated end date. For example:

  • If on January you begin a $1200 goal with July as the target month, the $200 you budget in June will fulfill the quota.
  • If you have a $288 repeating quota payable each June, the $24 you budget in May will fulfill the quota.

Schema

Each quota is assigned a category and a name (both strings), and consists of a series of month-instruction pairs, with the instructions in the format <INSTRUCTION> <ARGS>. For example, the key 2020-01 with the instruction set -a 100 sets a monthly quota of ¤100 starting on January 2020.

There are four instructions:

  • set: Creates or modifies a monthly quota.
    • -a/--amount NUMBER (int or float): The amount to save.
    • -x/--fixed (optional): Instead of requiring a balance, require a budgeted amount each month.
  • stop: Stops a quota, hiding it from view on this and future months.
  • repeat: Starts a yearly quota, or overwrites an existing one.
    • -a/--amount NUMBER (int or float): The amount to save.
    • -m/--month NUMBER (int): The month the quota shall recur on, as an integer.
    • -r/--interval NUMBER (int): The number of months between each payment: 6 for biyearly, 3 for quarterly, etc.
    • -p/--payee STRING (str, optional): Watch for transactions involving this payee.
    • -n/--narration STRING (str, optional): Watch for transactions involving this narration.
    • -c/--account STRING (str, optional): Watch for transactions involving this account.
  • yearly: Equivalent to repeat -r 12, and accepting the same arguments above.
  • goal: Starts a goal quota, or overwrites an existing one.
    • -a/--amount NUMBER (int or float): The amount to save.
    • -m/--month MONTH (str): The month the quota is expected to end, in YYYY-MM format.
    • -h/--have NUMBER (int or float, optional): The amount that has already been saved.
    • -x/--fixed (optional): Instead of requiring a balance, require a budgeted amount each month.
    • -p/--payee STRING (str, optional): Watch for transactions involving this payee.
    • -n/--narration STRING (str, optional): Watch for transactions involving this narration.
    • -c/--account STRING (str, optional): Watch for transactions involving this account.

Linking quotas to transactions

The funds for goal quotas are held until the quota is stopped. One might think the only way to release goal funds is to manually stop each quota on its target month. But there is a better way.

The -c, -n, and -p flags ("matchers") common to goal-like quotas allow these quotas to automatically release reserved funds after a matching transaction is entered.

For example, one might save for a large purchase with 2025-01 goal -a 1200 -m 2026-01 -p "The Expensive-Things Store". The last $100 will be saved in December 2025, and on New Year's Day the balance required in Expenses:Goals will still be $1200. Entering the following transaction will end the quota:

2016-01-05 * "The Expensive-Things Store" "An expensive thing"
  Liabilities:CreditCard -1200 USD
  Expenses:Goals          1200 USD

In the case of repeating quotas, a matcher is useful in case of early payments. In the case of a subscription renewed each July but prepaid one May, the quota will release funds in May, and require none in June and July (July being the month the funds would ordinarily be released). Saving begins anew the following August.

Examples

This is how the aforementioned quotas would look in quotas/USD.yaml:

Expenses:Vacation:
  Los Angeles: 2019-01 goal -a 1200 -m 2019-07 -p Hotel
  # From January to June, you'll budget $200/m. The reserved funds
  # will be released when a transaction with the payee "Hotel" is entered
  # (assuming the stay isn't pre-paid in an earlier month).
Expenses:Basics:Groceries:
  this: # name arbitrarily chosen to represent whole-account quotas
    2019-01: set -a 200
Expenses:Basics:Candles:
  this:
    2019-01: set -a 3600 -x
Expenses:Basics:Health:
  Gym:
    2019-01: set -a 70
Expenses:Gifts:NPO:
  Department of Redundancy Department:
    2022-01: set -a 20
  Benevolent and Proactive Order of Llamas:
    2022-01: set -a 10
  Feed the Childrens:
    2022-01: set -a 10
Expenses:Goals:Car:
  this:
    2018-01: goal -a 10000 -m 2019-01 -n "New car"
Expenses:Subs:USPS:
  this:
    2022-01: yearly -a 288 -m 6
    # equiv: repeat -a 288 -m 6 -r 12

Remaps

If you wish to track multiple categories as one line item, you can combine them using a remap.

This remap will map all categories containing the regex Mortgage:.* to the abstract category Expenses:Mortgage:

[remaps]
"Mortgage:.*" = "Expenses:Mortgage"

The category need not exist in the Beancount data; in this example, there is no open directive for Expenses:Mortgage.

Further reading

Metadata

Release files for beancount-budget 0.25.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for beancount-budget 0.25.1
File Size Uploaded Provenance Actions
beancount_budget-0.25.1.tar.gz 98.7 kB Without provenance Details for beancount_budget-0.25.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for beancount-budget 0.25.1
File Interpreter ABI Platform Actions
beancount_budget-0.25.1-py3-none-any.whl Python 3 none any Details for beancount_budget-0.25.1-py3-none-any.whl

Total release size: 136.8 kB

Release files / beancount_budget-0.25.1.tar.gz

Download URL beancount_budget-0.25.1.tar.gz
Size 98.7 kB
Tags Source
SHA-256 checksum
How to use checksums
8d8348facf39e4fabe425e306680ad92647a428fc1865f58697a6101682e3e99
BLAKE2b-256 checksum
How to use checksums
6dfb43620252dd388864e5187cf94413210be437f666a7e7f675d63a74b7e6f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / beancount_budget-0.25.1-py3-none-any.whl

Download URL beancount_budget-0.25.1-py3-none-any.whl
Size 38.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c93655704492f7b762c7f7f282f1cb3ba3c9cc3b9b51f398457d6f11fa940bb2
BLAKE2b-256 checksum
How to use checksums
b3e8c26293ed812f9a6e2a6f6e2a198ddb86d1e3821426bd3a9b9b764306d6d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Security for beancount-budget 0.25.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page