fix(P1): MDD 축소 수량 반영·ActiveTrade 전략명 필수화·스윙 매도에서 DT 보유분 제외
- mdd_manager: 강제 축소가 TradeHistory/remaining_pct 만 갱신하고 실제 수량은 어디도 줄이지 않아 다음 청산이 축소 전 수량으로 매도했음 → _reflect_reduction()으로 Portfolio·DT 인메모리 반영, ActiveTrade.quantity 도 잔량으로 갱신 (두 경로 모두) - swing/exit_service: close/upsert 에 strategy_name 미전달 시 db_worker 가 최신 active 행(=DT)을 닫거나 트레일링 고점을 덮어씀 → 항상 전략명 명시 - trade_executor._execute_sell: Portfolio 행(스윙+DT 합계)을 그대로 매도 대상으로 삼아 DT 보유분까지 팔았음 → 엔진 보유 수량을 제외한 자기 전략 보유분만 대상, upsert 에 전략명 - portfolio.sync: DT 태깅이 해외 루프에만 적용돼 국내 DT 매수분이 swing 으로 저장되면 스윙 엔진이 손절 → 국내에도 적용. _dt_qty 정의를 output1 블록 밖으로 올려 NameError 방지 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
44
backend/tests/test_p1_audit_fixes.py
Normal file
44
backend/tests/test_p1_audit_fixes.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""감사 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
|
||||
assert '"strategy_name": stock.get("strategy_type", "swing")' 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')
|
||||
Reference in New Issue
Block a user