TradingAgents/docs/portfolio/02_data_models.md

7.4 KiB

Data Models — Full Specification

All models live in tradingagents/portfolio/models.py as Python dataclass types. They must be fully type-annotated and support lossless to_dict / from_dict round-trips.


Portfolio

Represents a single managed portfolio (one user may eventually have multiple).

Fields

Field Type Required Description
portfolio_id str Yes UUID, primary key
name str Yes Human-readable name, e.g. "Main Portfolio"
cash float Yes Available cash balance in USD
initial_cash float Yes Starting capital (immutable after creation)
currency str Yes ISO 4217 code, default "USD"
created_at str Yes ISO-8601 UTC datetime string
updated_at str Yes ISO-8601 UTC datetime string
report_path str | None No Filesystem path to today's portfolio report dir
metadata dict No Free-form JSON for agent notes / tags

Computed / Derived Fields (not stored in DB)

Field Type Description
total_value float cash + sum of all holding current_value
equity_value float sum of all holding current_value
cash_pct float cash / total_value

Methods

def to_dict(self) -> dict:
    """Serialise all stored fields to a flat dict suitable for JSON / Supabase insert."""

def from_dict(cls, data: dict) -> "Portfolio":
    """Deserialise from a DB row or JSON dict. Missing optional fields default gracefully."""

def enrich(self, holdings: list["Holding"]) -> "Portfolio":
    """Compute total_value, equity_value, cash_pct from the provided holdings list."""

Holding

Represents a single open position within a portfolio.

Fields

Field Type Required Description
holding_id str Yes UUID, primary key
portfolio_id str Yes FK → portfolios.portfolio_id
ticker str Yes Stock ticker symbol, e.g. "AAPL"
shares float Yes Number of shares held
avg_cost float Yes Average cost basis per share (USD)
sector str | None No GICS sector name
industry str | None No GICS industry name
created_at str Yes ISO-8601 UTC datetime string
updated_at str Yes ISO-8601 UTC datetime string

Runtime-Computed Fields (not stored in DB)

These are populated by enrich() and available for agent/analysis use:

Field Type Description
current_price float | None Latest market price per share
current_value float | None current_price * shares
cost_basis float avg_cost * shares
unrealized_pnl float | None current_value - cost_basis
unrealized_pnl_pct float | None unrealized_pnl / cost_basis (0 if cost_basis == 0)
weight float | None current_value / portfolio_total_value

Methods

def to_dict(self) -> dict:
    """Serialise stored fields only (not runtime-computed fields)."""

def from_dict(cls, data: dict) -> "Holding":
    """Deserialise from DB row or JSON dict."""

def enrich(self, current_price: float, portfolio_total_value: float) -> "Holding":
    """
    Populate runtime-computed fields in-place and return self.

    Args:
        current_price: Latest market price for this ticker.
        portfolio_total_value: Total portfolio value (cash + equity) for weight calc.
    """

Trade

Immutable record of a single mock trade execution. Never modified after creation.

Fields

Field Type Required Description
trade_id str Yes UUID, primary key
portfolio_id str Yes FK → portfolios.portfolio_id
ticker str Yes Stock ticker symbol
action str Yes "BUY" or "SELL"
shares float Yes Number of shares traded
price float Yes Execution price per share (USD)
total_value float Yes shares * price
trade_date str Yes ISO-8601 UTC datetime of execution
rationale str | None No PM agent rationale for this trade
signal_source str | None No "scanner", "holding_review", "pm_agent"
metadata dict No Free-form JSON

Methods

def to_dict(self) -> dict:
    """Serialise all fields."""

def from_dict(cls, data: dict) -> "Trade":
    """Deserialise from DB row or JSON dict."""

PortfolioSnapshot

Point-in-time immutable record of the portfolio state. Taken after every trade execution session (Phase 5 of the workflow). Used for performance tracking.

Fields

Field Type Required Description
snapshot_id str Yes UUID, primary key
portfolio_id str Yes FK → portfolios.portfolio_id
snapshot_date str Yes ISO-8601 UTC datetime
total_value float Yes Cash + equity at snapshot time
cash float Yes Cash balance at snapshot time
equity_value float Yes Sum of position values at snapshot time
num_positions int Yes Number of open positions
holdings_snapshot list[dict] Yes Serialised list of holding dicts (as-of)
metadata dict No Free-form JSON (e.g. PM decision path)

Methods

def to_dict(self) -> dict:
    """Serialise all fields. `holdings_snapshot` is already a list[dict]."""

def from_dict(cls, data: dict) -> "PortfolioSnapshot":
    """Deserialise. `holdings_snapshot` parsed from JSON string if needed."""

Serialisation Contract

to_dict()

  • Returns a flat dict[str, Any]
  • All values must be JSON-serialisable (str, int, float, bool, list, dict, None)
  • datetime objects → ISO-8601 string (isoformat())
  • Decimal values → float
  • Runtime-computed fields (current_price, weight, etc.) are excluded
  • Complex nested fields (metadata, holdings_snapshot) are included as-is

from_dict()

  • Class method; must be callable as Portfolio.from_dict(row)
  • Handles missing optional fields with data.get("field", default)
  • Does not raise on extra keys in data
  • Does not populate runtime-computed fields (call enrich() separately)

Enrichment Logic

Holding.enrich(current_price, portfolio_total_value)

self.current_price = current_price
self.current_value = current_price * self.shares
self.cost_basis = self.avg_cost * self.shares
self.unrealized_pnl = self.current_value - self.cost_basis
if self.cost_basis > 0:
    self.unrealized_pnl_pct = self.unrealized_pnl / self.cost_basis
else:
    self.unrealized_pnl_pct = 0.0
if portfolio_total_value > 0:
    self.weight = self.current_value / portfolio_total_value
else:
    self.weight = 0.0
return self

Portfolio.enrich(holdings)

self.equity_value = sum(h.current_value or 0 for h in holdings)
self.total_value = self.cash + self.equity_value
if self.total_value > 0:
    self.cash_pct = self.cash / self.total_value
else:
    self.cash_pct = 1.0
return self

Type Alias Reference

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any

All metadata fields use dict[str, Any] with field(default_factory=dict). All optional fields default to None unless noted otherwise.