154 lines
4.6 KiB
SQL
154 lines
4.6 KiB
SQL
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);
|