콘텐츠로 이동

차트 오버레이 (Chart Overlays)

ScaleChart 객체의 메서드를 사용하여 차트 위에 시각적 요소를 표시할 수 있습니다. 오버레이는 전략의 시그널과 지표를 직관적으로 확인하는 데 활용됩니다.

런타임 차트와 스튜디오 차트의 overlay 범례는 기본적으로 최신 snapshot 값을 표시합니다. 사용자가 차트 위에 크로스헤어를 올리면 그 시점의 overlay 값으로 범례가 전환됩니다.

한국 주식 세션 기준

차트와 지표는 09:00~15:20 정규장 연속매매 구간을 기준으로 표시합니다. 상위 타임프레임(일봉 등)의 종가·거래량은 15:30 동시호가 체결 데이터까지 반영됩니다. 따라서 일부 증권사 MTS/HTS의 차트와 마지막 구간이 다를 수 있습니다.

Studio 테스트 오버레이 누적 방식

Studio 테스트 모드에서 line()histogram()매 평가 시점의 현재값 1개씩 차트에 누적됩니다. 다른 타임프레임 지표를 그릴 때(예: 5분봉 차트에 일봉 SMA 표시), 아직 마감되지 않은 봉의 종가가 매 틱마다 갱신되므로 일봉 SMA가 장중에 어떻게 변화하는지 연속적으로 확인할 수 있습니다.

line()

차트에 라인을 그립니다. 이동평균 등 연속적인 시계열을 표시하는 데 사용합니다.

chart_obj.line(name, value, color)

파라미터:

이름 타입 설명
name str 라인 이름 (범례에 표시)
value TSeries 표시할 시계열
color str 색상

예제:

c = chart("1D")
sma20 = ta.sma(c.close, 20)
ema50 = ta.ema(c.close, 50)

c.line("SMA 20", sma20, color="orange")
c.line("EMA 50", ema50, color="blue")

name을 생략하고 TSeries만 넘기면 이름이 자동 생성됩니다. histogram()도 동일합니다.

c.line(sma20)               # OK — 이름 자동 생성
c.line("SMA 20", sma20)     # 권장 — 범례에 이름 표시

histogram()

차트에 히스토그램(막대)을 그립니다. MACD 히스토그램이나 거래량 시각화에 적합합니다.

chart_obj.histogram(name, value, color)

파라미터:

이름 타입 설명
name str 히스토그램 이름
value TSeries 표시할 시계열
color str 색상

예제:

c = chart("1D")
macd_line, signal, hist = ta.macd(c.close, 12, 26, 9)

c.histogram("MACD Hist", hist, color="gray")
c.line("MACD", macd_line, color="blue")
c.line("Signal", signal, color="orange")

hline()

수평선을 그립니다. 과매수/과매도 수준, 지지/저항선 표시에 사용합니다.

chart_obj.hline(name, value, color)

파라미터:

이름 타입 설명
name str 라인 이름
value float 수평선 Y축 값
color str 색상

예제:

c = chart("1D")
rsi = ta.rsi(c.close, 14)

c.line("RSI", rsi, color="purple")
c.hline("과매수", 70, color="red")
c.hline("과매도", 30, color="green")
c.hline("중심선", 50, color="gray")

vline()

수직선을 그립니다. 특정 시점의 이벤트를 표시하는 데 사용합니다.

chart_obj.vline(name, color)

파라미터:

이름 타입 설명
name str 라인 이름
color str 색상

예제:

c = chart("1D")
sma5 = ta.sma(c.close, 5)
sma20 = ta.sma(c.close, 20)

if sma5.cross_up(sma20):
    c.vline("Golden Cross", color="gold")
    buy(tag="골든크로스")
elif sma5.cross_down(sma20):
    c.vline("Dead Cross", color="red")
    sell(tag="데드크로스")

marker()

차트에 상태형 마커(텍스트 라벨)를 표시합니다. 하위 호환을 위해 유지되는 기본 API이며, 새 스크립트는 status_marker() 또는 shape helper를 우선하는 편이 읽기 쉽습니다.

chart_obj.marker(text="", color="#f44336", position="above", shape="circle")

파라미터:

이름 타입 설명
text str 마커에 표시할 텍스트
color str 색상
position str 위치 ("above" 또는 "below")
shape str 모양 ("circle", "square", "arrow_up", "arrow_down")

예제:

c = chart("1D")
rsi = ta.rsi(c.close, 14)

if rsi[0] < 30:
    c.marker("BUY", color="green", position="below", shape="arrow_up")
    buy(tag="RSI 과매도")
elif rsi[0] > 70:
    c.marker("SELL", color="red", position="above", shape="arrow_down")
    sell(tag="RSI 과매수")

marker helper

shape/position enum을 반복하고 싶지 않다면 helper를 사용할 수 있습니다.

chart_obj.status_marker(text="", color="#f44336", position="above", shape="circle")
chart_obj.arrow_up_marker(text="", color="#f04452") chart_obj.arrow_down_marker(text="", color="#3182f6") chart_obj.circle_marker(text="", color="#f44336", position="above") chart_obj.square_marker(text="", color="#f44336", position="above")
  • status_marker()는 상태형 marker 의도를 이름으로 드러내는 기본 helper입니다.
  • arrow_up_marker()below + arrow_up을 고정합니다.
  • arrow_down_marker()above + arrow_down을 고정합니다.
  • circle_marker()square_marker()는 shape만 고정하고 position은 유지합니다.
  • 과거 예제의 triangle/arrow shape는 계속 허용되지만, 현재 표준 표기는 arrow_up/arrow_down입니다.
c = chart("1D")

if ta.crossover(ta.sma(c.close, 5), ta.sma(c.close, 20)):
    c.arrow_up_marker("GOLDEN")
elif ta.crossunder(ta.sma(c.close, 5), ta.sma(c.close, 20)):
    c.arrow_down_marker("DEAD")

pane()

서브 패인(별도 차트 영역)을 생성합니다. 오실레이터(RSI, ADX, MACD 등)처럼 가격과 다른 스케일의 지표를 분리 표시하는 데 사용합니다.

chart_obj.pane(name) → PaneProxy

파라미터:

이름 타입 설명
name str 패인 이름 (같은 이름이면 같은 패인에 추가)

PaneProxy 메서드:

line(), histogram(), hline(), vline(), marker(), status_marker(), shape helper — ScaleChart와 동일한 시그니처.

예제:

c = chart("1T")

# 메인 차트에는 가격 오버레이
c.line("EMA 20", ta.ema(c.close, 20), color="orange")

# ADX 서브 패인
p = c.pane("ADX")
adx = ta.adx(c.high, c.low, c.close, 14)
p.line("ADX", adx, color="purple")
p.hline("강한 추세", 25, color="gray")

# RSI 서브 패인
r = c.pane("RSI")
rsi = ta.rsi(c.close, 14)
r.line("RSI", rsi, color="cyan")
r.hline("과매수", 70, color="red")
r.hline("과매도", 30, color="green")

패인 이름이 같은 지표는 같은 서브 패인에 함께 표시됩니다. RSI, ADX, MACD 히스토그램처럼 메인 가격축과 다른 스케일의 지표를 분리해서 볼 수 있습니다.

범례 & 값 표시

Studio와 거래 탭 차트는 OHLCV 외에 오버레이 범례와 값을 함께 표시합니다.

  • 메인 차트 overlay legend는 OHLCV 바로 아래 줄에 표시됩니다.
  • named pane도 패인 안쪽 상단에 같은 형식의 legend/value row를 표시합니다.
  • linehistogram은 crosshair가 올라간 시점의 exact-time 값만 사용합니다. 같은 시점 포인트가 없으면 직전값으로 보정하지 않고 -를 표시합니다.
  • crosshair가 없을 때는 linehistogram 모두 latest snapshot 값을 표시합니다.
  • hline은 시계열 포인트가 아니라 현재 snapshot 가격선으로 취급하므로, crosshair 위치와 무관하게 현재 가격을 표시합니다.
  • 메인 차트와 named pane은 crosshair 시점을 함께 동기화하므로, 어느 패인을 hover해도 같은 시점 기준으로 값을 읽을 수 있습니다.

주문 마커

사용자가 marker()로 직접 그린 마커는 선언한 타임프레임 차트에만 표시됩니다. 다른 타임프레임 차트로 자동 복제되지 않습니다.

시스템 주문 마커(체결, 차단 등)는 strategy.order_on 기준 차트에 표시됩니다. 주문 상태의 최종 확인은 주문 패널과 거래 로그를 사용하세요.


색상 값

오버레이에서 사용할 수 있는 색상 이름의 예시입니다.

색상 일반 용도
빨강 "red" 매도, 과매수, 상한
녹색 "green" 매수, 과매도, 하한
파랑 "blue" 이동평균, 기본 라인
주황 "orange" 시그널 라인, 경고
보라 "purple" RSI, 보조 지표
회색 "gray" 중심선, 보조선
흰색 "white" 제로 라인
청록 "cyan" 보조 지표
금색 "gold" 특수 시그널
자홍 "magenta" 보조 지표

종합 예제

version("1.0")
description("풀 차트 오버레이 전략")

c = chart("1D")

# 볼린저밴드
upper, mid, lower = ta.bbands(c.close, 20, 2.0)
c.line("BB Upper", upper, color="red")
c.line("BB Mid", mid, color="gray")
c.line("BB Lower", lower, color="green")

# 이동평균
ema12 = ta.ema(c.close, 12)
c.line("EMA 12", ema12, color="orange")

# MACD
macd_line, signal, hist = ta.macd(c.close, 12, 26, 9)
c.histogram("MACD Hist", hist, color="gray")
c.line("MACD", macd_line, color="blue")
c.line("Signal", signal, color="orange")

# RSI 레벨
rsi = ta.rsi(c.close, 14)
c.hline("OB 70", 70, color="red")
c.hline("OS 30", 30, color="green")

# 시그널 마커
if c.close[0] < lower[0] and rsi[0] < 30:
    c.marker("BUY", color="green", position="below", shape="triangle")
    buy(tag="BB하단 + RSI과매도")
elif c.close[0] > upper[0] and rsi[0] > 70:
    c.marker("SELL", color="red", position="above", shape="triangle")
    sell(tag="BB상단 + RSI과매수")
else:
    hold()

인자 타입 검증

오버레이 메서드는 잘못된 타입의 인자가 전달되면 다음과 같이 동작합니다.

line(), histogram() — 자동 보정: 첫 번째 인자에 문자열 대신 TSeries를 넘기면 value로 자동 재배치하고 이름을 자동 생성합니다.

c.line(sma20)               # OK — c.line("series_xxxxx", sma20) 으로 처리
c.histogram(macd_hist)      # OK — 동일하게 자동 보정

hline(), vline(), pane(), marker() — 오류: name 또는 text 인자에 문자열이 아닌 값을 넘기면 실행 오류가 발생합니다. 오류 메시지가 스크립트 로그에 표시되며, 캔들 차트는 정상 렌더링됩니다.

c.hline(tseries, 70)         # 오류: "c.hline()의 name 인자는 문자열이어야 합니다"
c.pane(tseries)              # 오류: "c.pane()의 name 인자는 문자열이어야 합니다"
c.marker(tseries)            # 오류: "c.marker()의 text 인자는 문자열이어야 합니다"

성능 팁: ta.* 내장 함수 활용

ta.sma(), ta.ema(), ta.rsi(), ta.rma() 등 내장 지표 함수는 차트 시리즈(c.close, c.high 등)에 대해 증분 캐시를 사용합니다. 캔들이 추가될 때마다 새 값 1개만 계산하므로(O(1)), 백테스트나 장시간 라이브 실행에서도 속도가 일정합니다. 상위 타임프레임 봉이 진행 중일 때(봉 마감 전 틱 갱신)에도 마지막 값만 O(1)로 재계산되어 성능이 유지됩니다.

반면, TSeries 산술로 직접 계산하면 매번 전체 시계열을 다시 계산합니다.

# 권장: ta.* 함수 활용 — 증분 캐시 O(1)
ema20 = ta.ema(c.close, 20)
rsi14 = ta.rsi(c.close, 14)

# 비권장: 직접 산술로 만든 TSeries를 ta.* 입력에 사용
my_spread = (c.high - c.low) / c.close  # 매번 새 TSeries 생성
ta.ema(my_spread, 20)  # my_spread가 매번 새 객체이므로 캐시 불가

# 조건 판단에는 [0] 인��싱으로 스칼라 비교 (O(1))
if ta.ema(c.close, 12)[0] > ta.ema(c.close, 26)[0]:
    buy()

hl2, hlc3, ohlc4는 내장 최적화 대상

c.hl2, c.hlc3, c.ohlc4는 차트 객체에서 증분 관리되므로 (c.high + c.low) / 2 대신 c.hl2를 사용하세요.

커스텀 지표에서 var를 활용한 캐싱 패턴

ta.* 함수만으로 표현할 수 없는 커스텀 지표는 var를 사용하여 틱 간 상태를 직접 관리할 수 있습니다. 매 틱마다 새 값 1개만 계산하므로 O(1)입니다.

패턴 1: 캐싱된 지표를 조합

c = chart("1D")
# ta.* 결과끼리 조합 — 각각 캐싱되므로 효율적
ema_fast = ta.ema(c.close, 12)
ema_slow = ta.ema(c.close, 26)
# 마지막 값만 사용하면 O(1)
spread = ema_fast[0] - ema_slow[0]

패턴 2: var로 커스텀 EMA 캐싱

c = chart("1D")
var.init(my_ema=None)

k = 2.0 / (20 + 1)
if var.my_ema is None:
    var.my_ema = c.close[0]  # 첫 값으로 seed
else:
    var.my_ema = c.close[0] * k + var.my_ema * (1 - k)

패턴 3: var로 롤링 윈도우 직접 관리

c = chart("1D")
var.init(window=[], my_sum=0.0)

length = 20
var.window = var.window + [c.close[0]]
var.my_sum = var.my_sum + c.close[0]
if len(var.window) > length:
    var.my_sum = var.my_sum - var.window[0]
    var.window = var.window[1:]
my_sma = var.my_sum / len(var.window) if var.window else 0

패턴 4: 복합 커스텀 지표 (RSI 변형 등)

c = chart("1D")
var.init(avg_gain=0.0, avg_loss=0.0, prev_close=None, rsi_ready=False)

length = 14
if var.prev_close is not None:
    diff = c.close[0] - var.prev_close
    gain = diff if diff > 0 else 0.0
    loss = -diff if diff < 0 else 0.0
    if var.rsi_ready:
        var.avg_gain = (var.avg_gain * (length - 1) + gain) / length
        var.avg_loss = (var.avg_loss * (length - 1) + loss) / length
    else:
        # seed 단계: 단순 누적
        var.avg_gain = var.avg_gain + gain / length
        var.avg_loss = var.avg_loss + loss / length
var.prev_close = c.close[0]
if c.bars >= length + 1:
    var.rsi_ready = True

var 패턴의 제한

var에 저장된 값은 JSON 직렬화 가능한 타입(숫자, 문자열, 리스트, dict)만 가능합니다. _TSeries 객체를 var에 저장할 수 없습니다. 또한 var.to_dict()에 64KB 상한이 있으므로 큰 윈도우를 저장하면 잘릴 수 있습니다.


관련 문서