From c0ef58ef5e35e293aebe611291705e2b7e3554fc Mon Sep 17 00:00:00 2001 From: shuim Date: Thu, 26 Mar 2026 21:10:59 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=B5=9C=EC=A2=85=20=ED=9A=8C=EC=9D=98?= =?UTF-8?q?=20CRITICAL=201=EA=B1=B4=20+=20MEDIUM=202=EA=B1=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. swing/scheduler: mdd_manager.update() 매 사이클 호출 추가 — 새 스케줄러에서 MDD -10%/-20% 감지가 완전 무력화되어 있었음 2. mdd_manager: place_order() → safe_place_order() 변경 — 세마포어+분산락 보호로 이중 매도 방지 3. mdd_manager: 매도 후 ActiveTrade close/update 추가 — 전량 매도 시 enqueue_close_active_trade() — 부분 매도 시 remaining_pct 갱신 Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/services/mdd_manager.py | 17 ++++++++++++++++- backend/app/services/swing/scheduler.py | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/backend/app/services/mdd_manager.py b/backend/app/services/mdd_manager.py index a69b7bb..4a5d2ef 100644 --- a/backend/app/services/mdd_manager.py +++ b/backend/app/services/mdd_manager.py @@ -230,7 +230,8 @@ class MDDManager: sell_qty = min(sell_qty, quantity) try: - order_resp = await korea_investment_api.place_order( + from app.services.trade_executor import safe_place_order + order_resp = await safe_place_order( stock_code=stock_code, order_type="SELL", quantity=sell_qty, @@ -269,6 +270,20 @@ class MDDManager: "strategy_type": "mdd_reduction", }) + # [FIX] 전량 매도 시 ActiveTrade 닫기 + remaining_qty = quantity - sell_qty + if remaining_qty <= 0: + from app.services.db_worker import enqueue_close_active_trade + await enqueue_close_active_trade(stock_code) + else: + # 부분 매도: remaining_pct 갱신 + from app.services.db_worker import enqueue_active_trade_upsert + new_remaining = remaining_qty / quantity + await enqueue_active_trade_upsert({ + "stock_code": stock_code, + "remaining_pct": new_remaining, + }) + results.append({ "stock_code": stock_code, "stock_name": stock_name, diff --git a/backend/app/services/swing/scheduler.py b/backend/app/services/swing/scheduler.py index cb44dac..80dc990 100644 --- a/backend/app/services/swing/scheduler.py +++ b/backend/app/services/swing/scheduler.py @@ -93,7 +93,20 @@ async def swing_scheduler() -> None: except Exception as e: logger.error(f"[Swing] 청산 체크 오류: {e}", exc_info=True) - # ── Phase 2: MDD 체크 + 강제 축소 (세션 불필요) ── + # ── Phase 2: MDD 업데이트 + 체크 + 강제 축소 ── + # [FIX] mdd_manager.update()를 매 사이클 호출하여 MDD 감지 활성화 + try: + from app.services.korea_investment_api import korea_investment_api + balance_resp = await korea_investment_api.get_balance() + if balance_resp: + _total = float(balance_resp.get("output2", [{}])[0].get("tot_evlu_amt", 0)) + if _total > 0: + mdd_result = await mdd_manager.update(_total) + if mdd_result.get("action") != "NONE": + logger.warning(f"[Swing] MDD 상태 변경: {mdd_result}") + except Exception as e: + logger.debug(f"[Swing] MDD update 조회 실패 (무시): {e}") + can_open_new = await mdd_manager.can_open_new_position() logger.info(f"[Swing] MDD 체크: can_open={can_open_new}") if not can_open_new: