- portfolio_cache_bridge: 잔량 산출을 Portfolio(DB) 기준으로. 캐시는 sync 실패 시 최대 5분 낡아 실제와 다른 잔량이 기록될 수 있었음(감사 #14). 조회 실패 시 캐시 폴백. - ORB 캐시 Redis 영속화(save/load_orb_cache) — 재배포 시 인메모리가 날아가 당일 ORB 전략이 통째로 불가능했음. 수집 후 저장, 스케줄러 기동·수집 시 복원. - us_market_scheduler: 부분청산이 Portfolio.quantity 를 갱신하지 않던 문제 → reflect_sell 연동. - 듀얼 전략(MOMENTUM_BREAKOUT/PULLBACK_BUY)이 ActiveTrade.strategy_name 에 저장되지 않아 CLAUDE.md §8.4 모멘텀 전용 청산(+10%/+20% 분할)이 한 번도 실행된 적 없었음 → 태깅 전달. 매도 시에는 실제 행의 strategy_name 으로 close (swing 으로 닫으면 유령 행 잔존). - 수동매도: current_price NULL 시 TypeError 로 주문 후 상태 갱신이 통째로 누락되던 엣지 수정. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
46 lines
2.2 KiB
Python
46 lines
2.2 KiB
Python
"""감사 P1 수정 검증: MDD 수량 반영 / 전략명 명시 / 국내 DT 태깅."""
|
|
import inspect
|
|
|
|
import pytest
|
|
|
|
|
|
def test_mdd_reduction_reflects_quantities():
|
|
"""MDD 축소는 ActiveTrade·Portfolio·DT 인메모리 수량을 모두 줄여야 한다."""
|
|
from app.services import mdd_manager as m
|
|
src = inspect.getsource(m)
|
|
assert src.count("_reflect_reduction(stock_code, sell_qty") == 2, "두 축소 경로 모두 반영 필요"
|
|
assert '"quantity": remaining_qty' in src
|
|
helper = inspect.getsource(m._reflect_reduction)
|
|
assert "reflect_sell" in helper and "day_trading_state" in helper
|
|
assert "remove_position" in helper and 'pos["quantity"] = _left' in helper
|
|
|
|
|
|
def test_swing_exit_always_passes_strategy_name():
|
|
"""전략명을 비우면 db_worker 가 최신 active 행(=DT)을 닫거나 덮어쓴다."""
|
|
from app.services.swing import exit_service as se
|
|
src = inspect.getsource(se)
|
|
assert "strategy_name=None" not in src, "close 호출에 전략명 누락"
|
|
assert 'strategy_name=_strategy or "swing"' in src
|
|
assert '"strategy_name": _at_strategy' in src # 트레일링 고점 upsert
|
|
assert '"strategy_name": _strategy or "swing"' in src # 분할매도 upsert
|
|
|
|
|
|
def test_executor_sell_excludes_daytrading_shares():
|
|
"""Portfolio 행은 스윙+DT 합계 → 스윙 청산이 DT 보유분까지 팔면 안 된다."""
|
|
from app.services import trade_executor as te
|
|
src = inspect.getsource(te._execute_sell)
|
|
assert "day_trading_state" in src and "_dt_qty" in src
|
|
assert "owned_by_daytrading" in src
|
|
assert 'total_qty = _own' in src
|
|
# 분할매도 upsert 는 실제 ActiveTrade 행의 전략명(_at_name)을 사용한다
|
|
assert '"strategy_name": _at_name' in src
|
|
|
|
|
|
def test_sync_dt_tagging_applies_to_domestic_and_survives_missing_output1():
|
|
from app.api.endpoints import portfolio as pf
|
|
src = inspect.getsource(pf.sync_portfolio)
|
|
# 국내 신규/기존 행 + 해외 = 태깅 지점 4곳
|
|
assert src.count('quantity <= _dt_qty.get(stock_code, 0)') == 4
|
|
# 정의가 output1 블록보다 앞이어야 NameError 가 없다
|
|
assert src.index("_dt_codes = set(_dt_qty)") < src.index('if "output1" in balance_data')
|