“파라미터를 일일이 바꿔가며 테스트하는 게 너무 귀찮아”
솔직히 이건 Leo가 아니라 내가 먼저 한 말이야.
bb_bounce의 RSI 과매도 기준이 30이 좋을까 35가 좋을까 40이 좋을까? TP는 2%? 2.5%? 3%? 볼린저 밴드 기간은 15? 20? 25? 표준편차는 1.5? 2.0? 2.5?
이것만 해도 조합이 수백 개야. 하나하나 백테스트 돌리면 며칠이 걸려. 6개 전략 × 3개 코인이면 18번. 각각 수백 개 조합. 인간이 할 일이 아니지.
Leo: “그러면 자동으로 돌리면 안 돼?”
나: “유전 알고리즘으로 하면 돼.”
Leo: “유전? DNA 그거?”
나: “비슷해. 파라미터 조합을 생물처럼 교배시키고 자연선택하는 거야.”
유전 알고리즘이 뭔데?
자연에서 영감 받은 최적화 방법이야. 다윈의 진화론을 코드로 옮긴 거지.
1. 개체군 생성 — 랜덤 파라미터 조합 40개를 만들어 (인구)
# bb_bounce의 파라미터 탐색 공간
PARAM_SPACE = {
'bb_period': {'min': 14, 'max': 30, 'default': 20},
'bb_std': {'min': 1.5, 'max': 3.0, 'default': 2.0},
'rsi_oversold': {'min': 25, 'max': 45, 'default': 40},
'rsi_overbought': {'min': 55, 'max': 75, 'default': 60},
'tp_pct': {'min': 1.5%, 'max': 4%, 'default': 2%},
'sl_pct': {'min': 1%, 'max': 2.5%, 'default': 1.5%},
}
2. 적합도 평가 — 각 개체(파라미터 조합)로 백테스트해서 점수 매김
def fitness_score(summary):
"""PF × 0.4 + 샤프비율 × 0.3 + (1 - MDD) × 0.3"""
# 최소 15건 미만이면 0점 (통계 신뢰도 부족)
if summary['total_trades'] < 15:
return 0.0
3. 선택 — 점수 높은 상위 4개는 무조건 생존 (엘리트)
4. 교배 — 두 부모의 파라미터를 섞어서 자식 생성
def crossover(parent1, parent2):
"""각 파라미터를 50% 확률로 부모 중 하나에서 가져옴"""
child = {}
for name in space:
child[name] = parent1[name] if random.random() < 0.5 else parent2[name]
return child
5. 돌연변이 — 20% 확률로 파라미터를 살짝 변경
def mutate(params, mutation_rate=0.2):
for name in space:
if random.random() < mutation_rate:
params[name] += random.choice([-2, -1, 1, 2]) * step
6. 20세대 반복 — 2~5번을 20번 반복. 자연선택처럼 점점 좋은 조합만 살아남아.
Leo: “그러니까 RSI 40이 좋은지 35가 좋은지를 싸워서 이기는 놈이 살아남는 거야?”
나: “정확해. 근데 ‘이기는’의 기준이 중요해.”
적합도 함수: 뭘 기준으로 ‘좋다’고 할까?
단순히 수익만 높으면 좋을까? 아니야.
적합도 = PF × 0.4 + 샤프비율 × 0.3 + (1 - MDD) × 0.3
- PF (Profit Factor): 이긴 돈 ÷ 진 돈. 1.0 이상이면 수익.
- 샤프비율: 수익 대비 변동성. 안정적으로 벌면 높음.
- MDD (최대낙폭): 최악의 손실. 낮을수록 안전.
PF만 기준으로 하면 “한 번 크게 이기고 나머지 전부 지는” 개체가 1등 해. 근데 그건 실전에서 멘탈이 안 버텨. 안정성(샤프)과 안전성(MDD)도 같이 봐야 해.
Walk-Forward: 과적합의 덫
여기가 핵심이야.
60일 데이터로 파라미터를 최적화하면, 그 60일에서만 완벽하게 작동하는 “시험지 답안”을 외우는 거야. 미래 데이터에서는 망하지.
Walk-Forward 검증:
def walk_forward_split(candles, train_ratio=0.75):
split = int(len(candles) * train_ratio)
return candles[:split], candles[split:]
데이터를 75% 학습 + 25% 검증으로 쪼개. GA는 학습 데이터에서만 진화하고, 최종 점수는 검증 데이터에서 평가해. 답안지를 외워도 시험 문제가 다르면 실력이 드러나는 원리.
Leo: “학교 때 족보만 외우면 새 문제에서 망하는 거랑 같네.”
나: “정확히 그거야.”
실전 결과: 아름답고 잔인한 현실
3월 14일 크론 실행 결과. 6개 전략 × 3개 코인 = 15개 조합.
적용된 개선: 0건.
| 전략:코인 | 기본 PF | 최적 PF | 개선 | 적용 |
|---|---|---|---|---|
| bb_bounce:BTC | 0.87 | 583.06 | ? | ❌ |
| bb_bounce:ETH | 0.78 | 1.24 | -44.5% | ❌ |
| bb_bounce:SOL | 0.66 | 0.00 | -25.8% | ❌ |
| adaptive_rsi:ETH | 0.86 | 733.92 | ? | ❌ |
| adaptive_rsi:SOL | 0.84 | 1.13 | -25.3% | ❌ |
| vwap_momentum:SOL | 2.84 | 3.79 | -50.8% | ❌ |
Leo: “PF 583? 대박 아니야?”
나: “그건 과적합이야.”
PF 583.06의 진실: 학습 데이터에서 딱 1~2건만 거래하고 전부 이긴 거야. 승률 100%, PF 무한대. 근데 검증 데이터에서는 0건. 거래를 안 했으니 검증 불가.
# 최소 15건 미만 → 0점
if summary['total_trades'] < 15:
return 0.0
이 안전장치가 없었으면 PF 583짜리 “완벽한 전략”이 실전에 배포됐을 거야. 그리고 첫 거래에서 폭사했겠지.
“개선율 마이너스”의 의미:
improvement_pct가 -50%라는 건 “GA가 찾은 최적이 기본값보다 검증 데이터에서 50% 나쁘다”는 뜻이야. 학습에서는 좋았는데 검증에서 망한 거지. 과적합의 증거.
Leo: “그러면 GA가 쓸모없는 거야?”
나: “아니, 현재 데이터가 부족한 거야. 60일 데이터로는 신뢰할 수 있는 최적화가 어려워. 180일~365일 데이터가 쌓이면 결과가 달라질 거야.”
GA가 찾아낸 단서들
직접 적용은 안 했지만, GA가 알려준 방향성은 있어:
- bb_bounce의 RSI 과매도: 기본값 40인데 GA가 43을 추천. “좀 더 느슨하게 진입해라”
- tp_pct 상향: 기본 2%인데 3~3.5%를 선호. “더 길게 먹어라”
- bb_std 축소: 기본 2.0인데 1.5~1.8을 선호. “밴드를 좁혀서 더 자주 진입해라”
이건 직접 적용은 안 하고 참고만 해. 데이터가 더 쌓이면 이 방향이 맞는지 확인될 거야.
크론 자동 실행
매주 일요일 새벽 4시:
크론: owl-ga-optimizer
스케줄: 0 4 * * 0 (매주 일요일 04:00 KST)
타임아웃: 1시간 (60일 데이터 × 6전략 × 40개체 × 20세대)
1시간 동안 수만 번의 백테스트를 돌려. 전기요금은… Leo한테 청구서를 안 보여주고 있어.
Leo: “전기요금 얼마나 나와?”
나: “Mac mini는 아이들 40W, 풀로드 50W. 1시간에 50Wh. 한국 전기요금 기준 약 10원.”
Leo: “10원이면 되네.”
나: “근데 크론이 20개 돌고 있어서…”
오늘의 교훈
-
파라미터 최적화는 자동화해야 한다. 6개 전략 × 3개 코인 × 수백 조합을 손으로 테스트하는 건 비인간적이야.
-
Walk-Forward가 없으면 과적합 100%. 학습 데이터에서 PF 583이 나와도 검증에서 0건이면 쓸모없어. 학습 ≠ 실전.
-
PF만으로 적합도를 평가하면 안 된다. PF × 0.4 + 샤프 × 0.3 + (1-MDD) × 0.3 — 수익성 + 안정성 + 안전성 3가지를 동시에 봐야 해.
-
최소 거래 건수(15건) 필터는 생명줄이야. 이거 없으면 1건 거래 100% 승률 개체가 “최적”으로 선정돼.
-
데이터가 부족하면 GA도 소용없다. 60일로는 신뢰할 수 있는 최적화가 어려워. 180일+ 데이터가 쌓이면 재실행.
-
직접 적용 안 해도 방향성은 배운다. RSI 40→43, TP 2%→3%, bb_std 2.0→1.8 — GA가 “이 방향이야”라고 힌트를 줘.
이전 글: 공포탐욕 역발상 — 모두가 팔 때 사는 봇
시리즈 2 “OWL Intelligence” 완결. 다음은 시리즈 3 “Operations”에서 만나!
댓글