fix: 매매 파라미터 3중 소스 정합 + 리스크 가드 복원 + 죽은 코드 2,695줄 제거

## 배경
"전략이 혼란스럽고 현재와 맞지 않는다"를 조사한 결과, 전략 설계가 아니라
전략을 검증했다는 도구와 파라미터 소스가 어긋나 있었다.

## 파라미터 소스 단일화
- 우선순위가 adaptive > strategy_cfg(DB) > config.py 인데 문서는 맨 마지막인
  config.py 를 기술하고 있었다. 11곳이 불일치(BULL RSI 46 vs DB 30, SL -7% vs -6%).
- DEFAULT_PARAMS 를 config.py 에 정합 + alembic 0012 로 기존 DB 행 갱신
  (운영자가 UI 로 바꾼 값은 보존, 옛 기본값과 같은 행만 갱신, 테이블 가드·멱등)

## 백테스트를 실전 로직에 정합
- calculate_adx_simple 이 정확히 period 배(14.0×) 발산 → Aggressive 게이트
  `ADX>25` 가 전 레짐 100% 통과. 전환 빈도 5.6%→2.0%(-65%), 성과 상방 편향
- 백테스트 RSI 는 SMA, 실전은 Wilder → 진입 판정 10~11% 불일치
- us_backtest_engine RSI 1봉 지연 제거 (룩어헤드 없음을 절단 테스트로 증명)
- backend/backtest_results/ 25건은 폐기 대상 (§8.4)

## 리스크 가드 복원
- 4% 절대 상한이 try 블록 안에 있어 예외 시 적응형 6~7% 가 통과 → try 밖으로
- 청산을 매수보다 먼저 평가 (보유 종목이 추천에 들면 손절 대신 물타기가 나갔다)
- 리스크 가드 4개를 매수 전용화 → MDD -10%·보유한도 도달 시에도 손절 작동
- MDD 갱신 5회 연속 실패 시 신규 진입 차단 fail-safe (청산은 계속 허용)
- 스윙 청산이 DT 보유분까지 매도하던 문제, sell_pct 기준 혼재 정정

## 죽은 코드 제거 (2,695줄)
- short_term_strategy, kosdaq_scanner, app/strategy/ 패키지
- app/trading/ + korea_investment_api_adapter (real_market_api 정본은
  korea_investment_api.py:1482, adapter 는 import 0건)

## 지표·사이징 단일화 (수치 무변경 증명)
- indicators_core.py: 3,274건 비교 최대 오차 0.000e+00
- sizing_core.py: 42,105건 비교 불일치 0
- 미국 전략 상수 13개 config 편입 (hex 까지 동일, 3,168 케이스 전수 비교)

## 테스트 안전 (실 KIS 호출 차단)
- 유닛 테스트가 실제 KIS 에 매도 주문을 2회 전송하고 있었다. 리팩터링이
  close_stale_positions 의 대상을 _execute_exit → _execute_exit_stale 로
  바꿨는데 목이 전자에 걸려 빗나갔다. test_api_endpoints 도 캡처된 싱글톤
  참조 누수로 실제 잔고 조회를 타 141초를 소비했다.
- backend/tests/conftest.py 신설: 주문·토큰·시세·잔고를 호출 즉시 예외로 차단.
  예외는 BaseException 파생(프로덕션 except Exception 폴백에 삼켜지지 않게),
  클래스 메서드에 적용(캡처된 참조까지 덮음), DATABASE_URL `_test` 가드 강제
- 전체 유닛 스위트: 365초/6 failed → 11.8초/0 failed (소켓 차단 하 559 passed)

## 신규 (둘 다 플래그 OFF, 백테스트 미검증)
- range_trading_strategy.py — SIDEWAYS 담당 전략이 없던 빈 칸. 미배선
- check_bear_buy_signal_v2 — BEAR 4중 AND(발화율 2.3%) → 점수제(12.4%)

## 문서
- CLAUDE.md 를 실제 코드 기준으로 재작성. §0 파라미터 우선순위, §4.1 테스트 규약,
  §6.4 문서화되지 않았던 진입 차단 게이트 9종, §8.4 백테스트 폐기 근거,
  §8.5 미해결 19항목
- 루트 md 6개 → 2개(docs/ 이동), 중복 배치 문서 병합, 스테일 worktree 제거

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-28 08:58:18 +09:00
parent b4608e5abf
commit 1b9257c4d2
71 changed files with 5510 additions and 3685 deletions

View File

@@ -57,7 +57,9 @@ DEFAULT_PARAMS: Dict[str, tuple] = {
"MAX_POSITION_SIZE": ("RISK", "종목당 최대 비중", "1종목당 총자산 대비 최대 비율 (0.04 = 4%)", "float", "0.04", "0.01", "0.20"),
"MAX_DAILY_TRADES": ("RISK", "일일 최대 매매 횟수", "하루 최대 신규 매수 횟수", "int", "5", "1", "50"),
"MAX_HOLDINGS_COUNT": ("RISK", "최대 동시 보유 종목", "동시에 보유 가능한 최대 종목 수", "int", "20", "1", "100"),
"STOP_LOSS_PERCENT": ("RISK", "기본 손절 비율", "저변동 종목 손절선 (%)", "float", "-6.0", "-20", "-1"),
# [2026-09-27] config.py 정합: 기본값 -6.0 → -7.0 (config.py STOP_LOSS_PERCENT=7.0, _META는 음수 규약 유지)
"STOP_LOSS_PERCENT": ("RISK", "기본 손절 비율", "저변동 종목 손절선 (%)", "float", "-7.0", "-20", "-1"),
# [2026-09-27] config.py 정합 검토: config.py STOP_LOSS_HIGH_VOL=10.0(양수 저장) vs 여기 -10.0 — 크기 동일, 단순 부호 규약 차이이므로 변경하지 않음
"STOP_LOSS_HIGH_VOL": ("RISK", "고변동 손절 비율", "고변동성 종목 손절선 (%)", "float", "-10.0", "-30", "-1"),
"TAKE_PROFIT_PERCENT": ("RISK", "기본 익절 비율", "익절 기본값 (%)", "float", "5.0", "1", "50"),
"ATR_STOP_MULTIPLIER": ("RISK", "ATR 손절 배수", "ATR × 배수 = 손절선", "float", "2.5", "1.0", "5.0"),
@@ -67,7 +69,7 @@ DEFAULT_PARAMS: Dict[str, tuple] = {
"MIN_PRICE_KRW": ("RISK", "국내 최소 주가 (원)", "이 가격 미만 종목 매수 금지", "int", "5000", "100", "50000"),
"MIN_PRICE_USD": ("RISK", "해외 최소 주가 (달러)", "이 가격 미만 종목 매수 금지", "float", "4.0", "0.1", "100"),
"CONSECUTIVE_LOSS_BAN_COUNT":("RISK", "연속 손실 금지 횟수", "N회 연속 손실 시 해당 종목 진입 금지", "int", "3", "1", "10"),
"CONSECUTIVE_LOSS_BAN_DAYS": ("RISK", "연속 손실 금지 기간(일)", "진입 금지 유지 거래일 수", "int", "30", "1", "180"),
"CONSECUTIVE_LOSS_BAN_DAYS": ("RISK", "연속 손실 금지 기간(일)", "진입 금지 유지 캘린더일 수 (30거래일 ≈ 42)", "int", "42", "1", "180"),
"MEAN_REVERSION_MAX_CAPITAL":("RISK", "평균회귀 최대 자본 비율", "평균회귀 전략에 투입 가능한 총자본 비율", "float", "0.30", "0.05", "1.0"),
# ── FILTER ────────────────────────────────────────────────────────────────
@@ -90,25 +92,32 @@ DEFAULT_PARAMS: Dict[str, tuple] = {
"WATCHLIST_EXIT_SCORE": ("FILTER", "관심종목 제거 점수", "이 점수 이하이면 관심종목에서 제거", "int", "20", "0", "60"),
# ── MEAN_REVERSION ────────────────────────────────────────────────────────
"MEAN_REVERSION_RSI_THRESHOLD": ("MEAN_REVERSION", "평균회귀 RSI 과매도 기준", "RSI 이 값 이하면 과매도로 진입 검토", "float", "30.0", "10", "50"),
# [2026-09-27] config.py 정합: 기본값 30.0 → 46.0 (CLAUDE.md §8.2 BULL 진입 RSI<46), max 50 → 60
"MEAN_REVERSION_RSI_THRESHOLD": ("MEAN_REVERSION", "평균회귀 RSI 과매도 기준", "RSI 이 값 이하면 과매도로 진입 검토", "float", "46.0", "10", "60"),
"MEAN_REVERSION_MULTIPLIER_STRONG": ("MEAN_REVERSION", "강한 신호 포지션 배수", "강한 신호 시 기본 포지션 대비 배수", "float", "1.50", "1.0", "3.0"),
"MEAN_REVERSION_MULTIPLIER_NORMAL": ("MEAN_REVERSION", "중간 신호 포지션 배수", "중간 신호 시 기본 포지션 대비 배수", "float", "1.25", "1.0", "2.0"),
"MEAN_REVERSION_MULTIPLIER_WEAK": ("MEAN_REVERSION", "약한 신호 포지션 배수", "약한 신호 시 기본 포지션 대비 배수", "float", "1.00", "0.5", "1.5"),
# ── DAY_TRADE ─────────────────────────────────────────────────────────────
"DAY_TRADE_ENABLED": ("DAY_TRADE", "데이트레이딩 활성화", "데이트레이딩 전략 On/Off", "bool", "True", None, None),
"DAY_TRADE_MAX_POSITIONS": ("DAY_TRADE", "동시 최대 종목 수", "데이트레이딩 동시 보유 최대 종목 수", "int", "3", "1", "20"),
"DAY_TRADE_CAPITAL_RATIO": ("DAY_TRADE", "전체 자본 비율 상한", "데이트레이딩에 투입 가능한 총자본 비율", "float", "0.25", "0.05","1.0"),
"DAY_TRADE_PER_STOCK_RATIO": ("DAY_TRADE", "종목당 자본 비율", "1종목당 최대 투입 자본 비율", "float", "0.10", "0.01","0.5"),
# [2026-09-27] config.py 정합: 기본값 3 → 5
"DAY_TRADE_MAX_POSITIONS": ("DAY_TRADE", "동시 최대 종목 수", "데이트레이딩 동시 보유 최대 종목 수", "int", "5", "1", "20"),
# [2026-09-27] config.py 정합: 기본값 0.25 → 0.30 (스윙:DT = 70:30)
"DAY_TRADE_CAPITAL_RATIO": ("DAY_TRADE", "전체 자본 비율 상한", "데이트레이딩에 투입 가능한 총자본 비율", "float", "0.30", "0.05","1.0"),
# [2026-09-27] config.py 정합: 기본값 0.10 → 0.06 (min 0.01 이므로 범위 조정 불필요)
"DAY_TRADE_PER_STOCK_RATIO": ("DAY_TRADE", "종목당 자본 비율", "1종목당 최대 투입 자본 비율", "float", "0.06", "0.01","0.5"),
"DAY_TRADE_STOP_LOSS_PCT": ("DAY_TRADE", "데이트레이딩 손절 비율", "종목 손절선 (%)", "float", "0.02", "0.005","0.1"),
"DAY_TRADE_DAILY_LOSS_LIMIT": ("DAY_TRADE", "일일 손실 한도", "일일 손실 한도 초과 시 데이트레이딩 중단 (%)", "float", "-0.03", "-0.2", "-0.005"),
"DAY_TRADE_EOD_FLAT_HOUR": ("DAY_TRADE", "EOD 강제 청산 시 (시)", "장 마감 전 강제 청산 시작 시각 (시)", "int", "15", "13", "15"),
"DAY_TRADE_EOD_FLAT_MIN": ("DAY_TRADE", "EOD 강제 청산 시 (분)", "장 마감 전 강제 청산 시작 시각 (분)", "int", "20", "0", "59"),
"DAY_TRADE_ORB_VOL_MULT": ("DAY_TRADE", "ORB 거래량 배수", "ORB 돌파 확인용 최소 거래량 배수", "float", "1.5", "1.0", "5.0"),
"DAY_TRADE_TRAILING_TRIGGER": ("DAY_TRADE", "트레일링 발동 수익률", "이 수익률 초과 시 트레일링 스탑 발동", "float", "0.015", "0.005","0.1"),
# [2026-09-27] config.py 정합: 기본값 0.015 → 0.01 (min 0.005 이므로 범위 조정 불필요)
"DAY_TRADE_TRAILING_TRIGGER": ("DAY_TRADE", "트레일링 발동 수익률", "이 수익률 초과 시 트레일링 스탑 발동", "float", "0.01", "0.005","0.1"),
"DAY_TRADE_RSI_BREAKEVEN_THRESHOLD": ("DAY_TRADE", "RSI_BB 진입 기준", "5분봉 RSI 이 값 이하이면 RSI_BB 진입", "float", "40.0", "20", "60"),
"DAY_TRADE_ORB_STOP_LOSS_PCT": ("DAY_TRADE", "ORB 손절 비율", "ORB 전략 손절선", "float", "0.02", "0.005","0.1"),
"DAY_TRADE_REVERSION_STOP_LOSS_PCT": ("DAY_TRADE", "VWAP/RSI_BB 손절 비율", "VWAP/RSI_BB 전략 손절선", "float", "0.025", "0.005","0.1"),
# [2026-09-27] config.py 정합: 기본값 0.02 → 0.03 (노이즈 손절 방지)
"DAY_TRADE_ORB_STOP_LOSS_PCT": ("DAY_TRADE", "ORB 손절 비율", "ORB 전략 손절선", "float", "0.03", "0.005","0.1"),
# [2026-09-27] config.py 정합: 기본값 0.025 → 0.035 (반등 여유 확보)
"DAY_TRADE_REVERSION_STOP_LOSS_PCT": ("DAY_TRADE", "VWAP/RSI_BB 손절 비율", "VWAP/RSI_BB 전략 손절선", "float", "0.035", "0.005","0.1"),
"DAY_TRADE_TRAILING_ATR_MULT": ("DAY_TRADE", "트레일링 ATR 배수", "트레일링 스탑 계산 ATR 배수 (whipsaw 방지 1.5)", "float", "1.5", "0.5", "3.0"),
"DAY_TRADE_ORB_BREAKOUT_MARGIN": ("DAY_TRADE", "ORB 돌파 여유 비율", "ORB 돌파 판정 최소 여유율", "float", "0.002", "0", "0.02"),
"DAY_TRADE_ENTRY_CUTOFF_HOUR": ("DAY_TRADE", "신규 진입 마감 시 (시)", "이 시각 이후 신규 진입 불가 (시)", "int", "15", "9", "15"),
@@ -117,7 +126,8 @@ DEFAULT_PARAMS: Dict[str, tuple] = {
"DAY_TRADE_EOD_MIN": ("DAY_TRADE", "EOD 청산 시각 (분)", "EOD 강제 청산 시작 시각 (분)", "int", "20", "0", "59"),
# ── GENERAL ───────────────────────────────────────────────────────────────
"ANALYSIS_STOCK_COUNT": ("GENERAL", "배치 분석 종목 수", "아침 배치에서 분석할 최대 종목 수", "int", "150", "10", "1000"),
# [2026-09-27] config.py 정합: 기본값 150 → 300
"ANALYSIS_STOCK_COUNT": ("GENERAL", "배치 분석 종목 수", "아침 배치에서 분석할 최대 종목 수", "int", "300", "10", "1000"),
"AUTO_TRADE_TOP_N_PER_RULE": ("GENERAL", "규칙당 자동매매 종목 수", "매매 규칙 1개당 자동 실행 최대 종목 수", "int", "2", "1", "10"),
"ML_ENSEMBLE_ENABLED": ("GENERAL", "ML 앙상블 활성화", "LightGBM + LSTM + RF 앙상블 모델 사용", "bool", "True", None, None),
"ML_ENSEMBLE_RULE_WEIGHT": ("GENERAL", "룰베이스 스코어 비중", "앙상블 스코어에서 룰베이스 비중", "float", "0.3", "0", "1.0"),
@@ -128,7 +138,8 @@ DEFAULT_PARAMS: Dict[str, tuple] = {
"MCP_CONFIDENCE_THRESHOLD": ("GENERAL", "MCP AI 최소 신뢰도", "MCP AI 신뢰도 최솟값 (0~1)", "float", "0.5", "0", "1.0"),
"SHORT_TERM_MIN_ATR_RATIO": ("GENERAL", "단기 전략 최소 ATR 비율", "단기 전략 진입 조건: ATR/close 최솟값", "float", "0.008", "0", "0.1"),
"SHORT_TERM_RSI_LOOKBACK": ("GENERAL", "단기 전략 RSI 반등 확인 봉", "RSI 반등 확인용 과거 봉 수", "int", "3", "1", "10"),
"SHORT_TERM_VOLUME_MULTIPLIER": ("GENERAL", "단기 전략 거래량 배수", "단기 전략 진입 조건: 거래량 배수", "float", "1.5", "1.0", "5.0"),
# [2026-09-27] config.py 정합: 기본값 1.5 → 1.3
"SHORT_TERM_VOLUME_MULTIPLIER": ("GENERAL", "단기 전략 거래량 배수", "단기 전략 진입 조건: 거래량 배수", "float", "1.3", "1.0", "5.0"),
}