-- Polymarket Bot Database Schema CREATE TABLE IF NOT EXISTS trades ( id TEXT PRIMARY KEY, market_id TEXT NOT NULL, question TEXT NOT NULL, direction TEXT NOT NULL, -- BUY_YES | BUY_NO size_usdc DOUBLE PRECISION, entry_price DOUBLE PRECISION, shares DOUBLE PRECISION, fee_usdc DOUBLE PRECISION, net_cost DOUBLE PRECISION, timestamp TIMESTAMPTZ NOT NULL, reasoning TEXT, paper BOOLEAN DEFAULT TRUE ); CREATE TABLE IF NOT EXISTS metrics_daily ( id SERIAL PRIMARY KEY, timestamp TIMESTAMPTZ NOT NULL, total_trades INTEGER, total_deployed DOUBLE PRECISION, total_fees DOUBLE PRECISION, total_pnl DOUBLE PRECISION, win_rate DOUBLE PRECISION, avg_edge DOUBLE PRECISION, sharpe_ratio DOUBLE PRECISION, calibration_score DOUBLE PRECISION, paper_mode BOOLEAN DEFAULT TRUE ); CREATE TABLE IF NOT EXISTS markets ( id TEXT PRIMARY KEY, condition_id TEXT, question TEXT NOT NULL, category TEXT, end_date TEXT, active BOOLEAN DEFAULT TRUE, last_seen TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS signals ( id SERIAL PRIMARY KEY, market_id TEXT NOT NULL, timestamp TIMESTAMPTZ NOT NULL, polymarket_price DOUBLE PRECISION, estimated_prob DOUBLE PRECISION, edge DOUBLE PRECISION, confidence DOUBLE PRECISION, direction TEXT, acted_on BOOLEAN DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_trades_timestamp ON trades(timestamp DESC); CREATE INDEX IF NOT EXISTS idx_trades_market ON trades(market_id); CREATE INDEX IF NOT EXISTS idx_metrics_timestamp ON metrics_daily(timestamp DESC); CREATE INDEX IF NOT EXISTS idx_signals_timestamp ON signals(timestamp DESC); -- ───────────────────────────────────────────────────────────────────────────── -- Phase 1 migrations: edge neto real -- -- spread_estimate and commission are HEURISTICS, not exact Polymarket exchange -- costs. spread_estimate ≈ estimated half-spread for medium-liquidity markets. -- commission = COMMISSION_RATE (0.02) * size_usdc — mirrors Polymarket taker fee. -- edge_net = edge_gross - spread_estimate - commission/size_usdc -- = edge_gross - 0.02 - 0.02 (always 0.04 deduction at current rates) -- -- These are stored per-trade so we can audit whether the model's cost assumptions -- were met in practice once markets resolve. -- ───────────────────────────────────────────────────────────────────────────── ALTER TABLE trades ADD COLUMN IF NOT EXISTS edge_gross DOUBLE PRECISION; ALTER TABLE trades ADD COLUMN IF NOT EXISTS edge_net DOUBLE PRECISION; ALTER TABLE trades ADD COLUMN IF NOT EXISTS prior_prob DOUBLE PRECISION; ALTER TABLE trades ADD COLUMN IF NOT EXISTS final_prob DOUBLE PRECISION; ALTER TABLE trades ADD COLUMN IF NOT EXISTS mid_price DOUBLE PRECISION; ALTER TABLE trades ADD COLUMN IF NOT EXISTS spread_estimate DOUBLE PRECISION; ALTER TABLE trades ADD COLUMN IF NOT EXISTS commission DOUBLE PRECISION; ALTER TABLE trades ADD COLUMN IF NOT EXISTS family_key TEXT; -- ───────────────────────────────────────────────────────────────────────────── -- Phase 2 / Phase 5 migrations: market families + observability -- -- Signals table extended so each evaluated market carries its audit trail: -- skip_reason — why the market was not traded ("edge_net", "family", -- "gnews_priority", "regime", "prior_extreme", etc.) -- passed_gross — True if edge_gross alone met regime_min_edge -- passed_net — True if edge_net met regime_min_edge (the actual gate) -- family_key — market family slug (e.g. "texas-republican-2026") -- regime_min_edge — threshold that applied to this market/category -- ───────────────────────────────────────────────────────────────────────────── ALTER TABLE signals ADD COLUMN IF NOT EXISTS edge_gross DOUBLE PRECISION; ALTER TABLE signals ADD COLUMN IF NOT EXISTS edge_net DOUBLE PRECISION; ALTER TABLE signals ADD COLUMN IF NOT EXISTS family_key TEXT; ALTER TABLE signals ADD COLUMN IF NOT EXISTS regime_min_edge DOUBLE PRECISION; ALTER TABLE signals ADD COLUMN IF NOT EXISTS skip_reason TEXT; ALTER TABLE signals ADD COLUMN IF NOT EXISTS passed_gross BOOLEAN; ALTER TABLE signals ADD COLUMN IF NOT EXISTS passed_net BOOLEAN; CREATE INDEX IF NOT EXISTS idx_signals_market ON signals(market_id); CREATE INDEX IF NOT EXISTS idx_trades_family ON trades(family_key); -- ───────────────────────────────────────────────────────────────────────────── -- Position lifecycle: legacy scan can close erroneous paper positions. -- closed_at IS NULL → position is open (all open-position queries filter this). -- closed_at NOT NULL → position closed; close_reason explains why. -- ───────────────────────────────────────────────────────────────────────────── ALTER TABLE trades ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ; ALTER TABLE trades ADD COLUMN IF NOT EXISTS close_reason TEXT; CREATE INDEX IF NOT EXISTS idx_trades_closed ON trades(closed_at) WHERE closed_at IS NOT NULL;