9b1ef4a100
## 주요 변경사항 ### ✅ 완료된 11개 항목 - WBS-7.1: 캘리브레이션 실증 전환 도구 - WBS-7.2: T+5 지표 단일 진실원천 통일 - WBS-7.3: GAS→Python 공식 마이그레이션 재검토 + F05/F10 포팅 ✨ - WBS-7.4: Deprecated 별칭·시트 정리 - WBS-7.5: 임시 하드코딩 폴백 비례화 - WBS-7.6: 슬리피지 실측 보정 스캐폴딩 - WBS-7.7: E2E 통합 테스트 구축 - WBS-7.8: ETF NAV/공매도 자동화 검토 및 운영절차 명문화 - WBS-7.9: snapshot_admin Synology POC 기본 보안 게이트 - WBS-7.10: 어드민 페이지 Tabler 그리드 조회 - WBS-7.11: spec-코드 동기화 게이트 ### F05/F10 포팅 (이번 세션) **F05 (calc_exit_sell_action)** - 7단계 우선순위 계층 구현 - JavaScript Number.isFinite() 의미론 보장 via safe_float() - 가격 폴백 체인 (tp2 → tp1 → close) - 17개 parity 테스트 PASS **F10 (run_route_flow)** - 5개 게이트 순차 필터링 - Stop_Breach → Relative_Stop → Intraday_Lock → Heat_Gate → Mean_Reversion - 17개 parity 테스트 PASS ### 📊 테스트 상태 **Parity 테스트**: 64/64 PASS - F02/F04/F06 (price_basis): 8개 - F05 (execution_decision): 17개 - F07 (score_thresholds): 9개 - F10 (routing_decision): 17개 - F11 (classify_order_type): 13개 ### 🎯 최종 상태 Phase 1~6 모두 완료, Phase 7 보완·고도화 DONE → 엔진 전체 경화 완료. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> # Conflicts: # GatherTradingData.json # governance/gas_logic_migration_ledger_v1.yaml
230 lines
10 KiB
YAML
230 lines
10 KiB
YAML
name: KIS Data Collection (SQLite Canonical Feed)
|
|
|
|
# ─────────────────────────────────────────────────────────────────
|
|
# [중요] 이 워크플로우는 KIS Open API를 코어로 하는 read-only 데이터 수집만 수행한다.
|
|
# GatherTradingData.json + live read-only APIs를 통해 SQLite canonical store를 갱신한다.
|
|
# xlsx는 이 워크플로우의 직접 입력이 아니며, KIS 실패 시에만 별도 보조 경로에서 사용한다.
|
|
#
|
|
# 스케줄: 영업일(월~금) 08:00~17:00 KST, 2시간 간격(08/10/12/14/16시).
|
|
# Gitea Actions의 schedule cron은 UTC 기준으로 평가된다(서버 타임존이 별도
|
|
# 설정되어 있지 않은 경우의 기본값). 아래 cron은 UTC로 작성했다:
|
|
# KST 08:00 = UTC 전날 23:00 → 요일은 "한국 기준 평일"에 맞춰 UTC 0-4(일~목)로 이동
|
|
# KST 10/12/14/16:00 = UTC 01/03/05/07:00, 같은 날(UTC 월~금, 1-5)
|
|
#
|
|
# [실제 Gitea 서버 타임존이 Asia/Seoul로 설정되어 있다면] 아래 cron을 그대로
|
|
# "0 8,10,12,14,16 * * 1-5" 한 줄로 교체하면 된다 — 첫 실행 후 Actions 실행
|
|
# 기록의 타임스탬프를 확인해 KST 08시 전후로 도는지 검증할 것(추정하지 말고 확인).
|
|
#
|
|
# 스케줄 주기 변경: 아래 schedule 목록의 cron 줄을 추가/삭제/수정하면 된다.
|
|
# 예) 1시간 간격으로 바꾸려면 09,11,13,15시 슬롯을 추가.
|
|
# ─────────────────────────────────────────────────────────────────
|
|
|
|
on:
|
|
schedule:
|
|
- cron: "0 23 * * 0-4" # KST 월~금 08:00 (UTC 일~목 23:00)
|
|
- cron: "0 1 * * 1-5" # KST 월~금 10:00 (UTC 01:00)
|
|
- cron: "0 3 * * 1-5" # KST 월~금 12:00 (UTC 03:00)
|
|
- cron: "0 5 * * 1-5" # KST 월~금 14:00 (UTC 05:00)
|
|
- cron: "0 7 * * 1-5" # KST 월~금 16:00 (UTC 07:00)
|
|
workflow_dispatch: # 수동 실행 — 스케줄 검증/즉시 재시도용
|
|
|
|
jobs:
|
|
validate-kis-config-smoke:
|
|
if: github.event_name == 'workflow_dispatch'
|
|
runs-on: self-hosted
|
|
steps:
|
|
- name: Checkout Code
|
|
run: |
|
|
if [ -d .git ]; then
|
|
git remote set-url origin http://x-access-token:${{ secrets.GITHUB_TOKEN }}@192.168.123.100:8418/KimJaeHyun/myfinance.git
|
|
else
|
|
git init
|
|
git remote add origin http://x-access-token:${{ secrets.GITHUB_TOKEN }}@192.168.123.100:8418/KimJaeHyun/myfinance.git
|
|
fi
|
|
TARGET_REF="${GITHUB_REF_NAME:-main}"
|
|
git fetch origin "$TARGET_REF" --depth=1
|
|
git reset --hard FETCH_HEAD
|
|
|
|
- name: Setup Python Environment
|
|
run: |
|
|
VENV_BASE=/volume1/gitea/python_venv
|
|
REQ_HASH=$(md5sum tools/run_kis_data_collection_v1.py 2>/dev/null | cut -d' ' -f1 || echo "kis-default")
|
|
VENV="$VENV_BASE/$REQ_HASH"
|
|
|
|
if [ ! -f "$VENV/bin/python" ]; then
|
|
mkdir -p "$VENV_BASE"
|
|
/usr/bin/python3 -m venv "$VENV"
|
|
if [ ! -f "$VENV/bin/pip" ]; then
|
|
curl -sS https://bootstrap.pypa.io/pip/3.8/get-pip.py -o get-pip.py
|
|
"$VENV/bin/python" get-pip.py --quiet
|
|
rm get-pip.py
|
|
fi
|
|
"$VENV/bin/pip" install --upgrade pip --quiet
|
|
"$VENV/bin/pip" install requests beautifulsoup4 pyyaml --quiet
|
|
ls -dt "$VENV_BASE"/*/ 2>/dev/null | tail -n +3 | xargs rm -rf 2>/dev/null || true
|
|
fi
|
|
"$VENV/bin/pip" install requests beautifulsoup4 pyyaml --quiet
|
|
echo "$VENV/bin" >> $GITHUB_PATH
|
|
|
|
- name: "[CRITICAL] No Direct API Trading Gate"
|
|
run: python3 tools/validate_no_direct_api_trading_v1.py
|
|
|
|
- name: "[CRITICAL] Validate KIS API Credentials (mock)"
|
|
env:
|
|
# Gitea repository variables are injected here; the Python loader reads these env names.
|
|
KIS_APP_Key_TEST: ${{ vars.KIS_APP_KEY_TEST }}
|
|
KIS_APP_Secret_TEST: ${{ vars.KIS_APP_SECRET_TEST }}
|
|
run: |
|
|
if [ -z "${KIS_APP_Key_TEST:-}" ]; then
|
|
echo "::error::Gitea variable KIS_APP_KEY_TEST is missing or empty"
|
|
exit 1
|
|
fi
|
|
if [ -z "${KIS_APP_Secret_TEST:-}" ]; then
|
|
echo "::error::Gitea variable KIS_APP_SECRET_TEST is missing or empty"
|
|
exit 1
|
|
fi
|
|
python3 tools/validate_kis_api_credentials_v1.py \
|
|
--account mock \
|
|
--ticker 005930 \
|
|
--dry-run
|
|
|
|
collect-kis-data-live:
|
|
if: github.event_name == 'schedule'
|
|
runs-on: self-hosted
|
|
|
|
steps:
|
|
- name: Checkout Code
|
|
run: |
|
|
if [ -d .git ]; then
|
|
git remote set-url origin http://x-access-token:${{ secrets.GITHUB_TOKEN }}@192.168.123.100:8418/KimJaeHyun/myfinance.git
|
|
else
|
|
git init
|
|
git remote add origin http://x-access-token:${{ secrets.GITHUB_TOKEN }}@192.168.123.100:8418/KimJaeHyun/myfinance.git
|
|
fi
|
|
TARGET_REF="${GITHUB_REF_NAME:-main}"
|
|
git fetch origin "$TARGET_REF" --depth=1
|
|
git reset --hard FETCH_HEAD
|
|
|
|
- name: Prepare Raw Seed Snapshot
|
|
run: |
|
|
if [ -f GatherTradingData.json ]; then
|
|
echo "GatherTradingData.json present"
|
|
exit 0
|
|
fi
|
|
|
|
if [ -f .clasprc.json ]; then
|
|
echo "GatherTradingData.json missing; seed regeneration is not performed in this workflow."
|
|
echo "::error::Commit or pre-stage GatherTradingData.json before running this workflow."
|
|
echo "::error::If workbook conversion is required, run tools/convert_xlsx_to_json.py in a separate seed-prep step."
|
|
exit 1
|
|
fi
|
|
|
|
echo "::error::GatherTradingData.json is missing."
|
|
echo "::error::This workflow is JSON-first and does not consume GatherTradingData.xlsx directly."
|
|
echo "::error::Fix options:"
|
|
echo "::error:: 1) Commit GatherTradingData.json to the repository tree."
|
|
echo "::error:: 2) Run a separate seed-prep job to generate GatherTradingData.json from workbook sources."
|
|
exit 1
|
|
|
|
- name: Configure Runtime Paths
|
|
run: |
|
|
export PATH=/usr/local/bin:$PATH
|
|
echo "/usr/local/bin" >> $GITHUB_PATH
|
|
/usr/bin/python3 --version
|
|
|
|
- name: Setup Python Environment
|
|
run: |
|
|
VENV_BASE=/volume1/gitea/python_venv
|
|
REQ_HASH=$(md5sum tools/run_kis_data_collection_v1.py 2>/dev/null | cut -d' ' -f1 || echo "kis-default")
|
|
VENV="$VENV_BASE/$REQ_HASH"
|
|
|
|
if [ ! -f "$VENV/bin/python" ]; then
|
|
mkdir -p "$VENV_BASE"
|
|
/usr/bin/python3 -m venv "$VENV"
|
|
if [ ! -f "$VENV/bin/pip" ]; then
|
|
curl -sS https://bootstrap.pypa.io/pip/3.8/get-pip.py -o get-pip.py
|
|
"$VENV/bin/python" get-pip.py --quiet
|
|
rm get-pip.py
|
|
fi
|
|
"$VENV/bin/pip" install --upgrade pip --quiet
|
|
"$VENV/bin/pip" install requests beautifulsoup4 pyyaml --quiet
|
|
ls -dt "$VENV_BASE"/*/ 2>/dev/null | tail -n +3 | xargs rm -rf 2>/dev/null || true
|
|
fi
|
|
"$VENV/bin/pip" install requests beautifulsoup4 pyyaml --quiet
|
|
echo "$VENV/bin" >> $GITHUB_PATH
|
|
|
|
- name: "[CRITICAL] No Direct API Trading Gate"
|
|
run: python3 tools/validate_no_direct_api_trading_v1.py
|
|
|
|
- name: "[CRITICAL] Validate KIS API Credentials (mock)"
|
|
env:
|
|
# Gitea repository variables are injected here; the Python loader reads these env names.
|
|
KIS_APP_Key_TEST: ${{ vars.KIS_APP_KEY_TEST }}
|
|
KIS_APP_Secret_TEST: ${{ vars.KIS_APP_SECRET_TEST }}
|
|
run: |
|
|
if [ -z "${KIS_APP_Key_TEST:-}" ]; then
|
|
echo "::error::Gitea variable KIS_APP_KEY_TEST is missing or empty"
|
|
exit 1
|
|
fi
|
|
if [ -z "${KIS_APP_Secret_TEST:-}" ]; then
|
|
echo "::error::Gitea variable KIS_APP_SECRET_TEST is missing or empty"
|
|
exit 1
|
|
fi
|
|
python3 tools/validate_kis_api_credentials_v1.py \
|
|
--account mock \
|
|
--ticker 005930 \
|
|
--dry-run
|
|
|
|
- name: Collect KIS Market Data to SQLite (read-only)
|
|
env:
|
|
# Real collection uses repository variables, not Windows shell env syntax.
|
|
KIS_APP_Key: ${{ vars.KIS_APP_KEY }}
|
|
KIS_APP_Secret: ${{ vars.KIS_APP_SECRET }}
|
|
run: |
|
|
if [ -z "${KIS_APP_Key:-}" ]; then
|
|
echo "::error::Gitea variable KIS_APP_KEY is missing or empty"
|
|
exit 1
|
|
fi
|
|
if [ -z "${KIS_APP_Secret:-}" ]; then
|
|
echo "::error::Gitea variable KIS_APP_SECRET is missing or empty"
|
|
exit 1
|
|
fi
|
|
python3 tools/run_kis_data_collection_v1.py \
|
|
--input-json GatherTradingData.json \
|
|
--sqlite-db outputs/kis_data_collection/kis_data_collection.db \
|
|
--output-json Temp/kis_data_collection_v1.json \
|
|
--kis-account real
|
|
|
|
- name: Validate SQLite Artifact
|
|
run: |
|
|
python3 - <<'PY'
|
|
import json, sqlite3
|
|
from pathlib import Path
|
|
db = Path("outputs/kis_data_collection/kis_data_collection.db")
|
|
report = Path("Temp/kis_data_collection_v1.json")
|
|
assert db.exists(), f"missing db: {db}"
|
|
assert report.exists(), f"missing report: {report}"
|
|
conn = sqlite3.connect(db)
|
|
try:
|
|
run_count = conn.execute("SELECT COUNT(*) FROM collection_runs").fetchone()[0]
|
|
snap_count = conn.execute("SELECT COUNT(*) FROM collection_snapshots").fetchone()[0]
|
|
print(json.dumps({"run_count": run_count, "snapshot_count": snap_count}, ensure_ascii=False))
|
|
assert run_count >= 1
|
|
assert snap_count >= 1
|
|
finally:
|
|
conn.close()
|
|
PY
|
|
|
|
- name: Notify Run Result
|
|
if: always()
|
|
run: |
|
|
STATUS="${{ job.status }}"
|
|
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
SUMMARY_FILE="Temp/kis_data_collection_v1.json"
|
|
SUMMARY_TEXT="(요약 파일 없음)"
|
|
[ -f "$SUMMARY_FILE" ] && SUMMARY_TEXT=$(cat "$SUMMARY_FILE")
|
|
echo "=== KIS Data Collection Result ==="
|
|
echo "status: $STATUS"
|
|
echo "summary: $SUMMARY_TEXT"
|
|
echo "run log: $RUN_URL"
|