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)
111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from src.quant_engine.data_collection_store_v1 import (
|
|
CollectionRun,
|
|
append_collection_error,
|
|
fetch_latest_snapshots,
|
|
init_db,
|
|
iter_recent_snapshots,
|
|
upsert_collection_run,
|
|
upsert_collection_snapshot,
|
|
)
|
|
from src.quant_engine.data_collection_backend_v1 import CollectionStoreSpec, normalize_store_spec
|
|
|
|
|
|
def test_store_writes_and_reads_snapshots(tmp_path):
|
|
db_path = tmp_path / "collector.db"
|
|
init_db(db_path)
|
|
upsert_collection_run(
|
|
db_path,
|
|
CollectionRun(
|
|
run_id="run-1",
|
|
collector_name="collector",
|
|
started_at="2026-06-21T12:00:00+09:00",
|
|
status="RUNNING",
|
|
input_source="GatherTradingData.json",
|
|
output_json_path="Temp/kis_data_collection_v1.json",
|
|
output_db_path=str(db_path),
|
|
),
|
|
)
|
|
upsert_collection_snapshot(
|
|
db_path,
|
|
run_id="run-1",
|
|
dataset_name="data_feed",
|
|
ticker="005930",
|
|
name="삼성전자",
|
|
sector="반도체",
|
|
as_of_date="2026-06-21",
|
|
source_priority="kis_open_api>gathertradingdata_json",
|
|
source_status="OK",
|
|
payload={"ticker": "005930", "close": 1000},
|
|
provenance={"kis": {"status": "OK"}},
|
|
)
|
|
append_collection_error(
|
|
db_path,
|
|
run_id="run-1",
|
|
source_name="kis",
|
|
error_kind="TimeoutError",
|
|
error_message="timeout",
|
|
ticker="005930",
|
|
)
|
|
|
|
conn = sqlite3.connect(db_path)
|
|
try:
|
|
run_count = conn.execute("SELECT COUNT(*) FROM collection_runs").fetchone()[0]
|
|
snap_count = conn.execute("SELECT COUNT(*) FROM collection_snapshots").fetchone()[0]
|
|
err_count = conn.execute("SELECT COUNT(*) FROM collection_source_errors").fetchone()[0]
|
|
finally:
|
|
conn.close()
|
|
|
|
assert run_count == 1
|
|
assert snap_count == 1
|
|
assert err_count == 1
|
|
assert fetch_latest_snapshots(db_path, "005930")[0]["dataset_name"] == "data_feed"
|
|
assert len(list(iter_recent_snapshots(db_path, limit=5))) == 1
|
|
|
|
|
|
def test_store_overwrites_same_run_and_ticker(tmp_path):
|
|
db_path = tmp_path / "collector.db"
|
|
upsert_collection_snapshot(
|
|
db_path,
|
|
run_id="run-1",
|
|
dataset_name="data_feed",
|
|
ticker="005930",
|
|
name="삼성전자",
|
|
sector="반도체",
|
|
as_of_date="2026-06-21",
|
|
source_priority="kis_open_api",
|
|
source_status="OK",
|
|
payload={"ticker": "005930", "close": 1000},
|
|
provenance={"source_priority": ["kis_open_api"]},
|
|
)
|
|
upsert_collection_snapshot(
|
|
db_path,
|
|
run_id="run-1",
|
|
dataset_name="data_feed",
|
|
ticker="005930",
|
|
name="삼성전자",
|
|
sector="반도체",
|
|
as_of_date="2026-06-21",
|
|
source_priority="kis_open_api>naver_finance",
|
|
source_status="OK",
|
|
payload={"ticker": "005930", "close": 2000},
|
|
provenance={"source_priority": ["kis_open_api", "naver_finance"]},
|
|
)
|
|
rows = fetch_latest_snapshots(db_path, "005930")
|
|
assert rows[0]["source_priority"] == "kis_open_api>naver_finance"
|
|
|
|
|
|
def test_store_backend_normalization_supports_sqlite_paths(tmp_path):
|
|
backend, location = normalize_store_spec(CollectionStoreSpec(location=tmp_path / "collector.db"), ROOT)
|
|
assert backend == "sqlite"
|
|
assert str(location).endswith("collector.db")
|