refactor: web-ai V1 assets → signal_v1/ (graduation prep)
Atomic mv of root V1 assets (main_server.py + modules/ + data/ + tests/ + entry scripts + docs + logs) into signal_v1/ subdirectory. load_dotenv() updated to load web-ai/.env explicitly via Path. Adds web-ai/CLAUDE.md (workspace guide) and web-ai/start.bat (signal_v1 entry wrapper). Prepares for signal_v2/ Phase 2. Tests: signal_v1/tests/unit baseline preserved (no regression). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
279
signal_v1/modules/analysis/market_regime.py
Normal file
279
signal_v1/modules/analysis/market_regime.py
Normal file
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
시장 레짐 감지 모듈
|
||||
- 코스피 지수 수준에 따른 시장 레짐 분류
|
||||
- 코스피 6300 목표 수준에서의 모델 적합성 평가
|
||||
- 레짐별 전략 파라미터 자동 조정
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Optional, Dict
|
||||
|
||||
|
||||
class MarketRegime(Enum):
|
||||
BULL_EXTREME = "bull_extreme" # 코스피 5000+ (역사적 극고점, 6300 시나리오)
|
||||
BULL_STRONG = "bull_strong" # 코스피 3500~5000 (강한 상승장)
|
||||
BULL_NORMAL = "bull_normal" # 코스피 2500~3500 (정상 상승장)
|
||||
SIDEWAYS = "sideways" # 코스피 2000~2500 (횡보)
|
||||
BEAR_MILD = "bear_mild" # 코스피 1500~2000 (약세)
|
||||
BEAR_SEVERE = "bear_severe" # 코스피 1500 미만 (심각한 약세)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RegimeAnalysis:
|
||||
"""레짐 분석 결과"""
|
||||
regime: MarketRegime
|
||||
kospi_level: float
|
||||
description: str
|
||||
recommended_strategy: str
|
||||
buy_threshold_adj: float # 매수 임계값 조정치 (+: 더 엄격, -: 완화)
|
||||
position_size_adj: float # 포지션 크기 조정 배수 (1.0 = 기본)
|
||||
lstm_weight_adj: float # LSTM 앙상블 가중치 조정 (+0.1 = 10% 증가)
|
||||
model_recommendation: str # 모델 유지/교체 권고
|
||||
risk_level: str # LOW / MEDIUM / HIGH / EXTREME
|
||||
|
||||
|
||||
class MarketRegimeDetector:
|
||||
"""
|
||||
코스피 지수 수준 기반 시장 레짐 감지기
|
||||
|
||||
코스피 6300 시나리오:
|
||||
- 현재 한국 증시 역대 최고점(2021년 3300) 대비 약 2배 수준
|
||||
- BULL_EXTREME 레짐에 해당 → LSTM 단독 의존 지양, Transformer/Mamba 검토 필요
|
||||
- 추세 추종 강화 + 고점 리스크 관리 병행
|
||||
"""
|
||||
|
||||
# 레짐별 상세 파라미터
|
||||
_REGIME_PARAMS: Dict[MarketRegime, dict] = {
|
||||
MarketRegime.BULL_EXTREME: {
|
||||
"description": "코스피 극강세장 5000+ (6300 시나리오)",
|
||||
"recommended_strategy": (
|
||||
"추세 추종 극대화, 트레일링 스탑 확대(ATR×4), "
|
||||
"고점 과열 구간으로 포지션 축소 병행"
|
||||
),
|
||||
"buy_threshold_adj": -0.04, # 강세 모멘텀 → 진입 소폭 완화
|
||||
"position_size_adj": 0.75, # 고점 리스크로 포지션 축소
|
||||
"lstm_weight_adj": -0.12, # LSTM 비중 축소 (비선형 가격 동작)
|
||||
"model_recommendation": (
|
||||
"Temporal Fusion Transformer(TFT) 또는 Mamba(SSM) 교체 권고 - "
|
||||
"LSTM은 극강세 과열 구간에서 비선형 가격 동작 포착 한계"
|
||||
),
|
||||
"risk_level": "EXTREME",
|
||||
},
|
||||
MarketRegime.BULL_STRONG: {
|
||||
"description": "코스피 강상승장 3500~5000",
|
||||
"recommended_strategy": "추세 추종, 모멘텀 강화, 손절 완화(ATR×2.5)",
|
||||
"buy_threshold_adj": -0.03,
|
||||
"position_size_adj": 1.1,
|
||||
"lstm_weight_adj": 0.05,
|
||||
"model_recommendation": "현재 LSTM v3 적합 - 성능 모니터링 유지",
|
||||
"risk_level": "MEDIUM",
|
||||
},
|
||||
MarketRegime.BULL_NORMAL: {
|
||||
"description": "코스피 정상 상승장 2500~3500",
|
||||
"recommended_strategy": "기본 전략 유지 (기술+LSTM+LLM 균형)",
|
||||
"buy_threshold_adj": 0.0,
|
||||
"position_size_adj": 1.0,
|
||||
"lstm_weight_adj": 0.0,
|
||||
"model_recommendation": "현재 LSTM v3 최적 환경",
|
||||
"risk_level": "LOW",
|
||||
},
|
||||
MarketRegime.SIDEWAYS: {
|
||||
"description": "코스피 횡보장 2000~2500",
|
||||
"recommended_strategy": "박스권 매매, LLM 감성 비중 확대, 빠른 익절",
|
||||
"buy_threshold_adj": 0.03,
|
||||
"position_size_adj": 0.85,
|
||||
"lstm_weight_adj": -0.05,
|
||||
"model_recommendation": "현재 LSTM v3 적합 - 감성 분석 가중치 강화",
|
||||
"risk_level": "LOW",
|
||||
},
|
||||
MarketRegime.BEAR_MILD: {
|
||||
"description": "코스피 약세장 1500~2000",
|
||||
"recommended_strategy": "현금 비중 확대(50%+), 방어주 선별 매수",
|
||||
"buy_threshold_adj": 0.08,
|
||||
"position_size_adj": 0.5,
|
||||
"lstm_weight_adj": 0.0,
|
||||
"model_recommendation": "현재 LSTM v3 적합 - 리스크 관리 파라미터 강화",
|
||||
"risk_level": "HIGH",
|
||||
},
|
||||
MarketRegime.BEAR_SEVERE: {
|
||||
"description": "코스피 극약세장 1500 미만",
|
||||
"recommended_strategy": "전면 현금화, 매수 중단",
|
||||
"buy_threshold_adj": 0.20,
|
||||
"position_size_adj": 0.2,
|
||||
"lstm_weight_adj": 0.0,
|
||||
"model_recommendation": "매크로 팩터 기반 방어 모델 전환 필요",
|
||||
"risk_level": "EXTREME",
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def detect(
|
||||
cls,
|
||||
kospi_price: float,
|
||||
kospi_change_pct: float = 0.0,
|
||||
volatility_20d: float = 0.0,
|
||||
) -> RegimeAnalysis:
|
||||
"""
|
||||
코스피 지수 수준 + 변동성으로 시장 레짐 감지
|
||||
|
||||
Args:
|
||||
kospi_price: 현재 코스피 지수 (예: 2600, 6300)
|
||||
kospi_change_pct: 전일 대비 등락률 (%)
|
||||
volatility_20d: 20일 변동성 (선택, 0이면 무시)
|
||||
|
||||
Returns:
|
||||
RegimeAnalysis: 레짐 분석 결과 및 전략 파라미터
|
||||
"""
|
||||
# 1. 지수 수준으로 기본 레짐 결정
|
||||
if kospi_price >= 5000:
|
||||
regime = MarketRegime.BULL_EXTREME
|
||||
elif kospi_price >= 3500:
|
||||
regime = MarketRegime.BULL_STRONG
|
||||
elif kospi_price >= 2500:
|
||||
regime = MarketRegime.BULL_NORMAL
|
||||
elif kospi_price >= 2000:
|
||||
regime = MarketRegime.SIDEWAYS
|
||||
elif kospi_price >= 1500:
|
||||
regime = MarketRegime.BEAR_MILD
|
||||
else:
|
||||
regime = MarketRegime.BEAR_SEVERE
|
||||
|
||||
params = cls._REGIME_PARAMS[regime]
|
||||
|
||||
# 2. 변동성 기반 포지션 사이징 추가 조정
|
||||
position_adj = params["position_size_adj"]
|
||||
if volatility_20d > 30:
|
||||
position_adj *= 0.6 # 극단적 변동성 → 추가 50% 축소
|
||||
elif volatility_20d > 20:
|
||||
position_adj *= 0.8 # 높은 변동성 → 20% 축소
|
||||
|
||||
# 3. 급락 중 레짐 하향 조정 (패닉 감지)
|
||||
if kospi_change_pct <= -3.0:
|
||||
# 극단적 일일 급락 → 포지션 추가 축소
|
||||
position_adj *= 0.5
|
||||
print(f"[Regime] PANIC DETECTED (일일 {kospi_change_pct:.1f}%) → 포지션 50% 추가 축소")
|
||||
|
||||
return RegimeAnalysis(
|
||||
regime=regime,
|
||||
kospi_level=kospi_price,
|
||||
description=params["description"],
|
||||
recommended_strategy=params["recommended_strategy"],
|
||||
buy_threshold_adj=params["buy_threshold_adj"],
|
||||
position_size_adj=round(position_adj, 3),
|
||||
lstm_weight_adj=params["lstm_weight_adj"],
|
||||
model_recommendation=params["model_recommendation"],
|
||||
risk_level=params["risk_level"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate_model_for_regime(
|
||||
cls,
|
||||
regime: MarketRegime,
|
||||
backtest_sharpe: Optional[float] = None,
|
||||
backtest_winrate: Optional[float] = None,
|
||||
backtest_mdd: Optional[float] = None,
|
||||
) -> dict:
|
||||
"""
|
||||
현재 LSTM v3 모델이 해당 레짐에서 적합한지 검증
|
||||
|
||||
Returns:
|
||||
{
|
||||
"is_suitable": bool,
|
||||
"confidence_score": float (0~1),
|
||||
"recommendation": str,
|
||||
"should_replace": bool,
|
||||
"alternative_models": list[str],
|
||||
"reason": str,
|
||||
}
|
||||
"""
|
||||
result = {
|
||||
"is_suitable": True,
|
||||
"confidence_score": 0.75,
|
||||
"recommendation": "현재 LSTM v3 모델 유지",
|
||||
"should_replace": False,
|
||||
"alternative_models": [],
|
||||
"reason": "정상 상승장 구간 - LSTM v3 최적 환경",
|
||||
}
|
||||
|
||||
# 레짐 기반 기본 평가
|
||||
if regime == MarketRegime.BULL_EXTREME:
|
||||
result.update({
|
||||
"is_suitable": False,
|
||||
"confidence_score": 0.38,
|
||||
"recommendation": "Transformer 계열 모델 교체 강력 권고",
|
||||
"should_replace": True,
|
||||
"alternative_models": [
|
||||
"Temporal Fusion Transformer (TFT) - 장기 시계열 최강",
|
||||
"Mamba (SSM) - 초고속 추론 + 긴 컨텍스트",
|
||||
"PatchTST - Transformer 기반 주가 예측 특화",
|
||||
"TimesNet - 2D 시계열 변환 + CNN",
|
||||
"N-BEATS / N-HiTS - 해석 가능 딥러닝",
|
||||
],
|
||||
"reason": (
|
||||
"코스피 5000+ 극강세장에서 LSTM은 비선형적 가격 급등 패턴을 "
|
||||
"충분히 학습하지 못함. Attention 메커니즘만으로는 장기 상승 추세의 "
|
||||
"복잡한 의존성 포착에 한계 존재."
|
||||
),
|
||||
})
|
||||
elif regime == MarketRegime.BEAR_SEVERE:
|
||||
result.update({
|
||||
"is_suitable": False,
|
||||
"confidence_score": 0.30,
|
||||
"recommendation": "매크로 팩터 + Regime-Switching 모델 교체 권고",
|
||||
"should_replace": True,
|
||||
"alternative_models": [
|
||||
"Regime-Switching LSTM (HMM + LSTM)",
|
||||
"매크로 멀티팩터 모델 (환율, 금리, VIX 통합)",
|
||||
"GRU + Attention (LSTM 경량 대안)",
|
||||
],
|
||||
"reason": "극약세장에서는 기술적 지표보다 거시경제 팩터가 지배적",
|
||||
})
|
||||
elif regime == MarketRegime.BULL_STRONG:
|
||||
result.update({
|
||||
"confidence_score": 0.72,
|
||||
"reason": "강상승장 - LSTM 추세 학습 양호하나 성능 모니터링 필요",
|
||||
})
|
||||
elif regime == MarketRegime.SIDEWAYS:
|
||||
result.update({
|
||||
"confidence_score": 0.68,
|
||||
"reason": "횡보장 - LSTM 예측력 저하, LLM 감성 보완 필수",
|
||||
"recommendation": "현재 LSTM v3 유지 + LLM 감성 가중치 상향",
|
||||
})
|
||||
|
||||
# 백테스트 결과 반영
|
||||
if backtest_sharpe is not None:
|
||||
if backtest_sharpe < 0:
|
||||
result["confidence_score"] *= 0.5
|
||||
result["should_replace"] = True
|
||||
result["recommendation"] += " ⚠️ Sharpe < 0 → 즉시 교체 검토"
|
||||
elif backtest_sharpe < 0.5:
|
||||
result["confidence_score"] *= 0.75
|
||||
result["recommendation"] += f" (Sharpe={backtest_sharpe:.2f} 미흡)"
|
||||
|
||||
if backtest_winrate is not None and backtest_winrate < 45:
|
||||
result["confidence_score"] *= 0.8
|
||||
result["recommendation"] += f" (승률={backtest_winrate:.1f}% 미흡)"
|
||||
|
||||
if backtest_mdd is not None and backtest_mdd < -25:
|
||||
result["confidence_score"] *= 0.7
|
||||
result["should_replace"] = True
|
||||
result["recommendation"] += f" ⚠️ MDD={backtest_mdd:.1f}% 과다"
|
||||
|
||||
result["confidence_score"] = round(max(0.0, min(1.0, result["confidence_score"])), 3)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_regime_label(kospi_price: float) -> str:
|
||||
"""간략 레짐 라벨 반환 (로그/UI 표시용)"""
|
||||
if kospi_price >= 5000:
|
||||
return f"BULL_EXTREME({kospi_price:.0f})"
|
||||
elif kospi_price >= 3500:
|
||||
return f"BULL_STRONG({kospi_price:.0f})"
|
||||
elif kospi_price >= 2500:
|
||||
return f"BULL_NORMAL({kospi_price:.0f})"
|
||||
elif kospi_price >= 2000:
|
||||
return f"SIDEWAYS({kospi_price:.0f})"
|
||||
elif kospi_price >= 1500:
|
||||
return f"BEAR_MILD({kospi_price:.0f})"
|
||||
return f"BEAR_SEVERE({kospi_price:.0f})"
|
||||
Reference in New Issue
Block a user