WBS-7.6/7.9/7.7: 슬리피지 보정 + Naver 모니터링 + E2E 통합테스트

WBS-7.6 (슬리피지 5bps 보정):
- 이론치 5bps를 calibration_registry.yaml에 EXECUTION_SLIPPAGE_BPS 등록
- spec/55_execution_simulator_contract.yaml에서 threshold 참조로 변경
- calibration_trigger: 실제 거래 20건 누적 후 actual_slippage 추적해 필요시 보정

WBS-7.9 (Naver 스크래핑 Cloudflare 403 모니터링):
- tools/fetch_naver_market_data_v1.py: HTTP 403 감지 시 CLOUDFLARE_BLOCKED_403 상태 반환
- 구조화된 에러 처리로 무조건 실패 대신 graceful degradation 가능
- spec/exit/qualitative_sell_strategy_v1.yaml: WBS-7.9 처리 문서화
- 실제 차단 발생 시 대체 경로 없음(KRX=OTP 필수, investing.com=이미 차단)
  → 운영: 차단 발생 시 수동 실행 또는 slack 경고

WBS-7.7 (E2E 통합테스트):
- 기존 tests/integration/test_kis_collection_to_snapshot_admin_and_sell_strategy_v1.py 검증
- 3개 테스트 모두 PASS:
  * KIS 수집 → SQLite 적재 → snapshot_admin 대시보드 읽기 round-trip
  * Naver 폴백 차단 시 graceful degradation 검증 (개별 ticker 실패 흡수)
  * 정성매도전략 평가 → SQLite 저장 → 조회 round-trip
- 네트워크 미사용 (mock 데이터, graceful failure)으로 CI 안정성 확보

전체 테스트: 135/135 PASS (unit 61 + integration 3 + formula/formula_registry/... 71)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 23:02:33 +09:00
parent b1bb40c384
commit 13e9ccad55
4 changed files with 76 additions and 6 deletions
+36 -2
View File
@@ -55,7 +55,24 @@ def fetch_price_history(session: requests.Session, code: str, pages: int = 3) ->
rows: list[dict[str, Any]] = []
for page in range(1, pages + 1):
url = f"https://finance.naver.com/item/sise_day.naver?code={code}&page={page}"
resp = session.get(url, timeout=10)
try:
resp = session.get(url, timeout=10)
if resp.status_code == 403:
return {
"status": "CLOUDFLARE_BLOCKED_403",
"rows": [],
"error": "Cloudflare rejected request (403 Forbidden)",
"source_url": url,
"wbs_ref": "WBS-7.9: Naver 스크래핑 Cloudflare 모니터링",
}
resp.raise_for_status()
except requests.RequestException as e:
return {
"status": "FETCH_ERROR",
"rows": [],
"error": str(e),
"source_url": url,
}
resp.encoding = "euc-kr"
soup = BeautifulSoup(resp.text, "html.parser")
table = soup.find("table", {"class": "type2"})
@@ -88,7 +105,24 @@ def fetch_foreign_institution_flow(session: requests.Session, code: str, pages:
rows: list[dict[str, Any]] = []
for page in range(1, pages + 1):
url = f"https://finance.naver.com/item/frgn.naver?code={code}&page={page}"
resp = session.get(url, timeout=10)
try:
resp = session.get(url, timeout=10)
if resp.status_code == 403:
return {
"status": "CLOUDFLARE_BLOCKED_403",
"rows": [],
"error": "Cloudflare rejected request (403 Forbidden)",
"source_url": url,
"wbs_ref": "WBS-7.9: Naver 스크래핑 Cloudflare 모니터링",
}
resp.raise_for_status()
except requests.RequestException as e:
return {
"status": "FETCH_ERROR",
"rows": [],
"error": str(e),
"source_url": url,
}
resp.encoding = "euc-kr"
soup = BeautifulSoup(resp.text, "html.parser")
for table in soup.find_all("table", {"class": "type2"}):