- 스윙 체결가 확인 전 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
98 lines
3.6 KiB
Python
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()
|