Skip to content

TradeMate Data Model

All tables live in the trademate_main Postgres database (TimescaleDB + pgvector). A second database trademate_journal is used by the TradeTally fork (TM-27).

Multi-tenancy

Row-Level Security (RLS) is enabled on every user-scoped table. FastAPI middleware injects the authenticated user's ID via:

sql SELECT set_config('app.current_user', '<user_uuid>', true);

Every policy uses:

sql USING (user_id::text = current_setting('app.current_user', true))

The true flag returns NULL (not an error) when the variable is unset, so unauthenticated or service-role sessions see 0 rows by default. Alembic migrations run as the Postgres superuser (BYPASSRLS).

Extensions

Extension Purpose
pgcrypto gen_random_uuid() server-side UUID generation
vector pgvector — embedding columns (used by TM-35 news_items)
timescaledb Hypertables for OHLCV / economic / fundamentals (TM-35)

Entity Relationship Diagram

erDiagram
    user_profiles {
        string user_id PK
        tierenum tier
        string full_name
        string avatar_url
        jsonb preferences
        timestamptz created_at
        timestamptz updated_at
    }

    subscriptions {
        uuid id PK
        string user_id FK
        tierenum tier
        string dodo_customer_id
        string dodo_subscription_id
        subscriptionstatusenum status
        timestamptz current_period_start
        timestamptz current_period_end
        timestamptz cancelled_at
    }

    broker_connections {
        uuid id PK
        string user_id FK
        brokerenum broker
        text encrypted_access_token
        text encrypted_refresh_token
        string account_id
        string account_number
        bool is_paper
        bool is_active
        timestamptz token_expires_at
    }

    llm_provider_keys {
        uuid id PK
        string user_id FK
        llmproviderenum provider
        text encrypted_api_key
        bool is_active
    }

    portfolios {
        uuid id PK
        string user_id FK
        string name
        uuid broker_connection_id FK
        bool is_paper_trading
        string base_currency
    }

    risk_settings {
        uuid id PK
        uuid portfolio_id FK
        string user_id
        float max_position_size_pct
        float max_drawdown_pct
        float daily_loss_limit_pct
        float sector_concentration_pct
        int max_open_positions
    }

    strategy_configs {
        uuid id PK
        uuid portfolio_id FK
        string user_id
        strategyenum strategy
        bool is_active
        float allocation_pct
        jsonb params
    }

    watchlists {
        uuid id PK
        string user_id FK
        string name
        bool is_smart
        text[] tickers
        text[] current_tickers
        timestamptz last_refreshed_at
    }

    smart_watchlist_rules {
        uuid id PK
        uuid watchlist_id FK
        string user_id
        string field_name
        string operator
        jsonb value
        string logic
        int position
    }

    signals {
        uuid id PK
        uuid portfolio_id FK
        string user_id
        strategyenum strategy
        string ticker
        signalactionenum action
        float confidence_score
        text rationale
        jsonb signal_metadata
        timestamptz expires_at
        bool is_executed
    }

    trades {
        uuid id PK
        uuid portfolio_id FK
        string user_id
        uuid signal_id FK
        string ticker
        tradesideenum side
        float qty
        float limit_price
        float filled_price
        string order_id
        tradestatusenum status
        strategyenum strategy
        jsonb regime_snapshot
        jsonb agent_outputs
        text thesis
    }

    fills {
        uuid id PK
        uuid trade_id FK
        string user_id
        string ticker
        tradesideenum side
        float qty
        float price
        float commission
        timestamptz filled_at
        string broker_fill_id
    }

    positions {
        uuid id PK
        uuid portfolio_id FK
        string user_id
        string ticker
        float qty
        float avg_cost
        float market_value
        float unrealized_pnl
        float realized_pnl
        strategyenum strategy
        float stop_price
        float target_price
    }

    agent_runs {
        uuid id PK
        uuid portfolio_id FK
        string user_id
        string agent_name
        string trigger_event
        agentstatusenum status
        timestamptz started_at
        timestamptz completed_at
        int input_tokens
        int output_tokens
        float cost_usd
        jsonb run_metadata
    }

    agent_decisions {
        uuid id PK
        uuid agent_run_id FK
        string user_id
        string step_name
        string decision_type
        jsonb step_input
        jsonb step_output
        text reasoning
        string model_used
    }

    audit_log {
        uuid id PK
        string user_id
        uuid portfolio_id
        string action
        string resource_type
        string resource_id
        jsonb old_values
        jsonb new_values
        string ip_address
        timestamptz created_at
    }

    user_profiles ||--o{ subscriptions : "has"
    user_profiles ||--o{ broker_connections : "has"
    user_profiles ||--o{ llm_provider_keys : "has"
    user_profiles ||--o{ portfolios : "owns"
    user_profiles ||--o{ watchlists : "owns"

    portfolios ||--|| risk_settings : "has"
    portfolios ||--o{ strategy_configs : "has"
    portfolios ||--o{ signals : "generates"
    portfolios ||--o{ trades : "contains"
    portfolios ||--o{ positions : "holds"
    portfolios ||--o{ agent_runs : "triggers"

    broker_connections ||--o{ portfolios : "linked to"

    watchlists ||--o{ smart_watchlist_rules : "defined by"

    signals ||--o{ trades : "triggers"

    trades ||--o{ fills : "filled by"

    agent_runs ||--o{ agent_decisions : "records"

Tables by Domain

Identity & Auth

Table Description
user_profiles App-specific user data; user_id mirrors Better Auth UUID
subscriptions Dodo Payments subscription state per user
broker_connections Encrypted Alpaca/IBKR OAuth tokens plus durable broker account id/number and paper/live account type
llm_provider_keys BYOK Pro encrypted API keys per provider

Portfolio & Risk

Table Description
portfolios Top-level grouping; each execution-enabled portfolio maps to one broker account, and each broker account can be linked to only one portfolio
risk_settings Hard limits: position size, drawdown, daily loss, concentration
strategy_configs Active strategies + allocation weights per portfolio; MVP strategy passports live in params.strategy_passport

Watchlists

Table Description
watchlists Manual (user-curated) and Smart (rule-based) watchlists
smart_watchlist_rules Individual filter rules (field, operator, value, AND/OR logic)

Trading

Table Description
signals Agent-generated buy/sell/hold signals per strategy + ticker
trades Orders sent to broker; linked to signal + agent context snapshot
fills Individual fill records per trade (partial fills supported)
positions Current open positions with stop/target tracking

Agent Operations

Table Description
agent_runs One record per LangGraph agent execution; tracks tokens + cost
agent_decisions Step-by-step decision trace within an agent run

Observability

Table Description
audit_log Append-only audit trail for user + system actions

Time-Series Tables (TM-35)

The following tables are added in migration 0002 (TM-35) as TimescaleDB hypertables:

Table Partition key Retention
market_data time 5 years
economic_indicators time 10 years
fundamentals_snapshots time 10 years
news_items published_at 2 years

news_items includes a pgvector embedding vector(1536) column for semantic search.