Files
marketscanner/docker/db/init.sql
mindesbunister 074787f067 Initial project structure: MarketScanner - Fear-to-Fortune Trading Intelligence
Features:
- FastAPI backend with stocks, news, signals, watchlist, analytics endpoints
- React frontend with TailwindCSS dark mode trading dashboard
- Celery workers for news fetching, sentiment analysis, pattern detection
- TimescaleDB schema for time-series stock data
- Docker Compose setup for all services
- OpenAI integration for sentiment analysis
2026-01-08 14:15:51 +01:00

344 lines
14 KiB
PL/PgSQL

-- MarketScanner Database Initialization
-- TimescaleDB + PostgreSQL
-- Enable TimescaleDB extension
CREATE EXTENSION IF NOT EXISTS timescaledb;
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Enable pg_trgm for text search
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- =============================================================================
-- STOCKS TABLE
-- =============================================================================
CREATE TABLE IF NOT EXISTS stocks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
symbol VARCHAR(20) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
sector VARCHAR(100),
industry VARCHAR(100),
market_cap BIGINT,
exchange VARCHAR(50),
country VARCHAR(100) DEFAULT 'USA',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_stocks_symbol ON stocks(symbol);
CREATE INDEX idx_stocks_sector ON stocks(sector);
CREATE INDEX idx_stocks_industry ON stocks(industry);
-- =============================================================================
-- STOCK PRICES (Time-series)
-- =============================================================================
CREATE TABLE IF NOT EXISTS stock_prices (
time TIMESTAMPTZ NOT NULL,
stock_id UUID NOT NULL REFERENCES stocks(id) ON DELETE CASCADE,
open DECIMAL(15, 4),
high DECIMAL(15, 4),
low DECIMAL(15, 4),
close DECIMAL(15, 4) NOT NULL,
volume BIGINT,
adjusted_close DECIMAL(15, 4),
PRIMARY KEY (time, stock_id)
);
-- Convert to hypertable for time-series optimization
SELECT create_hypertable('stock_prices', 'time', if_not_exists => TRUE);
-- Create indexes
CREATE INDEX idx_stock_prices_stock_id ON stock_prices(stock_id, time DESC);
-- =============================================================================
-- NEWS ARTICLES
-- =============================================================================
CREATE TABLE IF NOT EXISTS news_articles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title TEXT NOT NULL,
content TEXT,
summary TEXT,
url TEXT UNIQUE NOT NULL,
source VARCHAR(100) NOT NULL,
author VARCHAR(255),
published_at TIMESTAMPTZ NOT NULL,
fetched_at TIMESTAMPTZ DEFAULT NOW(),
image_url TEXT,
-- Sentiment analysis results
sentiment_score DECIMAL(5, 2), -- -100 to +100
sentiment_label VARCHAR(20), -- negative, neutral, positive
sentiment_confidence DECIMAL(5, 4),
-- Processing status
is_processed BOOLEAN DEFAULT false,
processing_error TEXT
);
CREATE INDEX idx_news_published_at ON news_articles(published_at DESC);
CREATE INDEX idx_news_source ON news_articles(source);
CREATE INDEX idx_news_sentiment ON news_articles(sentiment_score);
CREATE INDEX idx_news_title_trgm ON news_articles USING gin(title gin_trgm_ops);
-- =============================================================================
-- NEWS-STOCK ASSOCIATIONS
-- =============================================================================
CREATE TABLE IF NOT EXISTS news_stock_mentions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
news_id UUID NOT NULL REFERENCES news_articles(id) ON DELETE CASCADE,
stock_id UUID NOT NULL REFERENCES stocks(id) ON DELETE CASCADE,
mention_type VARCHAR(50), -- direct, indirect, sector
relevance_score DECIMAL(5, 4),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(news_id, stock_id)
);
CREATE INDEX idx_mentions_news ON news_stock_mentions(news_id);
CREATE INDEX idx_mentions_stock ON news_stock_mentions(stock_id);
-- =============================================================================
-- PANIC EVENTS
-- =============================================================================
CREATE TABLE IF NOT EXISTS panic_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stock_id UUID NOT NULL REFERENCES stocks(id) ON DELETE CASCADE,
-- Event timing
start_time TIMESTAMPTZ NOT NULL,
peak_time TIMESTAMPTZ,
end_time TIMESTAMPTZ,
-- Price impact
price_at_start DECIMAL(15, 4) NOT NULL,
price_at_peak_panic DECIMAL(15, 4),
price_at_end DECIMAL(15, 4),
max_drawdown_percent DECIMAL(8, 4),
-- Sentiment
avg_sentiment_score DECIMAL(5, 2),
min_sentiment_score DECIMAL(5, 2),
news_volume INTEGER,
-- Recovery metrics
recovery_time_days INTEGER,
recovery_percent DECIMAL(8, 4),
-- Classification
event_type VARCHAR(100), -- earnings_miss, scandal, lawsuit, macro, etc.
event_category VARCHAR(50), -- company_specific, sector_wide, market_wide
-- Analysis
is_complete BOOLEAN DEFAULT false,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_panic_stock ON panic_events(stock_id);
CREATE INDEX idx_panic_time ON panic_events(start_time DESC);
CREATE INDEX idx_panic_type ON panic_events(event_type);
CREATE INDEX idx_panic_drawdown ON panic_events(max_drawdown_percent);
-- =============================================================================
-- HISTORICAL PATTERNS
-- =============================================================================
CREATE TABLE IF NOT EXISTS historical_patterns (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stock_id UUID NOT NULL REFERENCES stocks(id) ON DELETE CASCADE,
-- Pattern definition
pattern_type VARCHAR(100) NOT NULL, -- earnings_miss_recovery, scandal_recovery, etc.
-- Statistics (aggregated from multiple panic events)
avg_drawdown_percent DECIMAL(8, 4),
avg_recovery_days INTEGER,
avg_recovery_percent DECIMAL(8, 4),
median_drawdown_percent DECIMAL(8, 4),
median_recovery_days INTEGER,
median_recovery_percent DECIMAL(8, 4),
-- Sample size
event_count INTEGER NOT NULL,
-- Confidence
pattern_confidence DECIMAL(5, 4),
-- Time range
first_event_date DATE,
last_event_date DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(stock_id, pattern_type)
);
CREATE INDEX idx_patterns_stock ON historical_patterns(stock_id);
CREATE INDEX idx_patterns_type ON historical_patterns(pattern_type);
-- =============================================================================
-- BUY SIGNALS
-- =============================================================================
CREATE TABLE IF NOT EXISTS buy_signals (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stock_id UUID NOT NULL REFERENCES stocks(id) ON DELETE CASCADE,
panic_event_id UUID REFERENCES panic_events(id) ON DELETE SET NULL,
-- Signal details
signal_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
signal_price DECIMAL(15, 4) NOT NULL,
-- Confidence scoring
confidence_score DECIMAL(5, 4) NOT NULL, -- 0 to 1
-- Based on pattern
pattern_id UUID REFERENCES historical_patterns(id) ON DELETE SET NULL,
expected_recovery_percent DECIMAL(8, 4),
expected_recovery_days INTEGER,
-- Current metrics
current_drawdown_percent DECIMAL(8, 4),
current_sentiment_score DECIMAL(5, 2),
-- Signal status
status VARCHAR(20) DEFAULT 'active', -- active, triggered, expired, cancelled
triggered_at TIMESTAMPTZ,
-- Outcome tracking
outcome_price DECIMAL(15, 4),
outcome_percent DECIMAL(8, 4),
outcome_days INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_signals_stock ON buy_signals(stock_id);
CREATE INDEX idx_signals_time ON buy_signals(signal_time DESC);
CREATE INDEX idx_signals_confidence ON buy_signals(confidence_score DESC);
CREATE INDEX idx_signals_status ON buy_signals(status);
-- =============================================================================
-- WATCHLIST
-- =============================================================================
CREATE TABLE IF NOT EXISTS watchlist (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stock_id UUID NOT NULL REFERENCES stocks(id) ON DELETE CASCADE,
-- Alert thresholds
panic_alert_threshold DECIMAL(5, 2) DEFAULT -50,
price_alert_low DECIMAL(15, 4),
price_alert_high DECIMAL(15, 4),
-- Preferences
priority INTEGER DEFAULT 1, -- 1 = high, 2 = medium, 3 = low
notes TEXT,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(stock_id)
);
CREATE INDEX idx_watchlist_priority ON watchlist(priority, is_active);
-- =============================================================================
-- ALERT HISTORY
-- =============================================================================
CREATE TABLE IF NOT EXISTS alert_history (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stock_id UUID NOT NULL REFERENCES stocks(id) ON DELETE CASCADE,
signal_id UUID REFERENCES buy_signals(id) ON DELETE SET NULL,
alert_type VARCHAR(50) NOT NULL, -- panic_detected, buy_signal, price_target, etc.
alert_message TEXT NOT NULL,
-- Delivery status
sent_telegram BOOLEAN DEFAULT false,
sent_discord BOOLEAN DEFAULT false,
sent_email BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_alerts_time ON alert_history(created_at DESC);
CREATE INDEX idx_alerts_stock ON alert_history(stock_id);
-- =============================================================================
-- FUNCTIONS
-- =============================================================================
-- Function to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Apply triggers
CREATE TRIGGER update_stocks_updated_at BEFORE UPDATE ON stocks FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_panic_events_updated_at BEFORE UPDATE ON panic_events FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_patterns_updated_at BEFORE UPDATE ON historical_patterns FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_signals_updated_at BEFORE UPDATE ON buy_signals FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_watchlist_updated_at BEFORE UPDATE ON watchlist FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- =============================================================================
-- SEED DATA - Popular stocks to get started
-- =============================================================================
INSERT INTO stocks (symbol, name, sector, industry, exchange) VALUES
('AAPL', 'Apple Inc.', 'Technology', 'Consumer Electronics', 'NASDAQ'),
('MSFT', 'Microsoft Corporation', 'Technology', 'Software', 'NASDAQ'),
('GOOGL', 'Alphabet Inc.', 'Technology', 'Internet Services', 'NASDAQ'),
('AMZN', 'Amazon.com Inc.', 'Consumer Discretionary', 'E-Commerce', 'NASDAQ'),
('NVDA', 'NVIDIA Corporation', 'Technology', 'Semiconductors', 'NASDAQ'),
('META', 'Meta Platforms Inc.', 'Technology', 'Social Media', 'NASDAQ'),
('TSLA', 'Tesla Inc.', 'Consumer Discretionary', 'Electric Vehicles', 'NASDAQ'),
('JPM', 'JPMorgan Chase & Co.', 'Financials', 'Banking', 'NYSE'),
('V', 'Visa Inc.', 'Financials', 'Payments', 'NYSE'),
('JNJ', 'Johnson & Johnson', 'Healthcare', 'Pharmaceuticals', 'NYSE'),
('WMT', 'Walmart Inc.', 'Consumer Staples', 'Retail', 'NYSE'),
('XOM', 'Exxon Mobil Corporation', 'Energy', 'Oil & Gas', 'NYSE'),
('BA', 'Boeing Company', 'Industrials', 'Aerospace', 'NYSE'),
('DIS', 'Walt Disney Company', 'Communication Services', 'Entertainment', 'NYSE'),
('NFLX', 'Netflix Inc.', 'Communication Services', 'Streaming', 'NASDAQ'),
('AMD', 'Advanced Micro Devices', 'Technology', 'Semiconductors', 'NASDAQ'),
('INTC', 'Intel Corporation', 'Technology', 'Semiconductors', 'NASDAQ'),
('CRM', 'Salesforce Inc.', 'Technology', 'Software', 'NYSE'),
('ORCL', 'Oracle Corporation', 'Technology', 'Software', 'NYSE'),
('PYPL', 'PayPal Holdings Inc.', 'Financials', 'Payments', 'NASDAQ'),
-- Defense & Weapons (no morals, remember?)
('LMT', 'Lockheed Martin Corporation', 'Industrials', 'Defense', 'NYSE'),
('RTX', 'RTX Corporation', 'Industrials', 'Defense', 'NYSE'),
('NOC', 'Northrop Grumman Corporation', 'Industrials', 'Defense', 'NYSE'),
('GD', 'General Dynamics Corporation', 'Industrials', 'Defense', 'NYSE'),
('BA', 'Boeing Company', 'Industrials', 'Aerospace & Defense', 'NYSE'),
-- Mining
('NEM', 'Newmont Corporation', 'Materials', 'Gold Mining', 'NYSE'),
('FCX', 'Freeport-McMoRan Inc.', 'Materials', 'Copper Mining', 'NYSE'),
('RIO', 'Rio Tinto Group', 'Materials', 'Diversified Mining', 'NYSE'),
('BHP', 'BHP Group Limited', 'Materials', 'Diversified Mining', 'NYSE'),
-- Food & Agriculture
('ADM', 'Archer-Daniels-Midland Company', 'Consumer Staples', 'Agriculture', 'NYSE'),
('BG', 'Bunge Limited', 'Consumer Staples', 'Agriculture', 'NYSE'),
('MDLZ', 'Mondelez International', 'Consumer Staples', 'Food', 'NASDAQ'),
('KO', 'Coca-Cola Company', 'Consumer Staples', 'Beverages', 'NYSE'),
('PEP', 'PepsiCo Inc.', 'Consumer Staples', 'Beverages', 'NASDAQ'),
-- Oil & Gas
('CVX', 'Chevron Corporation', 'Energy', 'Oil & Gas', 'NYSE'),
('COP', 'ConocoPhillips', 'Energy', 'Oil & Gas', 'NYSE'),
('SLB', 'Schlumberger Limited', 'Energy', 'Oil Services', 'NYSE'),
-- Pharma & Biotech
('PFE', 'Pfizer Inc.', 'Healthcare', 'Pharmaceuticals', 'NYSE'),
('MRK', 'Merck & Co. Inc.', 'Healthcare', 'Pharmaceuticals', 'NYSE'),
('ABBV', 'AbbVie Inc.', 'Healthcare', 'Pharmaceuticals', 'NYSE'),
('BMY', 'Bristol-Myers Squibb', 'Healthcare', 'Pharmaceuticals', 'NYSE')
ON CONFLICT (symbol) DO NOTHING;
COMMIT;