stock-scraper / database /schema.sql
sbasu2512's picture
fix dashboard bugs and add resolution progress
a88e030
Raw
History Blame Contribute Delete
11.9 kB
-- ============================================================
-- Stock Signal SQLite Schema
-- Consolidated schema
--
-- This replaces the older schema.sql + phase2/phase3/phase4
-- migration chain for NEW database initialization.
--
-- Canonical lifecycle:
-- prediction -> resolution -> metrics -> retrain flag
-- ============================================================
-- ============================================================
-- 1. PREDICTIONS
-- ============================================================
CREATE TABLE IF NOT EXISTS predictions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- Model / feature provenance
model_version TEXT NOT NULL,
feature_version TEXT NOT NULL,
-- Prediction timing
prediction_date DATE NOT NULL,
prediction_timestamp TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
-- Stock
symbol TEXT NOT NULL,
-- Model output
predicted_probability REAL NOT NULL,
"rank" INTEGER NOT NULL,
-- Target contract
target_threshold REAL NOT NULL DEFAULT 0.03,
target_horizon_days INTEGER NOT NULL DEFAULT 5,
-- Price at prediction time
prediction_close REAL,
-- Resolution / forward evaluation
entry_date DATE,
entry_open REAL,
max_close_5d REAL,
evaluation_end_date DATE,
actual_return REAL,
actual_label INTEGER,
resolved_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
-- One prediction per symbol/model/date.
CONSTRAINT uq_predictions_date_symbol_model
UNIQUE (prediction_date, symbol, model_version),
CONSTRAINT predictions_probability_check
CHECK (
predicted_probability >= 0
AND predicted_probability <= 1
),
-- Binary classification target.
CONSTRAINT predictions_actual_label_check
CHECK (actual_label IS NULL OR actual_label IN (0, 1)),
-- Target contract validation.
CONSTRAINT predictions_target_threshold_check
CHECK (target_threshold >= 0),
CONSTRAINT predictions_target_horizon_check
CHECK (target_horizon_days > 0),
-- Rank must be positive.
CONSTRAINT predictions_rank_check
CHECK ("rank" > 0)
);
CREATE INDEX IF NOT EXISTS idx_predictions_date
ON predictions (prediction_date);
CREATE INDEX IF NOT EXISTS idx_predictions_symbol
ON predictions (symbol);
CREATE INDEX IF NOT EXISTS idx_predictions_unresolved
ON predictions (prediction_date, symbol)
WHERE actual_label IS NULL;
CREATE INDEX IF NOT EXISTS idx_predictions_resolution
ON predictions (prediction_date, symbol, actual_label);
CREATE INDEX IF NOT EXISTS idx_predictions_model_date
ON predictions (model_version, prediction_date);
-- ============================================================
-- 2. JOB RUNS
-- ============================================================
CREATE TABLE IF NOT EXISTS job_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_name TEXT NOT NULL,
run_date DATE NOT NULL,
started_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
finished_at TIMESTAMP,
status TEXT NOT NULL
CHECK (
status IN (
'RUNNING',
'SUCCESS',
'FAILED',
'SKIPPED'
)
),
rows_processed INTEGER NOT NULL DEFAULT 0,
total_rows INTEGER NOT NULL DEFAULT 0,
resolved_rows INTEGER NOT NULL DEFAULT 0,
failed_rows INTEGER NOT NULL DEFAULT 0,
last_updated_at TIMESTAMP,
error_message TEXT
);
CREATE INDEX IF NOT EXISTS idx_job_runs_name_date
ON job_runs (job_name, run_date);
-- Cross-process lease locks for singleton SQLite jobs. The token prevents an
-- expired owner from releasing a newer owner's lease.
CREATE TABLE IF NOT EXISTS job_locks (
lock_name TEXT PRIMARY KEY,
owner_token TEXT NOT NULL,
locked_until TIMESTAMP NOT NULL
);
-- ============================================================
-- 3. PREDICTION METRICS
-- ============================================================
CREATE TABLE IF NOT EXISTS prediction_metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
window_start DATE NOT NULL,
window_end DATE NOT NULL,
prediction_days INTEGER NOT NULL,
resolved_predictions INTEGER NOT NULL,
precision REAL,
pr_auc REAL,
hit_rate REAL,
positive_rate REAL,
average_return REAL,
metrics_json TEXT NOT NULL DEFAULT '{}',
calculated_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
model_version TEXT NOT NULL,
CONSTRAINT prediction_metrics_window_uq
UNIQUE (model_version, window_start, window_end),
CONSTRAINT prediction_metrics_window_ck
CHECK (window_start <= window_end),
CONSTRAINT prediction_metrics_prediction_days_ck
CHECK (prediction_days > 0),
CONSTRAINT prediction_metrics_resolved_ck
CHECK (resolved_predictions >= 0)
);
CREATE INDEX IF NOT EXISTS idx_prediction_metrics_window_end
ON prediction_metrics (window_end DESC);
-- ============================================================
-- 4. RETRAIN FLAGS
-- ============================================================
CREATE TABLE IF NOT EXISTS retrain_flags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
model_version TEXT NOT NULL,
triggered_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
metric_window_start DATE NOT NULL,
metric_window_end DATE NOT NULL,
baseline_precision REAL NOT NULL,
current_precision REAL NOT NULL,
deterioration REAL NOT NULL,
deterioration_threshold REAL NOT NULL DEFAULT 0.15,
status TEXT NOT NULL DEFAULT 'PENDING'
CHECK (
status IN (
'PENDING',
'ACKNOWLEDGED',
'RESOLVED',
'IGNORED'
)
),
reason TEXT NOT NULL,
resolved_at TIMESTAMP,
notes TEXT,
CONSTRAINT retrain_flags_window_uq
UNIQUE (model_version, metric_window_start, metric_window_end)
);
CREATE INDEX IF NOT EXISTS idx_retrain_flags_status
ON retrain_flags (status, triggered_at DESC);
-- ============================================================
-- END OF CONSOLIDATED SCHEMA
-- ============================================================
-- ============================================================
-- 5. MARKET DATA REGISTRY AND YFINANCE OHLCV
-- ============================================================
CREATE TABLE IF NOT EXISTS market_tickers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL UNIQUE,
yfinance_symbol TEXT NOT NULL UNIQUE,
market TEXT NOT NULL CHECK (market IN ('NSE', 'US', 'CONTEXT')),
name TEXT,
industry TEXT,
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
updated_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
CREATE TABLE IF NOT EXISTS market_ohlcv (
ticker_id INTEGER NOT NULL REFERENCES market_tickers(id) ON DELETE CASCADE,
trading_date DATE NOT NULL,
open REAL NOT NULL,
high REAL NOT NULL,
low REAL NOT NULL,
close REAL NOT NULL,
volume INTEGER,
source TEXT NOT NULL DEFAULT 'yfinance',
fetched_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
PRIMARY KEY (ticker_id, trading_date)
);
CREATE INDEX IF NOT EXISTS idx_market_ohlcv_ticker_date
ON market_ohlcv (ticker_id, trading_date DESC);
-- ============================================================
-- 6. MARKET DATA BY DATASET
--
-- The legacy market_ohlcv table remains only as a migration source for an
-- existing installation. New reads and writes use these dataset tables.
-- ============================================================
CREATE TABLE IF NOT EXISTS ohlcv (
ticker_id INTEGER NOT NULL REFERENCES market_tickers(id) ON DELETE CASCADE,
trading_date DATE NOT NULL,
open REAL NOT NULL, high REAL NOT NULL, low REAL NOT NULL, close REAL NOT NULL,
volume INTEGER, source TEXT NOT NULL DEFAULT 'yfinance',
fetched_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
PRIMARY KEY (ticker_id, trading_date)
);
CREATE TABLE IF NOT EXISTS nifty_ohlcv (
ticker_id INTEGER NOT NULL REFERENCES market_tickers(id) ON DELETE CASCADE,
trading_date DATE NOT NULL,
open REAL NOT NULL, high REAL NOT NULL, low REAL NOT NULL, close REAL NOT NULL,
volume INTEGER, source TEXT NOT NULL DEFAULT 'yfinance',
fetched_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
PRIMARY KEY (ticker_id, trading_date)
);
CREATE TABLE IF NOT EXISTS india_vix_ohlcv (
ticker_id INTEGER NOT NULL REFERENCES market_tickers(id) ON DELETE CASCADE,
trading_date DATE NOT NULL,
open REAL NOT NULL, high REAL NOT NULL, low REAL NOT NULL, close REAL NOT NULL,
volume INTEGER, source TEXT NOT NULL DEFAULT 'yfinance',
fetched_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
PRIMARY KEY (ticker_id, trading_date)
);
CREATE TABLE IF NOT EXISTS us_stocks_ohlcv (
ticker_id INTEGER NOT NULL REFERENCES market_tickers(id) ON DELETE CASCADE,
trading_date DATE NOT NULL,
open REAL NOT NULL, high REAL NOT NULL, low REAL NOT NULL, close REAL NOT NULL,
volume INTEGER, source TEXT NOT NULL DEFAULT 'yfinance',
fetched_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
PRIMARY KEY (ticker_id, trading_date)
);
CREATE TABLE IF NOT EXISTS macro_ohlcv (
ticker_id INTEGER NOT NULL REFERENCES market_tickers(id) ON DELETE CASCADE,
macro_type TEXT NOT NULL CHECK (macro_type IN ('gold', 'brent_crude_oil', 'usd_inr')),
trading_date DATE NOT NULL,
open REAL NOT NULL, high REAL NOT NULL, low REAL NOT NULL, close REAL NOT NULL,
volume INTEGER, source TEXT NOT NULL DEFAULT 'yfinance',
fetched_at TIMESTAMP NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')),
PRIMARY KEY (ticker_id, trading_date)
);
CREATE VIEW IF NOT EXISTS all_market_ohlcv AS
SELECT ticker_id, trading_date, open, high, low, close, volume, source, fetched_at FROM ohlcv
UNION ALL SELECT ticker_id, trading_date, open, high, low, close, volume, source, fetched_at FROM nifty_ohlcv
UNION ALL SELECT ticker_id, trading_date, open, high, low, close, volume, source, fetched_at FROM india_vix_ohlcv
UNION ALL SELECT ticker_id, trading_date, open, high, low, close, volume, source, fetched_at FROM us_stocks_ohlcv
UNION ALL SELECT ticker_id, trading_date, open, high, low, close, volume, source, fetched_at FROM macro_ohlcv;
-- Copy legacy data exactly once when upgrading an existing database.
INSERT OR IGNORE INTO ohlcv SELECT o.* FROM market_ohlcv o JOIN market_tickers t ON t.id = o.ticker_id WHERE t.market = 'NSE';
INSERT OR IGNORE INTO nifty_ohlcv SELECT o.* FROM market_ohlcv o JOIN market_tickers t ON t.id = o.ticker_id WHERE t.symbol = 'NIFTY';
INSERT OR IGNORE INTO india_vix_ohlcv SELECT o.* FROM market_ohlcv o JOIN market_tickers t ON t.id = o.ticker_id WHERE t.symbol = 'INDIA_VIX';
INSERT OR IGNORE INTO us_stocks_ohlcv SELECT o.* FROM market_ohlcv o JOIN market_tickers t ON t.id = o.ticker_id WHERE t.market = 'US';
INSERT OR IGNORE INTO macro_ohlcv (ticker_id, macro_type, trading_date, open, high, low, close, volume, source, fetched_at)
SELECT o.ticker_id, CASE t.symbol WHEN 'GOLD' THEN 'gold' WHEN 'BRENT' THEN 'brent_crude_oil' WHEN 'USDINR' THEN 'usd_inr' END,
o.trading_date, o.open, o.high, o.low, o.close, o.volume, o.source, o.fetched_at
FROM market_ohlcv o JOIN market_tickers t ON t.id = o.ticker_id WHERE t.symbol IN ('GOLD', 'BRENT', 'USDINR');
-- Derived feature data lives in Parquet beside this database, under
-- data/db/feature_store (or the equivalent directory for DATABASE_URL).