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
This commit is contained in:
105
backend/tests/test_bear_v2_hardening_r2_20261009.py
Normal file
105
backend/tests/test_bear_v2_hardening_r2_20261009.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""하드닝 루프 R2 (2026-10-09) — mean_reversion_strategy BEAR v2 회귀 테스트.
|
||||
|
||||
1. BEAR v2 진입 description 에 f-string `%%B` 리터럴이 남아 저널·ML 텍스트에 '%%B' 로 기록되던 결함.
|
||||
2. MEAN_REVERSION_BEAR_V2_ENABLED 가 실전 진입 경로에 배선되어 있지 않다는 사실(백테스트 전용)을
|
||||
고정한다. 누군가 실전 경로에 배선하면 이 테스트가 깨져 문서(docstring·config·CLAUDE.md §6.1)
|
||||
갱신을 강제한다.
|
||||
"""
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.config import settings
|
||||
import app.services.mean_reversion_strategy as mrs
|
||||
|
||||
BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _cfg_none():
|
||||
"""strategy_cfg(DB) 를 비워 settings 값으로 폴백시킨다(테스트 시점 바인딩)."""
|
||||
_m = MagicMock()
|
||||
_m.get.side_effect = lambda key, default=None: None
|
||||
with patch.object(mrs, "strategy_cfg", _m):
|
||||
yield
|
||||
|
||||
|
||||
def _full_score_signal():
|
||||
bear_thr = settings.MEAN_REVERSION_BEAR_RSI_THRESHOLD
|
||||
rsi = min(bear_thr, settings.MEAN_REVERSION_RSI_STRONG_THRESHOLD) - 5.0
|
||||
avg_vol = 1_000_000
|
||||
cur_vol = int(avg_vol * (settings.MEAN_REVERSION_BEAR_VOLUME_MULT + 1.0))
|
||||
return mrs.check_bear_buy_signal_v2(
|
||||
rsi=rsi,
|
||||
close=10_100.0, # %B = 0.05
|
||||
bb_upper=12_000.0,
|
||||
bb_lower=10_000.0,
|
||||
avg_volume_20d=avg_vol,
|
||||
current_volume=cur_vol,
|
||||
is_reversal=True,
|
||||
rsi_prev=rsi - 2.0,
|
||||
bear_rsi_threshold=bear_thr,
|
||||
)
|
||||
|
||||
|
||||
def test_bear_v2_description_has_single_percent_b():
|
||||
sig = _full_score_signal()
|
||||
assert sig is not None, "만점 입력에서 BEAR v2 신호가 나야 한다"
|
||||
desc = sig["description"]
|
||||
assert "%%B" not in desc
|
||||
assert re.search(r"\+ %B\(0\.05/", desc), desc
|
||||
|
||||
|
||||
def _calls_in(path, names):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
tree = ast.parse(f.read())
|
||||
hits = []
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Call):
|
||||
fn = node.func
|
||||
nm = fn.id if isinstance(fn, ast.Name) else (fn.attr if isinstance(fn, ast.Attribute) else None)
|
||||
if nm in names:
|
||||
hits.append(nm)
|
||||
return hits
|
||||
|
||||
|
||||
def test_bear_v2_is_backtest_only():
|
||||
"""check_buy_signal / check_bear_buy_signal_v2 의 실전 호출부가 없음을 고정한다."""
|
||||
allowed = {
|
||||
os.path.join(BACKEND_DIR, "app", "services", "mean_reversion_strategy.py"),
|
||||
os.path.join(BACKEND_DIR, "app", "services", "kr_backtest_engine.py"),
|
||||
os.path.join(BACKEND_DIR, "run_backtest.py"),
|
||||
}
|
||||
names = {"check_buy_signal", "check_bear_buy_signal_v2", "_bear_v2_enabled"}
|
||||
offenders = []
|
||||
for root, dirs, files in os.walk(BACKEND_DIR):
|
||||
dirs[:] = [d for d in dirs if d not in ("tests", ".venv", "venv", "__pycache__", "node_modules")]
|
||||
for fn in files:
|
||||
if not fn.endswith(".py"):
|
||||
continue
|
||||
p = os.path.join(root, fn)
|
||||
if p in allowed:
|
||||
continue
|
||||
try:
|
||||
hits = _calls_in(p, names)
|
||||
except SyntaxError:
|
||||
continue
|
||||
if hits:
|
||||
offenders.append((os.path.relpath(p, BACKEND_DIR), hits))
|
||||
assert not offenders, (
|
||||
f"BEAR v2/check_buy_signal 이 실전 경로에 배선됨: {offenders} — "
|
||||
"mean_reversion_strategy docstring·config 주석·CLAUDE.md §6.1 '백테스트 전용' 서술을 갱신할 것"
|
||||
)
|
||||
# 문서가 사실과 일치하는지
|
||||
assert "백테스트 전용" in (mrs.check_bear_buy_signal_v2.__doc__ or "")
|
||||
|
||||
|
||||
def test_recommendations_bear_branch_ignores_v2_flag():
|
||||
p = os.path.join(BACKEND_DIR, "app", "api", "endpoints", "recommendations.py")
|
||||
with open(p, encoding="utf-8") as f:
|
||||
src = f.read()
|
||||
assert "MEAN_REVERSION_BEAR_V2" not in src
|
||||
assert "check_bear_buy_signal_v2" not in src
|
||||
Reference in New Issue
Block a user