Skip to content

PortfolioDB 1.7.2

Choose a tag to compare

@amosgeva amosgeva released this 10 Sep 07:24
· 19 commits to main since this release
5709b27

The second pass over the 1.7.x audit: six findings the re-audit of 1.7.1
turned up, each a case the first pass had reasoned about and got wrong at
one edge — a split dated after the observation, a dividend paid before a
later split, a liquidated stretch on the chart, an unnamed account beside a
named one, a half-filled CSV trade row, and the write password sitting in the
one container built not to have it. No schema change and no migration.

Upgrading

  • If you ran add_income.py --backfill on 1.7.0 or 1.7.1 for a symbol that
    split after one of its dividends, the estimates for the earlier dividends
    are undercounted. Rerun with --backfill --replace-estimates for that
    symbol; it deletes the source='yfinance' rows first and says how many it
    replaced. Manual income rows are never touched.
  • Compose deployments that run the MCP server on
    PORTFOLIODB_MCP_ALLOW_RW_FALLBACK=1 must now add PORTFOLIODB_PASSWORD
    to the mcp service in docker-compose.override.yml
    (exposure shows the block). Deployments
    on the read-only role, which is the default, need nothing.
  • A CSV export with half-filled trade rows that imported clean before will
    now be rejected with the line numbers; fix the rows or blank all three
    trade fields to make them quote-only.

Security

  • The MCP container no longer receives the application's read-write
    password.
    The mcp compose service inherited the shared environment
    block, and with it PORTFOLIODB_PASSWORD, because the pool read the
    database address through load_config(), which insists on that password —
    so the one container built to hold only a SELECT-only role also held the
    login that could write. The address now comes from a credential-free
    db.load_target(), the compose file gives mcp the address and its own
    settings only, and the read-only path never looks at the write password.
    The deliberate PORTFOLIODB_MCP_ALLOW_RW_FALLBACK=1 opt-out still works but
    you now supply PORTFOLIODB_PASSWORD to the service yourself in
    docker-compose.override.yml (exposure
    shows the block); without it the server refuses to start and says so.

Fixed

  • CSV import rejects incomplete trade rows instead of skipping them. A
    row with some of Trade Date, Purchase Price and Quantity filled in
    was treated as "not a lot": nothing was imported for the trade, its price
    snapshot still went in, and the run reported zero rejections — a trade
    missing from the ledger behind a clean import. Such a row is now rejected
    with its line number and the missing field, and so is a trade row with no
    symbol; a row with all three trade fields blank is still a quote-only row,
    and a wholly blank line is still skipped. The transaction policy applies:
    atomic mode writes nothing from that file, --continue-on-error counts the
    row as rejected and writes the rest.

  • The weekly report no longer crashes when an unnamed account meets a named
    one.
    1.7.0 moved its position aggregation into Python and sorted
    (account, symbol) keys with the default ordering, which refuses to compare
    None with a string; any ledger with both an account-less lot and a named
    account stopped the report before it printed. Unnamed accounts now sort
    first, and the stored value is kept as is, so None and an empty-string
    account stay distinct.

  • The portfolio-value chart keeps the days on which everything was sold.
    The history dropped every zero-valued point, so a liquidated stretch
    vanished from the chart and the line bridged from the last funded day to
    the re-entry. Only the points before anything was ever held are dropped
    now; a zero after that is drawn as a flat line at zero, and a range that
    begins on such a day shows no percentage change rather than a division by
    zero.

  • Dividend backfill: a dividend paid before a later split is no longer
    undercounted.
    yfinance states every historical per-share dividend in
    today's split-adjusted units (Apple's $0.82 of August 2020 comes back as
    $0.205 after the 4:1), so the shares it is multiplied by must be in today's
    units too. 1.7.0 read the ledger as of the ex-date, which left a later split
    out and recorded half (or a quarter) of the cash for every dividend paid
    before one. The backfill now selects lots by ex-date and counts them in
    current units (ledger_inputs.load(units="current")). Existing
    estimates:
    rows written by earlier backfills carry the old amounts, and
    because the dedupe key includes the amount a rerun would insert the
    corrected row beside the old one. add_income.py --backfill --replace-estimates deletes the symbol's source='yfinance' rows first
    and reports how many it replaced; manual rows are never touched.

  • Historical MCP positions and the daily/EOD report state splits in the
    right units.
    get_positions with an as_of before a recorded split
    applied the split anyway, so "the day before a 2:1" reported twenty shares
    against the pre-split quote — twice the value that existed; a stale quote
    observed before an ex-date the cutoff was past was joined to restated
    shares the same way. The daily/EOD report restated its lots but compared
    raw previous, day-start and current quotes, so a split between two quotes
    printed a loss of the whole ratio (Delta: $-1,000.00 on a pure 2:1 with
    nothing else moving). Actions now apply only when dated on or before the
    observation date (ledger_inputs.prepare(as_of=…), shared by every
    date-filtered reader), stale quotes are restated into the cutoff's units,
    and the report's three comparison quotes go through the prepared ledger
    with their own timestamps. Installs without a corporate_actions row see
    no change.