Files
auto-stock/backend/tests/test_naver_close_quality_r4.py
shuim a264561576 fix: 하드닝 루프 8회차 — 체결 보정·고아 감지·청산 경보·실시간/차트·리스크 가드 일괄 수정
- 스윙 체결가 확인 전 executed_price NULL + fill_sweep, 진입가 resolve(ActiveTrade↔Portfolio), 0체결/부분체결 매수 장부 원복
- DT 고아 감지(근거 한정)·0체결 매도 복원·stale 점검 확대, 시간손절 -0.3% 원복
- 폴백 손절 ATR 캐시, 거래량부족-차단 신호 매수 차단, 현금 5% 유보 재적용, MDD peak/reset 수정
- KIS 서킷브레이커 청산 우회·경보, db_worker 재큐잉 순서, heartbeat·health-watch 사이드카
- 실시간 WS 재구독·41건 우선순위·다건 프레임 분할, 차트 샘플 제거·당일봉 합성, WS 서브프로토콜 인증
- 프론트: 미관리 그룹·확인 모달·차트 실시간 반영·예수금 표시 정정
- 전문가 에이전트 6종 추가(.claude/agents)
- 유닛 스위트 2667 passed (소켓 차단 하), tsc OK

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y83Za98XsNw9zuA4FTUytN
2026-10-10 12:17:49 +09:00

98 lines
3.6 KiB
Python

import asyncio
import httpx
import pytest
from app.services import naver_finance as nf
@pytest.fixture(autouse=True)
def _reset():
for k in nf._naver_close_stats:
nf._naver_close_stats[k] = 0
def test_tick_validity():
assert nf.is_krx_tick_valid(274500) # 500 단위 아님 -> 아래 확인
assert not nf.is_krx_tick_valid(274750)
assert not nf.is_krx_tick_valid(1837500)
assert nf.is_krx_tick_valid(1837000)
assert not nf.is_krx_tick_valid(30075)
assert nf.is_krx_tick_valid(30050)
assert not nf.is_krx_tick_valid(0)
def test_annotate_keeps_values_and_counts():
rows = [{"stck_clpr": "274750"}, {"stck_clpr": "274500"}]
out = nf.annotate_naver_bars_v2(rows)
assert out[0]["stck_clpr"] == "274750" and out[0]["krx_tick_ok"] is False
assert out[1]["krx_tick_ok"] is True
q = nf.get_naver_close_quality()
assert q["checked"] == 2 and q["off_tick"] == 1 and q["off_tick_ratio"] == 0.5
def test_etf_skipped():
rows = [{"stck_clpr": "10005"}]
nf.annotate_naver_bars_v2(rows, is_etf=True)
assert "krx_tick_ok" not in rows[0]
def test_compare_with_krx():
nv = [{"stck_bsop_date": "20260929", "stck_clpr": "318000"},
{"stck_bsop_date": "20260930", "stck_clpr": "100"}]
krx = [{"stck_bsop_date": "20260929", "stck_clpr": "315500"}]
r = nf.compare_naver_with_krx_v2(nv, krx)
assert r == {"compared": 1, "mismatch": 1}
assert nv[0]["krx_close_mismatch"] is True
assert "krx_close_mismatch" not in nv[1]
assert nf.get_naver_close_quality()["krx_mismatch_ratio"] == 1.0
def test_chart_fn_annotates_without_network():
body = '[["날짜","시가","고가","저가","종가","거래량"],\n["20260930","1","1","1","274750","10"],\n]'
def handler(req):
return httpx.Response(200, text=body)
async def run():
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as c:
return await nf.get_naver_daily_chart("005930", days=5, client=c)
res = asyncio.run(run())
assert res[0]["stck_clpr"] == "274750" and res[0]["krx_tick_ok"] is False
def _fetch(code, **kw):
body = '[["날짜","시가","고가","저가","종가","거래량"],\n["20260930","1","1","1","109530","10"],\n]'
def handler(req):
return httpx.Response(200, text=body)
async def run():
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as c:
return await nf.get_naver_daily_chart_v2(code, days=5, client=c, **kw)
return asyncio.run(run())
def test_benchmark_and_registered_etf_not_counted():
res = _fetch("069500")
assert "krx_tick_ok" not in res[0]
nf.register_etf_codes_v2(["069660"])
try:
res = asyncio.run(_wrap("069660"))
assert "krx_tick_ok" not in res[0]
finally:
nf._registered_etf_codes.discard("069660")
assert nf.get_naver_close_quality()["checked"] == 0
async def _wrap(code):
body = '[["날짜","시가","고가","저가","종가","거래량"],\n["20260930","1","1","1","109530","10"],\n]'
async with httpx.AsyncClient(transport=httpx.MockTransport(lambda r: httpx.Response(200, text=body))) as c:
return await nf.get_naver_daily_chart("069660", days=5, client=c)
def test_explicit_is_etf_and_stock_counted():
assert "krx_tick_ok" not in _fetch("005930", is_etf=True)[0]
assert _fetch("005930")[0]["krx_tick_ok"] is False
assert nf.get_naver_close_quality()["checked"] == 1
def test_quality_alert():
assert nf.naver_close_quality_alert_v2() is None
nf._naver_close_stats.update(krx_compared=100, krx_mismatch=30)
assert "30/100" in nf.naver_close_quality_alert_v2()