-- Init schema for stock_chart_site
-- Loaded automatically on first DB container start via /docker-entrypoint-initdb.d

\set ON_ERROR_STOP on

CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- 종목 마스터 (Phase 1 에서 KRX 전체 종목 시드. 검색은 name 또는 code 둘 다 지원)
CREATE TABLE IF NOT EXISTS symbols (
  code        TEXT PRIMARY KEY,
  name        TEXT NOT NULL,
  market      TEXT NOT NULL,           -- 'KOSPI' / 'KOSDAQ' / 'NASDAQ'
  sector      TEXT,
  active      BOOLEAN DEFAULT TRUE,
  is_seed     BOOLEAN DEFAULT FALSE,   -- 학습/배치 대상 10종목 여부
  created_at  TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS symbols_name_trgm ON symbols USING gin (name gin_trgm_ops);
CREATE INDEX IF NOT EXISTS symbols_active ON symbols(active);

-- 일별 시세
CREATE TABLE IF NOT EXISTS ohlcv_daily (
  code    TEXT NOT NULL REFERENCES symbols(code),
  date    DATE NOT NULL,
  open    NUMERIC,
  high    NUMERIC,
  low     NUMERIC,
  close   NUMERIC,
  volume  BIGINT,
  PRIMARY KEY (code, date)
);
SELECT create_hypertable('ohlcv_daily', 'date', if_not_exists => TRUE);
CREATE INDEX IF NOT EXISTS ohlcv_daily_code_date ON ohlcv_daily(code, date DESC);

-- 분봉 (M8 인트라데이용, 스키마만 미리 둔다)
CREATE TABLE IF NOT EXISTS ohlcv_1m (
  code    TEXT NOT NULL,
  ts      TIMESTAMPTZ NOT NULL,
  open    NUMERIC,
  high    NUMERIC,
  low     NUMERIC,
  close   NUMERIC,
  volume  BIGINT,
  PRIMARY KEY (code, ts)
);
SELECT create_hypertable('ohlcv_1m', 'ts', if_not_exists => TRUE);

-- 거시 / 환율 / 지수
CREATE TABLE IF NOT EXISTS macro_daily (
  date  DATE NOT NULL,
  key   TEXT NOT NULL,                 -- 'kospi','kosdaq','usdkrw','us10y',...
  value NUMERIC,
  PRIMARY KEY (date, key)
);

-- 외인 / 기관 순매수 (KRW 기준 거래대금)
CREATE TABLE IF NOT EXISTS trading_value_daily (
  code      TEXT NOT NULL REFERENCES symbols(code),
  date      DATE NOT NULL,
  foreign_net   NUMERIC,
  institution_net NUMERIC,
  individual_net  NUMERIC,
  PRIMARY KEY (code, date)
);

-- 뉴스 / 공시
CREATE TABLE IF NOT EXISTS news (
  id              BIGSERIAL PRIMARY KEY,
  code            TEXT REFERENCES symbols(code),
  source          TEXT NOT NULL,        -- 'naver_finance' / 'dart' / 'google_rss'
  published_at    TIMESTAMPTZ NOT NULL,
  title           TEXT NOT NULL,
  url             TEXT NOT NULL UNIQUE,
  body            TEXT,
  sentiment_score REAL,                 -- KR-FinBERT 출력 -1..+1
  sentiment_label TEXT,                 -- 'positive' / 'neutral' / 'negative'
  embedding       VECTOR(768),
  created_at      TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS news_code_pub ON news(code, published_at DESC);
CREATE INDEX IF NOT EXISTS news_pub ON news(published_at DESC);

-- 모델 예측 이력
-- user_triggered=TRUE 인 행만 다음날 outcomes 매칭/오차수정 학습에 사용
CREATE TABLE IF NOT EXISTS predictions (
  id              BIGSERIAL PRIMARY KEY,
  code            TEXT NOT NULL REFERENCES symbols(code),
  predicted_at    TIMESTAMPTZ NOT NULL,
  base_date       DATE NOT NULL,        -- 예측 기준일(=마지막 관측일)
  target_date     DATE NOT NULL,
  horizon         INT NOT NULL,         -- 1, 3, 5
  model           TEXT NOT NULL,        -- 'chronos2' / 'lgbm' / 'ensemble'
  direction       TEXT,                 -- 'up' / 'flat' / 'down'
  prob_up         REAL,
  prob_flat       REAL,
  prob_down       REAL,
  expected_return REAL,
  point_forecast  NUMERIC,              -- median 가격
  ci_low          NUMERIC,              -- quantile 10
  ci_high         NUMERIC,              -- quantile 90
  features_snapshot JSONB,
  user_triggered  BOOLEAN NOT NULL DEFAULT FALSE,
  created_at      TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE (code, base_date, target_date, horizon, model)
);
CREATE INDEX IF NOT EXISTS predictions_code_target ON predictions(code, target_date DESC);
CREATE INDEX IF NOT EXISTS predictions_user_triggered ON predictions(user_triggered) WHERE user_triggered = TRUE;

-- 예측 vs 실제 결과 (오차 수정 / 메트릭 / 가중치 튜닝의 입력)
CREATE TABLE IF NOT EXISTS prediction_outcomes (
  prediction_id   BIGINT PRIMARY KEY REFERENCES predictions(id) ON DELETE CASCADE,
  code            TEXT NOT NULL REFERENCES symbols(code),
  target_date     DATE NOT NULL,
  horizon         INT NOT NULL,
  model           TEXT NOT NULL,
  predicted_close NUMERIC,
  actual_close    NUMERIC,
  actual_return   REAL,
  direction_hit   BOOLEAN,              -- 방향성 적중 여부
  abs_error       REAL,
  resolved_at     TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS po_code_target ON prediction_outcomes(code, target_date DESC);
CREATE INDEX IF NOT EXISTS po_model ON prediction_outcomes(model);

-- 모델별 롤링 성능 (앙상블 가중치 튜닝에 사용)
CREATE TABLE IF NOT EXISTS model_performance (
  code            TEXT NOT NULL REFERENCES symbols(code),
  model           TEXT NOT NULL,
  window_days     INT NOT NULL,         -- 7, 30 등
  as_of           DATE NOT NULL,
  hit_rate        REAL,
  mae             REAL,
  brier           REAL,
  sample_count    INT,
  PRIMARY KEY (code, model, window_days, as_of)
);
