-- 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) );