Skip to content

Engine

wealthbraid.engine.money

Monetary value types: :class:Commodity and :class:Amount.

All monetary quantities are :class:decimal.Decimal. Floats are rejected at construction so floating-point error can never enter the accounting engine. An :class:Amount pairs an exact quantity with a commodity; arithmetic between amounts of different commodities is an error rather than a silent coercion.

Amount dataclass

An exact quantity of a single commodity.

The quantity is always a :class:~decimal.Decimal; passing a float is an error. The Decimal's own exponent carries the amount's precision, which the balancing rules use to infer tolerances.

Attributes:

Name Type Description
quantity Decimal

The exact signed quantity.

commodity Commodity

The commodity the quantity is denominated in.

fractional_digits property

Return the number of digits after the decimal point.

Returns:

Type Description
int

The count of fractional digits (0 for integers).

__add__(other)

Add two amounts of the same commodity.

Parameters:

Name Type Description Default
other Amount

The amount to add.

required

Returns:

Type Description
Amount

The sum as a new :class:Amount.

__mul__(factor)

Scale the amount by a scalar.

Parameters:

Name Type Description Default
factor Decimal | int | str

The scalar multiplier (Decimal, int, or decimal string).

required

Returns:

Type Description
Amount

The scaled amount as a new :class:Amount, or NotImplemented for

Amount

floats, booleans, and non-numeric factors, so Python raises

Amount

TypeError rather than letting floating-point error in.

__neg__()

Return the negated amount.

Returns:

Type Description
Amount

A new :class:Amount with the sign flipped.

__post_init__()

Coerce and validate the quantity.

int and str quantities are coerced to :class:~decimal.Decimal. float (and bool) are rejected to prevent floating-point error from entering the engine.

Raises:

Type Description
InvalidAmountError

If the quantity is a float/bool or otherwise not convertible to a finite Decimal.

__str__()

Render the amount as <quantity> <commodity>.

Returns:

Type Description
str

A human-readable string such as "10.00 USD".

__sub__(other)

Subtract an amount of the same commodity.

Parameters:

Name Type Description Default
other Amount

The amount to subtract.

required

Returns:

Type Description
Amount

The difference as a new :class:Amount.

is_zero(tolerance=None)

Report whether the amount is zero within an optional tolerance.

Parameters:

Name Type Description Default
tolerance Decimal | None

Optional non-negative tolerance; exact zero if omitted.

None

Returns:

Type Description
bool

True if the absolute quantity does not exceed the tolerance.

of(quantity, code) classmethod

Construct an amount from a quantity and a commodity code.

Parameters:

Name Type Description Default
quantity Decimal | int | str

The quantity, as a Decimal, int, or decimal string.

required
code str

The commodity code.

required

Returns:

Type Description
Amount

The constructed :class:Amount.

Commodity dataclass

A unit in which value is measured: a currency, security, or token.

Attributes:

Name Type Description
code str

The commodity's canonical code, for example "USD".

__post_init__()

Validate the commodity code against the commodity grammar.

Raises:

Type Description
InvalidCommodityError

If the code is empty or malformed.

__str__()

Return the commodity code.

Returns:

Type Description
str

The commodity code.

wealthbraid.engine.account

Accounts: the five account types and the :class:Account value type.

Account names are hierarchical, colon-delimited paths whose first component is one of the five canonical roots (Assets, Liabilities, Equity, Income, Expenses). Names are validated against a strict grammar so that typos become errors rather than silently created accounts.

Account dataclass

A declared account in the chart of accounts.

Attributes:

Name Type Description
name str

The full colon-delimited account name.

type AccountType

The account's root type, derived from name.

metadata Mapping[str, str]

Arbitrary key/value metadata.

aliases tuple[str, ...]

Alternative names that resolve to this account.

tags frozenset[str]

Free-form tags attached to the account.

components property

Return the account's path components, including the root.

Returns:

Type Description
tuple[str, ...]

The colon-separated components as a tuple.

__post_init__()

Validate the account name and derive its type.

Raises:

Type Description
InvalidAccountNameError

If the name is malformed.

is_child_of(other)

Report whether this account is other or a descendant of it.

Parameters:

Name Type Description Default
other str

A candidate ancestor account name.

required

Returns:

Type Description
bool

True if this account equals other or lies beneath it.

AccountType

Bases: Enum

The five roots of a double-entry chart of accounts.

The enum value is the canonical root name used as the first component of an account path.

is_debit_normal property

Report whether this account type increases on the debit side.

Assets and Expenses are debit-normal; Liabilities, Equity, and Income are credit-normal. This underpins report sign conventions.

Returns:

Type Description
bool

True for Assets and Expenses, False otherwise.

from_root(root) classmethod

Return the account type for a root component name.

Parameters:

Name Type Description Default
root str

The first component of an account path (e.g. "Assets").

required

Returns:

Type Description
AccountType

The matching :class:AccountType.

Raises:

Type Description
InvalidAccountNameError

If root is not a canonical root.

parse_account_name(name)

Validate an account name and split it into its type and components.

Parameters:

Name Type Description Default
name str

The full colon-delimited account name.

required

Returns:

Type Description
AccountType

A tuple of the account's :class:AccountType and its components

tuple[str, ...]

(including the root).

Raises:

Type Description
InvalidAccountNameError

If the name is empty, has an unknown root, or contains a malformed component.

wealthbraid.engine.transaction

Transactions and postings.

A :class:Transaction is a dated, balanced set of :class:Posting legs plus metadata (payee, description, tags, links, attachments). Postings may leave the amount elided (None); the balancing rules fill in at most one such amount. Both types are immutable so that transformations produce new values and history stays auditable.

Posting dataclass

A single leg of a transaction: an account and an optional amount.

Attributes:

Name Type Description
account str

The account name this leg posts to.

amount Amount | None

The signed amount, or None if it is to be inferred by balancing.

metadata Mapping[str, str]

Arbitrary key/value metadata for the leg.

with_amount(amount)

Return a copy of this posting with a concrete amount.

Parameters:

Name Type Description Default
amount Amount

The amount to set.

required

Returns:

Type Description
Posting

A new :class:Posting carrying amount.

Transaction dataclass

A dated, balanced double-entry transaction.

Attributes:

Name Type Description
date date

The transaction date.

postings tuple[Posting, ...]

The transaction's legs; at least one is required.

payee str | None

The counterparty, if any.

description str | None

A human-readable description of the transaction.

tags frozenset[str]

Free-form tags.

links frozenset[str]

Identifiers linking related transactions.

attachments tuple[str, ...]

Paths to supporting documents.

metadata Mapping[str, str]

Arbitrary key/value metadata (memo, notes, external ids).

id str | None

An optional stable identifier assigned by higher layers.

elided_postings property

Return the indices of postings whose amount is not yet set.

Returns:

Type Description
tuple[int, ...]

A tuple of indices into :attr:postings with amount is None.

__post_init__()

Validate structural invariants and normalise the postings container.

Raises:

Type Description
InvalidTransactionError

If there are no postings.

with_postings(postings)

Return a copy of this transaction with replaced postings.

Parameters:

Name Type Description Default
postings tuple[Posting, ...]

The new postings.

required

Returns:

Type Description
Transaction

A new :class:Transaction carrying postings.

wealthbraid.engine.balancing

Transaction balancing: the central accounting invariant.

A transaction must sum to zero within each commodity independently. This module infers per-commodity tolerances from the precision of the amounts involved, fills in at most one elided posting amount, and verifies that every commodity nets to zero within tolerance.

With the default multiplier, balancing is exact. The inferred tolerance is half of the last decimal place (0.5 * 10**-d for a commodity whose most precise amount has d fractional digits), but every amount is a multiple of 10**-d, so any non-zero residual is at least 10**-d and always exceeds it. Callers that want real slack (for example when importing rounded foreign amounts) can pass a larger multiplier.

balance_transaction(transaction, *, multiplier=_DEFAULT_TOLERANCE_MULTIPLIER)

Return a balanced copy of transaction, inferring any elided amount.

At most one posting may leave its amount elided; that posting absorbs the single-commodity residual so the transaction nets to zero. After filling, every commodity is verified to balance within the inferred tolerance.

Parameters:

Name Type Description Default
transaction Transaction

The transaction to balance.

required
multiplier Decimal

Tolerance multiplier passed to :func:infer_tolerances.

_DEFAULT_TOLERANCE_MULTIPLIER

Returns:

Type Description
Transaction

A new :class:Transaction whose postings all carry concrete amounts and

Transaction

sum to zero within tolerance.

Raises:

Type Description
AmbiguousBalanceError

If more than one posting has an elided amount.

UnresolvedElidedAmountError

If an elided amount cannot be inferred because the residual is empty or spans multiple commodities.

BalanceError

If the transaction does not balance within tolerance.

infer_tolerances(postings, *, multiplier=_DEFAULT_TOLERANCE_MULTIPLIER)

Infer a per-commodity balancing tolerance from posting precision.

Parameters:

Name Type Description Default
postings Iterable[Posting]

The postings to inspect (elided postings are ignored).

required
multiplier Decimal

Fraction of the last significant place to allow; defaults to one half.

_DEFAULT_TOLERANCE_MULTIPLIER

Returns:

Type Description
dict[Commodity, Decimal]

A mapping of commodity to tolerance. Commodities whose amounts are all

dict[Commodity, Decimal]

integers map to a tolerance of exactly zero.

is_balanced(transaction, *, multiplier=_DEFAULT_TOLERANCE_MULTIPLIER)

Report whether a transaction balances (allowing elided inference).

Parameters:

Name Type Description Default
transaction Transaction

The transaction to check.

required
multiplier Decimal

Tolerance multiplier passed to :func:infer_tolerances.

_DEFAULT_TOLERANCE_MULTIPLIER

Returns:

Type Description
bool

True if :func:balance_transaction would succeed.

residual(postings)

Sum the explicit posting amounts per commodity.

Parameters:

Name Type Description Default
postings Iterable[Posting]

The postings to sum (elided postings are ignored).

required

Returns:

Type Description
dict[Commodity, Decimal]

A mapping of commodity to net quantity across the explicit postings.

wealthbraid.engine.inventory

Multi-commodity balances: the :class:Inventory value type.

An inventory is a set of amounts keyed by commodity — the natural result of summing postings that may span several currencies. It is immutable: combining inventories yields new inventories. Zero balances are dropped so that a fully offset commodity does not clutter reports, and :meth:amounts returns a deterministic, commodity-sorted view.

Inventory

An immutable collection of amounts, at most one per commodity.

Commodities whose net quantity is zero are not stored, so an empty inventory represents a fully balanced position.

__eq__(other)

Compare inventories by their stored balances.

Parameters:

Name Type Description Default
other object

The object to compare against.

required

Returns:

Type Description
bool

True if both hold identical non-zero balances.

__hash__()

Return a hash consistent with equality.

Returns:

Type Description
int

A hash over the stored (commodity, quantity) balances.

__init__(balances=None)

Build an inventory from a commodity-to-quantity mapping.

Parameters:

Name Type Description Default
balances Mapping[Commodity, Decimal] | None

Optional mapping of commodity to net quantity. Zero quantities are dropped.

None

__repr__()

Return a debug representation listing the held amounts.

Returns:

Type Description
str

A string such as Inventory([10 USD, -5 EUR]).

add_amount(amount)

Return a new inventory with amount added.

Parameters:

Name Type Description Default
amount Amount

The amount to add.

required

Returns:

Type Description
Inventory

A new :class:Inventory.

amounts()

Return the held amounts, sorted by commodity code.

Returns:

Type Description
list[Amount]

A deterministic list of non-zero :class:Amount values.

from_amounts(amounts) classmethod

Build an inventory by summing amounts per commodity.

Parameters:

Name Type Description Default
amounts Iterable[Amount]

The amounts to accumulate.

required

Returns:

Type Description
Inventory

The resulting :class:Inventory.

get(commodity)

Return the net quantity held in commodity.

Parameters:

Name Type Description Default
commodity Commodity

The commodity to look up.

required

Returns:

Type Description
Decimal

The net quantity, or zero if the commodity is not held.

is_empty()

Report whether the inventory holds no non-zero balances.

Returns:

Type Description
bool

True if empty (fully balanced).

merge(other)

Return a new inventory combining this one with other.

Parameters:

Name Type Description Default
other Inventory

The inventory to merge in.

required

Returns:

Type Description
Inventory

A new :class:Inventory holding the summed balances.

wealthbraid.engine.ledger

The :class:Ledger aggregate: accounts, transactions, and balances.

The ledger is the in-memory root of the accounting model. It enforces the two structural invariants at the point of mutation: every posted account must have been declared (strict accounts), and every stored transaction must balance. It also computes account balances, including hierarchical roll-ups. It performs no I/O; persistence lives behind the storage ports.

Ledger

A collection of declared accounts, balanced transactions, and prices.

__init__()

Initialise an empty ledger.

account(name)

Return a declared account by name.

Parameters:

Name Type Description Default
name str

The account name.

required

Returns:

Type Description
Account

The declared :class:Account.

Raises:

Type Description
UndeclaredAccountError

If the account has not been declared.

account_balances()

Return the leaf balance of every declared account.

Every declared account is present in the result, with an empty inventory if it has no activity.

Returns:

Type Description
dict[str, Inventory]

A mapping of account name to its :class:Inventory.

accounts()

Return all declared accounts, sorted by name.

Returns:

Type Description
list[Account]

A deterministic, name-sorted list of accounts.

add_price(price)

Record an exchange rate.

Parameters:

Name Type Description Default
price Price

The price to add.

required

add_transaction(transaction)

Balance, validate, and store a transaction.

The transaction is balanced (inferring at most one elided amount), every referenced account is checked against the chart of accounts, and the balanced transaction is appended to the ledger.

Parameters:

Name Type Description Default
transaction Transaction

The transaction to add.

required

Returns:

Type Description
Transaction

The stored, balanced :class:Transaction.

Raises:

Type Description
UndeclaredAccountError

If any posting references an undeclared account.

AmbiguousBalanceError

If more than one posting elides its amount.

UnresolvedElidedAmountError

If an elided amount cannot be inferred.

BalanceError

If the transaction does not balance within tolerance.

balance(name, *, include_subaccounts=True)

Return the balance of an account, optionally rolling up descendants.

Parameters:

Name Type Description Default
name str

The account name to total.

required
include_subaccounts bool

If true, include all descendant accounts.

True

Returns:

Type Description
Inventory

The combined :class:Inventory for the account.

declare_account(account)

Register an account in the chart of accounts.

Parameters:

Name Type Description Default
account Account

The account to declare.

required

Raises:

Type Description
DuplicateAccountError

If an account with the same name already exists.

is_declared(name)

Report whether an account name has been declared.

Parameters:

Name Type Description Default
name str

The account name to check.

required

Returns:

Type Description
bool

True if the account is declared.

price_db()

Return a price database over the ledger's prices.

Returns:

Name Type Description
A PriceDB

class:~wealthbraid.engine.prices.PriceDB for valuation queries.

prices()

Return the recorded prices, sorted deterministically.

Returns:

Type Description
list[Price]

The prices sorted by date, base, then quote.

transactions()

Return the stored transactions in insertion order.

Returns:

Type Description
list[Transaction]

A shallow copy of the transaction list.

wealthbraid.engine.prices

Prices and cross-commodity valuation.

A :class:Price records an exchange rate for a commodity as of a date, for example 1 EUR = 1.10 USD. A :class:PriceDB answers rate queries using the most recent price on or before a date, considering both the direct rate and its inverse. Valuation converts amounts and inventories into a target commodity; commodities without a usable rate are left unconverted rather than silently dropped, keeping conversions explicit.

Price dataclass

An exchange rate for a commodity as of a date.

Attributes:

Name Type Description
date date

The date the rate applies from.

base Commodity

The commodity being priced.

rate Amount

The value of one unit of base, denominated in the quote commodity (rate.commodity).

quote property

Return the quote commodity of the rate.

Returns:

Type Description
Commodity

The commodity rate is denominated in.

PriceDB

A queryable collection of price records.

__init__(prices=None)

Create a price database.

Parameters:

Name Type Description Default
prices list[Price] | None

Optional initial prices.

None

add(price)

Add a price record.

Parameters:

Name Type Description Default
price Price

The price to add.

required

latest(on)

Return the most recent price for each (base, quote) pair on or before a date.

Parameters:

Name Type Description Default
on date

The as-of date.

required

Returns:

Type Description
dict[tuple[Commodity, Commodity], Price]

The latest price per directed pair.

path(base, quote, on)

Return the conversion steps from base to quote as of a date.

Parameters:

Name Type Description Default
base Commodity

The commodity to convert from.

required
quote Commodity

The commodity to convert to.

required
on date

The as-of date.

required

Returns:

Type Description
list[RateStep] | None

The steps in order (empty when base == quote), or None if no path exists.

prices()

Return all prices, sorted by date then base then quote.

Returns:

Type Description
list[Price]

A deterministic, sorted list of prices.

rate(base, quote, on)

Return the rate to convert base into quote as of a date.

Rates chain transitively: if there is no direct (or inverse) price, the shortest path through intermediate commodities is used (for example VWCG → EUR → USD). Each edge uses the most recent price on or before on. When several equal-length paths exist, neighbours are visited in commodity-code order so the result is deterministic.

Parameters:

Name Type Description Default
base Commodity

The commodity to convert from.

required
quote Commodity

The commodity to convert to.

required
on date

The as-of date.

required

Returns:

Type Description
Decimal | None

The conversion rate, or None if no path of prices connects them.

RateStep dataclass

One conversion step and the recorded price it relies on.

Attributes:

Name Type Description
rate Decimal

The multiplier applied in this step (the price's rate, or its inverse).

price Price

The recorded price the step uses.

value_amount(amount, target, prices, on)

Convert an amount into the target commodity as of a date.

Parameters:

Name Type Description Default
amount Amount

The amount to convert.

required
target Commodity

The target commodity.

required
prices PriceDB

The price database.

required
on date

The as-of date.

required

Returns:

Type Description
Amount | None

The converted amount, or None if no usable rate exists.

value_inventory(inventory, target, prices, on)

Convert an inventory into the target commodity where possible.

Amounts that cannot be converted (no usable rate) are retained in their original commodity, so the result is explicit about what was valued.

Parameters:

Name Type Description Default
inventory Inventory

The inventory to value.

required
target Commodity

The target commodity.

required
prices PriceDB

The price database.

required
on date

The as-of date.

required

Returns:

Type Description
Inventory

A new :class:Inventory with convertible holdings expressed in

Inventory

target and the rest unchanged.