# Phase 1 DB 스키마 ## 1. 설계 원칙 - PostgreSQL은 **운영 상태와 canonical metadata**를 담당합니다. - 대량 시계열과 연구용 매트릭스는 **Parquet/DuckDB**로 분리합니다. - 각 테이블은 가능한 한 **append-safe + upsert-safe** 하게 설계합니다. - 모든 핵심 테이블은 `created_at_utc`, `updated_at_utc`를 가집니다. - 모든 외부 문서는 source checksum을 가집니다. ## 2. PostgreSQL 핵심 테이블 ### 2.1 source_registry 외부 데이터 원천 정의 | column | type | note | |---|---|---| | source_name | text PK | `sec`, `alpaca`, `fred`, `finra` | | source_type | text | filing, market_data, macro, short_volume | | enabled | boolean | 사용 여부 | | config_json | jsonb | adapter 설정 | | created_at_utc | timestamptz | 생성 시각 | | updated_at_utc | timestamptz | 수정 시각 | ### 2.2 issuer_master 발행사 마스터 | column | type | note | |---|---|---| | issuer_id | text PK | 내부 발행사 ID | | cik | text unique | SEC CIK | | ticker | text | 대표 ticker | | issuer_name | text | 회사명 | | exchange | text | 거래소 | | country_code | text | 국가 | | is_active | boolean | 활성 여부 | | created_at_utc | timestamptz | | | updated_at_utc | timestamptz | | ### 2.3 symbol_master 종목 마스터 | column | type | note | |---|---|---| | symbol_id | text PK | 내부 종목 ID | | issuer_id | text FK | issuer_master | | ticker | text | 심볼 | | venue | text | 거래 venue | | asset_type | text | common_stock 등 | | currency | text | 통화 | | start_date | date | 유효 시작 | | end_date | date nullable | 유효 종료 | | is_primary | boolean | 대표 종목 여부 | | created_at_utc | timestamptz | | | updated_at_utc | timestamptz | | ### 2.4 job_runs 배치/수집 실행 로그 | column | type | note | |---|---|---| | job_run_id | uuid PK | 실행 ID | | job_name | text | job 이름 | | source_name | text | source | | run_date | date | 기준 일자 | | status | text | pending/running/succeeded/failed/partial | | started_at_utc | timestamptz | 시작 시각 | | finished_at_utc | timestamptz nullable | 종료 시각 | | records_seen | integer | 관측 수 | | records_written | integer | 적재 수 | | records_skipped | integer | 스킵 수 | | error_count | integer | 에러 수 | | error_summary | text nullable | 요약 | | metadata_json | jsonb | 기타 메타 | ### 2.5 raw_objects 원문 보관 메타데이터 | column | type | note | |---|---|---| | raw_object_id | text PK | 내부 원문 ID | | source_name | text | source | | source_object_key | text | 원천 고유 키 | | storage_path | text | raw 파일 경로 | | content_type | text | mime type | | checksum_sha256 | text | 체크섬 | | collected_at_utc | timestamptz | 수집 시각 | | published_at_utc | timestamptz nullable | 원천 게시 시각 | | metadata_json | jsonb | sidecar metadata | | created_at_utc | timestamptz | | ### 2.6 documents 정규화된 문서 메타 | column | type | note | |---|---|---| | document_id | text PK | 내부 문서 ID | | source_name | text | 보통 `sec` | | issuer_id | text FK | | | symbol_id | text nullable FK | | | accession_no | text nullable | SEC accession | | form_type | text | 8-K, 10-Q 등 | | filing_date | date | filing date | | accepted_at_utc | timestamptz nullable | SEC acceptance time | | primary_document_name | text nullable | primary doc | | raw_object_id | text FK | 원문 참조 | | text_path | text nullable | 추출 텍스트 경로 | | html_path | text nullable | html 경로 | | parsed_status | text | pending/succeeded/failed | | created_at_utc | timestamptz | | | updated_at_utc | timestamptz | | 고유 제약: - `(source_name, accession_no, primary_document_name)` unique ### 2.7 document_exhibits 부속 문서 목록 | column | type | note | |---|---|---| | exhibit_id | text PK | 내부 exhibit ID | | document_id | text FK | | | exhibit_code | text | EX-99.1 등 | | exhibit_name | text | 파일명 | | raw_object_id | text FK | | | text_path | text nullable | | | html_path | text nullable | | | created_at_utc | timestamptz | | ### 2.8 events 문서에서 추출된 이벤트 단위 | column | type | note | |---|---|---| | event_id | text PK | 내부 이벤트 ID | | issuer_id | text FK | | | symbol_id | text nullable FK | | | primary_document_id | text FK | | | event_type | text | earnings_release 등 | | event_direction | text | bullish/bearish/mixed/unknown | | event_date | date | 거래 로직 기준 일자 | | filed_at_utc | timestamptz nullable | filing/published time | | parser_version | text | parser 버전 | | parse_confidence | numeric | 0~1 | | status | text | pending/valid/rejected | | created_at_utc | timestamptz | | | updated_at_utc | timestamptz | | ### 2.9 event_parses 파서 원출력 저장 | column | type | note | |---|---|---| | event_parse_id | bigserial PK | | | event_id | text FK | | | parser_kind | text | rule / llm / merged | | parser_version | text | | | schema_version | text | | | output_json | jsonb | full parser output | | validation_status | text | valid/invalid | | validation_errors | jsonb nullable | | | created_at_utc | timestamptz | | ### 2.10 market_bars_daily 일봉 canonical 저장 | column | type | note | |---|---|---| | symbol_id | text FK | | | trade_date | date | | | open | numeric | | | high | numeric | | | low | numeric | | | close | numeric | | | volume | bigint | | | vwap | numeric nullable | | | trade_count | bigint nullable | | | source_name | text | | | created_at_utc | timestamptz | | | updated_at_utc | timestamptz | | PK: - `(symbol_id, trade_date, source_name)` ### 2.11 market_bars_intraday 분봉 canonical 저장 | column | type | note | |---|---|---| | symbol_id | text FK | | | bar_start_utc | timestamptz | | | timeframe | text | 1Min, 5Min 등 | | open | numeric | | | high | numeric | | | low | numeric | | | close | numeric | | | volume | bigint | | | vwap | numeric nullable | | | trade_count | bigint nullable | | | source_name | text | | | created_at_utc | timestamptz | | PK: - `(symbol_id, bar_start_utc, timeframe, source_name)` ### 2.12 macro_series 거시 시계열 메타 | column | type | note | |---|---|---| | series_id | text PK | FRED series_id | | title | text | | | frequency | text | | | units | text | | | source_name | text | | | metadata_json | jsonb | | | created_at_utc | timestamptz | | ### 2.13 macro_observations 거시 시계열 값 | column | type | note | |---|---|---| | series_id | text FK | | | observation_date | date | | | value | numeric nullable | | | created_at_utc | timestamptz | | PK: - `(series_id, observation_date)` ### 2.14 short_sale_daily FINRA short sale volume 정규화 | column | type | note | |---|---|---| | symbol_id | text nullable FK | mapping 실패 가능 | | ticker_raw | text | 원본 심볼 | | trade_date | date | | | short_volume | bigint | | | short_exempt_volume | bigint nullable | | | total_volume | bigint nullable | | | source_name | text | | | created_at_utc | timestamptz | | PK: - `(ticker_raw, trade_date, source_name)` ### 2.15 feature_snapshots 이벤트 시점 특징 저장 | column | type | note | |---|---|---| | feature_snapshot_id | bigserial PK | | | event_id | text FK | | | snapshot_name | text | market_v1, event_v1 등 | | snapshot_version | text | | | feature_json | jsonb | | | created_at_utc | timestamptz | | ### 2.16 order_plans Phase 1에서는 계획만 정의 | column | type | note | |---|---|---| | order_plan_id | bigserial PK | | | event_id | text FK | | | symbol_id | text FK | | | side | text | buy/sell | | planned_entry_date | date | | | planned_order_type | text | market/limit | | planned_price | numeric nullable | | | stop_price | numeric nullable | | | take_profit_price | numeric nullable | | | quantity_plan | numeric nullable | | | status | text | draft/ready/canceled | | created_at_utc | timestamptz | | ## 3. 권장 인덱스 - `documents(issuer_id, filing_date desc)` - `documents(form_type, filing_date desc)` - `events(event_type, event_date desc)` - `events(primary_document_id)` - `market_bars_daily(symbol_id, trade_date desc)` - `market_bars_intraday(symbol_id, timeframe, bar_start_utc desc)` - `short_sale_daily(ticker_raw, trade_date desc)` - `job_runs(job_name, run_date desc)` ## 4. DuckDB / Parquet 권장 구조 ```text data/parquet/ market/daily/year=2026/month=03/*.parquet market/intraday/date=2026-03-12/*.parquet events/year=2026/month=03/*.parquet features/version=v1/date=2026-03-12/*.parquet labels/horizon=3d/*.parquet ``` ## 5. SQL DDL 초안 ```sql create table if not exists source_registry ( source_name text primary key, source_type text not null, enabled boolean not null default true, config_json jsonb not null default '{}'::jsonb, created_at_utc timestamptz not null default now(), updated_at_utc timestamptz not null default now() ); create table if not exists issuer_master ( issuer_id text primary key, cik text unique, ticker text, issuer_name text not null, exchange text, country_code text, is_active boolean not null default true, created_at_utc timestamptz not null default now(), updated_at_utc timestamptz not null default now() ); create table if not exists symbol_master ( symbol_id text primary key, issuer_id text references issuer_master(issuer_id), ticker text not null, venue text, asset_type text, currency text, start_date date, end_date date, is_primary boolean not null default true, created_at_utc timestamptz not null default now(), updated_at_utc timestamptz not null default now() ); create table if not exists job_runs ( job_run_id uuid primary key, job_name text not null, source_name text, run_date date, status text not null, started_at_utc timestamptz not null default now(), finished_at_utc timestamptz, records_seen integer not null default 0, records_written integer not null default 0, records_skipped integer not null default 0, error_count integer not null default 0, error_summary text, metadata_json jsonb not null default '{}'::jsonb ); ``` 나머지 DDL은 migration 파일에서 분리 관리합니다. ## 6. 스키마 검증 체크포인트 - 동일 accession 문서 중복 적재가 없어야 합니다. - 동일 symbol/date/day-bar 중복 적재가 없어야 합니다. - parser output은 event와 별도 테이블에 버전별 보존되어야 합니다. - raw_object가 없으면 documents를 만들 수 없어야 합니다. - issuer/symbol 매핑 실패는 nullable 허용하되 경고 로그를 남겨야 합니다.