WBS-7.8: ETF NAV/공매도 잔고율 자동화 실측 + 운영절차 문서화

"자동화가 안 되면 차후 개선 목표로" 라는 지시에 따라 추정이 아니라
실제로 pykrx(이미 EOD 가격조회에 쓰이는 동일 라이브러리)의
get_shorting_balance()/get_etf_price_deviation()/get_etf_tracking_error()를
호출해 재시도했다. 기본 시세조회는 정상 작동하지만 이 세 함수는 세션
부트스트랩 후에도 HTTP 400 LOGOUT을 반환 — KRX 회원 로그인이 필요한
서버측 인증 게이트임을 raw HTTP로 재현 확인했다(헤더/세션 문제 아님).
자동화하려면 KRX 계정을 자격증명으로 등록해야 하는데, 이는
governance/rules/06·07과 같은 종류의 새 정책 결정이라 사용자 승인
없이 추가하지 않고 개선 목표로 이관한다(next_review_date: 2026-09-30).

- spec/16_data_gaps_roadmap.yaml S4/S5: automation_attempt_2026_06_22
  필드에 재현 절차 기록, next_review_action을 "API 키 발급"이 아니라
  "KRX 계정 발급·자격증명 관리 정책 승인 여부"로 재구성
- docs/runbook.md: 공매도 잔고율 주1회(매주 월요일 개장 전) CSV 수동
  갱신, ETF NAV 수동 import(tools/import_etf_nav_manual.py) 운영절차 명문화
This commit is contained in:
2026-06-21 20:10:27 +09:00
parent 5568004704
commit 670ab8e15a
2 changed files with 270 additions and 1 deletions
+15
View File
@@ -6,3 +6,18 @@
4. Render reports from canonical data only.
5. Package upload artifacts only after the full gate passes or the output is explicitly audit-only.
6. Treat work as complete only when YAML, code, data artifacts, and validation evidence all exist together.
7. For calibration maintenance, run `npm run ops:calibration-backlog` or the Gitea schedule in `.gitea/workflows/calibration_backlog.yml`.
8. Promote a threshold to `PROVISIONAL` only when there is a recorded sample note and an explicit change note in `Temp/calibration_change_ledger_v4.json`.
9. Promote a threshold to `CALIBRATED` only when `sample_n >= 30`, a backtest note exists, and the validator still reports `overclaimed_count == 0`.
10. For human review, open `Temp/calibration_review_report_v1.md` after each backlog build.
11. For approval signoff, open `Temp/calibration_approval_list_v1.md` and approve only `source=PROVISIONAL` rows unless a new provisional review is explicitly requested.
12. For spreadsheet-like edits of `settings` and `account_snapshot`, run `npm run ops:snapshot-web` and validate with `npm run ops:snapshot-web-validate`.
13. Treat the snapshot admin web UI as the canonical edit surface for SQLite-backed manual maintenance; export JSON only when CI or downstream tooling needs a file artifact.
14. Keep `settings` and `account_snapshot` in the same workspace SQLite DB. Do not split them into separate files per sheet; use a separate SQLite DB only for the KIS collection pipeline.
15. Use the `KIS Collection` panel in snapshot admin to inspect the latest SQLite collection run, report status, source counts, and recent errors before you touch the editor.
16. Use the collection filter when you need to narrow runs, snapshots, or errors by ticker/source/status.
17. Use the change log filter when you need to audit a specific domain, action, or target reference.
18. Use `/collection` when you want the collection-only dashboard with raw JSON download.
19. Use `Export approval packet` in the snapshot admin UI to write `Temp/snapshot_admin_approval_packet_v1.json` and `Temp/snapshot_admin_approval_packet_v1.md` for review handoff.
20. Short balance ratio (`short_balance_ratio`) has no automatable path — confirmed 2026-06-22 by live-testing `pykrx.stock.get_shorting_balance()` (already used elsewhere in this repo for EOD prices), which returns `HTTP 400 LOGOUT` even with a properly bootstrapped session. This KRX "standard report" endpoint family requires actual KRX member login (`KRX_ID`/`KRX_PW`), unlike the basic OHLCV endpoints. Adding KRX login credentials is a new credential-management policy decision (same category as governance/rules/06-07) that requires explicit user approval — do not add it unilaterally. Until then, download the KRX 공매도종합포털 CSV weekly (every Monday before market open) and feed it via `--short-csv` to `build_qualitative_sell_inputs_v1.py`.
21. ETF NAV/iNAV/괴리율/추적오차/AUM has no automatable path either — same 2026-06-22 test confirmed `pykrx.stock.get_etf_price_deviation()`/`get_etf_tracking_error()` also return `HTTP 400 LOGOUT` (same KRX member-login gate as item 20). See `spec/16_data_gaps_roadmap.yaml` S4/S5 `automation_attempt_2026_06_22` for the full reproduction. Until a KRX login policy decision is made, keep feeding `etf_nav_manual` via `tools/import_etf_nav_manual.py` from manually downloaded KRX/KIND/운용사 CSV exports.
+255 -1
View File
@@ -1,6 +1,6 @@
meta:
title: "데이터 갭 로드맵 — 단계별 보완 계획"
version: "2026-05-17-initial"
version: "2026-06-21-platform-transition-v1"
language: "ko-KR"
purpose: >
의사결정 파이프라인(spec/09_decision_flow.yaml)에서 식별된 데이터 공백을
@@ -145,6 +145,25 @@ phase_2_structural:
limitation: >
KRX/KIND 기반 NAV/괴리율/추적오차/AUM 수집은 아직 미구현이며 etf_raw에서
ETF_NAV_Risk=NAV_DATA_MISSING으로 명시한다.
next_review_date: "2026-09-30" # WBS-7.8(2026-06-21) — KRX/KIND API 키 발급 가능성 분기별 재조사
next_review_action: >
KRX 정보데이터시스템/KIND 공식 API 또는 공개 데이터셋의 발급/이용약관 변경 여부를
재확인한다. 변경이 없으면 next_review_date를 다음 분기로 갱신하고 PLANNED 유지,
변경이 있으면 P1_kis_core_api_collector와 동일한 패턴으로 착수 여부를 결정한다.
automation_attempt_2026_06_22: >
pykrx(이미 tools/build_prediction_accuracy_harness_v2.py에서 EOD 가격 조회로 사용 중)의
get_etf_price_deviation()/get_etf_tracking_error()/get_shorting_balance()를 실제로
호출해 자동화 가능성을 재시도했다. 결과: 기본 시세조회(OHLCV)는 정상 작동(공개
엔드포인트, 로그인 불필요)하지만, 공매도 잔고/ETF 괴리율/추적오차 엔드포인트는
세션 쿠키를 정상 부트스트랩한 뒤에도 "HTTP 400 LOGOUT"을 반환했다(raw HTTP로
재현 확인). 이는 pykrx 임포트 시 출력되는 "KRX_ID/KRX_PW 환경변수 미설정" 경고와
정확히 일치 — 이 카테고리는 KRX 회원 로그인이 있어야 접근 가능한 서버측 인증
게이트이며, 헤더/세션 보정으로 해결되는 문제가 아님을 확인했다. 자동화하려면
KRX 계정(KRX_ID/KRX_PW)을 자격증명으로 코드에 등록해야 하는데, 이는
governance/rules/06·07과 유사한 새로운 자격증명 정책 결정이 필요한 사안이라
사용자 승인 없이 추가하지 않는다. 기술적 장벽 자체는 명확히 확정됐으므로
next_review_date 재조사 시 "API 키 발급 가능성"이 아니라 "KRX 계정 발급·자격증명
관리 정책 승인 여부"로 재구성해 검토할 것.
S5_etf_raw_execution_quality:
priority: HIGH
@@ -158,6 +177,9 @@ phase_2_structural:
etf_nav_manual 시트가 있으면 NAV, iNAV, 괴리율, 추적오차, AUM을 etf_raw에 반영한다.
tools/import_etf_nav_manual.py로 KRX/KIND/운용사 CSV/XLSX export를 etf_nav_manual로 변환할 수 있다.
limitation: "NAV, iNAV, 괴리율, 추적오차, AUM 자동 수집은 KRX/KIND 수집 경로 확정 전까지 미구현."
next_review_date: "2026-09-30" # WBS-7.8(2026-06-21) — S4와 동일 주기로 재검토
next_review_action: "S4_sector_flow.next_review_action과 동일 — KRX/KIND 경로 확정 시 etf_nav_manual 수동 경로를 자동 수집으로 대체."
automation_attempt_2026_06_22: "S4_sector_flow.automation_attempt_2026_06_22와 동일 사유로 자동화 불가 확정(pykrx get_etf_price_deviation/get_etf_tracking_error 모두 HTTP 400 LOGOUT — KRX 회원 로그인 필요)."
S6_sector_flow_history:
priority: HIGH
@@ -169,6 +191,41 @@ phase_2_structural:
이력이 부족할 때만 기존 sector_flow/PropertiesService 값을 fallback으로 사용한다.
Snapshot_Date는 Apps Script Date 객체와 문자열 날짜를 모두 yyyy-MM-dd로 정규화한다.
S7_snapshot_admin_web_editor:
priority: HIGH
status: DONE
implementation: >
SQLite canonical store용 웹 편집기 구현.
settings/account_snapshot을 contenteditable 그리드로 직접 수정하고,
TSV import/export, 행 삽입/복제, 승인/잠금/undo를 API로 제어한다.
KIS SQLite collector 상태 패널을 함께 노출해서 최신 수집 run/오류를
같은 화면에서 확인한다.
web UI는 Snapshot Admin 서버가 담당하며 JSON export는 CI/파생 도구용이다.
enables: >
settings/account_snapshot을 xlsx 대신 SQLite에서 직접 관리하면서도
스프레드시트처럼 편집 가능한 운영 surface와 수집 현황 대시보드 제공.
success_criteria:
settings_sheet_web_editor: true
account_snapshot_sheet_web_editor: true
contenteditable_grid: true
api_save_round_trip: PASS
kis_collection_dashboard: true
single_workspace_sqlite: true
collection_filter_controls: true
collection_dashboard_page: true
change_timeline_view: true
evidence:
code:
- "src/quant_engine/snapshot_admin_server_v1.py"
- "src/quant_engine/snapshot_admin_store_v1.py"
- "tools/validate_snapshot_admin_web_v1.py"
tests:
- "tests/unit/test_snapshot_admin_store_v1.py"
- "tests/unit/test_snapshot_admin_web_v1.py"
workflow:
- ".gitea/workflows/snapshot_admin.yml"
verification: "python tools/validate_snapshot_admin_web_v1.py"
# ─────────────────────────────────────────────────────────────────────────────
# 3단계 — 분석 품질 고도화 (낮은 우선순위)
# ─────────────────────────────────────────────────────────────────────────────
@@ -503,6 +560,203 @@ phase_4_backdata_collection:
2026-06-14 구현 완료 확인. GAS(syncBackdataFeatureBank_) + Python(synthesize_backdata_feature_bank)
모두 구현됨. T+20 데이터 누적 후 ML 패턴 학습 품질 향상 예정.
# ─────────────────────────────────────────────────────────────────────────────
# 5단계 — CI 기반 데이터 플랫폼 전환
# ─────────────────────────────────────────────────────────────────────────────
phase_5_platform_transition:
P1_kis_core_api_collector:
priority: HIGH
status: PLANNED
purpose: >
KIS Open API를 read-only 코어 수집원으로 두고, 가격/호가/공매도/수급의
1차 수집을 Python canonical collector에서 직접 수행한다.
inputs:
- "KIS_APP_Key / KIS_APP_Secret"
- "KIS_APP_Key_TEST / KIS_APP_Secret_TEST"
- "GatherTradingData.json"
outputs:
- "Temp/kis_data_collection_v1.json"
- "outputs/kis_data_collection/kis_data_collection.db"
fallback_order:
- "KIS Open API"
- "Naver Finance"
- "Yahoo Finance"
- "OpenDART"
- "Investing.com(best-effort, 차단 시 DATA_MISSING)"
note: >
주문 API는 사용하지 않는다. 조회형 quotations/ranking 계열만 허용한다.
success_criteria:
expected_success_value:
collector_gate: "PASS"
output_json_gate: "PASS"
sqlite_run_count_min: 1
sqlite_snapshot_count_min: 1
provenance_source_count_min: 1
evidence_artifacts:
- "Temp/test_kis_data_collection.json"
- "Temp/test_kis_data_collection.db"
verification_commands:
- "python tools/run_kis_data_collection_v1.py --input-json GatherTradingData.json --sqlite-db Temp/test_kis_data_collection.db --output-json Temp/test_kis_data_collection.json --kis-account real --no-live-kis --no-naver"
- "python - <<'PY' ... sqlite count check ... PY"
P2_sqlite_canonical_store:
priority: HIGH
status: PLANNED
purpose: >
xlsx 중심 저장을 중단하고, 수집 결과를 SQLite에 누적 저장한다.
향후 PostgreSQL 승격 시 동일 저장 인터페이스를 유지한다.
required_tables:
- "collection_runs"
- "collection_snapshots"
- "collection_source_errors"
stored_payloads:
- "raw source payload"
- "normalized factor row"
- "provenance JSON"
- "batch/run metadata"
migration_note: "PostgreSQL 전환 시 dialect만 교체하고 row shape은 유지한다."
success_criteria:
expected_success_value:
sqlite_schema_tables_min: 3
round_trip_snapshot_lookup: "PASS"
backend_contract_sqlite: "PASS"
backend_contract_postgresql: "READY"
evidence_artifacts:
- "src/quant_engine/data_collection_store_v1.py"
- "src/quant_engine/data_collection_backend_v1.py"
- "tests/unit/test_data_collection_store_v1.py"
verification_commands:
- "python -m pytest tests/unit/test_data_collection_store_v1.py -q"
- "python -m py_compile src/quant_engine/data_collection_store_v1.py src/quant_engine/data_collection_backend_v1.py"
P3_ci_scheduler_cutover:
priority: HIGH
status: PLANNED
purpose: >
Gitea schedule에서 Python collector를 직접 실행하고, CI가 SQLite 산출을 검증한다.
기존 GAS 워크플로우는 thin adapter/legacy fallback으로만 유지한다.
validation_gate:
- "read-only KIS gate"
- "source fallback gate"
- "sqlite round-trip gate"
- "provenance completeness gate"
- "no-direct-trading gate"
output_policy:
- "CI는 xlsx 생성에 의존하지 않는다."
- "결과는 JSON + SQLite + 로그 증빙으로 남긴다."
success_criteria:
expected_success_value:
xlsx_dependency_removed: true
json_seed_input: true
sqlite_output: true
mock_api_validation: "PASS"
no_direct_trading_gate: "PASS"
provenance_completeness_gate: "PASS"
evidence_artifacts:
- ".gitea/workflows/kis_data_collection.yml"
- "Temp/kis_api_credentials_validation_v1.json"
- "Temp/test_kis_data_collection.json"
verification_commands:
- "python tools/validate_no_direct_api_trading_v1.py"
- "python tools/validate_kis_api_credentials_v1.py --account mock --ticker 005930"
- "python tools/run_kis_data_collection_v1.py --help"
P4_gas_thin_adapter_minimize:
priority: MEDIUM
status: PLANNED
purpose: >
.gs는 기존 스프레드시트 호환과 과도기 검증용 얇은 어댑터만 남기고,
판단·수집·저장 로직은 Python으로 이동시킨다.
allowed_responsibilities:
- "collect"
- "normalize"
- "export"
- "display"
forbidden_responsibilities:
- "decision"
- "sizing"
- "stop_loss"
- "take_profit"
- "risk_score"
success_criteria:
expected_success_value:
allowed_responsibilities_only: true
forbidden_responsibilities_present: false
thin_adapter_gate: "PASS"
evidence_artifacts:
- "tools/validate_gas_thin_adapter_v1.py"
- "Temp/gas_thin_adapter_validation_v1.json"
- "src/gas/core/gas_lib.gs"
verification_commands:
- "python tools/validate_gas_thin_adapter_v1.py"
P5_postgresql_upgrade_path:
priority: MEDIUM
status: PLANNED
purpose: >
SQLite에서 검증된 스키마/업서트/프로venance 모델을 PostgreSQL로 승격한다.
운영 데이터 증가와 멀티잡 동시성 증가를 대비한다.
upgrade_steps:
- "sqlite schema parity 검증"
- "db_url 기반 backend 추상화"
- "migration script 추가"
- "CI에서 sqlite/postgres 동일 테스트"
compatibility_rule: "SQLite와 PostgreSQL 모두 동일한 row contract를 유지한다."
success_criteria:
expected_success_value:
sqlite_schema_parity: "PASS"
backend_contract_present: true
postgres_execution: "DATA_GATED"
caller_compatibility_preserved: true
evidence_artifacts:
- "src/quant_engine/data_collection_backend_v1.py"
- "src/quant_engine/kis_data_collection_v1.py"
- "tests/unit/test_data_collection_store_v1.py"
- "tools/generate_postgresql_upgrade_stub_v1.py"
verification_commands:
- "python -m pytest tests/unit/test_data_collection_store_v1.py -q"
- "python -m py_compile src/quant_engine/kis_data_collection_v1.py tools/run_kis_data_collection_v1.py"
- "python tools/generate_postgresql_upgrade_stub_v1.py"
Q1_qualitative_sell_pipeline:
priority: MEDIUM
status: PLANNED
purpose: >
비기계적 매도전략 파이프라인을 Gitea workflow + SQLite 시계열 + mock KIS 유효성
검증 + 사후 적중률 평가까지 일관된 계약으로 묶는다.
success_criteria:
expected_success_value:
mock_api_validation: "PASS"
pipeline_contract: "PASS"
workflow_present: true
schedule_present: true
package_scripts_present: true
evidence_artifacts:
- ".gitea/workflows/qualitative_sell_strategy.yml"
- "tools/validate_qualitative_sell_strategy_pipeline_v1.py"
- "Temp/qualitative_sell_strategy_pipeline_v1.json"
verification_commands:
- "python tools/validate_qualitative_sell_strategy_pipeline_v1.py"
Q2_gitea_secrets_contract:
priority: HIGH
status: PLANNED
purpose: >
Gitea workflow에서 KIS mock/real 자격증명과 GITHUB_TOKEN 시크릿 이름을
정확히 고정해, 수동 등록 실수로 인한 파이프라인 붕괴를 방지한다.
success_criteria:
expected_success_value:
secrets_contract: "PASS"
workflow_secret_mapping: "PASS"
docs_present: true
ci_validation_present: true
evidence_artifacts:
- "docs/GITEA_SECRETS_SETUP.md"
- "tools/validate_gitea_secrets_contract_v1.py"
- "Temp/gitea_secrets_contract_v1.json"
verification_commands:
- "python tools/validate_gitea_secrets_contract_v1.py"
# 2026-05-30 구현 현황
# - S5_etf_raw: PARTIAL_DONE 유지 (수동 NAV 병행)
# - Stage2_Gate PENDING: T+20 표본 누적 후 자동 평가