7.4 KiB
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)
datetimeobjects → ISO-8601 string (isoformat())Decimalvalues →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.