Skip to content

v1.1.0 — Portfolio becomes real

Choose a tag to compare

@laanito laanito released this 05 Jul 18:23
· 59 commits to main since this release
f080b72

[1.1.0] - 2026-07-05

The "portfolio becomes real" release. v1.0 hardened the research half of the
north star (never show fabricated market data as live); 1.1 turns that same
integrity lens on the portfolio half — a real cash/transaction spine, honest
realized-vs-unrealized P&L, and the retirement of the old global, shared-across-
users portfolio (a privacy contradiction) in favour of a per-user one.

Added

  • Portfolio cash ledger (v1.1 "portfolio becomes real", spine) — cash is now
    derived from the transaction ledger as the single source of truth: a buy debits
    cash, a sell/dividend/deposit credits it, a withdrawal debits it, and fees always
    cost cash. New deposit/withdrawal transaction types (cash-only, no symbol).
    GET /api/portfolios, /api/portfolios/{id}, and /api/portfolios/{id}/analytics
    now report cash_balance and net_liquidation_value (holdings + cash).
  • Portfolio transaction UI (v1.1) — the Portfolio Manager can now record
    buy/sell/dividend/deposit/withdrawal transactions (the endpoints existed but no
    UI ever called them), with a live cash-impact preview and a trade-history table.
    New Net Liquidation and Cash metric cards surface the ledger balance.
  • Upcoming events in the Portfolio Manager (v1.1) — dividends, earnings and
    corporate actions for your holdings now show in the Manager's events calendar,
    not only on the legacy view / dedicated Dividends page.
  • Deep analytics on the per-user portfolio (v1.1, consolidation) — the
    Portfolio Manager now carries everything the old view had: correlation matrix,
    benchmark-overlay equity curve, the full risk-metric set (Sharpe, Sortino, beta,
    alpha, information ratio, max drawdown), an upcoming-dividend tracker, and
    Brinson + factor attribution — all scoped to the selected portfolio via new
    owner-checked GET /api/portfolios/{id}/analytics/* and /attribution routes
    (with a primary alias for dashboards).
  • AI Risk Assessment + backtesting in the Manager (v1.1) — the local-LLM risk
    narrative (on-demand) and the backtester (seeded from the portfolio's holdings)
    now live in the Manager.
  • Sell from a holding row (v1.1) — each holding has a "Sell" button that
    pre-fills the transaction form (sell / that symbol / full size / current price)
    so a sale is attributed to the position instead of typed from scratch.
  • Holdings CSV export in the Manager (it already had CSV import).

Changed

  • The equity portfolio is now per-user, full stop (v1.1, consolidation) — the
    Portfolio Manager (/api/portfolios) is the only portfolio; the legacy
    manager|legacy view toggle is gone and every surface that read a portfolio
    (home, cockpit, HUD, launchpad, correlation dashboard, risk / stress / factor /
    dividends / reports) now reads the caller's own primary portfolio via
    GET /api/portfolios/primary.

Removed

  • The global, user-less legacy portfolio (v1.1, consolidation) — the shared
    Holding table and its /api/portfolio, /api/portfolio/analytics/*,
    /api/portfolio/holdings* and /api/portfolio/{id}/attribution endpoints are
    deleted. On a shared instance every user saw and edited the same holdings — a
    contradiction of the "private terminal" thesis; portfolios are per-user now.
    (The global /watchlists/items feed is unaffected.)
  • Tax-lot accounting (the TaxLot model, /api/portfolio/tax-lots* endpoints
    and the Tax Lot Manager UI) — a jurisdiction-specific rabbit hole where a wrong
    number is worse than none; deliberately shelved.

Fixed

  • Import from Legacy / bulk add crashed with a 500 (v1.1) — adding a holding
    auto-generated its lot_id from a second-resolution timestamp, so adding the
    same symbol more than once within a second (as the legacy import does) collided
    on the (portfolio_id, symbol, lot_id) unique constraint. Auto lot_ids now
    carry a random suffix, and a genuine duplicate lot_id returns a clean 409
    instead of an unhandled 500.
  • Realized P&L was proceeds, not gains (v1.1) — analytics summed
    shares * sell_price - fees, overstating realized P&L by the entire cost basis.
    Now computed by replaying the ledger chronologically (running average cost per
    symbol): shares_sold * (sell_price - avg_cost) - fees, dividends excluded
    (they remain in dividend_income_ytd). New pure backend/services/portfolio_pnl.py.
    Annualized return now uses net liquidation (holdings + cash) as current equity.
    The "Total P&L" card is relabelled "Unrealized P&L" to match what it shows.
  • Multi-portfolio analytics annualized-return crashed on portfolios whose
    transactions carried bare YYYY-MM-DD dates (naive vs. tz-aware datetime
    subtraction); inception dates are now normalised to UTC.
  • Multi-portfolio analytics used India defaults for risk metrics (risk-free 0.06,
    benchmark NIFTY50); now 0.04 / S&P500 in line with the de-India defaults.