Skip to content

Data model

The Supabase/Postgres tables behind your workspace — accounts, holdings, transactions, strategies, backtests, watchlists, alerts, orders — plus the leaderboard view and row-level security model.

Your workspace is persisted in Postgres (via Supabase). Every table has row-level security (RLS) enabled, so the database itself enforces who can see and change what — the app never grants blanket access. Sensitive columns like weights and metrics are stored as JSONB.

Tables

accounts

id, user_id, name, broker, currency (default USD), type (default brokerage), timestamps. RLS: owner-only for select/insert/update/delete.

holdings

id, account_id, symbol, qty, cost_basis, timestamps. RLS: authorized through the owning account — you must own the account. Deleting an account cascades to its holdings.

transactions

id, account_id, symbol, side (buy/sell/dividend/deposit/withdrawal), qty, price, executed_at. RLS: through the owning account.

strategies

id, user_id, name, type (default momentum), params (JSONB), code, is_public (default false), timestamps. RLS: readable if is_public or owner; only the owner writes. Powers the public strategy browser.

backtests

id, user_id, strategy_id (nullable), config (JSONB), metrics (JSONB), equity_curve (JSONB), is_public (default false), created_at. RLS: readable if is_public or owner; only the owner writes. See publishing.

watchlists

id, user_id, name, symbols (text[]), timestamps. RLS: owner-only.

alerts

id, user_id, symbol, condition (JSONB), is_active (default true), timestamps. RLS: owner-only.

orders

id, user_id, account_id, symbol, side (buy/sell), qty, status (draft/submitted/filled/cancelled), timestamps. Schema only — reserved for future live trading, no UI wires it up today. RLS: owner-only.

broker_connections (encrypted broker credentials) is intentionally deferred — not created in the current schema.

leaderboard

leaderboard is a view, not a table. It joins public backtests with public profiles and exposes ranking columns — sharpe, annualized_return, max_drawdown — pulled from the metrics JSONB.

Two safety properties:

  • security_invoker = true — the view runs with the caller’s privileges, so the underlying RLS still governs visibility. It never leaks private runs.
  • Safe numeric coercion — because metrics is user-written, the view parses each numeric with a guard (util.safe_numeric) that returns NULL on anything that isn’t a finite decimal. One malformed row becomes blank cells instead of taking down the whole leaderboard.

Row-level security in one line

  • Owner-only tables: accounts, holdings (via account), transactions (via account), watchlists, alerts, orders.
  • Public-or-owner tables: strategies, backtests (gated on is_public).
  • Nothing is readable by an anonymous visitor except explicitly public strategies/backtests and the leaderboard view.