Accounting cookbook (Livebook)
Recipes from the accounting cookbook, runnable in Livebook.
Mix.install([
{:beancount_ex, "~> 0.4"},
{:kino, "~> 0.13"}
])
Application.put_env(:beancount_ex, :engine, Beancount.Engine.Elixir)
Open accounts
base_opens = [
Beancount.open(~D[2026-01-01], "Assets:US:BofA:Checking", ["USD"]),
Beancount.open(~D[2026-01-01], "Assets:Cash", ["USD"]),
Beancount.open(~D[2026-01-01], "Liabilities:US:Amex:Card", ["USD"]),
Beancount.open(~D[2026-01-01], "Expenses:Food:Restaurant", ["USD"]),
Beancount.open(~D[2026-01-01], "Income:US:Acme:Salary", ["USD"]),
Beancount.open(~D[2026-01-01], "Equity:Opening", ["USD"])
]
Cash withdrawal
cash_withdrawal =
Beancount.transaction(~D[2026-06-28], "*", "ATM", "Withdrawal", [
Beancount.posting("Assets:US:BofA:Checking", Decimal.new("-200"), "USD"),
Beancount.posting("Assets:Cash", nil, nil)
])
Credit card meal
restaurant =
Beancount.transaction(~D[2026-05-23], "*", "CAFE", "Dinner", [
Beancount.posting("Liabilities:US:Amex:Card", Decimal.new("-45.00"), "USD"),
Beancount.posting("Expenses:Food:Restaurant", nil, nil)
])
Salary deposit
payroll =
Beancount.transaction(~D[2026-01-31], "*", "ACME INC", "PAYROLL", [
Beancount.posting("Assets:US:BofA:Checking", Decimal.new("3500"), "USD"),
Beancount.posting("Income:US:Acme:Salary", Decimal.new("-3500"), "USD")
])
Stock purchase and sale
investment_opens = [
Beancount.open(~D[2026-01-01], "Assets:US:Broker:AAPL", ["AAPL"], booking: "FIFO"),
Beancount.open(~D[2026-01-01], "Assets:US:Broker:Cash", ["USD"])
]
buy =
Beancount.transaction(~D[2026-01-15], "*", "Broker", "Buy AAPL", [
Beancount.posting("Assets:US:Broker:AAPL", Decimal.new("10"), "AAPL",
cost: %{amount: Decimal.new("150"), currency: "USD"}
),
Beancount.posting("Assets:US:Broker:Cash", Decimal.new("-1500"), "USD")
])
sell =
Beancount.transaction(~D[2026-06-01], "*", "Broker", "Sell AAPL", [
Beancount.posting("Assets:US:Broker:AAPL", Decimal.new("-10"), "AAPL",
price: %{amount: Decimal.new("180"), currency: "USD", type: :unit}
),
Beancount.posting("Assets:US:Broker:Cash", Decimal.new("1800"), "USD")
])
Validate and inspect
ledger = base_opens ++ investment_opens ++ [cash_withdrawal, restaurant, payroll, buy, sell]
case Beancount.check(ledger) do
{:ok, _} -> :ok
{:error, r} -> r.normalized.errors
end
{:ok, income} = Beancount.income_statement(ledger)
Kino.DataTable.new(Beancount.Query.Result.to_maps(income))
{:ok, holdings} = Beancount.holdings(ledger)
Kino.DataTable.new(Beancount.Query.Result.to_maps(holdings))
Balance assertion with pad
reconcile = [
Beancount.open(~D[2026-01-01], "Assets:Cash", ["USD"]),
Beancount.open(~D[2026-01-01], "Equity:Opening", ["USD"]),
Beancount.pad(~D[2026-01-02], "Assets:Cash", "Equity:Opening"),
Beancount.balance(~D[2026-01-03], "Assets:Cash", Decimal.new("100"), "USD")
]
Beancount.check(reconcile)