TVExtBot가이드
파인스크립트 강좌

손절·익절·트레일링 스탑

파인스크립트 strategy.exit()로 퍼센트 손절·익절, ATR 기반 손절, 트레일링 스탑을 구현하는 방법과 손절·익절마다 다른 자동매매 주문메시지를 보내는 방법을 전체 코드(Pine Script v6)로 설명합니다.

진입 신호만큼 중요한 것이 청산입니다. 손절이 없는 전략은 한 번의 큰 손실로 그동안의 수익을 모두 잃을 수 있습니다.

이 글에서는 strategy.exit() 로 퍼센트 손절·익절, ATR 손절, 트레일링 스탑을 만들고, 각각 TVExtBot 주문메시지로 자동매매하는 방법을 설명합니다.

이 글에서 배우는 것

  • strategy.exit() 의 주요 인수
  • 퍼센트 기준 / ATR 기준 손절·익절
  • 트레일링 스탑
  • 익절·손절마다 다른 얼러트 메시지 보내기

※ 예제 코드는 파인스크립트 v6 기준입니다. 전략이 처음이라면 지표를 전략으로 바꾸는 방법을 먼저 읽어 주세요.

1. strategy.exit() 기본

strategy.exit(id, from_entry, limit, stop, profit, loss, trail_price, trail_points, trail_offset, ...)
인수의미단위
from_entry청산할 진입 주문 이름 (예: "Long")
limit익절 가격가격
stop손절 가격가격
profit / loss진입가 기준 익절·손절 거리틱
trail_price / trail_points트레일링 시작 가격 / 시작 거리가격 / 틱
trail_offset최고가에서 얼마나 내려오면 청산할지틱
alert_message청산 시 얼러트 메시지
alert_profit / alert_loss / alert_trailing익절 / 손절 / 트레일링으로 청산될 때 각각의 메시지

틱은 종목의 최소 가격 단위(syminfo.mintick)입니다. 퍼센트나 가격으로 계산하는 편이 여러 종목에 쓰기 쉬워, 이 글에서는 limit, stop 가격을 직접 계산합니다.

strategy.exit() 는 호출한 봉에서 끝나지 않고, 포지션이 청산될 때까지 계속 유효한 주문으로 남습니다. 봉마다 호출하면 가격이 갱신됩니다.

2. 퍼센트 손절·익절

tpPct = input.float(3.0, "익절 (%)", minval = 0.1, step = 0.1)
slPct = input.float(1.5, "손절 (%)", minval = 0.1, step = 0.1)

if strategy.position_size > 0
    entry = strategy.position_avg_price
    strategy.exit("Exit", "Long",
         limit = entry * (1 + tpPct / 100),
         stop  = entry * (1 - slPct / 100))
  • strategy.position_avg_price 는 평균 진입가입니다. 진입 직후 봉부터 값이 있습니다.
  • 숏 포지션이라면 limit 은 진입가 아래, stop 은 진입가 위로 계산합니다.

3. ATR 기준 손절·익절

변동성이 큰 시기에는 손절 폭을 넓게, 작은 시기에는 좁게 잡는 방법입니다. 진입할 때의 ATR 값을 저장해 두고 사용해야 손절가가 봉마다 흔들리지 않습니다.

atrLen  = input.int(14, "ATR 기간")
slMult  = input.float(2.0, "손절 ATR 배수")
tpMult  = input.float(3.0, "익절 ATR 배수")

atr = ta.atr(atrLen)
var float entryAtr = na

if buySignal and strategy.position_size == 0
    entryAtr := atr                                   // 진입 시점의 ATR 저장
    strategy.entry("Long", strategy.long)

if strategy.position_size > 0
    entry = strategy.position_avg_price
    strategy.exit("Exit", "Long",
         limit = entry + entryAtr * tpMult,
         stop  = entry - entryAtr * slMult)

4. 트레일링 스탑

가격이 목표만큼 오르면 추적을 시작하고, 최고가에서 일정 비율 내려오면 청산합니다. 추세를 길게 따라가면서 수익을 지키는 방법입니다.

trailStartPct  = input.float(2.0, "트레일링 시작 (%)")
trailOffsetPct = input.float(1.0, "최고가 대비 하락 (%)")

if strategy.position_size > 0
    entry = strategy.position_avg_price
    strategy.exit("Trail", "Long",
         stop         = entry * (1 - slPct / 100),                                   // 기본 손절
         trail_price  = entry * (1 + trailStartPct / 100),                           // 이 가격에 도달하면 추적 시작
         trail_offset = math.max(1, math.round(entry * trailOffsetPct / 100 / syminfo.mintick)))  // 틱 단위

※ trail_offset 은 틱 단위입니다. 퍼센트를 가격으로 바꾼 뒤 syminfo.mintick 으로 나눠 틱 수로 변환합니다.

5. 전체 코드: 슈퍼트렌드 + 손절·익절·트레일링

지표를 전략으로 바꾸는 방법에서 만든 슈퍼트렌드 전략에 청산 규칙을 더했습니다. 익절, 손절, 트레일링, 추세 반전 청산마다 다른 주문메시지를 보낼 수 있습니다.

전략 스크립트 (Pine Script v6)

//@version=6
strategy("슈퍼트렌드 + 손절·익절", overlay = true,
     initial_capital   = 10000,
     default_qty_type  = strategy.percent_of_equity,
     default_qty_value = 10,
     commission_type   = strategy.commission.percent,
     commission_value  = 0.05,
     slippage          = 1)

// ── 설정 ─────────────────────────────
atrPeriod = input.int(10, "ATR 기간", minval = 1, group = "슈퍼트렌드")
factor    = input.float(3.0, "ATR 배수", minval = 0.1, step = 0.1, group = "슈퍼트렌드")

tpPct          = input.float(4.0, "익절 (%)", minval = 0.1, step = 0.1, group = "청산")
slPct          = input.float(2.0, "손절 (%)", minval = 0.1, step = 0.1, group = "청산")
useTrail       = input.bool(true, "트레일링 스탑 사용", group = "청산")
trailStartPct  = input.float(2.0, "트레일링 시작 (%)", minval = 0.1, step = 0.1, group = "청산")
trailOffsetPct = input.float(1.0, "최고가 대비 하락 (%)", minval = 0.1, step = 0.1, group = "청산")

entryMsg = input.text_area("", "롱 진입 주문메시지", group = "TVExtBot")
tpMsg    = input.text_area("", "익절 주문메시지",    group = "TVExtBot")
slMsg    = input.text_area("", "손절 주문메시지",    group = "TVExtBot")
trailMsg = input.text_area("", "트레일링 청산 주문메시지", group = "TVExtBot")
exitMsg  = input.text_area("", "추세 반전 청산 주문메시지", group = "TVExtBot")

// ── 신호 ─────────────────────────────
[st, dir] = ta.supertrend(factor, atrPeriod)
plot(dir < 0 ? st : na, "상승 추세", color.green, style = plot.style_linebr)
plot(dir > 0 ? st : na, "하락 추세", color.red,   style = plot.style_linebr)

buySignal  = ta.change(dir) < 0
sellSignal = ta.change(dir) > 0

// ── 진입 ─────────────────────────────
if buySignal and strategy.position_size == 0
    strategy.entry("Long", strategy.long, alert_message = entryMsg)

// ── 청산 ─────────────────────────────
if strategy.position_size > 0
    entry     = strategy.position_avg_price
    tpPrice   = entry * (1 + tpPct / 100)
    slPrice   = entry * (1 - slPct / 100)
    trailTick = math.max(1, math.round(entry * trailOffsetPct / 100 / syminfo.mintick))

    strategy.exit("Exit", "Long",
         limit          = useTrail ? na : tpPrice,
         stop           = slPrice,
         trail_price    = useTrail ? entry * (1 + trailStartPct / 100) : na,
         trail_offset   = useTrail ? trailTick : na,
         alert_profit   = tpMsg,
         alert_loss     = slMsg,
         alert_trailing = trailMsg)

    if sellSignal
        strategy.close("Long", alert_message = exitMsg)

// ── 차트에 손절·익절 가격 표시 ─────────────
plot(strategy.position_size > 0 ? strategy.position_avg_price * (1 - slPct / 100) : na, "손절가", color.red,   style = plot.style_linebr)
plot(strategy.position_size > 0 and not useTrail ? strategy.position_avg_price * (1 + tpPct / 100) : na, "익절가", color.green, style = plot.style_linebr)

얼러트 메시지 칸에는 {{strategy.order.alert_message}} 한 줄만 입력합니다. 청산 이유에 따라 해당 주문메시지가 전송됩니다. (얼러트 메시지 정리 참고)

6. 전략 손절과 거래소 손절 주문의 차이

구분전략의 손절 (이 글의 방법)거래소에 미리 거는 손절 주문
동작손절가에 닿으면 얼러트 → 시장가 주문거래소가 가격을 보고 직접 체결
장점백테스트와 같은 규칙으로 동작, 전략 안에서 관리트레이딩뷰·네트워크에 문제가 있어도 동작
주의얼러트 전송이 늦거나 급변동이면 손절가보다 불리하게 체결될 수 있음전략 백테스트와 따로 관리해야 함

※ 전략의 손절은 얼러트가 전송되어야 주문이 나갑니다. 레버리지를 크게 쓴다면 청산가보다 충분히 여유 있게 손절을 잡아 주세요.

7. 자주 하는 실수

  • from_entry 이름이 진입 이름과 다름: strategy.entry("Long", ...) 이라면 strategy.exit(..., "Long", ...) 처럼 같은 이름을 써야 합니다.
  • profit, loss 에 퍼센트나 가격을 넣음: 두 인수는 틱 단위입니다. 가격을 쓰려면 limit, stop 을 사용합니다.
  • ATR을 매 봉 새로 계산해 손절가가 움직임: 진입 시점의 값을 var 변수에 저장해서 쓰세요.
  • 같은 봉 안에서 손절과 익절에 모두 닿음: 과거 봉에서는 봉 안의 가격 순서를 알 수 없어 트레이딩뷰가 추정해 체결합니다. 손절·익절 폭이 봉 크기보다 충분히 커야 결과를 믿을 수 있습니다.

손절·익절 퍼센트는 TVExtBot 옵티마이저로 여러 조합을 자동 백테스트해 비교해 볼 수 있습니다. → 옵티마이저에 맞는 input 설계법

이 페이지에서