Repository navigation
v0.5.0
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.
- Rebuild with
make all. Gold migrations 0113 to 0124 apply on the
first read-write open. - 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 --forcefor 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 nextload, because their parsers changed. - Run
wealthdb reload -a. It fills the cost basis, the lots and the
lot engine's ledger for every source. - Run
wealthdb web refresh, thenwealthdb 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_%andsavings_rate_%are now
twr_pct,twr_ann_pct,mwr_pct,mwr_ann_pct,share_pctand
savings_rate_pct. This changes the keys of-f jsonand 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. Seewealthdb/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 newlotsblock 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.basisin 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_aliasesin 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 rebuildruns the engine by hand. Everyloadand
reloadruns 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,coverageandcheck. The aggregate views add
up. Aqualitycolumn names every gap, andcoveragesays per
account where the figures are blind. Seewealthdb/docs/GAINS.md.wealthdb gains checkcompares the rebuilt figures with what the
sources state.--missing-basis ignore|zeroreads a missing cost basis as blank or as
0. The config'slots.missing_basissets the default.wealthdb holdings positionsoffers the cost basis, its stamp, the
clean value and the unrealized gain as opt-in columns.wealthdb returnsshowsgain_<CCY>, the money behind the
percentages.- The MCP server has a
gainstool 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
statustool and the opt-inholdings sourcescolumnobserved_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-invalued_datecolumn.
Reports and dashboards
- GBP is a fourth reporting currency beside USD, CHF and EUR. The
dashboards open in the config'sdefault_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 configworks 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.
exploreand 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 --checkmakes 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 demobuilds 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.