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:
@@ -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"}):
|
||||
|
||||
Reference in New Issue
Block a user