본문 바로가기
업비트 트레이더

5편: 트레일링 스탑 최적화 — "진입 즉시 활성화"가 정말 맞을까?

by WELab74 2026. 3. 22.
반응형
실전 운용을 시작하고 나서 한 가지 찜찜한 부분이 있었다. 트레일링 스탑이 진입 직후부터 작동한다는 점이었다.

1. 문제 제기: 조기 청산의 함정

외부에서 이런 의견을 받았다.

"BTC 가격이 골든크로스 직후 +1% 올랐다가 -3% 빠지는 경우,
손절가(-4%)에는 도달하지 않았지만 트레일링 스탑(-3%)이 먼저 발동해서
조기 청산될 수 있다."

생각해보니 맞는 말이었다. 트레일링 스탑은 최고점에서 일정 %만큼 빠지면 청산하는 로직이다. 진입 직후에도 활성화되면, 작은 등락에도 청산되는 경우가 생긴다.

현재 코드는 이렇게 되어 있었다.

# 기존 코드 - 진입 즉시 활성화
if (peak_price - current_price) / peak_price >= trail:
    return 'TRAIL'

해결책은 활성화 임계값을 두는 것이다. 최고점 수익이 일정 % 이상 달성됐을 때만 트레일링 스탑을 켠다.

# 개선 코드 - trail_activate 조건 추가
max_pnl = (peak - entry_price) / entry_price
if max_pnl >= trail_activate and (peak - current_price) / peak >= trail:
    return 'TRAIL'

2. 백테스트 설계: 3가지 시나리오

3가지 시나리오를 비교했다.

시나리오 트레일링 스탑 활성화 조건
현행 (즉시) 진입 즉시 활성화
수정A (+2%) 최고점 수익이 +2% 이상일 때만 활성화
수정B (+3%) 최고점 수익이 +3% 이상일 때만 활성화

- 기간: 2023-01-01 ~ 2026-03-22 (약 3년)
- 전략: MA(20/120) 골든크로스 + RSI(14) < 70
- 나머지 조건: 손절 BTC -4% / SOL -5%, 익절 BTC +8% / SOL +10%, 트레일 -3%


3. 백테스트 결과: BTC vs SOL

KRW-BTC (1시간봉)

시나리오 거래 승률 수익률 MDD 평균손익 익절 손절 트레일
현행 (즉시) 87 48.3% +144% -19.1% +1.12% 18 2 67
수정A (+2%) ★ 72 55.6% +154% -19.9% +1.42% 19 20 33
수정B (+3%) 70 60.0% +122% -19.9% +1.27% 19 26 25

수정A가 수익률 +10%p 우세했다. 핵심 변화는 트레일 청산이 67회 → 33회로 줄고, 손절이 2회 → 20회로 늘어난 것이다.

처음엔 이게 나쁜 것 같았다. 손절이 늘었으니까. 그런데 생각해보면 맞다. 기존엔 트레일로 청산되던 것들이 이제는 제대로 손절 또는 익절로 처리된다. "불필요한 조기 청산"이 줄어든 것이다.

KRW-SOL (30분봉)

시나리오 거래 승률 수익률 MDD 평균손익 익절 손절 트레일
현행 (즉시) ★ 198 42.4% +175% -25.2% +0.61% 25 3 170
수정A (+2%) 171 52.0% +130% -36.2% +0.64% 26 52 93
수정B (+3%) 163 58.3% +182% -31.8% +0.81% 30 60 73

SOL은 결과가 반대였다. 수정A에서 수익률이 크게 떨어지고(-45%p) MDD도 악화됐다. 현행이 가장 좋다.


4. 왜 BTC와 SOL이 다른가: 변동성의 차이

이 차이가 흥미로웠다.

BTC는 1시간봉 기준으로 큰 추세를 타는 경향이 있다. 진입 직후 소폭 등락이 잦다. 이 구간에서 트레일이 즉시 활성화되면 노이즈에 청산당하는 경우가 많다. +2% 활성화 임계값이 이 노이즈를 걸러주는 역할을 한다.

SOL은 변동성이 더 크다. 추세가 시작되면 빠르게 움직이는 경향이 있다. 즉시 활성화해야 급등 후 급락 시 수익을 지킬 수 있다. 임계값을 두면 오히려 수익 실현 타이밍을 놓친다.


5. 최종 결정: 코인별 맞춤 설정

코인 트레일링 스탑 활성화 조건
KRW-BTC 최고점 수익 +2% 이상일 때만 활성화
KRW-SOL 진입 즉시 활성화 (현행 유지)

src/risk.py의 PARAMS에 trail_activate 파라미터를 추가했다.

PARAMS = {
    'KRW-BTC': {
        'tp': 0.08, 'sl': 0.04, 'trail': 0.03,
        'cooldown_h': 48,
        'trail_activate': 0.02,  # +2% 이상일 때만 트레일 활성화
    },
    'KRW-SOL': {
        'tp': 0.10, 'sl': 0.05, 'trail': 0.03,
        'cooldown_h': 60,
        'trail_activate': 0.00,  # 즉시 활성화
    },
}

6. 미채택 의견: 눌림목 진입 조건

이 분석을 하면서 다른 의견도 받았다.

"BTC 가격이 전고점 부근에서 횡보 중. 골든크로스가 이미 진행 중이므로
MA(20) 근처까지 가격이 내려왔을 때 눌림목 진입 조건을 추가하면 유리할 수 있다."

검토해봤지만 맞지 않는 의견이었다. 우리 전략의 매수 조건은 골든크로스가 발생하는 순간에만 진입한다.

golden = prev['ma20'] <= prev['ma120'] and curr['ma20'] > curr['ma120']

골든크로스가 "진행 중"이면 이미 그 시점에 진입했거나, 놓쳤으면 다음 크로스까지 대기하는 구조다. 고점에서 계속 진입 가능한 전략이 아니다. 눌림목 조건 추가는 불필요하다.


7. 서버 반영: 배포 완료

변경 사항을 Oracle Cloud 서버에 SCP로 업로드하고 서비스를 재시작했다.

scp src/risk.py ubuntu@{서버IP}:~/upbit-trader/src/
ssh ubuntu@{서버IP} "sudo systemctl restart upbit-trader"

이제 BTC는 진입 후 +2% 이상 올랐을 때부터 트레일링 스탑이 작동한다. 조기 청산 가능성이 줄었다.


8. 다음 편 예고

전략 최적화도 했고, 서버도 잘 돌아가고 있다.

그런데 한 가지 욕심이 생겼다.

"BTC/SOL 골든크로스는 자주 발생하지 않는다.
신호가 없는 구간이 꽤 길다. 이 쉬는 시간에 다른 코인으로 단타를 칠 수 있지 않을까?"

이 아이디어가 생각보다 깊은 탐구로 이어졌다. 10회 이상의 백테스트와 숱한 실패를 거친 단타 전략 탐색 이야기를 다음 편에서 다룬다.

반응형