fix: 매수 차단·백테스트 왜곡 유발한 버그 5종 수정

수개월간 매수 0건 + 백테스트가 손실로 보이던 근본 원인들을 수정.

- ADX 발산(mean_reversion_strategy, run_backtest): Wilder 평활에 /period
  누락으로 ADX가 100 초과 발산 → 실서비스 SIDEWAYS 게이트(ADX<20) 영구
  무력화. 공식 복원(표준 Wilder와 대수적으로 동일 검증).
- 백테스트 워밍업(run_backtest): 국면판정 220봉을 start_date부터 세서
  14개월 미만 백테스트의 ~90%가 강제 BEAR → 가짜 손실. start_date 340일
  전부터 프리로드하고 성과는 sim_start_date부터만 집계. CAGR 분모도 워밍업
  제외한 실제 거래일로 수정. → 최근1년 -0.33%→+6.95%, 가산성 회복.
- ML HOLD 게이트(trade_executor): 신뢰도 무관 매수 차단 → 데이터 부족으로
  ML이 실패(conf=0)한 HOLD까지 모든 매수를 막던 것을 conf>=0.6 조건부로.
- 빈 Redis 추천 캐시(swing/scheduler): 빈 캐시를 '히트'로 처리해 DB 폴백을
  건너뛰던 것을 recommendations_by_rule 비면 DB 폴백하도록.
- alembic 로깅(env.py, main.py): lifespan의 alembic upgrade가 fileConfig로
  앱 로거를 꺼 startup 이후 로그 전멸 → 스킵(스키마는 create_all 담당).

검증: code-verifier 통과(실전 경로 CONFIRMED 버그 없음), walk-forward 6/9구간
양(+)·과적합 아님(조건부 견고). 서바이버십 편향은 별도 이슈로 검증 진행 중.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBmzf1HDnvFHLraJ8eaaKJ
This commit is contained in:
2026-09-16 10:03:56 +09:00
parent 3965760021
commit a0973812fb
7 changed files with 112 additions and 26 deletions

View File

@@ -147,19 +147,17 @@ async def lifespan(app: FastAPI):
logger.error(traceback.format_exc())
raise
# ── Alembic 자동 마이그레이션 (서버 이전 시 DB 스키마 자동 동기화) ──────
try:
from alembic.config import Config as AlembicConfig
from alembic import command as alembic_command
import os
alembic_ini = os.path.join(os.path.dirname(os.path.dirname(__file__)), "alembic.ini")
if os.path.exists(alembic_ini):
alembic_cfg = AlembicConfig(alembic_ini)
alembic_cfg.set_main_option("script_location", os.path.join(os.path.dirname(os.path.dirname(__file__)), "alembic"))
alembic_command.upgrade(alembic_cfg, "head")
logger.info("✅ Alembic migration completed (upgrade head)")
except Exception as e:
logger.warning(f"⚠️ Alembic migration 실패 (non-critical): {e}")
# ── Alembic 자동 마이그레이션: 의도적으로 비활성화 ────────────────────────
# [FIX] alembic_command.upgrade()를 lifespan에서 호출하면 두 가지 문제가 있었다:
# (1) env.py가 이벤트 루프 내부에서 asyncio.run()을 호출 → 항상 실패
# ("asyncio.run() cannot be called from a running event loop"). 즉 수개월간
# 실제 마이그레이션이 한 번도 동작하지 않았다.
# (2) 그 실패 이전에 env.py 모듈 로딩 시 fileConfig()가 alembic.ini의 root 로거
# 설정(level=WARN)으로 앱 로깅을 덮어써, startup 이후 모든 INFO 로그(스케줄러
# 사이클/진입 로그 등)가 침묵 → 매매 관측 불가의 원인.
# 스키마는 위의 create_all()로 생성되며, 컬럼 변경 등 실제 마이그레이션은 배포 시
# CLI(`alembic upgrade head`)로 수행한다. 따라서 여기서는 호출하지 않는다.
logger.info("ℹ️ Alembic auto-migration skipped (schema via create_all; migrations run via CLI)")
# ── KRX 일봉 데이터 초기 수집 (DB 비어있을 때만 백그라운드 실행) ──────
try: