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.korea_investment_api import korea_investment_api
|
||||||
from app.services.mean_reversion_strategy import (
|
from app.services.mean_reversion_strategy import (
|
||||||
calculate_atr, calculate_rsi, calculate_bollinger, check_exit_signal,
|
calculate_atr, calculate_rsi, calculate_bollinger, check_exit_signal,
|
||||||
|
MarketRegime, # ① regime 파라미터 전달용 추가
|
||||||
)
|
)
|
||||||
from app.services.mdd_manager import mdd_manager
|
from app.services.mdd_manager import mdd_manager
|
||||||
from app.services.loss_tracker import loss_tracker
|
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 기반:
|
CLAUDE.md 섹션 6.2 + 7.3 + 7.4 기반:
|
||||||
- ATR 기반 손절: min(최저가 - 2.5×ATR, 진입가 -8%)
|
- ATR 기반 손절: min(최저가 - 2.5×ATR, 진입가 -6%) ← -8% → -6%
|
||||||
- 1차 익절(50%): RSI ≥ 50 + BB 중앙선 상회
|
- 1차 익절(30%/50%): RSI 55~65 + BB 중앙선 상회
|
||||||
- 2차 익절(전량): RSI 60~70 + BB 상단선
|
- 2차 익절(50%): RSI ≥ 65 + BB 상단선 도달
|
||||||
|
- 트레일링 스탑: 최고가 대비 -3% 하락 시 잔여 전량 청산
|
||||||
- 폴백: 고정 비율 손절 (STOP_LOSS_PERCENT)
|
- 폴백: 고정 비율 손절 (STOP_LOSS_PERCENT)
|
||||||
"""
|
"""
|
||||||
from app.core.config import settings
|
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:
|
if atr and rsi is not None and bb:
|
||||||
bb_upper, bb_middle, bb_lower = bb
|
bb_upper, bb_middle, bb_lower = bb
|
||||||
lowest_since = min(lows[-5:]) if lows else avg_price
|
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(
|
exit_signal = check_exit_signal(
|
||||||
|
regime=regime,
|
||||||
rsi=rsi,
|
rsi=rsi,
|
||||||
close=latest_price,
|
close=latest_price,
|
||||||
bb_upper=bb_upper,
|
bb_upper=bb_upper,
|
||||||
@@ -565,6 +577,7 @@ async def check_and_execute_exit_trades(db: AsyncSession):
|
|||||||
atr=atr,
|
atr=atr,
|
||||||
lowest_since_entry=lowest_since,
|
lowest_since_entry=lowest_since,
|
||||||
remaining_pct=remaining_pct,
|
remaining_pct=remaining_pct,
|
||||||
|
trailing_high=trailing_high, # ② 트레일링 스탑 활성화
|
||||||
)
|
)
|
||||||
except Exception as chart_err:
|
except Exception as chart_err:
|
||||||
logger.debug(f"ATR 청산 계산 실패 ({stock_code}): {chart_err}")
|
logger.debug(f"ATR 청산 계산 실패 ({stock_code}): {chart_err}")
|
||||||
@@ -637,7 +650,12 @@ async def check_and_execute_exit_trades(db: AsyncSession):
|
|||||||
|
|
||||||
# 분할 청산 시 ActiveTrade 잔여 비율 업데이트
|
# 분할 청산 시 ActiveTrade 잔여 비율 업데이트
|
||||||
if exit_signal and exit_signal.action == "SELL_PARTIAL" and active_trade:
|
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()
|
await db.commit()
|
||||||
else:
|
else:
|
||||||
# 전량 매도
|
# 전량 매도
|
||||||
|
|||||||
Reference in New Issue
Block a user