Files
QuantEngineByItz/tools/generate_postgresql_upgrade_stub_v1.py
kjh2064 4cb206a269 KIS Open API 조회전용 연동 + 직접매매 절대금지 안전게이트
매수/매도 주문 및 계좌 잔고조회를 API로 직접 실행하지 않는다는 원칙을
코드 레벨에서 강제하는 안전게이트(governance/rules/06, 07)와 함께,
시세/호가/공매도거래비중 등 조회전용 KIS Open API 연동 및 SQLite
수집 파이프라인을 추가한다.

- kis_api_client_v1: 모든 요청이 _assert_read_only를 통과해야 하며
  /trading/ 경로·주문 TR_ID는 RuntimeError로 즉시 차단
- kis_data_collection_v1: KIS 우선 + Naver 폴백, 네트워크 실패는
  개별 ticker 단위로 흡수(배치 전체 중단 없음)
- data_collection_store_v1 / storage_backend_v1: SQLite 캐노니컬
  저장소, PostgreSQL 전환 대비 백엔드 추상화
- Gitea 영업일 스케줄(2시간 간격) + CI 강제 게이트
  (validate_no_direct_api_trading_v1, validate_kis_api_credentials_v1)
2026-06-21 20:04:44 +09:00

116 lines
3.3 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
TABLE_SCHEMAS: dict[str, str] = {
"collection_runs": """
CREATE TABLE collection_runs (
run_id TEXT PRIMARY KEY,
collector_name TEXT NOT NULL,
started_at TEXT NOT NULL,
finished_at TEXT,
status TEXT NOT NULL,
input_source TEXT,
output_json_path TEXT,
output_db_path TEXT,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
""".strip(),
"collection_snapshots": """
CREATE TABLE collection_snapshots (
run_id TEXT NOT NULL,
dataset_name TEXT NOT NULL,
ticker TEXT NOT NULL,
name TEXT,
sector TEXT,
as_of_date TEXT,
source_priority TEXT,
source_status TEXT,
payload_json TEXT NOT NULL,
provenance_json TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (run_id, dataset_name, ticker)
);
""".strip(),
"collection_source_errors": """
CREATE TABLE collection_source_errors (
run_id TEXT NOT NULL,
ticker TEXT,
source_name TEXT NOT NULL,
error_kind TEXT NOT NULL,
error_message TEXT NOT NULL,
payload_json TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
""".strip(),
"sell_strategy_results": """
CREATE TABLE sell_strategy_results (
id BIGSERIAL PRIMARY KEY,
code TEXT NOT NULL,
generated_at TEXT NOT NULL,
action TEXT,
conviction TEXT,
market_regime TEXT,
composite_score DOUBLE PRECISION,
rationale TEXT,
raw_json TEXT NOT NULL,
inserted_at TIMESTAMPTZ DEFAULT NOW()
);
""".strip(),
"satellite_recommendations": """
CREATE TABLE satellite_recommendations (
id BIGSERIAL PRIMARY KEY,
ticker TEXT NOT NULL,
generated_at TEXT NOT NULL,
satellite_action TEXT,
attractiveness_score DOUBLE PRECISION,
market_regime TEXT,
raw_json TEXT NOT NULL,
inserted_at TIMESTAMPTZ DEFAULT NOW()
);
""".strip(),
}
def main() -> int:
ap = argparse.ArgumentParser(description="Emit PostgreSQL migration stub from current canonical row contract.")
ap.add_argument("--output-json", type=Path, default=ROOT / "Temp" / "postgresql_upgrade_stub_v1.json")
ap.add_argument("--output-sql", type=Path, default=ROOT / "Temp" / "postgresql_upgrade_stub_v1.sql")
args = ap.parse_args()
sql_lines = [
"-- PostgreSQL upgrade stub",
"-- This file is a contract placeholder only. It is not executed by CI.",
"",
]
for name, ddl in TABLE_SCHEMAS.items():
sql_lines.append(f"-- {name}")
sql_lines.append(ddl)
sql_lines.append("")
sql_text = "\n".join(sql_lines).rstrip() + "\n"
args.output_sql.parent.mkdir(parents=True, exist_ok=True)
args.output_sql.write_text(sql_text, encoding="utf-8")
payload: dict[str, Any] = {
"formula_id": "POSTGRESQL_UPGRADE_STUB_V1",
"gate": "DATA_GATED",
"tables": sorted(TABLE_SCHEMAS.keys()),
"output_sql": str(args.output_sql),
"note": "DDL stub only; execution deferred until PostgreSQL rollout.",
}
args.output_json.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(payload, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())