전략 취약점 15건 수정 (코드 리뷰 기반)
## CRITICAL/HIGH (S1-S8) - S1: Aggressive 모드 %B 스케일링 캡 1.3 (BB 중앙선 위 매수 방지) - S2: ML scaler train-only fit (데이터 누수 제거) - S3: SIDEWAYS = ADX<20 AND slope≈0 (트렌드 초기 차단 해소) - S4: EOD 청산 실패 시 재시도 + CRITICAL 로그 - S5: 일일 손실 한도에 미실현 P&L 포함 - S6: trailing stop 고점 매 사이클 갱신 - S7: 섹터 필터 미매핑 종목 "기타" 분류 (max 3 적용) - S8: 스캐너 RSI 기준 25→46, BB 점수 -3%~+1.5% 구간 ## MEDIUM (M1-M7) - M1: 스캐너 최소 거래량 100만→10만주 - M2: ADX 히스테리시스 18/22 (국면 플리핑 방지) - M3: 분할매도 최소 수익 1.0%→0.5% - M4: ORB 갭 필터 4% + 레인지 폭 0.3% 최소 - M5: 쿨다운 스트릭 부분매도 시 미변경 - M6: MDD 강제매도 loss_tracker 기록 제거 (허위 ban 방지) - M7: RSI Wilder 스무딩으로 통일 ## 인프라/보안 - 인증 누락 엔드포인트 수정 (analysis, journals) - nginx 보안 헤더 location 블록 누락 수정 - Alembic 마이그레이션 IF NOT EXISTS 가드 추가 - SellRequest/UsOrderRequest 입력 검증 강화 - 차트 네이버 폴백 추가 (DB 데이터 없을 때) - 서버 이전 시 자동 DB 마이그레이션 + KRX 초기 수집 - CLAUDE.md 섹션 5.1 백테스트 값 동기화 (4%→6%, -6%→-7%) - MAX_SWING_POSITIONS 25→20 (CLAUDE.md 준수) - mdd_manager failed_count에서 skip 종목 제외 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -217,6 +217,7 @@ class MDDManager:
|
||||
holdings = sorted(holdings, key=lambda h: (not _is_leverage(h),))
|
||||
|
||||
results = []
|
||||
skipped = 0
|
||||
for h in holdings:
|
||||
stock_code = h.get("pdno", "")
|
||||
stock_name = h.get("prdt_name", stock_code)
|
||||
@@ -224,6 +225,7 @@ class MDDManager:
|
||||
current_price = float(h.get("prpr", 0) or 0)
|
||||
|
||||
if quantity <= 0 or current_price <= 0:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
sell_qty = max(int(quantity * reduce_pct), 1)
|
||||
@@ -243,16 +245,9 @@ class MDDManager:
|
||||
profit_loss = (current_price - avg_price) * sell_qty
|
||||
profit_loss_rate = ((current_price - avg_price) / avg_price * 100) if avg_price > 0 else 0.0
|
||||
|
||||
# [BUG FIX] loss_tracker 기록 — MDD 강제매도도 연속 손실 추적 대상
|
||||
try:
|
||||
from app.services.loss_tracker import loss_tracker
|
||||
is_loss = profit_loss_rate < 0
|
||||
await loss_tracker.record_trade_result_safe(
|
||||
stock_code, is_loss
|
||||
)
|
||||
await loss_tracker.save_to_db(stock_code=stock_code)
|
||||
except Exception as lt_err:
|
||||
logger.warning(f"[MDD] loss_tracker 기록 실패 ({stock_code}): {lt_err}")
|
||||
# MDD 강제매도는 시스템적(systemic) 리스크 축소이며,
|
||||
# 개별 종목의 전략 실패가 아니므로 loss_tracker에 기록하지 않음.
|
||||
# 기록 시 양호한 종목이 MDD 축소로 인해 false ban될 수 있음.
|
||||
|
||||
await enqueue_trade_history({
|
||||
"stock_code": stock_code,
|
||||
@@ -304,7 +299,7 @@ class MDDManager:
|
||||
except Exception as e:
|
||||
logger.error(f"[MDD] 축소 매도 실패 ({stock_code}): {e}")
|
||||
|
||||
failed_count = len(holdings) - len(results)
|
||||
failed_count = len(holdings) - len(results) - skipped
|
||||
if failed_count > 0 and executed_key is not None:
|
||||
# 매도 실패 종목이 있으면 플래그 리셋 → 다음 주기에 재시도
|
||||
async with self._lock:
|
||||
|
||||
Reference in New Issue
Block a user