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:
@@ -142,6 +142,12 @@ class Settings(BaseSettings):
|
||||
MAX_HOLDINGS_COUNT: int = 20 # 동시 보유 최대 20종목 (섹션 5.1)
|
||||
LEVERAGE_ETF_MAX_RATIO: float = 0.20 # 레버리지/인버스 ETF 최대 20% (섹션 5.1)
|
||||
|
||||
# [HIGH-4] 섹터 미매핑 종목에 "UNKNOWN" 단일 버킷으로 섹터당 최대 3종목 한도를 적용할지.
|
||||
# True로 켜면 §5.1 섹터캡이 전 종목에 적용되지만, _STOCK_CODE_SECTOR_MAP 커버리지가
|
||||
# 낮은 현재 상태에서는 총 보유가 ~3종목으로 묶여 MAX_HOLDINGS_COUNT(20)에 도달할 수 없다.
|
||||
# → 매핑 테이블 확충 + 백테스트 검증 후 활성화할 것 (correlation_filter 참고).
|
||||
SECTOR_CAP_UNMAPPED_ENABLED: bool = False
|
||||
|
||||
# 자본 배분 글로벌 가드 — 전 전략 합산 현금 사용 상한
|
||||
MAX_TOTAL_CAPITAL_USAGE: float = 0.95 # 전체 자본의 95%까지만 투자 (5% 현금 보유)
|
||||
|
||||
@@ -162,6 +168,18 @@ class Settings(BaseSettings):
|
||||
MDD_PAUSE_THRESHOLD: float = -10.0 # -10% MDD: 신규진입 중단 + 30% 축소
|
||||
MDD_FORCE_THRESHOLD: float = -20.0 # -20% MDD: 50% 강제 축소
|
||||
|
||||
# [항목7 2026-09-27] MDD 갱신 실패 시 fail-safe (신규 진입 차단)
|
||||
# swing/scheduler 의 mdd_manager.update() 가 peak/drawdown 을 갱신하는 유일한 실효
|
||||
# 경로다. KIS 잔고 조회가 계속 실패하면 peak/drawdown 이 그 시점에 얼어붙고
|
||||
# -10%/-20% 게이트는 "무증상으로" 영구 미발동한다 → 리스크 게이트가 눈이 먼 상태에서
|
||||
# 계속 진입하게 되고, 실제 낙폭이 -20% 를 넘어가도 추가 진입이 무제한 허용된다.
|
||||
# 거래 기회를 잃는 손실(<진입 몇 건)보다 게이트 없는 매매의 손실이 압도적으로 크므로
|
||||
# 기본값을 True(차단)로 둔다. 청산은 절대 막지 않는다(can_open_new_position 한정).
|
||||
MDD_BLIND_BLOCK_ENTRY: bool = True
|
||||
# 몇 회 연속 갱신 실패부터 차단할지. 1~2회는 KIS 순간 오류로 흔하므로
|
||||
# 알림 임계값(swing/scheduler.MDD_UPDATE_FAILURE_ALERT)과 동일한 5회로 맞춘다.
|
||||
MDD_BLIND_FAILURE_THRESHOLD: int = 5
|
||||
|
||||
# 가격 필터 (섹션 5.4)
|
||||
MIN_PRICE_KRW: int = 5000 # 국내 5,000원 이하 매수 금지
|
||||
MIN_PRICE_USD: float = 4.0 # 미국 $4 이하 매수 금지
|
||||
@@ -175,7 +193,10 @@ class Settings(BaseSettings):
|
||||
|
||||
# 연속 손실 관리 (섹션 5.5)
|
||||
CONSECUTIVE_LOSS_BAN_COUNT: int = 3 # 3회 연속 손실
|
||||
CONSECUTIVE_LOSS_BAN_DAYS: int = 30 # 30거래일 진입 금지
|
||||
# [HIGH-3 수정] 기존 30은 어디서도 쓰이지 않는 죽은 값이었고, 실제 ban 기간은
|
||||
# loss_tracker의 하드코딩 42캘린더일이었다. 문서 §5.1("3연패 → 42캘린더일 ≈ 30거래일")과
|
||||
# 실동작에 맞춰 42(캘린더일)로 정정하고, loss_tracker가 이 값을 읽도록 연결했다.
|
||||
CONSECUTIVE_LOSS_BAN_DAYS: int = 42 # 진입 금지 기간(캘린더일) — 30거래일 ≈ 42캘린더일
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# 평균회귀(스윙) 전략 (섹션 6)
|
||||
@@ -199,6 +220,39 @@ class Settings(BaseSettings):
|
||||
MEAN_REVERSION_ADX_STRONG_THRESHOLD: float = 15.0 # ADX < 15 → 추세 약함
|
||||
MEAN_REVERSION_ADX_STRONG_REDUCE: float = 30.0 # ADX > 30 → 추세 강함
|
||||
|
||||
# ── BEAR 진입 점수제 v2 (§8.5-9) ──────────────────────────────────────────
|
||||
# 기존 BEAR 분기는 RSI<35 AND %B≤0.20~0.30 AND 거래량>1.5×20MA AND 반전캔들의
|
||||
# 4중 AND 여서 사실상 미발화했다. v2는 %B/거래량/반전캔들을 가중 점수로 바꾸고
|
||||
# RSI 반등(rsi>rsi_prev)과 RSI 상한만 필수(AND)로 유지한다.
|
||||
# ⚠️ 백테스트 미검증 — True 전환 금지.
|
||||
# (1) 실전과 동일한 지표(Wilder RSI/ADX, indicators_core)로 재산출한 백테스트,
|
||||
# (2) BEAR 국면 구간만 분리한 승률·Profit Factor·MDD,
|
||||
# (3) 최대 연속 손실 횟수가 기존 경로 대비 악화되지 않음
|
||||
# — 3개를 모두 통과한 뒤에만 켠다. 하락장 진입을 늘리는 변경이므로 특히 위험.
|
||||
MEAN_REVERSION_BEAR_V2_ENABLED: bool = False # 기본 False = 기존 4중 AND 경로 그대로
|
||||
# RSI 상한(필수 AND): 기존 임계값(35) + relax, 단 hard cap(40) 이하.
|
||||
# 근거: 40은 BULL 진입 임계(46)보다 낮게 유지해 "하락장은 더 보수적"이라는 원칙을 깨지 않는다.
|
||||
MEAN_REVERSION_BEAR_V2_RSI_RELAX: float = 5.0
|
||||
MEAN_REVERSION_BEAR_V2_RSI_HARD_CAP: float = 40.0
|
||||
# 진입 임계 점수. 가중치 합은 %B 0.40 + 반전 0.35 + 거래량 0.25 = 1.00 (+RSI깊이 보너스 0.15).
|
||||
# 0.60 = "핵심 조건(%B) + 보조 1개" 또는 "보조 2개 + 깊은 과매도" 수준.
|
||||
MEAN_REVERSION_BEAR_V2_MIN_SCORE: float = 0.60
|
||||
# %B: 평균회귀 엣지의 직접 근거(하단 이탈 후 되돌림) → 최대 가중치.
|
||||
MEAN_REVERSION_BEAR_V2_W_PCT_B: float = 0.40
|
||||
# 반전캔들: 낙하 중 진입 방지 대리변수. RSI 반등과 상호보완이나 도지/데이터부족 시
|
||||
# 판정 불가라 필수화하면 다시 미발화한다 → 높은 가중치의 점수 항목으로만 둔다.
|
||||
MEAN_REVERSION_BEAR_V2_W_REVERSAL: float = 0.35
|
||||
# 거래량: 확인 지표. 하락장 대량 거래는 투매(추가 하락)일 수도 있어 단독 예측력이 약함 → 최소 가중치.
|
||||
MEAN_REVERSION_BEAR_V2_W_VOLUME: float = 0.25
|
||||
# RSI 깊이 보너스: RSI<20 → 만점, RSI<25 → 절반. 깊은 과매도의 반등 확률 프리미엄.
|
||||
MEAN_REVERSION_BEAR_V2_W_RSI_DEPTH: float = 0.15
|
||||
# %B 부분점수 배수: pct_b <= pct_b_max*1.6 이면 절반 점수.
|
||||
MEAN_REVERSION_BEAR_V2_PCT_B_RELAX_MULT: float = 1.6
|
||||
# %B 하드 상한(필수 AND 성격의 잔존 하한): BB 중심선(0.5) 위는 평균회귀 진입이 아니다.
|
||||
MEAN_REVERSION_BEAR_V2_PCT_B_HARD_CAP: float = 0.50
|
||||
# 거래량 중간 등급 배수: 1.2×20MA 이상이면 부분 점수(0.6배).
|
||||
MEAN_REVERSION_BEAR_V2_VOLUME_MID_MULT: float = 1.2
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# 단기(15분봉) 전략 (섹션 7.2)
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
@@ -259,6 +313,41 @@ class Settings(BaseSettings):
|
||||
DAY_TRADE_TRAILING_MIN_HOLD_SEC: int = 300 # 트레일링 활성화 최소 보유 시간 (초, 5분)
|
||||
DAY_TRADE_MIN_STOP_LOSS_PCT: float = 0.02 # 최소 손절폭 -2.0% (기존 1.0% → 노이즈 손절 방지)
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# 박스권(SIDEWAYS) 레인지 매매 (CLAUDE.md §8.5 항목 9)
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# ⚠️ **백테스트 미검증 — True 로 바꾸지 말 것 (CLAUDE.md §8.1-4).**
|
||||
# 이 전략은 SIDEWAYS 국면 전용이며, market_analyzer 의 SIDEWAYS 전면 차단(§6.1)을
|
||||
# 의도적으로 우회한다. False 인 동안 swing/entry_service.scan_range_trading_signals()
|
||||
# 는 함수 진입 즉시 [] 를 반환하므로 기존 동작에 어떤 영향도 없다.
|
||||
# 켜기 전 통과해야 할 검증: SIDEWAYS 표본 ≥ 60거래일 확보 / in-sample 승률 ≥ 50% ·
|
||||
# PF ≥ 1.3 · MDD ≥ -8% / walk-forward 4구간 전부 PF ≥ 1.0 · MDD -10% 초과 없음 /
|
||||
# 파라미터 민감도 그리드 과반 통과. 상세는 range_trading_strategy.py docstring 참조.
|
||||
RANGE_TRADE_ENABLED: bool = False
|
||||
RANGE_TRADE_LOOKBACK_DAYS: int = 20 # 박스 판정 구간(일). SIDEWAYS 기울기 비교 기간(20일)과 정합
|
||||
RANGE_TRADE_MIN_WIDTH_PCT: float = 6.0 # 박스 최소 폭. 왕복 이익이 거래비용(왕복 ~0.5%)+손절폭을 못 넘으면 무의미
|
||||
RANGE_TRADE_MAX_WIDTH_PCT: float = 20.0 # 박스 최대 폭. 이상이면 박스가 아니라 추세/고변동 구간
|
||||
RANGE_TRADE_TOUCH_TOLERANCE_PCT: float = 2.0 # 상·하단 접촉 허용 오차. 정확히 같은 값을 찍는 경우는 드뭄
|
||||
RANGE_TRADE_MIN_TOUCHES: int = 2 # 상단·하단 각 최소 접촉 봉 수 (지지/저항 확인)
|
||||
RANGE_TRADE_MIN_ALTERNATIONS: int = 2 # 상단↔하단 접촉 교대 최소 횟수 = 레인지 내 왕복 이력
|
||||
RANGE_TRADE_MAX_ADX: float = 20.0 # ADX 상한. SIDEWAYS 히스테리시스(진입 18/해제 22)의 중앙값
|
||||
RANGE_TRADE_ENTRY_ZONE_PCT: float = 25.0 # 박스 하단 25% 구간에서만 진입 (지지대 매수)
|
||||
RANGE_TRADE_BB_PCT_B_MAX: float = 0.25 # %B 상한. 평균회귀 BULL(0.20~0.35)보다 보수적 중간값
|
||||
RANGE_TRADE_RSI_ENTRY: float = 40.0 # 진입 RSI. 평균회귀 BULL(46)보다 타이트 — 미검증 전략이라 보수적
|
||||
RANGE_TRADE_VOLUME_RATIO: float = 1.0 # 거래량 ≥ 20일 평균 × 배수 (지지 반등 수급 확인, 과도한 요구는 표본 소멸)
|
||||
RANGE_TRADE_RSI_PARTIAL_EXIT: float = 55.0 # 1차 익절 RSI (§8.2 MEAN_REVERSION_RSI_EXIT_THRESHOLD 와 동일)
|
||||
RANGE_TRADE_RSI_FULL_EXIT: float = 65.0 # 전량 청산 RSI. 박스 상단에서 오래 버티지 않음 → 스윙(70)보다 빠르게
|
||||
RANGE_TRADE_EXIT_ZONE_PCT: float = 80.0 # 박스 상단 20% 진입 시 전량 익절 (상단 정확히 기다리면 미체결 위험)
|
||||
RANGE_TRADE_BOX_BREAK_PCT: float = 1.0 # 박스 하단 -1% 하향 이탈 → 즉시 전량 손절 (노이즈 여유 1%)
|
||||
RANGE_TRADE_ATR_STOP_MULTIPLIER: float = 2.0 # ATR 손절 배수. 스윙(2.5)보다 타이트 — 박스 폭이 좁아 여유가 불필요
|
||||
RANGE_TRADE_STOP_LOSS_PERCENT: float = 5.0 # 기본 손절 -5%. 스윙(-7%)보다 타이트, 박스 폭 하한(6%)과 정합
|
||||
RANGE_TRADE_TRAILING_ATR_MULT: float = 1.5 # 트레일링 = 고점 − 1.5×ATR (§5.2 동일)
|
||||
RANGE_TRADE_MIN_PROFIT_PCT: float = 0.5 # 익절 최소 수익률 (수수료+세금 ~0.4% 커버)
|
||||
RANGE_TRADE_MAX_POSITIONS: int = 3 # 동시 보유 상한 (미검증 전략 노출 최소화)
|
||||
RANGE_TRADE_MAX_CAPITAL_RATIO: float = 0.10 # 스윙 몫(SWING_MAX_CAPITAL_RATIO=0.70) **내부** 비율 → 총자본 7%
|
||||
RANGE_TRADE_POSITION_MULTIPLIER: float = 0.5 # 종목당 = MAX_POSITION_SIZE(4%) × 0.5 = 2% (미검증 전략)
|
||||
RANGE_TRADE_SCAN_LIMIT: int = 10 # 사이클당 차트 조회 종목 수 상한 (KIS 레이트 리밋)
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# 코스닥 스캐너 (섹션 7.1)
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
@@ -271,6 +360,31 @@ class Settings(BaseSettings):
|
||||
US_MAX_CAPITAL_RATIO: float = 0.30 # 전체 자본 대비 미국 주식 최대 비중 (30%)
|
||||
US_SCAN_INTERVAL: int = 300 # 미국장 스캔 간격 (초, 기본 5분)
|
||||
|
||||
# ── [§8.5-8 상수 편입 2026-09-27] us_stock_strategy / us_market_scheduler ──
|
||||
# 아래 키는 두 모듈에 하드코딩돼 있던 숫자를 **값 변경 없이** 그대로 옮긴 것이다
|
||||
# (CLAUDE.md §8.1-4: 백테스트 없이 전략값 변경 금지 — 이번 작업은 "출처 단일화"뿐).
|
||||
# US_MAX_CAPITAL_RATIO(위)는 모듈 상수와 값이 완전히 같았으므로 이 키를 정본으로 쓰고
|
||||
# 모듈 상수는 제거했다.
|
||||
# 국내 키와 값이 겹치는 것(US_MIN_PRICE_USD/US_STOP_LOSS_PCT/US_ATR_STOP_MULT)은
|
||||
# 국내 키를 재사용하지 않고 US 전용 키를 새로 뒀다 — 미국은 거래비용·틱·변동성이
|
||||
# 달라 앞으로 갈라질 값이고, 공유하면 국내 튜닝이 미국에 조용히 전파된다.
|
||||
US_MAX_POSITION_SIZE: float = 0.05 # 미국 종목당 최대 5% (국내 MAX_POSITION_SIZE=0.04 와 별개, 의도적으로 더 큼)
|
||||
US_RSI_BUY_THRESHOLD: float = 35.0 # RSI(14) 매수 상한 (국내 평균회귀 46 대비 보수적)
|
||||
US_BB_LOWER_MARGIN_LOW: float = 0.97 # 진입 허용 구간 하한 = BB하단 × 0.97 (-3%)
|
||||
US_BB_LOWER_MARGIN_HIGH: float = 1.02 # 진입 허용 구간 상한 = BB하단 × 1.02 (+2%)
|
||||
US_MIN_PRICE_USD: float = 4.0 # 페니스탁 필터(이 값 "이하" 진입 불가). 현재 MIN_PRICE_USD(4.0)와 값 동일 — 별 키로 분리 보관
|
||||
US_STOP_LOSS_PCT: float = -7.0 # 기본 손절 **음수 규약**(국내 STOP_LOSS_PERCENT=7.0 양수와 부호 규약 다름, 절대값은 현재 동일). us_backtest_engine 이 `pnl_pct <= US_STOP_LOSS_PCT` 로 음수 그대로 소비하므로 음수 유지
|
||||
US_ATR_STOP_MULT: float = 2.5 # ATR 손절 배수. 현재 ATR_STOP_MULTIPLIER(2.5)와 값 동일 — 별 키로 분리 보관
|
||||
US_FIRST_EXIT_RSI: float = 55.0 # 1차 익절 RSI 기준
|
||||
US_FIRST_EXIT_SELL_PCT: float = 0.30 # 1차 익절 매도 비율 30%
|
||||
US_TRAILING_STOP_PCT: float = 0.04 # 트레일링 스탑 고점 대비 -4%
|
||||
US_MAX_NEW_ENTRIES_PER_CYCLE: int = 3 # 스캔 사이클당 신규 진입 종목 수 상한
|
||||
# 환율 폴백: KIS 해외잔고로 환율을 역산할 수 없을 때(잔고 조회 실패, 또는 USD 평가액+예수금이 0)
|
||||
# 사용된다. 이 값은 포지션 사이징(KRW예산 ÷ 환율 → USD → 수량)과 TradeHistory.fx_rate 에
|
||||
# 그대로 들어가므로 **실제 주문 수량·기록 금액에 직접 영향**을 준다. 실환율과 크게 벌어지면
|
||||
# 주문 수량이 그만큼 어긋난다(예: 실제 1450 인데 1350 사용 → 예산 대비 약 7% 과다 주문).
|
||||
US_DEFAULT_FX_RATE: float = 1350.0
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# 필터 & 시장 국면 (섹션 5.4)
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user