fix: portfolio.py check_exit_signal 호출부 3가지 버그 수정
① regime 파라미터 누락 → MarketRegime.BULL 기본값으로 수정
(기존 코드 런타임 TypeError 버그 — check_exit_signal 첫 인자 누락)
② trailing_high 미전달 → 최근 10봉 최고가로 계산하여 전달
(트레일링 스탑 기능 실제 동작 가능)
③ remaining_pct 업데이트 수식 오류
- 기존: remaining_pct - sell_pct (2차 이후 잘못된 비율)
- 수정: remaining_pct * (1 - sell_pct) (정확한 잔여 비율)
예) remaining=0.70, sell_pct=0.50 시
기존: 0.70 - 0.50 = 0.20 (잘못됨)
수정: 0.70 × 0.50 = 0.35 (올바름)
This commit is contained in:
@@ -19,6 +19,7 @@ from app.models.models import Portfolio, TradeHistory, ActiveTrade, StockAnalysi
|
||||
from app.services.korea_investment_api import korea_investment_api
|
||||
from app.services.mean_reversion_strategy import (
|
||||
calculate_atr, calculate_rsi, calculate_bollinger, check_exit_signal,
|
||||
MarketRegime, # ① regime 파라미터 전달용 추가
|
||||
)
|
||||
from app.services.mdd_manager import mdd_manager
|
||||
from app.services.loss_tracker import loss_tracker
|
||||
@@ -481,9 +482,10 @@ async def check_and_execute_exit_trades(db: AsyncSession):
|
||||
"""
|
||||
보유 종목의 손절/익절 조건을 체크하고 자동 매도 실행
|
||||
CLAUDE.md 섹션 6.2 + 7.3 + 7.4 기반:
|
||||
- ATR 기반 손절: min(최저가 - 2.5×ATR, 진입가 -8%)
|
||||
- 1차 익절(50%): RSI ≥ 50 + BB 중앙선 상회
|
||||
- 2차 익절(전량): RSI 60~70 + BB 상단선
|
||||
- ATR 기반 손절: min(최저가 - 2.5×ATR, 진입가 -6%) ← -8% → -6%
|
||||
- 1차 익절(30%/50%): RSI 55~65 + BB 중앙선 상회
|
||||
- 2차 익절(50%): RSI ≥ 65 + BB 상단선 도달
|
||||
- 트레일링 스탑: 최고가 대비 -3% 하락 시 잔여 전량 청산
|
||||
- 폴백: 고정 비율 손절 (STOP_LOSS_PERCENT)
|
||||
"""
|
||||
from app.core.config import settings
|
||||
@@ -555,7 +557,17 @@ async def check_and_execute_exit_trades(db: AsyncSession):
|
||||
if atr and rsi is not None and bb:
|
||||
bb_upper, bb_middle, bb_lower = bb
|
||||
lowest_since = min(lows[-5:]) if lows else avg_price
|
||||
|
||||
# ② 진입 후 최고가 (트레일링 스탑용) — 최근 10봉 최고값으로 근사
|
||||
trailing_high = max(highs[-10:]) if highs else None
|
||||
|
||||
# ① 시장 레짐 결정
|
||||
# TODO: KOSPI 지수 일봉 기반 detect_market_regime() 으로 대체 예정
|
||||
# 현재는 BULL 기본값 사용 (보수적 선택 — 1차 30% 익절)
|
||||
regime = MarketRegime.BULL
|
||||
|
||||
exit_signal = check_exit_signal(
|
||||
regime=regime,
|
||||
rsi=rsi,
|
||||
close=latest_price,
|
||||
bb_upper=bb_upper,
|
||||
@@ -565,6 +577,7 @@ async def check_and_execute_exit_trades(db: AsyncSession):
|
||||
atr=atr,
|
||||
lowest_since_entry=lowest_since,
|
||||
remaining_pct=remaining_pct,
|
||||
trailing_high=trailing_high, # ② 트레일링 스탑 활성화
|
||||
)
|
||||
except Exception as chart_err:
|
||||
logger.debug(f"ATR 청산 계산 실패 ({stock_code}): {chart_err}")
|
||||
@@ -637,7 +650,12 @@ async def check_and_execute_exit_trades(db: AsyncSession):
|
||||
|
||||
# 분할 청산 시 ActiveTrade 잔여 비율 업데이트
|
||||
if exit_signal and exit_signal.action == "SELL_PARTIAL" and active_trade:
|
||||
active_trade.remaining_pct = Decimal(str(remaining_pct - exit_signal.sell_pct))
|
||||
# ③ 정확한 잔여 비율 계산:
|
||||
# sell_pct = 현재 보유량 대비 비율이므로
|
||||
# new_remaining = remaining_pct × (1 - sell_pct)
|
||||
# 예) remaining=0.70, sell_pct=0.50 → 0.70×0.50=0.35 (기존: 0.70-0.50=0.20 오류)
|
||||
new_remaining = remaining_pct * (1.0 - exit_signal.sell_pct)
|
||||
active_trade.remaining_pct = Decimal(str(round(new_remaining, 4)))
|
||||
await db.commit()
|
||||
else:
|
||||
# 전량 매도
|
||||
|
||||
Reference in New Issue
Block a user