Consolidated rows are collapsible and remember their state per currency in localStorage. Share percentages render as badges on a shared subgrid column so they line up regardless of amount width. Currency and account menus drop their extra padding to align with the card title.
A consolidated total on the Net Worth page was a single number: it said
what everything is worth in one currency, but not how much of it each
currency makes up, which is what you want to know when you hold the same
money in several currencies (or in currencies standing for gold, stocks
or crypto).
calculate_currency_totals now keeps, next to each consolidated total, the
components it was built from: the currency's own total and the exchanged
total of every currency that exchanges into it, all in the consolidated
currency. calculate_consolidated_shares gives each of them its share of
the total as a percentage, drops the ones that add nothing and sorts the
rest from largest to smallest. Shares are taken over the absolute total,
so a currency that adds to it always has a positive share and one that
takes from it (debt) a negative one; with a zero total there's no share.
Currencies that hide their exchanged amounts still show up, since they
still count towards the consolidated total, and so do exchange targets
that only exist through the deep search.
On the Net Worth page, each Consolidated row now lists its components
with their share and amount in the consolidated currency. Clicking one
shows that currency's line on the charts, like the currency rows above.
Account.displayed_exchange_currency holds the account/currency precedence for transactions, and shown_exchange_currency() expresses the same rule as a query expression for the insights and account totals, replacing the per-module checks.
Currencies and accounts get a "Show exchanged amounts" switch, on by
default. Turning it off hides the amounts converted to the exchange
currency without removing the exchange currency, so consolidated totals
on the Net Worth page keep working.
The switch belongs to whichever one provides the exchange currency:
- Transactions (including both legs of combined transfers) use the
account's switch when the account has its own exchange currency, and
the currency's switch otherwise, matching how the exchanged amount is
picked.
- Account cards and Net Worth account rows follow the account.
- Currency cards, the monthly summary and daily allowance, Net Worth
currency rows and the month-by-month and year-by-year insights follow
the currency.
Currency totals still convert hidden currencies, because the
consolidated totals are built from those values; the setting only
leaves them out of the displayed data. The API keeps returning
exchanged_amount for transactions, and the account serializer exposes
the new field (currencies already serialize all fields).
Closes#486
- Amounts default to the linked transaction's amount when left empty
- Linked transactions must be in the strategy's payment/target currency; pickers only search those
- Allow setting entities on transactions created from an entry
- Fix crash when only the expense transaction was linked
Holdings under a year annualized their rate before turning it back into
the period return, so a few days with a modest gain (+1.3% in one day is
~10,600% a year) fell outside xirr's search range and showed no percentage.
Solve the rate over the period held instead.
- Shared header (date, description, badges); each side gets its own row:
account with its category, tags and entities, amount + exchanged amount
- Timeline rail from the transfer icon branching into the out/in icons
- Amounts drop below on small screens and sit bottom-right on lg, like
regular items; spacing matches regular item metadata rows
- Notes move below the legs on transfers so they don't sit in the rail
- One hero number with a single return line; secondary totals behind a Details toggle
- Holdings in one list card with quiet group subheaders, value and %/yr per row
- Group holdings by account group or asset class, remembered in the session
- Detail page: value and chart up top, Your position and home-currency sidebar
- Replace tooltips with visible text; keep amounts from wrapping on phones
- Unit-priced accounts now honour their income categories, tags and entities:
a matching transaction is a gain or loss, and any other unlinked one is money
in or out at that day's market price. Before, every unlinked outflow was a
loss and yields were booked as new money in.
- A transfer out of an account tracked by value counts its own amount. It used
the other side converted at the day's rate, so a coin bought from a savings
account moved the coin's price change onto the savings account.
AccountSerializer assigned the many-to-many fields with setattr, which Django rejects, so any write that included them returned a 500. The default ModelSerializer create/update already handles them.
Investments landed without demo data. The ETF, Bitcoin and savings accounts become investments, and new accounts cover the rest of the tracker: Apple shares (dividends paid into checking, a partial sale, gifted shares with an estimated cost), a retirement fund tracked by value (quarterly gains and a loss, a yearly fee) and gold. Asset classes have targets that add up to 100%, plus one without a target to show how rebalancing leaves it out.
The investment examples use their own random seed, so the rest of the demo is unchanged. The demo test now renders the investment pages and checks each kind of holding has figures.
Account names on a combined transfer were cut at a fixed 14rem even when the row had room. On large screens the row now stays on one line and the account side can grow up to half of it, so names only truncate past that.
The transfer form had no entities field and always created paid transfers. Each side now has its own entities, shown as from -> to in the list like category and tags, and a Projected/Paid toggle applies to both sides.
Editing a transfer also offered only active categories, tags and entities and non-archived accounts, so saving silently dropped a deactivated tag or failed on an archived account. The transfer's own items stay selectable, as in the transaction form.
Brazilian banks through Open Finance. Users give their Pluggy client
credentials and the ids of their connections (items), from Meu Pluggy.
- Uses the cursor-paginated /v2/transactions; /transactions is removed
after 2026-12-31. Our own filters are sent again with each cursor.
- The direction comes from `type`, not the amount's sign: credit cards
keep the card's sign, purchases are positive. Checked against a real
Meu Pluggy card and checking account; the sandbox data doesn't match
production here.
- Pending transactions keep their id once they post (a card's open bill
stays pending until it closes), so they're imported unpaid and settled
later. Future-dated ones (installments, scheduled payments) wait for
their date. Installments already come dated in their own month.
- Foreign purchases use the account's amount, with the original amount
in the notes.
- Connections that need the user to log in again show as an error.
Providers can now return pending transactions, flagged as such, when
their ids survive posting. They're imported unpaid right away and, once
the bank posts them, marked paid with their final amount and date.
- Settling happens once, so later edits stick, and one marked paid by
hand is left alone. It goes through save() so a transfer's other side
follows; no rules run again. reference_date moves only if it still
followed the date.
- Each sync looks back to the oldest unpaid synced transaction, since a
card purchase can post weeks later when the bill closes. Pending ones
older than 60 days are taken as cancelled and no longer followed.
Users paste a one-time setup token. On save it's claimed and the access
URL it returns is stored; credentials go in a header, never the URL, so
errors don't leak them.
- Providers can validate or transform their settings (clean_settings),
run by the service form once every other field is valid, so a token
isn't spent on a form that fails anyway.
- Providers can declare a minimum sync interval. SimpleFIN Bridge
disables tokens that go much past ~24 requests a day, so it's 6 hours.
- Tokens and access URLs are user input pointing the server at a host:
only SimpleFIN Bridge is allowed unless SIMPLEFIN_ALLOWED_HOSTS says
otherwise. Checked again on every request, for restored settings.
- Transactions are fetched per account in 90-day windows, Bridge's
limit per request. Pending ones are skipped.