CREATE EXTENSION IF NOT EXISTS timescaledb; CREATE TABLE IF NOT EXISTS venues ( id TEXT PRIMARY KEY, name TEXT NOT NULL, city TEXT NOT NULL, country TEXT NOT NULL, altitude_meters INTEGER, timezone TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS teams ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, fifa_rank INTEGER, current_elo_rating DOUBLE PRECISION, group_name TEXT ); CREATE TABLE IF NOT EXISTS bookmakers ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS matches ( id TEXT PRIMARY KEY, home_team_id TEXT NOT NULL REFERENCES teams (id), away_team_id TEXT NOT NULL REFERENCES teams (id), venue_id TEXT NOT NULL REFERENCES venues (id), match_time_utc TIMESTAMPTZ NOT NULL, status TEXT NOT NULL DEFAULT 'pre-match', home_xg DOUBLE PRECISION, away_xg DOUBLE PRECISION ); CREATE TABLE IF NOT EXISTS odds_history ( id BIGSERIAL PRIMARY KEY, match_id TEXT NOT NULL REFERENCES matches (id), bookmaker_id TEXT NOT NULL REFERENCES bookmakers (id), market_type TEXT NOT NULL, selection TEXT NOT NULL, decimal_odds DOUBLE PRECISION NOT NULL, implied_probability DOUBLE PRECISION NOT NULL, recorded_at TIMESTAMPTZ NOT NULL ); CREATE TABLE IF NOT EXISTS smart_money_flow ( id BIGSERIAL PRIMARY KEY, match_id TEXT NOT NULL REFERENCES matches (id), market_type TEXT NOT NULL, selection TEXT NOT NULL, ticket_pct DOUBLE PRECISION NOT NULL, handle_pct DOUBLE PRECISION NOT NULL, sharp_indicator BOOLEAN NOT NULL, recorded_at TIMESTAMPTZ NOT NULL ); CREATE INDEX IF NOT EXISTS idx_matches_time_status ON matches (match_time_utc, status); CREATE INDEX IF NOT EXISTS idx_odds_match_market_recorded_at ON odds_history (match_id, market_type, recorded_at DESC); CREATE INDEX IF NOT EXISTS idx_money_flow_match_recorded_at ON smart_money_flow (match_id, market_type, recorded_at DESC); CREATE TABLE IF NOT EXISTS value_bet_recommendations ( id TEXT PRIMARY KEY, match_id TEXT NOT NULL REFERENCES matches (id), market_type TEXT NOT NULL, selection TEXT NOT NULL, stake DOUBLE PRECISION NOT NULL, recommended_odds DOUBLE PRECISION NOT NULL, closing_odds DOUBLE PRECISION, is_win BOOLEAN NOT NULL DEFAULT FALSE, settled_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), clv_ratio DOUBLE PRECISION, pnl DOUBLE PRECISION NOT NULL DEFAULT 0, note TEXT ); CREATE INDEX IF NOT EXISTS idx_value_bet_recommendations_match_time ON value_bet_recommendations (match_id, settled_at DESC); SELECT create_hypertable( 'odds_history', 'recorded_at', chunk_time_interval => INTERVAL '1 hour', if_not_exists => TRUE ); CREATE INDEX IF NOT EXISTS idx_odds_history_time_gist ON odds_history USING GIST (recorded_at); -- Stage 33: Affiliate Marketing CREATE TABLE IF NOT EXISTS affiliate_bookmakers ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, tracking_url TEXT NOT NULL, commission_rate DOUBLE PRECISION NOT NULL DEFAULT 0.0 ); CREATE TABLE IF NOT EXISTS affiliate_clicks ( id BIGSERIAL PRIMARY KEY, bookmaker_id TEXT NOT NULL REFERENCES affiliate_bookmakers (id), user_ip_hash TEXT NOT NULL, user_agent TEXT, referrer TEXT, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), converted BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS idx_affiliate_clicks_timestamp ON affiliate_clicks (timestamp DESC); SELECT create_hypertable( 'affiliate_clicks', 'timestamp', chunk_time_interval => INTERVAL '1 day', if_not_exists => TRUE ); -- Note: TimescaleDB continuous aggregates require a bit more setup in modern versions. -- A simple materialized view for daily conversion rates: CREATE MATERIALIZED VIEW IF NOT EXISTS affiliate_daily_conversions WITH (timescaledb.continuous) AS SELECT time_bucket('1 day', timestamp) AS bucket, bookmaker_id, COUNT(*) AS total_clicks, COUNT(*) FILTER (WHERE converted = TRUE) AS total_conversions FROM affiliate_clicks GROUP BY bucket, bookmaker_id WITH NO DATA; -- Stage 35: Social Trading (Copy Bets & Leaderboard) CREATE TABLE IF NOT EXISTS user_profiles ( id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, clv_score DOUBLE PRECISION NOT NULL DEFAULT 0.0, roi_30d DOUBLE PRECISION NOT NULL DEFAULT 0.0, sharp_rating INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS copy_bets ( id BIGSERIAL PRIMARY KEY, follower_id TEXT NOT NULL REFERENCES user_profiles(id), leader_id TEXT NOT NULL REFERENCES user_profiles(id), recommendation_id TEXT NOT NULL REFERENCES value_bet_recommendations(id), follower_stake DOUBLE PRECISION NOT NULL, copied_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_copy_bets_leader_time ON copy_bets (leader_id, copied_at DESC);