284 lines
10 KiB
SQL
284 lines
10 KiB
SQL
-- Invest Copilot database initialization
|
|
-- This script runs on first container start
|
|
|
|
-- Enable TimescaleDB extension
|
|
CREATE EXTENSION IF NOT EXISTS timescaledb;
|
|
|
|
-- Create the prices hypertable
|
|
CREATE TABLE IF NOT EXISTS prices (
|
|
ticker VARCHAR(20) NOT NULL,
|
|
date TIMESTAMPTZ NOT NULL,
|
|
open DECIMAL(15,4),
|
|
high DECIMAL(15,4),
|
|
low DECIMAL(15,4),
|
|
close DECIMAL(15,4),
|
|
volume BIGINT,
|
|
adjusted_close DECIMAL(15,4),
|
|
PRIMARY KEY (ticker, date)
|
|
);
|
|
|
|
-- Convert to hypertable
|
|
SELECT create_hypertable('prices', 'date', if_not_exists => TRUE);
|
|
|
|
-- Enable columnstore for compression
|
|
ALTER TABLE prices SET (timescaledb.compress, timescaledb.compress_segmentby = 'ticker');
|
|
|
|
-- Create compression policy (compress data older than 90 days)
|
|
SELECT add_compression_policy('prices', INTERVAL '90 days');
|
|
|
|
-- Create stock profiles table
|
|
CREATE TABLE IF NOT EXISTS stock_profiles (
|
|
ticker VARCHAR(20) PRIMARY KEY,
|
|
name VARCHAR(500),
|
|
exchange VARCHAR(20),
|
|
sector VARCHAR(100),
|
|
industry VARCHAR(200),
|
|
market_cap BIGINT,
|
|
description TEXT,
|
|
website TEXT,
|
|
ceo VARCHAR(255),
|
|
employees INTEGER,
|
|
pe_ratio DECIMAL(10,2),
|
|
eps DECIMAL(10,4),
|
|
dividend_yield DECIMAL(8,4),
|
|
beta DECIMAL(6,4),
|
|
last_updated TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Create users table
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
password_hash VARCHAR(255),
|
|
name VARCHAR(255),
|
|
avatar_url TEXT,
|
|
timezone VARCHAR(50) DEFAULT 'UTC',
|
|
settings JSONB DEFAULT '{}',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Create watchlists table
|
|
CREATE TABLE IF NOT EXISTS watchlists (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
is_default BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_watchlists_user ON watchlists(user_id);
|
|
|
|
-- Create watchlist items
|
|
CREATE TABLE IF NOT EXISTS watchlist_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
watchlist_id UUID NOT NULL REFERENCES watchlists(id) ON DELETE CASCADE,
|
|
ticker VARCHAR(20) NOT NULL,
|
|
type VARCHAR(20) NOT NULL CHECK (type IN ('stock', 'etf', 'index')),
|
|
notes TEXT,
|
|
price_at_addition DECIMAL(15,4),
|
|
added_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_items_unique ON watchlist_items(watchlist_id, ticker);
|
|
|
|
-- Create strategies
|
|
CREATE TABLE IF NOT EXISTS strategies (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(20) NOT NULL CHECK (type IN ('technical', 'fundamental', 'hybrid')),
|
|
conditions JSONB NOT NULL,
|
|
backtest_results JSONB,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Create watchlist-strategy associations
|
|
CREATE TABLE IF NOT EXISTS watchlist_strategies (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
watchlist_id UUID NOT NULL REFERENCES watchlists(id) ON DELETE CASCADE,
|
|
strategy_id UUID NOT NULL REFERENCES strategies(id) ON DELETE CASCADE,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_strategies_unique ON watchlist_strategies(watchlist_id, strategy_id);
|
|
|
|
-- Create screeners
|
|
CREATE TABLE IF NOT EXISTS screeners (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
conditions JSONB NOT NULL,
|
|
results_count INTEGER DEFAULT 0,
|
|
last_run_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Create screener results
|
|
CREATE TABLE IF NOT EXISTS screener_results (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
screener_id UUID NOT NULL REFERENCES screeners(id) ON DELETE CASCADE,
|
|
ticker VARCHAR(20) NOT NULL,
|
|
match_score DECIMAL(5,4),
|
|
ranked_position INTEGER,
|
|
result_data JSONB,
|
|
generated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_screener_results_screener ON screener_results(screener_id);
|
|
|
|
-- Create SEC filings
|
|
CREATE TABLE IF NOT EXISTS sec_filings (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
ticker VARCHAR(20) NOT NULL,
|
|
cik VARCHAR(20),
|
|
form_type VARCHAR(10) NOT NULL,
|
|
filing_date DATE NOT NULL,
|
|
report_date DATE,
|
|
accession_number VARCHAR(50),
|
|
url TEXT,
|
|
content_summary TEXT,
|
|
key_metrics JSONB,
|
|
sentiment_score DECIMAL(5,4),
|
|
tags TEXT[],
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sec_filings_ticker ON sec_filings(ticker);
|
|
CREATE INDEX IF NOT EXISTS idx_sec_filings_form ON sec_filings(form_type);
|
|
CREATE INDEX IF NOT EXISTS idx_sec_filings_date ON sec_filings(filing_date);
|
|
|
|
-- Create insider trades
|
|
CREATE TABLE IF NOT EXISTS insider_trades (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
ticker VARCHAR(20) NOT NULL,
|
|
insider_name VARCHAR(500),
|
|
insider_title VARCHAR(500),
|
|
transaction_date DATE NOT NULL,
|
|
transaction_type VARCHAR(10),
|
|
shares INTEGER,
|
|
price_per_share DECIMAL(10,4),
|
|
total_value DECIMAL(15,4),
|
|
shares_owned_after INTEGER,
|
|
filing_date DATE,
|
|
source_url TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_insider_trades_ticker ON insider_trades(ticker);
|
|
CREATE INDEX IF NOT EXISTS idx_insider_trades_type ON insider_trades(transaction_type);
|
|
|
|
-- Create alerts
|
|
CREATE TABLE IF NOT EXISTS alerts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
watchlist_id UUID NOT NULL REFERENCES watchlists(id) ON DELETE CASCADE,
|
|
strategy_id UUID REFERENCES strategies(id),
|
|
ticker VARCHAR(20),
|
|
type VARCHAR(30) NOT NULL,
|
|
trigger_type VARCHAR(50),
|
|
message TEXT NOT NULL,
|
|
severity VARCHAR(10) DEFAULT 'info' CHECK (severity IN ('info', 'warning', 'critical')),
|
|
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'resolved', 'dismissed')),
|
|
triggered_at TIMESTAMPTZ DEFAULT NOW(),
|
|
resolved_at TIMESTAMPTZ,
|
|
metadata JSONB DEFAULT '{}'
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_alerts_watchlist ON alerts(watchlist_id);
|
|
CREATE INDEX IF NOT EXISTS idx_alerts_status ON alerts(status);
|
|
CREATE INDEX IF NOT EXISTS idx_alerts_triggered ON alerts(triggered_at);
|
|
|
|
-- Create sector rotations
|
|
CREATE TABLE IF NOT EXISTS sector_rotations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
detection_date DATE NOT NULL,
|
|
sector_ticker VARCHAR(20) NOT NULL,
|
|
sector_name VARCHAR(100),
|
|
rank_now INTEGER,
|
|
rank_previous INTEGER,
|
|
rank_change INTEGER,
|
|
momentum_20d DECIMAL(8,4),
|
|
momentum_50d DECIMAL(8,4),
|
|
momentum_200d DECIMAL(8,4),
|
|
relative_strength DECIMAL(8,4),
|
|
rotation_signal VARCHAR(20),
|
|
macro_context JSONB,
|
|
analysis_summary TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sector_rotations_date ON sector_rotations(detection_date);
|
|
CREATE INDEX IF NOT EXISTS idx_sector_rotations_signal ON sector_rotations(rotation_signal);
|
|
|
|
-- Create peer groups
|
|
CREATE TABLE IF NOT EXISTS peer_groups (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
ticker VARCHAR(20) NOT NULL,
|
|
peer_ticker VARCHAR(20) NOT NULL,
|
|
similarity_score DECIMAL(5,4),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE (ticker, peer_ticker)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_peer_groups_ticker ON peer_groups(ticker);
|
|
|
|
-- Create rotation alert preferences
|
|
CREATE TABLE IF NOT EXISTS rotation_alert_preferences (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
sectors TEXT[] NOT NULL DEFAULT '{}',
|
|
min_rank_change INTEGER DEFAULT 2,
|
|
email_enabled BOOLEAN DEFAULT TRUE,
|
|
push_enabled BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Create continuous aggregates for common queries
|
|
CREATE MATERIALIZED VIEW IF NOT EXISTS prices_daily
|
|
WITH (timescaledb.continuous) AS
|
|
SELECT ticker,
|
|
time_bucket('1 day', date) AS bucket,
|
|
first(open, date) AS open,
|
|
max(high) AS high,
|
|
min(low) AS low,
|
|
last(close, date) AS close,
|
|
sum(volume) AS volume
|
|
FROM prices
|
|
GROUP BY ticker, bucket;
|
|
|
|
-- Create hourly aggregates for intraday analysis
|
|
CREATE MATERIALIZED VIEW IF NOT EXISTS prices_hourly
|
|
WITH (timescaledb.continuous) AS
|
|
SELECT ticker,
|
|
time_bucket('1 hour', date) AS bucket,
|
|
first(open, date) AS open,
|
|
max(high) AS high,
|
|
min(low) AS low,
|
|
last(close, date) AS close,
|
|
sum(volume) AS volume
|
|
FROM prices
|
|
GROUP BY ticker, bucket;
|
|
|
|
-- Seed data for sector ETFs (for rotation tracking)
|
|
INSERT INTO stock_profiles (ticker, name, exchange, sector, industry, market_cap, last_updated) VALUES
|
|
('XLK', 'Technology Select Sector SPDR Fund', 'NYSEARCA', 'Technology', 'ETF', 72000000000, NOW()),
|
|
('XLF', 'Financial Select Sector SPDR Fund', 'NYSEARCA', 'Financials', 'ETF', 45000000000, NOW()),
|
|
('XLE', 'Energy Select Sector SPDR Fund', 'NYSEARCA', 'Energy', 'ETF', 32000000000, NOW()),
|
|
('XLV', 'Health Care Select Sector SPDR Fund', 'NYSEARCA', 'Health Care', 'ETF', 38000000000, NOW()),
|
|
('XLI', 'Industrial Select Sector SPDR Fund', 'NYSEARCA', 'Industrials', 'ETF', 18000000000, NOW()),
|
|
('XLY', 'Consumer Discretionary Select Sector SPDR Fund', 'NYSEARCA', 'Consumer Discretionary', 'ETF', 20000000000, NOW()),
|
|
('XLP', 'Consumer Staples Select Sector SPDR Fund', 'NYSEARCA', 'Consumer Staples', 'ETF', 15000000000, NOW()),
|
|
('XLU', 'Utilities Select Sector SPDR Fund', 'NYSEARCA', 'Utilities', 'ETF', 16000000000, NOW()),
|
|
('XLRE', 'Real Estate Select Sector SPDR Fund', 'NYSEARCA', 'Real Estate', 'ETF', 5000000000, NOW()),
|
|
('XLB', 'Materials Select Sector SPDR Fund', 'NYSEARCA', 'Materials', 'ETF', 4000000000, NOW()),
|
|
('SPY', 'SPDR S&P 500 ETF Trust', 'NYSEARCA', 'Broad Market', 'ETF', 520000000000, NOW())
|
|
ON CONFLICT (ticker) DO NOTHING;
|