cyberhybridhub/server/migrations/004_trading.sql

98 lines
3.1 KiB
SQL

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();