Skip to content

Latest commit

 

History

History
567 lines (431 loc) · 26 KB

README.md

File metadata and controls

567 lines (431 loc) · 26 KB

knut ‒ a plain text accounting tool

Go

there are 29 knuts in a sickle

knut is a plain-text, double-entry accounting tool for the command line. It produces various reports based on simple accounting directives in a text file. knut is written in Go, and its primary use cases are personal finance and investing.

$ knut balance --color=false -v CHF --months --from 2020-01-01 --to 2020-04-01 doc/example.knut
+---------------+------------+------------+------------+------------+
|    Account    | 2020-01-31 | 2020-02-29 | 2020-03-31 | 2020-04-01 |
+---------------+------------+------------+------------+------------+
| Assets        |            |            |            |            |
|   BankAccount |      1,800 |      4,127 |      4,127 |      4,127 |
|   Portfolio   |      1,025 |        919 |        856 |        819 |
|               |            |            |            |            |
| Total (A+L)   |      2,825 |      5,046 |      4,983 |      4,946 |
+---------------+------------+------------+------------+------------+
| Equity        |            |            |            |            |
|   Equity      |          3 |      2,825 |      5,046 |      4,983 |
|               |            |            |            |            |
| Income        |            |            |            |            |
|   Portfolio   |         26 |       -106 |        -63 |        -37 |
|   Salary      |      5,000 |      5,000 |            |            |
|               |            |            |            |            |
| Expenses      |            |            |            |            |
|   Rent        |     -2,000 |     -2,000 |            |            |
|   Fees        |         -4 |            |            |            |
|   Groceries   |       -200 |       -673 |            |            |
|               |            |            |            |            |
| Total (E+I+E) |      2,825 |      5,046 |      4,983 |      4,946 |
+---------------+------------+------------+------------+------------+
| Delta         |            |            |            |            |
+---------------+------------+------------+------------+------------+


Table of contents

Commands

$ knut --help
knut is a plain text accounting tool for tracking personal finances and investments.

Usage:
  knut [command]

Available Commands:
  balance     create a balance sheet
  check       check the journal
  completion  output shell completion code [bash|zsh]
  fetch       Fetch quotes from Yahoo! Finance
  format      Format the given journal
  help        Help about any command
  import      Import financial account statements
  infer       Auto-assign accounts in a journal
  portfolio   Portfolio management commands
  print       print the journal
  transcode   transcode to beancount

Flags:
  -h, --help      help for knut
  -v, --version   version for knut

Use "knut [command] --help" for more information about a command.

Print a balance

knut has a powerful balance command, with various options to tune the result.

Basic balance

Without additional options, knut will print a balance with counts of the various commodities per account.

$ knut balance --color=false doc/example.knut --to 2020-04-01
+---------------+------+------------+
|    Account    | Comm | 2020-04-01 |
+---------------+------+------------+
| Assets        |      |            |
|   BankAccount | CHF  |     14,127 |
|   Portfolio   | AAPL |         12 |
|               | CHF  |         31 |
|               | USD  |         97 |
|               |      |            |
| Total (A+L)   | AAPL |         12 |
|               | CHF  |     14,158 |
|               | USD  |         97 |
+---------------+------+------------+
| Equity        |      |            |
|   Equity      | AAPL |         12 |
|               | CHF  |      9,031 |
|               | USD  |        101 |
|               |      |            |
| Income        |      |            |
|   Salary      | CHF  |     10,000 |
|               |      |            |
| Expenses      |      |            |
|   Rent        | CHF  |     -4,000 |
|   Fees        | USD  |         -4 |
|   Groceries   | CHF  |       -873 |
|               |      |            |
| Total (E+I+E) | AAPL |         12 |
|               | CHF  |     14,158 |
|               | USD  |         97 |
+---------------+------+------------+
| Delta         | AAPL |            |
|               | CHF  |            |
|               | USD  |            |
+---------------+------+------------+


Monthly balance in a given commodity

If prices are available, knut can valuate the balance in any of the available commodities. And the result is guaranteed to balance:

$ knut balance --color=false -v CHF --months --to 2020-04-01 doc/example.knut
+---------------+------------+------------+------------+------------+------------+
|    Account    | 2019-12-31 | 2020-01-31 | 2020-02-29 | 2020-03-31 | 2020-04-01 |
+---------------+------------+------------+------------+------------+------------+
| Assets        |            |            |            |            |            |
|   BankAccount |     10,000 |     11,800 |     14,127 |     14,127 |     14,127 |
|   Portfolio   |            |      1,025 |        919 |        856 |        819 |
|               |            |            |            |            |            |
| Total (A+L)   |     10,000 |     12,825 |     15,046 |     14,983 |     14,946 |
+---------------+------------+------------+------------+------------+------------+
| Equity        |            |            |            |            |            |
|   Equity      |     10,000 |     10,003 |     12,825 |     15,046 |     14,983 |
|               |            |            |            |            |            |
| Income        |            |            |            |            |            |
|   Portfolio   |            |         26 |       -106 |        -63 |        -37 |
|   Salary      |            |      5,000 |      5,000 |            |            |
|               |            |            |            |            |            |
| Expenses      |            |            |            |            |            |
|   Rent        |            |     -2,000 |     -2,000 |            |            |
|   Fees        |            |         -4 |            |            |            |
|   Groceries   |            |       -200 |       -673 |            |            |
|               |            |            |            |            |            |
| Total (E+I+E) |     10,000 |     12,825 |     15,046 |     14,983 |     14,946 |
+---------------+------------+------------+------------+------------+------------+
| Delta         |            |            |            |            |            |
+---------------+------------+------------+------------+------------+------------+


It balances in any currency:

$ knut balance --color=false -v USD --months --to 2020-04-01 doc/example.knut
+---------------+------------+------------+------------+------------+------------+
|    Account    | 2019-12-31 | 2020-01-31 | 2020-02-29 | 2020-03-31 | 2020-04-01 |
+---------------+------------+------------+------------+------------+------------+
| Assets        |            |            |            |            |            |
|   BankAccount |     10,324 |     12,172 |     14,583 |     14,716 |     14,693 |
|   Portfolio   |            |      1,058 |        949 |        892 |        852 |
|               |            |            |            |            |            |
| Total (A+L)   |     10,324 |     13,230 |     15,532 |     15,608 |     15,544 |
+---------------+------------+------------+------------+------------+------------+
| Equity        |            |            |            |            |            |
|   Equity      |     10,324 |     10,327 |     13,230 |     15,532 |     15,608 |
|               |            |            |            |            |            |
| Income        |            |            |            |            |            |
|   Portfolio   |            |         29 |       -108 |        -57 |        -40 |
|   BankAccount |            |         -5 |         60 |        133 |        -23 |
|   Salary      |            |      5,157 |      5,103 |            |            |
|               |            |            |            |            |            |
| Expenses      |            |            |            |            |            |
|   Rent        |            |     -2,067 |     -2,063 |            |            |
|   Fees        |            |         -4 |            |            |            |
|   Groceries   |            |       -207 |       -690 |            |            |
|               |            |            |            |            |            |
| Total (E+I+E) |     10,324 |     13,230 |     15,532 |     15,608 |     15,544 |
+---------------+------------+------------+------------+------------+------------+
| Delta         |            |            |            |            |            |
+---------------+------------+------------+------------+------------+------------+


Filter transactions by account or commodity

Use --diff to look into period differences. Use --account to filter for transactions affecting a single account, or --commodity to filter for transactions which affect a commodity. Both --account and --commodity take regular expressions, to select multiple matches.

$ knut balance --color=false -v CHF --months --from 2020-01-01 --to 2020-04-01 --diff --account Portfolio doc/example.knut
+---------------+------------+------------+------------+------------+
|    Account    | 2020-01-31 | 2020-02-29 | 2020-03-31 | 2020-04-01 |
+---------------+------------+------------+------------+------------+
| Assets        |            |            |            |            |
|   Portfolio   |      1,025 |       -106 |        -63 |        -37 |
|               |            |            |            |            |
| Total (A+L)   |      1,025 |       -106 |        -63 |        -37 |
+---------------+------------+------------+------------+------------+
| Income        |            |            |            |            |
|   Portfolio   |         26 |       -132 |         43 |         26 |
|               |            |            |            |            |
| Total (E+I+E) |         26 |       -132 |         43 |         26 |
+---------------+------------+------------+------------+------------+
| Delta         |        999 |         26 |       -106 |        -63 |
+---------------+------------+------------+------------+------------+


$ knut balance --color=false -v CHF --months --from 2020-01-01 --to 2020-04-01 --diff --commodity AAPL doc/example.knut
+---------------+------------+------------+------------+------------+
|    Account    | 2020-01-31 | 2020-02-29 | 2020-03-31 | 2020-04-01 |
+---------------+------------+------------+------------+------------+
| Assets        |            |            |            |            |
|   Portfolio   |        900 |       -106 |        -62 |        -37 |
|               |            |            |            |            |
| Total (A+L)   |        900 |       -106 |        -62 |        -37 |
+---------------+------------+------------+------------+------------+
| Equity        |            |            |            |            |
|   Equity      |        874 |         26 |       -106 |        -62 |
|               |            |            |            |            |
| Income        |            |            |            |            |
|   Portfolio   |         26 |       -132 |         44 |         25 |
|               |            |            |            |            |
| Total (E+I+E) |        900 |       -106 |        -62 |        -37 |
+---------------+------------+------------+------------+------------+
| Delta         |            |            |            |            |
+---------------+------------+------------+------------+------------+


Collapse accounts

Use -m to map accounts matching a certain regex to a reduced number of segments. This can be used to completely hide an account (-m0 - its positions will show up in the delta):

$ knut balance --color=false -v CHF --months --from 2020-01-01 --to 2020-04-01 --diff -m0,(Income|Expenses) doc/example.knut
+---------------+------------+------------+------------+------------+
|    Account    | 2020-01-31 | 2020-02-29 | 2020-03-31 | 2020-04-01 |
+---------------+------------+------------+------------+------------+
| Assets        |            |            |            |            |
|   BankAccount |      1,800 |      2,327 |            |            |
|   Portfolio   |      1,025 |       -106 |        -63 |        -37 |
|               |            |            |            |            |
| Total (A+L)   |      2,825 |      2,221 |        -63 |        -37 |
+---------------+------------+------------+------------+------------+
| Equity        |            |            |            |            |
|   Equity      |          3 |      2,822 |      2,221 |        -63 |
|               |            |            |            |            |
| Total (E+I+E) |          3 |      2,822 |      2,221 |        -63 |
+---------------+------------+------------+------------+------------+
| Delta         |      2,822 |       -601 |     -2,284 |         26 |
+---------------+------------+------------+------------+------------+


Alternatively, with a number > 0, subaccounts will be aggregated:

$ knut balance --color=false -v CHF --months --from 2020-01-01 --to 2020-04-01 --diff -m1,(Income|Expenses|Equity) doc/example.knut
+---------------+------------+------------+------------+------------+
|    Account    | 2020-01-31 | 2020-02-29 | 2020-03-31 | 2020-04-01 |
+---------------+------------+------------+------------+------------+
| Assets        |            |            |            |            |
|   BankAccount |      1,800 |      2,327 |            |            |
|   Portfolio   |      1,025 |       -106 |        -63 |        -37 |
|               |            |            |            |            |
| Total (A+L)   |      2,825 |      2,221 |        -63 |        -37 |
+---------------+------------+------------+------------+------------+
| Equity        |          3 |      2,822 |      2,221 |        -63 |
|               |            |            |            |            |
| Income        |      5,026 |       -132 |     -4,957 |         26 |
|               |            |            |            |            |
| Expenses      |     -2,204 |       -469 |      2,673 |            |
|               |            |            |            |            |
| Total (E+I+E) |      2,825 |      2,221 |        -63 |        -37 |
+---------------+------------+------------+------------+------------+
| Delta         |            |            |            |            |
+---------------+------------+------------+------------+------------+


Fetch quotes

knut price sources are configured in yaml format:

# doc/prices.yaml
- commodity: "USD"
  target_commodity: "CHF"
  file: "USD.prices"
  symbol: "USDCHF=X"
- commodity: "AAPL"
  target_commodity: "USD"
  file: "AAPL.prices"
  symbol: "AAPL"

Once configured, prices can be updated with one command:

knut fetch doc/prices.yaml

Infer accounts

knut has a built-in Bayes engine to automatically assign accounts for new transactions. Simply use TBD as the account in a transaction and let knut decide how to replace it, based on previous entries. The bigger the journal, the more reliable this mechanism becomes.

knut infer -t doc/example.knut doc/example.knut

Format the journal

knut can format a journal, such that accounts and numbers are aligned. Any comments and whitespace between directives are preserved.

knut format doc/example.knut

Import transactions

knut has a few built-in importers for statements from Swiss banks:

$ knut import --help
Import financial account statements

Usage:
  knut import [command]

Available Commands:
  ch.cumulus            Import Cumulus credit card statements
  ch.postfinance        Import Postfinance CSV account statements
  ch.supercard          Import Supercard credit card statements
  ch.swisscard          Import Swisscard credit card statements (before mid 2023)
  ch.swisscard2         Import Swisscard credit card statements (from mid 2023)
  ch.swissquote         Import Swissquote account reports
  ch.viac               Import VIAC values from JSON files
  revolut               Import Revolut CSV account statements
  revolut2              Import Revolut CSV account statements
  us.interactivebrokers Import Interactive Brokers account reports

Flags:
  -h, --help   help for import

Use "knut import [command] --help" for more information about a command.

Transcode to beancount

While knut has advanced terminal-based visualization options, it lacks any web-based visualization tools. To allow the usage of the amazing tooling around the beancount ecosystem, such as fava, knut has a command to convert an entire journal into beancount's file format:

knut transcode -c CHF doc/example.knut

This command should also allow beancount users to use knut's built-in importers.

Editor support

There is an experimental Visual Studio Code extension which provides syntax highlighting, code folding and an outline view.

File format

An accounting journal in knut is represented as a sequence of plain-text directives. The journal consists of a set of directives and comments. Directives are prices, account openings, transactions, value directives, balance assertions, and account closings. Lines starting with either # (comment) or * (org-mode title) are ignored. Files can include other files using an include directive. The order of the directives in the journal file is not important, they are always evaluated by date.

The following is an example for a knut journal:

# doc/example.knut
include "USD.prices"
include "AAPL.prices"

* Open Accounts

2019-12-31 open Equity:Equity
2019-12-31 open Assets:BankAccount
2019-12-31 open Assets:Portfolio

2019-12-31 open Expenses:Groceries
2019-12-31 open Expenses:Fees
2019-12-31 open Expenses:Rent

2019-12-31 open Income:Salary
2019-12-31 open Income:Dividends

* Opening Balances

2019-12-31 "Opening balance"
Equity:Equity           Assets:BankAccount           10000 CHF

* 2020-01

2020-01-25 "Salary January 2020"
Income:Salary           Assets:BankAccount            5000 CHF

2020-01-02 "Rent January"
Assets:BankAccount      Expenses:Rent                 2000 CHF

2020-01-15 "Groceries"
Assets:BankAccount      Expenses:Groceries             200 CHF

2020-01-05 "Transfer to portfolio"
Assets:BankAccount      Assets:Portfolio              1000 CHF

2020-01-06 "Currency exchange"
Equity:Equity           Assets:Portfolio              1001 USD
Assets:Portfolio        Equity:Equity                  969 CHF

2020-01-06 "Buy 12 AAPL shares"
Equity:Equity           Assets:Portfolio                12 AAPL
Assets:Portfolio        Equity:Equity                  900 USD
Assets:Portfolio        Expenses:Fees                    4 USD

* 2020-02

2020-02-25 "Salary January 2020"
Income:Salary           Assets:BankAccount            5000 CHF

2020-02-02 "Rent January"
Assets:BankAccount      Expenses:Rent                 2000 CHF

2020-02-05 "Groceries"
Assets:BankAccount      Expenses:Groceries             250 CHF

2020-02-25 "Groceries"
Assets:BankAccount      Expenses:Groceries             423 CHF

Open and close

An account consists of a sequence of segments, separated by ':'. The first segment must be one of Assets, Liabilities, Equity, Income, Expenses or TBD. Before an account can be used in a transaction, for example, it must be opened using an open directive:

YYYY-MM-DD open <account name>

Once an account is not needed anymore, it can be closed, to prevent further bookings. An account can only be closed if its balance is zero at the closing time.

YYYY-MM-DD close <account name>

Transactions

A transaction describes the flow of money between multiple accounts. Transaction always balance by design in knut.

YYYY-MM-DD "<description>"
<credit account> <debit account> <amount> <commodity>
<credit account> <debit account> <amount> <commodity>
...

A transaction starts with a date, followed by a description withing double quotes on the same line. It must have one or more bookings on the lines immediately following. Every booking references two accounts, a credit account (first) and a debit account (second). The amount is usually a positive numbers, and the semantics is that money "flows from left to right".

The transaction syntax deviates from similar tools like ledger or beancount for several reasons:

  • It ensures that a transaction always balances, which is not guaranteed by formats where each booking references only one account.
  • It creates unambigous flows between two accounts, which is helpful when analyzing the flows of money.
  • The representation is more compact.

Accruals (experimental)

Accruals are annotation placed on transactions to describe how the transaction's flows are to be broken up over time. Suppose you pay your yearly tax bill for 2020 on 24 March of that same year:

2020-03-24 "2020 Taxes"
Assets:BankAccount Expenses:Taxes 12000 USD

This will heavily impact your net income in March due to the large cash outflow, while the taxes are actually owed for the entire year. Enter accruals:

@accrue monthly 2020-01-01 2020-12-01 Assets:PrepaidTax
2020-03-24 "2020 Taxes"
Assets:BankAccount Expenses:Taxes 12000 USD

This annotation will replace the original transaction with an accrual, moving the money from expenses to a virtual asset account. In addition, the annotation will generate a series of small transactions which continuously move money from the virtual asset account to the expense account:

# Accrual leg:
2020-03-24 "2020 Taxes"
Assets:BankAccount Assets:PrepaidTax 12000 USD

# Expense legs:
2020-01-31 "2020 Taxes"
Assets:PrepaidTax Expenses:Taxes 1000 USD

2020-02-29 "2020 Taxes"
Assets:PrepaidTax Expenses:Taxes 1000 USD

2020-03-31 "2020 Taxes"
Assets:PrepaidTax Expenses:Taxes 1000 USD

# ... etc, in total 12 transactions

knut will take care that the total impact remains the same. Also, amounts are properly split, without remainder.

@accrue <once|daily|weekly|monthly|quarterly|yearly> <T0> <T1> <accrual account>
<transaction>

Balance assertions

It is often helpful to check whether the balance at a date corresponds to an expected value, for example a value given by a bank account statement. A balance assertion in knut performs this check and reports an error if the check fails:

YYYY-MM-DD balance <account> <amount> <commodity>

Value directive

Value directives can be used to declare a certain account balance at a specific date. When encountering a value directive during evaluation, knut will automatically generate a transaction wich makes sure that the balance matches the indicated value. The generated transaction always has exactly one booking, and the two accounts are the given account and a special Equity:Valuation account.

YYYY-MM-DD value <account> <amount> <commodity>

Value directives are handy in particular for modeling investment portfolios, where it is too much work to model every individual trade, for example in an automated trading system. In such a situation, declare inflows and outflows of the investment as usual, and provide value directives for any day the value of the investment can be established (ideally daily). knut will automatically generate transaction representing the value changes of the investment, after considering any given bookings affecting the account.

Prices

knut has a power valuation engine, which can be used to create balance sheets in any currency or security which has pricing information. Prices are declared using price directives:

YYYY-MM-DD price <commodity> <price> <target_commodity>

For example, 2020-10-03 price AAPL 45 USD declares that AAPL cost 45 USD on 2020-10-03 (you wish...). knut is smart enough to derive indirect prices. For example, knut can print a balance with an AAPL position in CHF if a price for USD in CHF and a price for AAPL in USD exists. Prices are automatically inverted, as needed. knut will always use the latest available price for every given day. If a valuation is requried for a date before the first price is given, an error is reported.

Include directives

Income directives can be used to split a journal across a set of files. The given path is interpreted relative to the location of the file where the include directive appears.

include "<relative path>"

It is entirely a matter of preference whether to use large files or a set of smaller files. knut ignores lines starting with '*', so those with a powerful editor can use org-mode to fold sections of a file, making it easy to manage files with tens of thousands of lines.