-- ===================================================================== -- sg-eqdp-scanner — DuckDB schema (Streamlit + DuckDB-in-repo edition) -- -- Apply on first run via `python -m scripts.init_duckdb`. Idempotent — -- every CREATE uses IF NOT EXISTS. Identical column names + types to the -- old Supabase Postgres schema, with the following intentional drops: -- - auth.users / user_profiles / engagement_events (no auth in v2) -- - row-level security and policies (irrelevant in DuckDB) -- - GENERATED ALWAYS AS (...) STORED columns (DuckDB doesn't support; -- compute in Python) -- - PostgreSQL-only types (uuid_generate_v4, jsonb) (use UUID, JSON) -- ===================================================================== -- DuckDB has UUID as a built-in type; pipelines pass UUID strings produced -- via Python's uuid.uuid4(), so no extension is required. -- --------------------------------------------------------------------- -- Operations -- --------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS pipeline_runs ( run_id UUID PRIMARY KEY DEFAULT uuid(), job_name TEXT NOT NULL, started_at TIMESTAMPTZ NOT NULL DEFAULT now(), completed_at TIMESTAMPTZ, status TEXT NOT NULL CHECK (status IN ('running','succeeded','partial','failed')), metrics_json JSON, error_message TEXT ); CREATE INDEX IF NOT EXISTS pipeline_runs_job_status_idx ON pipeline_runs(job_name, status, started_at DESC); -- --------------------------------------------------------------------- -- Universe -- --------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS tickers ( ticker TEXT PRIMARY KEY, sgx_code TEXT, name TEXT NOT NULL, sector TEXT, market_cap_band TEXT CHECK (market_cap_band IN ('large','mid','small','micro')), listing_board TEXT CHECK (listing_board IN ('Mainboard','Catalist')), in_sti30 BOOLEAN DEFAULT false, in_next50 BOOLEAN DEFAULT false, restricted_sector BOOLEAN DEFAULT false, t1_flag BOOLEAN DEFAULT false, t2_flag BOOLEAN DEFAULT false, t3_flag BOOLEAN DEFAULT false, broker_named_count INTEGER DEFAULT 0, first_seen DATE, last_seen DATE, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID ); CREATE TABLE IF NOT EXISTS eqdp_managers ( manager_id TEXT PRIMARY KEY, canonical_name TEXT NOT NULL, aliases TEXT[] DEFAULT [], tranche INTEGER CHECK (tranche IN (1,2)), appointed_date DATE, created_at TIMESTAMPTZ DEFAULT now() ); -- --------------------------------------------------------------------- -- Prices and factors -- --------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS prices_daily ( ticker TEXT NOT NULL, trade_date DATE NOT NULL, open DOUBLE, high DOUBLE, low DOUBLE, close DOUBLE, adj_close DOUBLE, volume BIGINT, -- dollar_volume was a generated column in Postgres; pipelines compute it. dollar_volume DOUBLE, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID, PRIMARY KEY (ticker, trade_date) ); CREATE INDEX IF NOT EXISTS prices_daily_date_idx ON prices_daily(trade_date DESC); CREATE TABLE IF NOT EXISTS factor_returns ( trade_date DATE PRIMARY KEY, market DOUBLE NOT NULL, smb DOUBLE, hml DOUBLE, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID ); CREATE TABLE IF NOT EXISTS betas ( ticker TEXT NOT NULL, window_end DATE NOT NULL, window_days INTEGER NOT NULL, alpha DOUBLE NOT NULL, beta DOUBLE NOT NULL, r_squared DOUBLE, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID, PRIMARY KEY (ticker, window_end) ); -- --------------------------------------------------------------------- -- Impact estimation -- --------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS abnormal_returns ( ticker TEXT NOT NULL, event_id TEXT NOT NULL, t INTEGER NOT NULL, benchmark TEXT NOT NULL CHECK (benchmark IN ('capm','ff3','market_adjusted')), ar DOUBLE, car DOUBLE, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID, PRIMARY KEY (ticker, event_id, t, benchmark) ); CREATE INDEX IF NOT EXISTS abnormal_returns_event_idx ON abnormal_returns(event_id, benchmark, t); CREATE TABLE IF NOT EXISTS liquidity_metrics ( ticker TEXT NOT NULL, trade_date DATE NOT NULL, amihud DOUBLE, amihud_60d DOUBLE, turnover_velocity DOUBLE, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID, PRIMARY KEY (ticker, trade_date) ); CREATE INDEX IF NOT EXISTS liquidity_metrics_date_idx ON liquidity_metrics(trade_date DESC); CREATE TABLE IF NOT EXISTS synthetic_controls ( ticker TEXT NOT NULL, event_id TEXT NOT NULL, t INTEGER NOT NULL, observed DOUBLE, counterfactual DOUBLE, ci_lower DOUBLE, ci_upper DOUBLE, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID, PRIMARY KEY (ticker, event_id, t) ); CREATE TABLE IF NOT EXISTS placebo_results ( placebo_date DATE NOT NULL, panel TEXT NOT NULL, metric TEXT NOT NULL, value DOUBLE, p_value DOUBLE, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID, PRIMARY KEY (placebo_date, panel, metric) ); CREATE TABLE IF NOT EXISTS bootstrap_cis ( metric TEXT NOT NULL, scope TEXT NOT NULL, point_estimate DOUBLE, lower_5 DOUBLE, upper_95 DOUBLE, n_replications INTEGER, computed_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID, PRIMARY KEY (metric, scope) ); -- --------------------------------------------------------------------- -- Candidate score (no return inputs) -- --------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS candidate_scores ( ticker TEXT NOT NULL, score_date DATE NOT NULL, liquidity_rise DOUBLE, institutional_proxy DOUBLE, index_inclusion INTEGER CHECK (index_inclusion IN (0,1)), broker_named DOUBLE, filing_present INTEGER CHECK (filing_present IN (0,1)), total_score DOUBLE, eqdp_tier TEXT CHECK (eqdp_tier IN ('T1','T2','T3','control','none')), created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID, PRIMARY KEY (ticker, score_date) ); CREATE INDEX IF NOT EXISTS candidate_scores_latest_idx ON candidate_scores(score_date DESC, total_score DESC); -- --------------------------------------------------------------------- -- T1 ground truth — SGXNet substantial-shareholder filings -- --------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS filings_t1 ( filing_id TEXT PRIMARY KEY, manager_id TEXT NOT NULL, ticker TEXT NOT NULL, effective_date DATE NOT NULL, filing_date DATE NOT NULL, stake_pct DOUBLE, direction TEXT CHECK (direction IN ('acquired','disposed','crossed_up','crossed_down')), source_url TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT now(), pipeline_run_id UUID ); CREATE INDEX IF NOT EXISTS filings_t1_filing_date_idx ON filings_t1(filing_date DESC); CREATE INDEX IF NOT EXISTS filings_t1_ticker_idx ON filings_t1(ticker, filing_date DESC); -- ===================================================================== -- End of schema. user_profiles, engagement_events, and RLS policies are -- intentionally absent — there is no auth in the DuckDB edition. -- =====================================================================