Holdings / Positions
A holding (or position) is a specific asset held within an account — typically identified by ticker, CUSIP, or an internal security ID, with associated share count, cost basis, market value, and (in tax-aware systems) lot-level acquisition history.
The holding is the atomic unit of portfolio analytics. Every aggregation — by account, by asset class, by sector, by tax-lot — rolls up from holdings. Every action — rebalance, harvest, transfer — operates on holdings. Schema design at the holding level determines what's possible at every higher level.
The key design decision is aggregation depth. The thinnest schema records only ticker + shares + market value — enough to display a position list, not enough to compute realized gain on a partial sale. The middle schema adds cost basis at the position level — enough for aggregate gain tracking, not enough for tax-lot relief. The full schema breaks the position into individual lots, each with its own acquisition date, basis, holding-period flag, and wash-sale carrying. Tax-aware features (TLH, gain harvesting, specific-lot relief) require the full schema; aggregate-basis platforms ship with a known correctness gap.
Holdings carry several derived fields that look static but actually drift. Market value updates whenever the price feed updates — typically every 15 minutes during market hours, with end-of-day reconciliation. Cost basis can change without a trade: corporate actions adjust per-share basis (splits), distributions reduce basis (return-of-capital), wash sales add to basis. A platform that doesn't reprocess these events on time falls out of sync with the broker's books and produces tax forms that don't reconcile.
Realistic synthetic holdings need lot-level granularity for any tax-aware test scenario. Aggregate snapshots are insufficient. Each position should carry 1–N lots, with an empirical distribution of basis-vs-market spreads (not all positions at exactly cost), an age mix that produces both short-term and long-term lots, and a small fraction with wash-sale-disallowed carry-on basis.
Common pitfalls
- Computing realized gain from aggregate basis instead of lot-level — produces wrong answers on partial sales.
- Failing to refresh market value during the trading day — daily-only refresh creates stale-value windows that break intraday-rebalance logic.
- Not propagating return-of-capital basis adjustments — REITs and MLPs accumulate distributions whose tax character is partly RoC, requiring retroactive basis updates.
- Treating fractional shares as a rounding error — they accumulate via DRIP and now matter for cost-basis reporting on every retail position.
Examples
Schema fragment for a single VTI position with 3 lots.
{
"symbol": "VTI",
"total_shares": 175,
"current_price": 248.31,
"market_value": 43454.25,
"lots": [
{ "shares": 50, "basis_per_share": 198.42, "acquired": "2023-04-12" },
{ "shares": 75, "basis_per_share": 215.10, "acquired": "2024-08-30" },
{ "shares": 50, "basis_per_share": 232.55, "acquired": "2025-11-04" }
],
"asset_class": "equity",
"subclass": "us_total_market"
}