- docker-compose.yml: timescaledb-ha (timescaledb 2.27 + vectorscale + pgvector + pgai) + backend (FastAPI, CUDA 12.1) + web (Next.js 14) - docker-compose.gpu.yml: GPU profile overlay for RTX 3070 Ti - build.bat: Windows bootstrap, auto-detects nvidia-smi and selects GPU/CPU compose - backend: Dockerfile, pyproject.toml, FastAPI skeleton with /health and /health/db - DB migration 001_init.sql: symbols (with trigram search), ohlcv_daily/1m (hypertables), macro_daily, trading_value_daily, news (vector embedding), predictions (with user_triggered flag for on-demand UX), prediction_outcomes, model_performance - web: Next.js 14 + Tailwind + lightweight-charts placeholder - README: KIS/DART/HuggingFace token issuance guides + 10 seed tickers + run instructions
140 lines
5.2 KiB
SQL
140 lines
5.2 KiB
SQL
-- 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)
|
|
);
|