- 스윙 체결가 확인 전 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
106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
"""하드닝 루프 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
|