Skip to content

v0.5.0

Choose a tag to compare

@ptu-gh ptu-gh released this 13 Oct 10:44
· 173 commits to main since this release

wealthdb now tracks cost basis. Gold holds the cost basis every source
states, rebuilds it with a lot engine where a source states none, and
reports realized and unrealized gains on the command line, over MCP and
in a new Gains dashboard.

Upgrading

Back up the data root first: several collectors re-read their bronze.

  1. Rebuild with make all. Gold migrations 0113 to 0124 apply on the
    first read-write open.
  2. Load each collector's silver once from bronze, so the new cost-basis
    columns and lot tables fill for past downloads:
    ./collectors/<name>/<name> load --force for angellist, carta,
    cointracking, manual, schwab-api, swissquote, ubs-psn and ubs-web.
    fidelity-web, schwab-web and svb re-read their archive by themselves
    on the next load, because their parsers changed.
  3. Run wealthdb reload -a. It fills the cost basis, the lots and the
    lot engine's ledger for every source.
  4. Run wealthdb web refresh, then wealthdb mcp restart. A running MCP
    server refuses a schema newer than its binary.

Breaking for scripts

  • Percentage headers no longer carry a %. twr_%, twr_ann_%,
    mwr_%, mwr_ann_%, share_% and savings_rate_% are now
    twr_pct, twr_ann_pct, mwr_pct, mwr_ann_pct, share_pct and
    savings_rate_pct. This changes the keys of -f json and the headers
    of -f csv.
  • Env files are sourced by bash. Quote and escape values the way bash
    expects. A password with $, a backtick or a backslash belongs in
    single quotes.
  • MFA waits (--mfa-timeout) default to five minutes.
  • A Schwab web feed without the Schwab API feed is refused at load. The
    web rows reach accounts only through the API's account numbers.

Cost basis

  • Every book value in gold carries a stamp saying what kind of cost
    basis it is: its origin, its method and whether purchase fees are in.
  • Gold stores each source's open lots and realized lots, one row per lot
    per tax document. One set is marked primary, so a sale counts once.
  • The collectors read the cost basis their sources state:
    • Fidelity and SVB statements, 1099-Bs and the positions page's lots;
    • Schwab statements' tax lots, realized lots and 1099-Bs, and the API's
      average cost;
    • UBS holdings' book cost and statement trades;
    • Swissquote's average cost and bonds' accrued interest;
    • Plaid's tax lots;
    • the capital paid in for Carta, EquityZen, AngelList and the manual
      book, with K-1 property distributions taken off.
  • Market value includes accrued interest and declared dividends. The
    accrued part is reported apart.

Lot engine

  • Where a source states no cost basis, a lot engine rebuilds it from the
    trades: the open lots, a cost basis on every position, and a realized
    lot for every sale. It covers CoinTracking, the SVB statements, and
    the history Fidelity and Schwab leave without lots. A stated figure is
    never replaced. See wealthdb/docs/LOTS.md.
  • First in first out by default. Last in first out, highest or lowest
    cost first, or the average can be set per source, portfolio or account
    in the new lots block of the config.
  • A fourth mode, stated, covers the private markets: Carta, EquityZen,
    AngelList and the manual book. Each sale realizes against the cost
    basis the position states, pro rata by quantity, net of its fee. An
    SPV's payout on AngelList is its sale.
  • The manual book takes an exits.csv: what a holding brought on the day
    it left the book. Gold books it as a sale.
  • lots.basis in the config states the unit cost and acquisition date of
    an instrument's lots the engine cannot cost, such as shares that reach
    a custodian from a transfer agent.
  • holding_aliases in the config names a holding a source keys two ways,
    so the gains reports read it as one holding.
  • Seeds, implied disposals and snapshot blips name where the history
    does not explain a holding. A later statement's lots resolve a seed
    back to its opening.
  • wealthdb lots rebuild runs the engine by hand. Every load and
    reload runs it, and skips it when nothing changed.

Gains

  • wealthdb gains <view> reports realized and unrealized gains over a
    window: summary, sources, portfolios, accounts, positions,
    realized, lots, coverage and check. The aggregate views add
    up. A quality column names every gap, and coverage says per
    account where the figures are blind. See wealthdb/docs/GAINS.md.
  • wealthdb gains check compares the rebuilt figures with what the
    sources state.
  • --missing-basis ignore|zero reads a missing cost basis as blank or as
    0. The config's lots.missing_basis sets the default.
  • wealthdb holdings positions offers the cost basis, its stamp, the
    clean value and the unrealized gain as opt-in columns.
  • wealthdb returns shows gain_<CCY>, the money behind the
    percentages.
  • The MCP server has a gains tool with the same views.
  • A Gains dashboard in Metabase: realized and unrealized gains, the gain
    by month split into positions sold and held, the largest winners and
    losers, gains by source, asset class and tax wrapper, a tax-year table,
    and the cost basis coverage per source. A picker reads a missing cost
    basis as blank or 0. It has no privacy twin.

Freshness

  • Gold records when each source was last read, not only when its latest
    fact is dated. A portal that marks quarterly and was downloaded today
    is current.
  • The Data Freshness dashboard counts a source's age from its last read
    and shows how long it has been quiet. wealthdb status, the MCP
    status tool and the opt-in holdings sources column observed_date
    show the same date.
  • A holding that AngelList, Carta or EquityZen stopped listing ends at
    the download that no longer listed it. A download counts only when it
    read the whole portfolio. The other holdings keep their mark dates, in
    an opt-in valued_date column.

Reports and dashboards

  • GBP is a fourth reporting currency beside USD, CHF and EUR. The
    dashboards open in the config's default_currency.
  • The Wealth Overview and Allocation dashboards have a currency picker.
  • The cumulative return chart uses a linear percent axis.
  • Every subcommand accepts flags after its positional arguments. --
    ends the flags.
  • wealthdb config works through the wrapper.

Collectors

  • Loads skip downloads that never completed, and load each download in
    one transaction, so a failed load leaves silver unchanged.
  • Collector code repeated across the fleet lives once in collectorkit.
  • explore and the login pre-fills never log a typed or clicked field's
    value. Error logs no longer echo session cookies or signed URLs.
  • The schwab-api refresh token is written 0600 from the first byte.
    login --check makes an authenticated call.
  • schwab-web keeps one id for a transaction across overlapping exports,
    and loads rows dated "as of" an earlier day. Statement holdings add up
    to the printed account value.
  • fidelity-web: one failed phase no longer aborts the whole download.
  • carta finds the portfolio when the app lands on another page.
  • AngelList reads the portal's renamed GraphQL operations.
  • chase waits longer for its sign-in form.
  • UBS: incremental loads reach the business day of a new PSN report.
    ubs-web keeps partial downloads it loaded.
  • firstcitizens and raiffeisen_at keep a quiet account's current
    balance in the latest holdings.
  • Swissquote re-sends its push every minute.
  • Swissquote and Relevate require pypdf 6.19 or newer.

Web

  • The admin password reaches provisioning in the environment, never on
    a command line.
  • Provisioning stops when a Metabase listing fails, instead of creating
    duplicates.

Build and docs

  • Gold migration 0124 drops report macros nothing reads.
  • make demo builds the demo household with open and realized lots, and
    a crypto account whose cost basis the lot engine rebuilds.
  • Every README, DESIGN and AGENTS doc was checked against the code. Each
    mechanism is explained once and linked from the others.