업데이트: 2026년 11월 3일. TradingView 공식 Pine Script v6 문서에서 확인한 동작과 이 글에서 제안하는 검증 절차를 구분합니다. 스크립트와 코드는 구조를 보여 주는 예시이며, 실제 매매 성과나 특정 전략의 유효성을 보여 주지 않습니다.
Pine Script는 차트 위의 빠른 시제품에 맞는 도구다
Pine Script는 TradingView 차트에서 실행되는 스크립트 언어입니다. 선언문에 따라 두 종류로 나뉩니다. indicator()는 값을 계산해 차트에 그리고, strategy()는 내장 브로커 에뮬레이터로 가상 주문을 체결해 Strategy Tester에 성과 보고서를 만듭니다. 현재 문서는 v6 기준이며, 이전 버전 스크립트를 옮길 때는 공식 v6 마이그레이션 가이드를 확인합니다.
장점은 아이디어를 차트에서 바로 확인할 수 있다는 점입니다. 반면 검증 절차를 자유롭게 설계하기는 어렵습니다. 여러 검증 구간을 나누고, 시도한 파라미터를 모두 기록하고, 다른 데이터와 결합하는 작업은 외부 환경에서 하는 편이 통제하기 쉽습니다. 이 글의 흐름은 Pine에서 규칙을 명확히 하고, 같은 규칙을 Python으로 옮겨 검증한다입니다.
지표 스크립트: 교차 신호는 확정된 봉 기준으로 표시한다
이동평균 교차는 규칙이 단순해 이식 예시로 쓰기 좋습니다. 신호의 유효성을 보여 주려는 예시는 아닙니다.
//@version=6
indicator("MA Crossover Signal", overlay = true)
fastLen = input.int(20, "Fast length", minval = 1)
slowLen = input.int(50, "Slow length", minval = 2)
float fast = ta.sma(close, fastLen)
float slow = ta.sma(close, slowLen)
plot(fast, "Fast SMA", color.blue, 2)
plot(slow, "Slow SMA", color.orange, 2)
// 실시간 봉에서는 교차가 생겼다 사라질 수 있으므로 확정된 봉에서만 표시한다
bool buySignal = ta.crossover(fast, slow) and barstate.isconfirmed
bool sellSignal = ta.crossunder(fast, slow) and barstate.isconfirmed
plotshape(buySignal, "Buy", shape.triangleup, location.belowbar, color.green, size = size.small)
plotshape(sellSignal, "Sell", shape.triangledown, location.abovebar, color.red, size = size.small)
ta.crossover(a, b)는 이번 봉에서 a가 b보다 크고 직전 봉에서는 a가 b 이하였을 때 참입니다. 실시간 봉은 종가가 확정되기 전까지 값이 바뀌므로, 과거 봉에 표시된 신호와 실시간에 본 신호가 다를 수 있습니다. barstate.isconfirmed 조건은 그 차이를 줄이는 방법 중 하나입니다.
다른 심볼을 불러올 때는 lookahead와 거래 시간을 확인한다
request.security()는 다른 심볼이나 시간 프레임의 데이터를 가져옵니다. 공식 문서에 따르면 lookahead의 기본값은 barmerge.lookahead_off이며, 상위 시간 프레임 데이터를 lookahead_on으로 가져오면서 값을 한 봉 미루지([1]) 않으면 과거 봉에 미래 정보가 들어갈 수 있습니다. gaps는 새 값이 없는 봉에서 na를 줄지(gaps_on), 마지막 확정값을 이어 쓸지(gaps_off, 기본값)를 정합니다.
아래 예시는 두 심볼의 수익률 상관을 계산합니다. 가격 수준끼리의 상관은 두 계열이 모두 추세를 가질 때 관계가 없어도 크게 나올 수 있으므로, 변화율로 비교하는 편이 해석하기 쉽습니다.
//@version=6
indicator("BTC vs DXY return correlation", overlay = false)
lenInput = input.int(20, "Correlation length (bars)", minval = 2)
// 각 심볼의 봉에서 1봉 변화율을 계산해 가져온다. lookahead는 기본값(off)을 유지한다
float btcRet = request.security("BINANCE:BTCUSDT", timeframe.period, ta.roc(close, 1))
float dxyRet = request.security("TVC:DXY", timeframe.period, ta.roc(close, 1))
float corr = ta.correlation(btcRet, dxyRet, lenInput)
plot(corr, "Correlation", corr >= 0 ? color.red : color.green, 2)
hline(0, "Zero", color.gray, hline.style_dashed)
거래 시간이 다른 심볼을 섞을 때는 주의가 필요합니다. 암호화폐는 주말에도 거래되지만 달러 지수는 그렇지 않으므로, 기본 설정(gaps_off)에서는 DXY가 쉬는 봉에 마지막 값이 반복됩니다. 이 반복이 상관 계산에 섞입니다. 일봉 이상에서 비교하거나, 두 심볼이 모두 거래된 봉만 쓰는 처리를 따로 검토합니다. 이 상관이 매매 시점을 알려 준다는 근거는 이 글에서 확인하지 않았습니다.
strategy()에는 비용과 체결 가정을 명시한다
Strategy Tester의 결과는 설정한 가정의 결과입니다. 공식 문서에 따르면 브로커 에뮬레이터는 기본적으로 시장가 주문을 다음 봉의 시가에 체결하고, process_orders_on_close 같은 파라미터로 이 동작을 바꿀 수 있습니다. 수수료는 commission_type과 commission_value로, 슬리피지는 틱 수 기준의 slippage로 기본값을 지정합니다.
//@version=6
strategy("MA Crossover Strategy", overlay = true,
initial_capital = 10000,
default_qty_type = strategy.percent_of_equity, default_qty_value = 100,
commission_type = strategy.commission.percent, commission_value = 0.1,
slippage = 2)
fastLen = input.int(20, "Fast length", minval = 1)
slowLen = input.int(50, "Slow length", minval = 2)
float fast = ta.sma(close, fastLen)
float slow = ta.sma(close, slowLen)
bool longEntry = ta.crossover(fast, slow)
bool longExit = ta.crossunder(fast, slow)
if longEntry
strategy.entry("Long", strategy.long)
if longExit
strategy.close("Long")
수수료 0.1%와 슬리피지 2틱은 설명용 가정입니다. 실제 값은 거래소의 수수료 체계와 호가 단위, 주문 크기를 보고 정하고, 값을 바꿔 가며 결과가 얼마나 민감한지 확인합니다. 문서에 따르면 전략 보고서의 성과 데이터는 XLSX로 내려받을 수 있고, 내보낸 파일에는 Properties 탭의 설정도 포함됩니다. Python 쪽 재현 결과와 비교할 때 이 설정값을 함께 기록해 두면 차이의 원인을 찾기 쉽습니다.
Python으로 옮길 때는 신호 시점과 체결 시점을 분리한다
Pine의 기본 동작을 Python에서 재현하려면, 봉 종가에서 확정된 신호를 다음 봉부터 반영해야 합니다. 같은 봉 종가에 체결한다고 가정하면 백테스트가 실제로는 불가능한 주문을 포함하게 됩니다.
import numpy as np
import pandas as pd
def crossover(a, b):
"""Pine ta.crossover(a, b)와 같은 정의: 이번 봉 a > b, 직전 봉 a <= b."""
return (a > b) & (a.shift(1) <= b.shift(1))
def crossunder(a, b):
return (a < b) & (a.shift(1) >= b.shift(1))
def ma_cross_positions(close, fast=20, slow=50):
f, s = close.rolling(fast).mean(), close.rolling(slow).mean()
buy, sell = crossover(f, s), crossunder(f, s)
# 봉 종가에서 확정된 신호 -> 다음 봉부터 보유 (Pine 기본: 다음 봉 시가 체결)
state = pd.Series(np.nan, index=close.index)
state[buy] = 1.0
state[sell] = 0.0
signal = state.ffill().fillna(0.0)
position = signal.shift(1).fillna(0.0)
return pd.DataFrame({"fast": f, "slow": s, "buy": buy, "sell": sell,
"signal": signal, "position": position})
def net_returns(open_, position, cost_rate=0.001):
"""시가-시가 수익률에 보유를 곱하고, 포지션 변경 시 비용을 뺀다."""
ret = open_.pct_change().shift(-1).fillna(0.0) # t 시가에서 t+1 시가까지
trades = position.diff().abs().fillna(position.abs())
return position * ret - trades * cost_rate
rng = np.random.default_rng(3)
n = 400
close = pd.Series(100 * np.exp(np.cumsum(rng.normal(0, 0.01, n))))
open_ = close.shift(1).fillna(close.iloc[0]) # 가상 데이터: 시가 = 직전 종가
out = ma_cross_positions(close)
pnl = net_returns(open_, out["position"])
# 1) 신호가 난 봉에서는 아직 포지션이 바뀌지 않는다
first_buy = out.index[out["buy"]][0]
assert out.loc[first_buy, "position"] == 0.0 and out.loc[first_buy + 1, "position"] == 1.0
# 2) 미래 가격을 바꿔도 과거 신호는 바뀌지 않는다 (룩어헤드 없음)
shocked = close.copy()
shocked.iloc[300:] *= 1.5
assert out["signal"].iloc[:300].equals(ma_cross_positions(shocked)["signal"].iloc[:300])
# 3) 크로스 이벤트는 slow 기간이 채워진 뒤에만 생긴다
assert not out[["buy", "sell"]].iloc[:50].any().any()
# 4) 비용은 포지션이 바뀐 봉에서만 발생한다
assert np.isclose((out["position"].diff().abs().fillna(0) * 0.001).sum(),
(out["position"] * open_.pct_change().shift(-1).fillna(0) - pnl).sum())
두 번째 검사처럼 “미래 데이터를 바꿔도 과거 출력이 같다”는 조건은 이식한 코드가 룩어헤드를 포함하지 않는지 확인하는 간단한 방법입니다. 같은 데이터에서 Pine 보고서의 거래 목록과 Python의 진입·청산 시점을 비교해 일치하는지도 확인합니다. 일치한 뒤에야 파라미터 탐색과 시간순 검증으로 넘어갑니다. 분할 방법은 과적합 점검 칼럼에서 다룹니다.
요금제는 기능 한도로 비교하고, 가격은 공식 페이지에서 확인한다
TradingView에는 무료 Basic과 Essential, Plus, Premium, Ultimate 등의 유료 플랜이 있습니다. 공식 요금 페이지는 플랜별로 차트당 지표 수, 탭당 차트 수, 알림 수, 저장 레이아웃, 과거 봉 수, 초 단위 시간 프레임 같은 한도를 비교합니다. 가격과 한도는 결제 주기와 프로모션에 따라 달라지고 수시로 바뀌므로 이 글에 숫자를 적지 않습니다. 최신 정보는 TradingView 공식 요금 페이지에서 확인하세요.
플랜을 고를 때는 전략 검증에 직접 영향을 주는 항목을 먼저 봅니다. 한 차트에서 동시에 쓸 스크립트 수, 백테스트에 필요한 과거 봉 수, 사용할 시간 프레임이 대표적입니다.
추천 링크 안내: TradingView 요금 페이지는 Alphaware의 추천 링크입니다. TradingView 추천 프로그램 안내에 따르면, 이전에 계정이 없던 신규 사용자가 추천 링크로 가입하고 3개월 안에 유료 회원이 되면 15달러 할인 쿠폰을 받고, 추천인도 보상을 받습니다. 조건은 TradingView 공식 안내에서 확인하세요.
자료와 적용 범위
- Pine Script v6 Strategies 문서: 브로커 에뮬레이터의 체결 시점,
commission_type·commission_value·slippage, 보고서 XLSX 내보내기. - Pine Script v6 Other timeframes and data 문서:
request.security()의lookahead·gaps동작. - Pine Script v6 마이그레이션 가이드: 이전 버전 스크립트를 v6로 옮길 때의 변경점.
- TradingView 요금 페이지: 플랜 이름과 플랜별 기능 한도.
- TradingView 추천 프로그램 안내: 추천 링크 할인 조건.
- 이 글은 이동평균 교차나 BTC·DXY 상관이 수익을 내는 규칙이라고 주장하지 않으며, 예시는 이식과 검증 절차를 보여 주기 위한 것입니다.

