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)
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user