진입 신호만큼 중요한 것이 청산입니다. 손절이 없는 전략은 한 번의 큰 손실로 그동안의 수익을 모두 잃을 수 있습니다.
이 글에서는 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)

 1//@version=6
 2strategy("슈퍼트렌드 + 손절·익절", overlay = true,
 3     initial_capital   = 10000,
 4     default_qty_type  = strategy.percent_of_equity,
 5     default_qty_value = 10,
 6     commission_type   = strategy.commission.percent,
 7     commission_value  = 0.05,
 8     slippage          = 1)
 9
10// ── 설정 ─────────────────────────────
11atrPeriod = input.int(10, "ATR 기간", minval = 1, group = "슈퍼트렌드")
12factor    = input.float(3.0, "ATR 배수", minval = 0.1, step = 0.1, group = "슈퍼트렌드")
13
14tpPct          = input.float(4.0, "익절 (%)", minval = 0.1, step = 0.1, group = "청산")
15slPct          = input.float(2.0, "손절 (%)", minval = 0.1, step = 0.1, group = "청산")
16useTrail       = input.bool(true, "트레일링 스탑 사용", group = "청산")
17trailStartPct  = input.float(2.0, "트레일링 시작 (%)", minval = 0.1, step = 0.1, group = "청산")
18trailOffsetPct = input.float(1.0, "최고가 대비 하락 (%)", minval = 0.1, step = 0.1, group = "청산")
19
20entryMsg = input.text_area("", "롱 진입 주문메시지", group = "TVExtBot")
21tpMsg    = input.text_area("", "익절 주문메시지",    group = "TVExtBot")
22slMsg    = input.text_area("", "손절 주문메시지",    group = "TVExtBot")
23trailMsg = input.text_area("", "트레일링 청산 주문메시지", group = "TVExtBot")
24exitMsg  = input.text_area("", "추세 반전 청산 주문메시지", group = "TVExtBot")
25
26// ── 신호 ─────────────────────────────
27[st, dir] = ta.supertrend(factor, atrPeriod)
28plot(dir < 0 ? st : na, "상승 추세", color.green, style = plot.style_linebr)
29plot(dir > 0 ? st : na, "하락 추세", color.red,   style = plot.style_linebr)
30
31buySignal  = ta.change(dir) < 0
32sellSignal = ta.change(dir) > 0
33
34// ── 진입 ─────────────────────────────
35if buySignal and strategy.position_size == 0
36    strategy.entry("Long", strategy.long, alert_message = entryMsg)
37
38// ── 청산 ─────────────────────────────
39if strategy.position_size > 0
40    entry     = strategy.position_avg_price
41    tpPrice   = entry * (1 + tpPct / 100)
42    slPrice   = entry * (1 - slPct / 100)
43    trailTick = math.max(1, math.round(entry * trailOffsetPct / 100 / syminfo.mintick))
44
45    strategy.exit("Exit", "Long",
46         limit          = useTrail ? na : tpPrice,
47         stop           = slPrice,
48         trail_price    = useTrail ? entry * (1 + trailStartPct / 100) : na,
49         trail_offset   = useTrail ? trailTick : na,
50         alert_profit   = tpMsg,
51         alert_loss     = slMsg,
52         alert_trailing = trailMsg)
53
54    if sellSignal
55        strategy.close("Long", alert_message = exitMsg)
56
57// ── 차트에 손절·익절 가격 표시 ─────────────
58plot(strategy.position_size > 0 ? strategy.position_avg_price * (1 - slPct / 100) : na, "손절가", color.red,   style = plot.style_linebr)
59plot(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 설계법