CREATE TABLE IF NOT EXISTS market_data_snapshots ( id BIGSERIAL PRIMARY KEY, symbol TEXT NOT NULL, asset_class TEXT NOT NULL DEFAULT 'us_equity', feed TEXT NOT NULL DEFAULT 'iex', metric TEXT NOT NULL, price NUMERIC, volume NUMERIC, as_of TIMESTAMPTZ NOT NULL, raw JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS market_data_snapshots_symbol_as_of_idx ON market_data_snapshots (symbol, as_of DESC); CREATE TABLE IF NOT EXISTS trading_config_templates ( name TEXT PRIMARY KEY, config JSONB NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS user_trading_config ( firebase_uid TEXT PRIMARY KEY REFERENCES users (firebase_uid) ON DELETE CASCADE, template_name TEXT REFERENCES trading_config_templates (name), config JSONB NOT NULL DEFAULT '{}', enabled BOOLEAN NOT NULL DEFAULT false, updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS user_trading_state ( firebase_uid TEXT PRIMARY KEY REFERENCES users (firebase_uid) ON DELETE CASCADE, last_ingest_at TIMESTAMPTZ, last_eval_at TIMESTAMPTZ, context JSONB NOT NULL DEFAULT '{}', updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS trade_orders ( id UUID PRIMARY KEY, firebase_uid TEXT NOT NULL REFERENCES users (firebase_uid) ON DELETE CASCADE, client_order_id TEXT NOT NULL UNIQUE, alpaca_order_id TEXT, symbol TEXT NOT NULL, side TEXT NOT NULL, order_type TEXT NOT NULL, notional_usd NUMERIC, qty NUMERIC, status TEXT NOT NULL, question_id UUID REFERENCES questions (id), rule_id TEXT, submitted_at TIMESTAMPTZ, filled_at TIMESTAMPTZ, raw JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); INSERT INTO trading_config_templates (name, config) VALUES ( 'default_paper_watchlist', '{ "version": 1, "enabled": true, "mode": "paper", "data_inputs": [ { "id": "primary_watchlist", "source": "alpaca", "asset_class": "us_equity", "symbols": ["AAPL", "MSFT", "SPY"], "feed": "iex", "poll_interval_seconds": 60, "metrics": ["last_trade", "daily_bar", "prev_close"] } ], "rules": [ { "id": "dip_confirm", "type": "price_below_pct_of_ref", "symbol": "SPY", "ref_metric": "prev_close", "threshold_pct": -1.5, "question_template": "SPY is down {{pct}}% vs yesterday. Swipe +10 to approve a small buy, -10 to skip.", "on_answer_match": { "action": "propose_order", "side": "buy", "notional_usd": 10 } } ], "guardrails": { "max_orders_per_day": 3, "max_notional_usd_per_4h": 100, "require_question_before_order": true, "symbols_blocklist": [] } }'::jsonb ) ON CONFLICT (name) DO UPDATE SET config = EXCLUDED.config, updated_at = now();