자동매매 봇, 시작하자마자 풀사이즈?
자동매매 봇을 켜자마자 100% 자금으로 매매를 시작하는 것은 가장 흔하면서도 위험한 실수입니다. 시장 상황 파악 없이 진입하면 슬리피지, 잘못된 시그널, 데이터 불안정 등으로 초기 손실이 발생합니다. 이 글에서는 자동매매 워밍업(Warm-up) 전략을 설계하는 방법을 다룹니다.
워밍업이 필요한 이유
자동매매 봇이 처음 시작되면 여러 가지 불확실성이 존재합니다.
- 지표 초기화 부족: 이동평균, RSI, MACD 등 기술 지표는 충분한 과거 데이터가 쌓여야 정확한 값을 산출합니다. 200일 이동평균을 계산하려면 최소 200개의 캔들이 필요합니다.
- 시장 레짐 미파악: 현재 시장이 추세장인지 횡보장인지 판단하지 못한 상태에서 진입하면 전략과 맞지 않는 포지션을 잡게 됩니다.
- 연결 안정성 미확인: API 응답 속도, 웹소켓 안정성, 호가 데이터 정합성 등이 검증되지 않은 상태입니다.
- 슬리피지 추정 불가: 실제 체결 환경의 슬리피지를 모르면 백테스트 결과와 괴리가 발생합니다.
3단계 워밍업 프레임워크
실전에서 검증된 워밍업 프레임워크는 관찰 → 소량 진입 → 정상 운영 3단계로 구성됩니다.
1단계: 관찰 모드 (Observation Phase)
봇이 시작되면 일정 기간 동안 매매 없이 데이터만 수집합니다. 이 단계에서 기술 지표를 초기화하고 시장 상태를 파악합니다.
class WarmupManager:
def __init__(self, observation_candles=50, ramp_up_trades=10):
self.phase = "OBSERVATION"
self.candles_collected = 0
self.observation_target = observation_candles
self.trades_in_ramp = 0
self.ramp_up_target = ramp_up_trades
self.size_multiplier = 0.0
def on_new_candle(self):
if self.phase == "OBSERVATION":
self.candles_collected += 1
if self.candles_collected >= self.observation_target:
self.phase = "RAMP_UP"
self.size_multiplier = 0.25
print("✅ 관찰 완료 → 소량 진입 단계 전환")
def get_position_size(self, base_size: float) -> float:
if self.phase == "OBSERVATION":
return 0.0
return base_size * self.size_multiplier
observation_candles=50은 사용하는 지표에 따라 조정합니다. 가장 긴 룩백 기간의 1.5배를 권장합니다. 예를 들어 200 EMA를 사용한다면 최소 300개의 캔들을 관찰합니다.
2단계: 소량 진입 (Ramp-up Phase)
관찰이 끝나면 정상 포지션의 25%부터 시작하여 점진적으로 사이즈를 늘립니다. 각 거래의 실제 슬리피지와 체결률을 측정하며, 문제가 없으면 사이즈를 증가시킵니다.
def on_trade_complete(self, trade_result):
if self.phase != "RAMP_UP":
return
self.trades_in_ramp += 1
slippage = abs(trade_result['expected_price'] - trade_result['actual_price'])
# 슬리피지가 임계값 초과 시 사이즈 증가 보류
if slippage > trade_result['expected_price'] * 0.002:
print(f"⚠️ 슬리피지 과다: {slippage:.4f}, 사이즈 유지")
return
# 정상이면 사이즈 점진적 증가
progress = self.trades_in_ramp / self.ramp_up_target
self.size_multiplier = min(1.0, 0.25 + 0.75 * progress)
if self.size_multiplier >= 1.0:
self.phase = "NORMAL"
print("🚀 워밍업 완료 → 정상 운영 모드")
핵심은 슬리피지 임계값입니다. 실제 체결이 기대 가격 대비 0.2% 이상 벗어나면 사이즈 증가를 보류합니다. 이렇게 하면 유동성이 낮은 시간대에 과도한 포지션을 잡는 것을 방지할 수 있습니다.
3단계: 정상 운영 (Normal Phase)
소량 진입 단계에서 모든 검증이 완료되면 100% 사이즈로 전환합니다. 이후에도 주기적으로 슬리피지와 체결률을 모니터링하여, 이상 징후가 감지되면 다시 사이즈를 축소하는 동적 조절이 필요합니다.
시간 기반 워밍업 vs 이벤트 기반 워밍업
워밍업 조건을 설정하는 방식은 두 가지입니다.
| 구분 | 시간 기반 | 이벤트 기반 |
|---|---|---|
| 조건 | 봇 시작 후 N분 경과 | 캔들 N개 수집 완료 |
| 장점 | 구현 간단 | 지표 정확도 보장 |
| 단점 | 캔들 부족 가능 | 대기 시간 가변적 |
| 추천 | 단타/스캘핑 | 스윙/포지션 |
실전에서는 두 가지를 조합하는 것이 가장 안전합니다. “캔들 50개 수집 AND 최소 10분 경과” 같은 복합 조건을 사용하면 됩니다.
재시작 시 워밍업 단축
봇이 장애로 재시작될 때마다 전체 워밍업을 반복하면 비효율적입니다. 체크포인트에 워밍업 상태를 저장하여 빠르게 복원할 수 있습니다. 체크포인트 패턴에 대한 자세한 내용은 자동매매 장애 복구 설계 글을 참고하세요.
import json, time
from pathlib import Path
def save_warmup_state(manager: WarmupManager, indicators: dict):
state = {
"phase": manager.phase,
"size_multiplier": manager.size_multiplier,
"candles_collected": manager.candles_collected,
"indicator_cache": indicators,
"saved_at": time.time()
}
Path("warmup_state.json").write_text(json.dumps(state))
def restore_warmup(manager: WarmupManager) -> bool:
path = Path("warmup_state.json")
if not path.exists():
return False
state = json.loads(path.read_text())
# 1시간 이내 저장된 상태만 복원
if time.time() - state["saved_at"] > 3600:
return False
manager.phase = state["phase"]
manager.size_multiplier = state["size_multiplier"]
manager.candles_collected = state["candles_collected"]
print(f"♻️ 워밍업 상태 복원: {manager.phase}, 사이즈 {manager.size_multiplier:.0%}")
return True
저장된 상태가 1시간 이상 오래되면 시장 상황이 변했을 수 있으므로 처음부터 워밍업을 다시 수행합니다.
전략별 워밍업 파라미터 가이드
- 스캘핑 (1분봉): 관찰 30캔들, 소량 진입 20회, 최소 대기 5분
- 데이트레이딩 (15분봉): 관찰 50캔들, 소량 진입 10회, 최소 대기 30분
- 스윙 (4시간봉): 관찰 100캔들, 소량 진입 5회, 최소 대기 없음
- 페어 트레이딩: 관찰 200캔들 (공적분 재검증용), 소량 진입 3회
페어 트레이딩처럼 통계적 관계에 의존하는 전략은 워밍업 기간이 길어야 합니다. 관련 전략은 파이썬 페어 트레이딩 전략에서 확인할 수 있습니다.
마무리
워밍업 전략은 자동매매의 첫 5분을 살리는 기술입니다. 봇을 켜자마자 풀사이즈로 진입하는 것은 눈을 감고 고속도로에 합류하는 것과 같습니다. 관찰 → 소량 진입 → 정상 운영의 3단계를 적용하면 초기 손실을 크게 줄이고, 전략의 실제 성능을 정확하게 측정할 수 있습니다. 특히 슬리피지 기반 사이즈 조절은 실전에서 즉시 효과를 볼 수 있는 기법이니 반드시 적용해 보시기 바랍니다.