#04 1분봉 트레일링을 처음부터 켜다 — 백테스트-라이브 정합성 통일기

오늘의 질문: “왜 백테스트랑 라이브가 다르게 동작하지?”

HMA 트레이딩 봇을 운영하면서 계속 신경 쓰이던 부분이 있었다. 백테스트에서는 1분봉 기준으로 트레일링 스탑이 처음부터 촘촘하게 움직이는데, 라이브 엔진에서는 일정 수익(profit_mid) 구간에 도달해야만 1분봉 보호 루프가 켜지는 구조였다.

겉보기엔 비슷해 보이지만, 실제로는 세 가지 차이가 있었다:

  1. 시작 시점 — 백테스트는 진입 즉시, 라이브는 수익 도달 후
  2. 데이터 소스 — 백테스트는 1분봉 H/L, 라이브는 ticker 현재가만
  3. trail anchor — 백테스트는 누적 최고점, 라이브는 현재 바의 고점

이 세 가지를 하나씩 잡아나간 이야기다.

Step 1: profit_mid 게이팅을 없애면?

먼저 use_profit_mid_trailing_1m_activation 플래그를 ON/OFF로 돌려봤다. 2025년 1월부터 최근까지의 백테스트 결과:

지표 OFF (1m 즉시) ON (profit_mid 이후)
Profit Factor 1.39 1.35
Sharpe 3.45 3.25
승률 43.7% 43.1%
MFE>1R 후 손실 48건 57건

1분봉을 처음부터 쓰는 게 모든 지표에서 앞섰다. 게이팅의 원래 의도는 “초반에 너무 타이트하게 잡히는 걸 방지”하는 것이었는데, 실제로는 1분봉의 세밀한 추적 덕에 수익 구간에서 놓치는 트레이드가 줄어드는 효과가 더 컸다.

Step 2: 라이브 엔진도 1분봉 OHLCV로

기존 라이브 엔진은 60초마다 fetch_ticker로 현재가 하나만 가져왔다. 그 사이에 고점이 3050을 찍었다가 내려와도, 조회 시점에 3020이면 3050은 기록되지 않는다.

해결은 간단했다. fetch_ohlcv('1m', limit=1)로 바꿔서 마지막 완성 1분봉의 H/L을 가져오는 것. API 호출 비용도 동일하고, 이렇게 하면 백테스트가 보는 데이터와 라이브가 보는 데이터가 같아진다.

Step 3: trail anchor — 현재 바 vs 누적 최고점

여기서 예상치 못한 차이를 하나 더 발견했다. 백테스트는 포지션의 역대 최고점(max_fav_move)을 anchor로 쓰는데, 라이브는 그냥 현재 바의 고점을 anchor로 쓰고 있었다.

예를 들어 고점이 3100이었다가 현재 바 고점이 3050이고, adaptive mult가 0.4로 타이트해졌을 때:

  • Cumulative: trail_sl = 3100 – ATR×0.4 = 3080
  • Bar: trail_sl = 3050 – ATR×0.4 = 3030

“이건 파라미터 최적화 문제 아닌가? bar에 맞춰 파라미터를 다시 튜닝하면 비슷하지 않을까?” 좋은 질문이었고, 실제로 양쪽 모두 파라미터를 최적화해서 비교했다.

지표 Cumulative (최적) Bar (최적)
PnL +12.0% 기준 -5.0%
Profit Factor 1.31 1.27
MDD -29.3% -40.8%
Sharpe 2.19 1.92

공정하게 재최적화해도 cumulative가 확실히 우세했다. 특히 MDD 차이가 -29% vs -41%로 압도적이었다. 그래서 라이브 엔진의 30분/1분 trailing 모두 누적 최고점 anchor로 통일했다.

파라미터 최적화

정합성을 맞춘 후, 135개 조합을 스윕해서 최적 파라미터를 탐색했다. 종합 점수(PF 30% + Sharpe 25% + MDD 25% + PnL 20%) 기준 결과:

파라미터 기존값 최적값
adaptive_trail_mult_wide 4.5 5.0
adaptive_trail_profit_mid 1.0 0.75
adaptive_trail_profit_tight 2.0 2.25

slope, mult_mid, mult_tight도 125개 조합을 별도로 돌렸는데, 현재 값(3.0, 1.75, 0.4)이 상위 6%(8위/125개)로 이미 충분히 좋았다. 상위권은 PnL이 더 높지만 MDD가 -37~41%로 크게 악화되는 구간이라, MDD와 Sharpe를 우선하는 현재 값이 라이브 운용에 더 적합하다고 판단했다.

최종 결과

지표 변경 전 변경 후 변화
Profit Factor 1.25 1.31 +0.06
Sharpe 1.96 2.19 +0.23
MDD -33.7% -29.3% +4.4%p
Trail Capture% 51% 53% +2%p
MFE>1R 후 손실 96건 83건 -13건

정리

이번 작업의 핵심은 “성능을 높이자”가 아니라 “백테스트와 라이브가 같은 규칙으로 움직이게 하자”였다. 결과적으로 정합성을 맞추는 과정에서 성능도 따라왔다.

  • 불필요한 use_profit_mid_trailing_1m_activation 플래그를 제거하고 코드를 단순화했다
  • 라이브 엔진이 백테스트와 동일한 데이터(1m OHLCV H/L)와 동일한 anchor(누적 최고점)를 사용하게 됐다
  • 파라미터는 “가장 높은 PnL”이 아니라 “MDD와 Sharpe 밸런스”로 선택했다

백테스트 결과를 믿으려면, 먼저 라이브와 같은 조건에서 돌아가야 한다. 당연한 말인데 실제로 맞추려면 꽤 디테일한 작업이 필요했다.

댓글 남기기