4cb206a269
매수/매도 주문 및 계좌 잔고조회를 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)
116 lines
3.3 KiB
Python
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())
|