MobChartDocs
MobChart 열기

사용자 지표 API

JavaScript로 나만의 지표를 작성하고, 차트 옆의 안전한 샌드박스에서 실행하며, 다른 사용자와 공유할 수 있습니다. API는 시장 데이터(캔들, 오더북, 클러스터, 청산, 파생상품 데이터), 플로팅 기본 요소, 기술적 분석 헬퍼와 알림을 제공합니다.

AI로 지표 생성하기

전체 API는 언어 모델용 단일 텍스트 파일로도 제공됩니다: AI-scripting.txt. 이 파일을 ChatGPT, Claude, Gemini 또는 다른 LLM에 첨부하고 원하는 지표를 설명하면 됩니다.

프롬프트 예시:

Read the attached [AI-scripting.txt] https://mobchart.com/docs/public/AI-scripting.txt file and use it as the only reference for the MobChart scripting API.
Create a custom indicator that plots a 20-period EMA and a 50-period EMA on the chart. Color the candles green when EMA20 is above EMA50 and red otherwise.
Use only the documented API. Do not invent functions. Return the complete indicator code.

샌드박스와 제한

지표 개발

지표 개발은 별도의 위젯에서 진행되며, 이 위젯은 지표를 활성화할 차트 옆에 배치할 수 있습니다.

지표 만들기

define(title, description, type, isPublic, isPublicCode, options)

시스템에 지표를 등록합니다.

매개변수타입설명
titlestring지표 이름
descriptionstring짧은 설명
type'onchart' | 'offchart'지표 배치(차트 위 또는 별도 패널)
isPublictrue | false지표 공개 여부
isPublicCodetrue | false소스 코드 공개 여부
optionsobject (선택){ fullRange, warmup }, 아래 참고

예시:

define("My Indicator", "Shows EMA line", "onchart", true, false);

계산 범위. 기본적으로 스크립트는 차트에서 계산이 필요한 부분만 계산합니다. 즉, 뒤로 스크롤하여 로드된 바 또는 새로고침 시 최신 바와 함께, 계산에는 필요하지만 반환되지는 않는 그 이전의 warmup 바(기본값 500)만큼의 히스토리를 계산합니다. 지표에 더 많거나 적은 히스토리가 필요하면 warmup을 선언하세요. 예를 들어 EMA(200)에는 { warmup: 200 }을 사용합니다. 값이나 도형이 보이는 전체 범위에 의존하는 스크립트(첫 번째 보이는 캔들부터의 VWAP, 누적 델타, 화면 내 최댓값의 라벨, 차트 전체에 그려지는 레벨)는 반드시 { fullRange: true }를 선언해야 하며, 그렇지 않으면 히스토리를 더 로드한 후 결과가 잘못됩니다. 이 모드는 스크립트를 저장할 때마다 define()에서 읽힙니다. fullRange 없이 저장된 스크립트는 부분적으로 계산되므로, 기존의 누적 스크립트를 편집할 때 이 플래그를 추가하세요. 부분 스크립트는 나뉘어서 로드되기도 합니다. 긴 히스토리에서 스크립트를 켜면 차트는 먼저 최신 500개 캔들(warmup이 더 크면 warmup개 캔들)을 요청하고, 더 오래된 부분은 한 번에 하나의 요청씩 이어서 로드되므로 보이는 부분이 즉시 나타납니다. fullRange가 있는 스크립트는 항상 전체 범위에 대해 단일 요청으로 계산됩니다.

define("Window max", "Label at the highest visible high", "onchart", true, true, { fullRange: true });

onchart와 offchart의 지표 배치:

입력 매개변수

input 및 style 클래스를 사용하면 사용자가 설정할 수 있는 설정을 만들 수 있습니다(PineScript의 input()과 유사).

메서드설명
number(title, defaultValue, min, max, step)숫자 입력
range(title, defaultValue, min, max, step)범위 입력
select(title, defaultValue, values)드롭다운 선택
color(title, defaultValue)색상 선택기
boolean(title, defaultValue)불리언 입력

주요 매개변수용 예시(input):

const length = input.number("Period", 14, 1, 100, 1);
const type = input.select("Type", 'sma', ['sma', 'ema']);
const color = input.color("Line Color", "#FF0000");

또는 시각적 매개변수용(style):

const length = style.number("Size", 14, 1, 100, 1);
const color = style.select("Background Color", '#FF0000', ["#FF0000", "#AA0000", "#BB0000"]);
const color = style.color("Background Color", "#FF0000");

데이터 소스

source 클래스는 서버에서 시장 데이터를 가져옵니다. 캔들 기반 메서드는 동일한 매개변수를 받으며, 두 가지 실시간 스냅샷은 다릅니다. orderbook()은 거래소와 티커를, levels()는 거래소와 설정 객체를 받습니다.

매개변수설명
exchange, ticker, timeframe기본값은 지표가 열린 차트입니다. 다른 시장의 데이터를 가져오려면 다른 값을 전달하세요.
lookback현재 날짜로부터의 히스토리 바 개수, 최대 5000.

ohlcv(exchange, ticker, timeframe, lookback?)

캔들 데이터.

[
{
"open": 115886.17,
"high": 115911.96,
"low": 115764.27,
"close": 115815.16,
"volume": 228.41528,
"startTime": 1757699100000
},
{...},
...
]

depths(exchange, ticker, timeframe, lookback?)

캔들당 하나씩의 과거 오더북 스냅샷입니다. 가장 큰 지정가 주문만 저장됩니다(히트맵의 기반 데이터).

[
{
"data": {
"asks": {
"109425": 5.467,
"109495": 3.564,
...
},
"bids": {
"33000": 3.746,
"35000": 16.49,
...
}
},
"startTime": 1757699100000
},
{...},
...
]

clusters(exchange, ticker, timeframe, lookback?)

클러스터(풋프린트) 데이터: 캔들의 모든 가격 레벨별 매수 및 매도 거래량입니다.

[
{
"data": {
"totalBuy": 16053385,
"totalSell": 20490989,
"0.8654": { "buy": 286845, "sell": 336689 },
"0.8653": { "buy": 271848, "sell": 259167 },
"0.8652": { "buy": 318814, "sell": 373497 },
...
},
"startTime": 1757699100000
},
{...},
...
]

liquidations(exchange, ticker, timeframe, lookback?)

캔들의 모든 가격 레벨별 청산 거래량입니다(선물 시장만 해당). 형식은 clusters()와 같습니다.

[
{
"data": {
"totalBuy": 16053385,
"totalSell": 20490989,
"0.8654": { "buy": 286845, "sell": 336689 },
"0.8653": { "buy": 271848, "sell": 259167 },
"0.8652": { "buy": 318814, "sell": 373497 },
...
},
"startTime": 1757699100000
},
{...},
...
]

moreData(exchange, ticker, timeframe, lookback?)

캔들별 파생상품 데이터: 펀딩비, 미결제약정 캔들, 롱/숏 비율입니다(선물 시장만 해당).

[
{
"data": {
"FR": 0.00003777, // 펀딩비
"OI": { // 미결제약정 캔들
"open": 85645.708,
"close": 83496.225,
"high": 85801.04,
"low": 83397.918
},
"LS": { "l": 0.5426, "s": 0.4574 }, // 롱/숏 비율
"taLS": { "l": 0.552, "s": 0.448 }, // 탑 트레이더 계정 비율
"tpLS": { "l": 0.5996, "s": 0.4004 } // 탑 트레이더 포지션 비율
},
"startTime": 1757699100000
},
{...},
...
]

orderbook(exchange, ticker)

실시간 오더북 스냅샷: 캔들에 묶이지 않은 현재 전체 오더북입니다.

{
"asks": {
"109425": 5.467,
"109495": 3.564,
...
},
"bids": {
"109400": 1.2,
"109390": 0.8,
...
}
}

레벨은 정렬되어 있지 않으므로, 오더북을 다룰 때는 ta 오더북 헬퍼(ta.bestBid, ta.sumBids, ...)를 사용하세요. 캔들의 과거 오더북은 depths()에서 가져옵니다. depths()[i].data는 형식이 같지만, 그곳에는 가장 큰 지정가 주문만 저장됩니다.

예시:

const candles = source.ohlcv(); // exchange, ticker, timeframe 매개변수의 기본값은 현재 차트입니다.
const closes = candles.map(c => c.close);

levels(exchange, settings?)

유동성 스크리너의 레벨: 거래소의 대형 대기 지정가 주문을 스크리너 페이지와 동일한 설정으로 필터링한 것입니다. 캔들에 묶이지 않은 실시간 데이터입니다. 플랜이 없으면 근접도는 1%, 지속 시간은 60분, 결과는 200개 레벨로 제한되며, 플랜이 있으면 최대 2000개 레벨까지 가능합니다. 가장 큰 주문이 먼저 옵니다. 데이터 소스 요청 1회로 계산됩니다.

{
"BTCUSDT": {
"asks": {
"109500": {
"price": 109500,
"quantity": 42.5, // 기초 자산 기준 수량
"quantityUSDT": 4653750, // 견적 통화 기준 수량
"distance": 0.62, // 현재 가격으로부터의 거리, 퍼센트
"duration": 184, // 주문이 유지된 시간(분)
"touches": 2,
"currentTouches": 1,
"filledPercent": 12.4,
"strength": 95.5, // 퍼센트, 스크리너 참고
"diversity": 11,
"isContinuous": true,
"date": "2026-10-10T06:12:03.000Z"
},
...
},
"bids": { ... },
"volume1hTotal": 183000000
},
...
}

설정 (모두 선택 사항):

설정기본값설명
tickers모든 티커거래소의 이 티커들만
exclude[]건너뛸 티커
type"all""all", "bids" 또는 "asks"
nearness5현재 가격으로부터의 최대 거리, 퍼센트
size100000견적 통화 기준 최소 주문 크기
lifespan0주문이 유지된 최소 시간(분)
touches, currentTouches0레벨 / 현재 주문의 최소 터치 수
filled0주문의 최소 체결 비율, 퍼센트
strength, diversity0스크리너와 같은 최소 강도(퍼센트) 및 다양성
continuousOnlyfalse사라졌다가 다시 나타난 주문은 건너뜀
volume1h0견적 통화 기준 티커의 최소 1시간 거래량
spotAndFuturesfalse현물과 선물 시장 모두에 존재하는 티커만
limit500최대 레벨 수, 큰 것부터(최대 2000)

예시:

// 차트 시장의 가격 주변 대형 주문을 수평선으로 표시하고, 크기와 지속 시간으로 라벨을 붙입니다
const candles = await source.ohlcv();
const lastDate = candles.at(-1).startTime;
const levels = await source.levels(currentExchange, { tickers: [currentTicker], nearness: 3, size: 300000, lifespan: 30 });
const own = levels[currentTicker] || { asks: {}, bids: {} };
for (const [type, color] of [["asks", "#ef5350"], ["bids", "#26a69a"]]) {
for (const [price, level] of Object.entries(own[type])) {
plot.horizontalLine(`${type} ${price}`, +price, color, 0.8, Math.min(4, 1 + level.quantityUSDT / 1e6));
plot.text(`${type} ${price} label`, `${(level.quantityUSDT / 1e6).toFixed(2)}M · ${level.duration}m`, lastDate, +price, 10, color);
}
}

HTTP 요청

http 클래스는 외부 HTTP API로 요청을 보내고 응답을 반환하므로, 지표는 source에서 제공되지 않는 데이터도 사용할 수 있습니다: 자체 서버, 공개 REST API, 온체인 지표 제공자 등. source.*와 마찬가지로 호출은 동기 함수처럼 작성되며, await가 필요하지 않습니다.

메서드설명
get(url, options?)GET 요청
post(url, body?, options?)POST 요청. 객체나 배열인 body는 JSON(Content-Type: application/json)으로, 문자열은 그대로 전송됩니다

options는 { headers: { name: value } }이며, 최대 10개의 사용자 지정 헤더(예: Authorization 또는 x-api-key)를 지정할 수 있습니다.

두 메서드 모두 동일한 응답 객체를 반환하며 예외를 던지지 않습니다:

{
ok: true, // 상태 코드 200–299
status: 200, // HTTP 상태 코드, 요청이 전송되지 않았으면 0
headers: { "content-type": "application/json", ... },
data: { ... }, // JSON으로 파싱된 응답 본문, 본문이 JSON이 아니면 null
text: "...", // 원본 응답 본문
error: null // 요청 실패 사유: "timeout", "url not allowed", "too many requests", ...
}

가능한 error 값: invalid url, url not allowed, invalid headers, invalid body, body too large, response too large, timeout, too many requests, rate limit, busy, proxy unavailable, network error.

예시 (GET):

define("Fear & Greed", "Crypto Fear & Greed index", "offchart", true, true);
const res = http.get("https://api.alternative.me/fng/?limit=30");
if (!res.ok) logger.log("request failed:", res.status, res.error);
// res.data = { data: [{ value: "71", timestamp: "1757635200" }, ...] }
const byDay = {};
(res.data?.data || []).forEach((row) => { byDay[Number(row.timestamp) * 1000] = Number(row.value); });
const candles = source.ohlcv();
const values = candles.map((c) => c ? byDay[c.startTime - (c.startTime % 86400000)] ?? null : null);
plot.lineSeries("Fear & Greed", values, "#00C8FF");

예시 (POST):

const candles = source.ohlcv();
const last = candles.at(-1);
const res = http.post(
"https://example.com/api/signals",
{ exchange: currentExchange, ticker: currentTicker, close: last.close },
{ headers: { "x-api-key": "YOUR_KEY" } } // 비공개 코드에서만
);
logger.log("signal sent:", res.ok, res.status, res.data);

전역 변수

변수설명예시
currentExchange차트의 현재 거래소binance, binanceFutures, coinbase, ...
currentTicker차트의 현재 티커(형식은 거래소에 따라 다름)BTCUSDT, ADA-USD, BTC/USDC, ...
currentTimeframe차트의 현재 타임프레임1m, 5m, 15m, ...

예시:

const candles = source.ohlcv(currentExchange, currentTicker, currentTimeframe);
const closes = candles.map(c => c.close);

로깅

logger 클래스는 정보 출력과 디버깅에 사용됩니다.

메서드설명
log(...args)로그에 항목을 추가합니다(길이와 개수 제한 있음)

예시:

const candles = source.ohlcv();
logger.log("First candle:", candles?.[0]);

시각화

plot 클래스는 그래픽 요소와 데이터 시리즈를 그리는 역할을 합니다.

기하 도형

메서드설명
box(title, date1, price1, date2, price2, fill, opacity, stroke, strokeOpacity, strokeWidth)사각형
square(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth)정사각형
diamond(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth)마름모
triangle(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth)삼각형
circle(title, date1, price1, radius = 10, fill, opacity, stroke, strokeOpacity, strokeWidth)원
text(title, value, date1, price1, fontSize, fill, opacity)차트 위 텍스트
table(title, rows, options)차트 모서리에 고정된 테이블, 테이블 참고
straightLine(title, date1, price1, date2, price2, stroke, strokeOpacity, strokeWidth, strokeDasharray)직선
horizontalLine(title, price1, stroke, strokeOpacity, strokeWidth, strokeDasharray)차트 전체를 가로지르는 수평선
verticalLine(title, date1, stroke, strokeOpacity, strokeWidth, strokeDasharray)차트 전체를 가로지르는 수직선
매개변수데이터 타입설명
titlestring요소 이름(차트에서의 고유 식별자)
date1number (타임스탬프, ms)시작 타임스탬프
price1number시작 가격(Y축 좌표)
date2number (타임스탬프, ms)종료 타임스탬프(선과 사각형용)
price2number종료 가격(선과 사각형용)
sizenumber도형 크기(픽셀)
radiusnumber원의 반지름(기본값 10)
fillstring (hex, 예: "#FF0000")도형의 채우기 색상
opacitynumber (0–1)채우기 불투명도
strokestring (hex, 예: "#000000")테두리 색상
strokeOpacitynumber (0–1)테두리 불투명도
strokeWidthnumber테두리 두께(픽셀)
strokeDasharrayarray (예: [5,5])점선 스타일
fontSizenumber텍스트 크기(픽셀)
valuestring텍스트 값(text 메서드용)

예시:

plot.text("Text object", "Hello world!", 1757699100000, 115000, 10, "#00C8FF", 0.5);
plot.straightLine(`${namePrefix} Open`, level.timeStart, level.open, level.timeEnd, level.open, color);
plot.circle(`${namePrefix} High`, level.timeStart, level.high, 3, color);

차트에는 최대 3000개의 도형만 표시할 수 있습니다.

데이터 시리즈

메서드설명
lineSeries(title, values, stroke, strokeOpacity, strokeWidth, strokeDasharray)라인 차트
barSeries(title, values, fill, opacity)막대 차트
candleSeries(title, openValues, closeValues, highValues, lowValues, fill, opacity)캔들
areaSeries(title, values, fill, opacity, stroke, strokeOpacity, strokeWidth, base)선과 base(기본값: 차트 하단) 사이를 채운 영역
bandSeries(title, upperValues, lowerValues, fill, opacity, stroke, strokeOpacity, strokeWidth, step)두 시리즈 사이를 채운 밴드, 각 캔들은 자체 색상과 불투명도를 가집니다
profile(title, values, options)보이는 캔들에 대한 볼륨 프로파일 형태의 히스토그램, 프로필 참고
histogram(title, rows, options)완성된 가격 맵을 프로필로 그림, 히스토그램 참고
매개변수데이터 타입설명
values, upperValues, lowerValues(number \| null)[]캔들당 하나의 값, null이면 해당 캔들을 숨깁니다
fill, strokestring (hex) 또는 function색상, 또는 캔들의 색상을 반환하는 함수: lineSeries, barSeries, candleSeries, areaSeries는 (value, i), bandSeries는 (upper, lower, i)
opacitynumber (0–1) 또는 function채우기 불투명도, areaSeries / bandSeries에서는 함수 (value, i) / (upper, lower, i)
basenumber | nullareaSeries의 기준 레벨, null = 차트 하단
stepbooleanbandSeries 스타일: true = 캔들당 하나의 열(기본값), false = 연속된 다각형

예시:

plot.lineSeries("EMA", ta.ema(closes, 20), "#00C8FF");
// 각 캔들이 자체 색상을 가집니다: 종가가 EMA보다 위면 녹색, 아래면 빨간색
plot.lineSeries("Close", closes, (v, i) => v > ema[i] ? colorBuy : colorSell);
plot.barSeries("Premium %", premiumPercents, ((d) => d > 0 ? color1 : color2), 1);
plot.candleSeries(
"Candles",
opens, closes, highs, lows,
(v, i) => {
return opens?.[i] > closes?.[i] ? colorBuy : colorSell
}
);
plot.areaSeries("Volume", volumes, "#00C8FF", 0.3);
// 캔들별로 색칠된 영역, 큰 캔들일수록 더 불투명하게
plot.areaSeries("Delta", deltas, (v, i) => v >= 0 ? colorBuy : colorSell, (v, i) => Math.min(1, Math.abs(v) / maxDelta));
// 종가와 그보다 1% 아래 사이의 압력 밴드, 캔들의 불균형 비율에 따라 색상과 음영을 적용
plot.bandSeries(
"Bid pressure",
closes,
closes.map(c => c * 0.99),
(upper, lower, i) => ratios[i] > 0 ? bidColor : askColor,
(upper, lower, i) => Math.abs(ratios[i]) / 100
);

차트에는 최대 20개의 데이터 시리즈만 표시할 수 있습니다.

프로필

plot.profile(title, values, options?)는 내장 볼륨 프로파일처럼 가격별 히스토그램을 그립니다. values[i]는 ohlcv()의 캔들 i에 대한 가격 맵입니다: { price: value } 또는 { price: { buy, sell } }(clusters() 또는 liquidations() 레코드의 data를 그대로 전달할 수 있습니다), 행이 없는 캔들은 null. 차트에 보이는 캔들의 행은 차트 자체에서 가격 단위별로 합산되므로, 프로필은 새로 실행하지 않아도 이동, 확대/축소, 추가 로드를 따라갑니다. 단일 값은 한 가지 색상으로 그려지고(음수 값은 fill2로), 쌍은 막대를 매수와 매도 부분으로 나누며 델타 모드를 사용할 수 있습니다. profile()은 알림 핸들을 반환하지 않습니다.

옵션기본값설명
step차트 틱 크기가격 단위의 행 높이, 가격은 이 값 단위로 내림됩니다
orient"right"막대가 자라나는 가장자리, "right" 또는 "left"
width30가장 넓은 행의 너비, 차트 너비 대비 퍼센트
fill"#26a69a"값 또는 매수 부분의 색상
fill2"#ef5350"매도 부분의 색상
opacity0.6막대 불투명도
deltafalse합계 대신 buy - sell을 그림, 쌍에만 해당
poctrue가장 큰 값을 가진 행에 선 표시
valueArea70POC 주변에서 완전 불투명도로 그려지는 전체 대비 퍼센트, 비활성화하려면 false
valueAreaOpacity0.3밸류 에어리어 밖의 행 불투명도
labelstrue공간이 있을 때 막대에 행 합계 표시

제한: 실행당 프로필 3개, 캔들당 300행, 프로필당 200000행. 제한을 넘는 행은 버려지며 실행 로그에 메모가 남습니다.

예시:

// 차트의 볼륨 프로파일, 내장 도구와 같은 그림
const rows = await source.clusters();
plot.profile("Volume", rows.map((r) => r?.data ?? null), { step: 5 });
// 대량 거래만, 델타 색상, 왼쪽 가장자리
const minSize = input.number("Min trade size", 50, 0, 100000, 1);
const big = rows.map((r) => {
if (!r?.data) return null;
const out = {};
for (const [price, c] of Object.entries(r.data)) {
if (price === "totalBuy" || price === "totalSell") continue;
if (c.buy + c.sell >= minSize) out[price] = { buy: c.buy, sell: c.sell };
}
return out;
});
plot.profile("Large trades", big, { step: 5, delta: true, orient: "left" });

히스토그램

plot.histogram(title, rows, options?)는 이미 만들어 둔 히스토그램을 그립니다: 차트 전체에 대한 하나의 가격 맵이며, 캔들에서 합산되는 것은 없습니다. 행이 캔들에 속하고 그림이 보이는 범위를 따라가야 할 때는 profile()을, 행이 최종 결과일 때는 histogram()을 사용하세요: 오더북 스냅샷, 스크립트가 계산한 레벨, 다른 시장의 데이터 등.

rows는 profile()의 캔들 항목과 같은 형식을 가집니다: { price: value }, { price: { buy, sell } } 또는 [price, buy, sell] 행의 목록. 단일 값은 한 가지 색상으로 그려지고(음수 값은 fill2로), 쌍은 막대를 매수와 매도 부분으로 나누며 델타 모드를 사용할 수 있습니다. 옵션은 profile()과 같으며, 위의 표를 참고하세요.

히스토그램은 실행의 마지막 캔들 날짜를 가지며, 차트는 제목별로 가장 최신 히스토그램을 유지하므로 오래된 캔들에 대한 부분 실행(히스토리 로딩)이 현재 히스토그램을 대체하지 않습니다. 스크립트가 새로고침될 때마다 다시 그려지므로, 오더북 히스토그램은 실시간 오더북을 따라갑니다. histogram()은 알림 핸들을 반환하지 않습니다.

제한: 실행당 히스토그램 3개, 5000행. 제한을 넘는 행은 버려지며 실행 로그에 메모가 남습니다.

예시, 오더북:

// 실시간 오더북을 오른쪽 가장자리에 프로필로 표시: 매수 호가는 매수 색상, 매도 호가는 매도 색상
const book = await source.orderbook();
const rows = {};
for (const [price, qty] of Object.entries(book.bids)) rows[price] = { buy: qty };
for (const [price, qty] of Object.entries(book.asks)) rows[price] = { sell: qty };
plot.histogram("Order book", rows, { step: 5, width: 25, valueArea: false, poc: false });

예시, 직접 만든 레벨:

// 직접 만든 히스토그램: 최근 100개 캔들의 클러스터 거래량, 최근 캔들일수록 가중치가 큼
const rows = await source.clusters();
const weighted = {};
rows.slice(-100).forEach((r, i, arr) => {
if (!r?.data) return;
const weight = (i + 1) / arr.length;
for (const [price, c] of Object.entries(r.data)) {
if (price === "totalBuy" || price === "totalSell") continue;
const cell = weighted[price] || (weighted[price] = { buy: 0, sell: 0 });
cell.buy += (c.buy || 0) * weight;
cell.sell += (c.sell || 0) * weight;
}
});
plot.histogram("Weighted volume", weighted, { step: 5, orient: "left", delta: true });

테이블

plot.table(title, rows, options?)는 차트 패널의 모서리에 고정된 작은 표를 그립니다: 멀티 타임프레임 요약, 로드된 범위의 통계, 신호 대시보드 등. rows는 행의 배열이고, 행은 셀의 배열이며, 셀은 문자열, 숫자 또는 { text, color, bg, align, bold }입니다. 같은 모서리의 테이블은 순서대로 쌓입니다. 테이블은 실행의 마지막 캔들 날짜를 가지며, 차트는 제목별로 가장 최신 테이블을 유지하므로 오래된 캔들에 대한 부분 실행(히스토리 로딩)이 현재 테이블을 대체하지 않습니다. 너비가 500 px보다 좁은 차트에는 테이블이 그려지지 않습니다. 테이블은 마우스로 드래그할 수 있습니다: 위치는 워크스페이스와 함께 저장되며, 스크립트가 position 옵션을 변경하면 초기화됩니다. table()은 알림 핸들을 반환하지 않습니다.

옵션기본값설명
position"top-right"패널의 모서리: "top-right", "top-left", "bottom-right", "bottom-left"
headerfalse첫 번째 행을 음영 띠 위에 굵게 표시
fontSize11텍스트 크기(px), 8부터 16까지
color, bg, border테마에 따름기본 텍스트, 패널, 격자 색상; 셀에서 color와 bg를 재정의할 수 있습니다
opacity0.85패널 불투명도
align"left"기본 셀 정렬, "left", "center" 또는 "right"

제한: 실행당 테이블 3개, 50행, 10열, 셀당 100자. 더 긴 텍스트는 말줄임표로 잘리며, 한 열은 차트 너비의 최대 30%를 차지합니다.

예시:

const candles = await source.ohlcv();
const closes = candles.map((c) => c.close);
const rsi = ta.rsi(closes, 14);
const last = rsi.at(-1);
const change = (closes.at(-1) / closes.at(-25) - 1) * 100;
plot.table("Stats", [
["Metric", "Value"],
["RSI 14", { text: last.toFixed(1), color: last > 70 ? "#ef5350" : last < 30 ? "#26a69a" : undefined }],
["Change 24 bars", { text: change.toFixed(2) + "%", align: "right" }],
["Volume", candles.slice(-24).reduce((s, c) => s + c.volume, 0).toFixed(0)],
], { position: "top-right", header: true });

기술적 분석

ta 클래스는 PineScript v5의 ta.* 함수를 구현합니다. Pine은 바 단위로 동작하지만, 여기서는 모든 함수가 전체 시리즈(배열, 캔들당 하나의 값, 오래된 것부터)를 받아 바로 플로팅할 수 있는 같은 길이의 시리즈를 반환합니다. 기간이 아직 채워지지 않은 동안 값은 null이며, null 입력은 Pine의 na처럼 전파됩니다. high, low, close 또는 volume을 암묵적으로 읽는 Pine 함수는 해당 배열을 인수로 받습니다.

여러 시리즈를 반환하는 함수(bb, kc, macd, stoch, dmi, supertrend)는 이름이 붙은 속성도 가진 배열을 반환합니다: const [middle, upper, lower] = ta.bb(closes, 20, 2) 또는 ta.bb(closes, 20, 2).upper.

헬퍼 함수

메서드설명
highest(values, length) / lowest(values, length)length개 바 동안의 최고값 / 최저값
highestbars(values, length) / lowestbars(values, length)최고 / 최저 바까지의 오프셋(0 또는 음수)
max(values) / min(values)각 바까지의 역대 최고 / 최저값
range(values, length)highest - lowest
change(values, length = 1)values[i] - values[i - length]
mom(values, length)모멘텀
roc(values, length)변화율, %
cum(values)누적 합계
rising(values, length) / falling(values, length)값이 이전 length개의 값 각각보다 위 / 아래인지 여부
variance(values, length, biased = true)분산
stdev(values, length, biased = true)표준편차
dev(values, length)평균 절대 편차
median(values, length)중앙값
mode(values, length)최빈값(동률이면 가장 작은 값)
percentile_linear_interpolation(values, length, percentage)백분위수, 가장 가까운 두 순위 사이의 선형 보간
percentile_nearest_rank(values, length, percentage)백분위수, 최근접 순위 방식
percentrank(values, length)이전 length개 값 중 현재 값 이하인 값의 비율(%)
correlation(series1, series2, length)상관 계수
crossover(series1, series2)상향 교차(어느 인수든 숫자가 될 수 있음: ta.crossover(rsi, 70))
crossunder(series1, series2)하향 교차
cross(series1, series2)모든 교차
barssince(condition)condition(불리언 배열)이 마지막으로 참이었던 이후의 바 수
valuewhen(condition, values, occurrence = 0)condition이 occurrence번 전에 참이었을 때의 values 값
pivothigh(values, leftbars, rightbars) / pivotlow(...)확인 바에서의 피벗 값, 피벗 자체는 rightbars개 바 이전에 있습니다

이동평균

메서드설명
sma(values, length)단순 이동평균
ema(values, length)지수 이동평균
rma(values, length)와일더 이동평균(RSI / ATR에서 사용)
wma(values, length)가중 이동평균
hma(values, length)헐 이동평균
alma(values, length, offset = 0.85, sigma = 6, floor = false)아르노 르구 이동평균(ALMA)
swma(values)4개 바에 대한 대칭 가중 이동평균
vwma(values, volumes, length)거래량 가중 이동평균
linreg(values, length, offset = 0)선형 회귀 곡선

오실레이터

메서드설명
rsi(values, length)상대강도지수(RSI)
cmo(values, length)샹드 모멘텀 오실레이터
cog(values, length)무게 중심(Center of Gravity)
mfi(values, volumes, length)자금 흐름 지수(values는 보통 hlc3)
tsi(values, shortLength, longLength)진정 강도 지수(TSI), -1..1
wpr(highs, lows, closes, length)윌리엄스 %R
stoch(highs, lows, closes, period = 14, smoothK = 3, smoothD = 3)스토캐스틱 [k, d], smoothK = 1이면 원시 값
cci(values, length) / cci(highs, lows, closes, length)상품 채널 지수(두 번째 형식은 대표 가격 기준)
macd(values, fast = 12, slow = 26, signalPeriod = 9)MACD [macd, signal, histogram]

밴드와 채널

메서드설명
bb(values, length = 20, mult = 2) / bollinger(...)볼린저 밴드 [middle, upper, lower]
bbw(values, length, mult)볼린저 밴드 폭 (upper - lower) / middle
kc(values, highs, lows, closes, length = 20, mult = 2, useTrueRange = true)켈트너 채널 [middle, upper, lower]
kcw(values, highs, lows, closes, length, mult, useTrueRange)켈트너 채널 폭 (upper - lower) / middle

추세와 변동성

메서드설명
tr(highs, lows, closes, handleNa = false)실제 범위(True Range, handleNa = true이면 첫 번째 바에서 high - low)
atr(highs, lows, closes, period = 14)평균 실제 범위(ATR)
dmi(highs, lows, closes, diLength = 14, adxSmoothing = 14)방향성 지표 [plus, minus, adx]
adx(highs, lows, closes, period = 14)평균 방향성 지수(ADX)
sar(highs, lows, closes, start = 0.02, inc = 0.02, max = 0.2)파라볼릭 SAR
supertrend(highs, lows, closes, factor = 3, atrPeriod = 10)슈퍼트렌드 [supertrend, direction], direction -1 = 상승 추세, 1 = 하락 추세

거래량

메서드설명
vwap(values, volumes, anchor?) / vwap(highs, lows, closes, volumes, anchor?)누적 VWAP(두 번째 형식은 대표 가격 기준), anchor(불리언 배열)가 참인 바에서 다시 시작
obv(closes, volumes)온밸런스 볼륨(OBV)
accdist(highs, lows, closes, volumes)매집 / 분산(A/D)
iii(highs, lows, closes, volumes)일중 강도 지수(Intraday Intensity Index)
pvt(closes, volumes)가격-거래량 추세(PVT)
nvi(closes, volumes) / pvi(closes, volumes)음 / 양 거래량 지수
wad(highs, lows, closes)윌리엄스 매집 / 분산
wvad(opens, highs, lows, closes, volumes)윌리엄스 가변 매집 / 분산

오더북 헬퍼

orderbook은 source.orderbook() 또는 source.depths()[i].data입니다: {asks: {price: quantity}, bids: {price: quantity}}.

메서드설명
orderbookSide(orderbook, side)한쪽('bids' / 'asks')을 최우선 가격부터 정렬된 [[price, quantity], ...]로 반환
bestBid(orderbook) / bestAsk(orderbook)최우선 매수 / 매도 호가(빈 오더북이면 null)
sumBids(orderbook, percent, price, inQuote)price(기본값: 최우선 매수 호가)와 price * (1 - percent / 100) 사이의 누적 매수 수량, inQuote = true이면 price * quantity를 합산
sumAsks(orderbook, percent, price, inQuote)price(기본값: 최우선 매도 호가)와 price * (1 + percent / 100) 사이의 누적 매도 수량

예시:

const candles = source.ohlcv();
const closes = candles.map(c => c.close);
const rsiValues = ta.rsi(closes, 14);
plot.lineSeries("RSI", rsiValues, "#00C8FF");

알림

사용자는 차트의 지표 설정에 있는 알림 탭에서 여러분의 지표에 대한 알림을 만들 수 있습니다. 알림은 서버 측에서 정기적으로 확인되며, 알림 메시지는 Telegram 및/또는 사용자가 지정한 웹훅 URL로 전송됩니다.

알림 설정 — alertInput

alertInput 클래스는 알림 설정을 선언합니다. input과 똑같이 동작하지만, 필드는 알림 탭에 표시되며 각 사용자가 자신의 알림에 대해 고유한 값을 설정합니다.

alertInput 선언이 없는 스크립트는 알림에 사용할 수 없습니다.

메서드설명
number(title, defaultValue, min, max, step)숫자 입력
range(title, defaultValue, min, max, step)범위 입력
select(title, defaultValue, values)드롭다운 선택
color(title, defaultValue)색상 선택기
boolean(title, defaultValue)불리언 입력

예시:

const alertVolumeThreshold = alertInput.number("Volume threshold", 1000, 0, 10000000, 1);

알림 발생 — alert.fire

메서드설명
fire(title, message, date)알림을 발생시킵니다
  • 세 매개변수 모두 필수입니다. title, message 또는 date가 누락된 호출은 건너뜁니다(로그에 경고가 기록됩니다).
  • 알림 제목은 고정하고, 고유한 제목을 생성하지 마세요. 각 제목은 실행당 한 번만 발생합니다(실행당 최대 20개 제목).
  • date는 신호가 속한 캔들의 startTime입니다. 같은 제목은 더 새로운 캔들에 대해 발생할 때만 다시 알립니다. 사용자가 알림을 만든 시점보다 오래된 캔들의 신호는 무시됩니다(스팸 방지).

예시:

if (candle.volume > alertVolumeThreshold) {
alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);
}

도형 알림 — plot.<shape>(...).alert(...)

모든 기하 도형(box, square, diamond, triangle, circle, straightLine, horizontalLine, verticalLine, text)은 .alert() 메서드가 있는 핸들을 반환합니다. 알림은 도형의 date1로 자동 발생하므로 추가 코드 없이 스팸 방지 로직이 동작합니다. 캔들별 신호에는 이 방법을 권장합니다.

두 가지 형식:

// 간단한 형식: 새 날짜에 도형이 그려질 때마다 발생
plot.box(...).alert("High volume", `vol ${candle.volume}`);
// 콜백: 건너뛰려면 아무것도 반환하지 않고, 발생시키려면 [title, message]를 반환
plot.box(...).alert((shape) => {
if (candle.volume > alertVolumeThreshold)
alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);
});

콜백은 도형 데이터(date1, price1, date2, price2, ...)를 받습니다.

시리즈 알림 — plot.<series>(...).alert(...)

데이터 시리즈(lineSeries, barSeries, candleSeries, areaSeries, bandSeries)도 .alert() 핸들을 반환합니다. 콜백은 최신 데이터 포인트와 최신 캔들 날짜를 받으며, 알림은 그 날짜로 발생합니다.

  • lineSeries / barSeries / areaSeries — 콜백은 마지막 값(숫자)을 받습니다
  • candleSeries — 콜백은 마지막 캔들 {open, close, high, low}를 받습니다
  • bandSeries — 콜백은 마지막 {upper, lower}를 받습니다
plot.lineSeries("RSI", rsiValues, color).alert((rsi, date) => {
if (rsi > alertRsiThreshold) alert.fire("RSI high", `RSI ${rsi.toFixed(1)}`, latestCandle.startTime);
});

참으로 유지되는 조건은 새 캔들마다 한 번 알립니다. 일회성 신호에는 교차 방식의 조건(ta.crossover)을 사용하세요.

전체 예시

const title = "Volume alerts";
const description = "Shows boxes on high-volume candles";
define(title, description, "onchart", true, true);
const color = style.color("Color", "#00C8FF");
const volumeThreshold = input.number("Volume threshold", 1000, 0, 10000000, 1);
const alertVolumeThreshold = alertInput.number("Alert volume threshold", 5000, 0, 10000000, 1);
const alertCloseThreshold = alertInput.number("Alert close threshold", 100000, 0, 10000000, 1);
const candles = source.ohlcv();
const latestCandle = candles.at(-1);
const closes = candles.map(c => c.close);
// 시리즈 알림: 최신 종가를 확인합니다
plot.lineSeries("Closes", closes, color).alert((close, date) => {
if (close > alertCloseThreshold) alert.fire("Close above threshold", `close ${close}`, latestCandle.startTime);
});
for (let i = 0; i < candles.length; i++) {
const candle = candles[i];
if (candle.volume > volumeThreshold) {
const right = i < candles.length - 1 ? candles[i + 1].startTime : candle.startTime + 60000;
// 도형 알림: 사용자의 알림 임계값과 비교합니다
plot.box(`Volume ${i}`, candle.startTime, candle.high, right, candle.low, "#00C8FF", 0.3)
.alert(() => {
if (candle.volume > alertVolumeThreshold)
alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);
});
}
}

지표 예시

const title = "Your first indicator";
const description = "Shows closes lines on the chart";
const indicatorType = "onchart";
define(title, description, indicatorType, true, true);
const color = input.color("Line Color", "#00C8FF");
const candles = source.ohlcv();
const closes = candles.map(c => c.close);
plot.lineSeries("Closes", closes, color);
define("Example Indicator", "Demonstrates basic plotting", "onchart", true, true);
const color = input.color("Line Color", "#00FF99");
const candles = source.ohlcv();
const closes = candles.map(c => c.close);
const ema20 = ta.ema(closes, 20);
plot.lineSeries("EMA 20", ema20, color);
const title = "Colored Volume Bars";
const description = "Volumes colored based on candle direction.";
const indicatorType = "onchart";
const isPublic = true;
const isPublicCode = true;
define(title, description, indicatorType, isPublic, isPublicCode);
// OHLC 캔들 가져오기
const candles = source.ohlcv();
const colorUp = style.color("Color 1", "#2BB462"); // 상승 가격용 녹색 계열
const colorDown = style.color("Color 2", "#FB4C51"); // 하락 가격용 빨간색 계열
const volumes = candles.map(candle => candle.volume);
plot.barSeries("Volume", volumes, (value, index) => {
const currentCandle = candles[index];
return currentCandle.close >= currentCandle.open ? colorUp : colorDown;
});
define('Coinbase Premium Index', 'Coinbase Premium Index', 'offchart', true, false);
const color1 = style.color("Color 1", "#2BB462");
const color2 = style.color("Color 2", "#FB4C51");
// OHLC 캔들 가져오기
const cbCandles = source.ohlcv('coinbase', 'BTC-USD');
const binanceCandles = source.ohlcv('binance', 'BTCUSDT');
// 종가 추출
const cbCloses = cbCandles.map(c => c.close);
const binanceCloses = binanceCandles.map(c => c.close);
// --- Coinbase 프리미엄 지수 계산 ---
const premiumPercent = cbCloses.map((cb, i) => {
const b = binanceCloses[i];
return ((cb - b) / b) * 100;
});
// 프리미엄 지수 플로팅
plot.barSeries("Premium %", premiumPercent, ((d) => d > 0 ? color1 : color2)) // 퍼센트 차이
const title = "Candles";
const description = "Simple candles draw";
const indicatorType = "offchart";
const isPublic = true;
const isPublicCode = true;
define(title, description, indicatorType, isPublic, isPublicCode);
const colorBuy = style.color("Buy Color", "#2BB462");
const colorSell = style.color("Sell Color", "#7a3d3f");
const candles = source.ohlcv();
const closes = candles.map(c => c.close);
const opens = candles.map(c => c.open);
const highs = candles.map(c => c.high);
const lows = candles.map(c => c.low);
plot.candleSeries(
"Candles",
opens, closes, highs, lows,
(v, i) => {
return opens?.[i] > closes?.[i] ? colorBuy : colorSell
}
);

Order Book Pressure(가격 주변의 매수 / 매도 호가 깊이 불균형 밴드로, 트레이딩 터미널의 "Order Book Pressure" 오버레이와 유사):

const title = "Order Book Pressure";
const description = "Bid / ask depth imbalance bands around the price";
define(title, description, "onchart", true, true);
const minBidRatio = input.number("Min bid ratio %", 20, 0, 100, 1);
const minAskRatio = input.number("Min ask ratio %", 20, 0, 100, 1);
const depthFrom = input.select("Depth from", "0%", ["0%", "1%", "2.5%", "5%"]);
const depthTo = input.select("Depth to", "5%", ["1%", "2.5%", "5%", "10%"]);
const bidColor = style.color("Bid fill color", "#a5d6a7");
const askColor = style.color("Ask fill color", "#f1627e");
const extremeColor = style.color("Extreme color", "#ffff00");
const highlight = style.boolean("Highlight extremes", true);
const showRef = style.boolean("Show reference lines", true);
const alertMinRatio = alertInput.number("Alert min |ratio| %", 40, 0, 100, 1);
const bounds = [1, 2.5, 5, 10]; // 종가로부터의 깊이 밴드(%)
const from = parseFloat(depthFrom);
const to = parseFloat(depthTo);
const candles = source.ohlcv();
const depths = source.depths();
const liveBook = source.orderbook();
// 각 캔들의 오더북: depths()[i]는 candles[i]의 스냅샷(가장 큰 지정가 주문), 최신 캔들은 실시간 오더북을 사용
const lastIndex = candles.length - 1;
const books = candles.map((c, i) => {
if (!c) return null;
if (i === lastIndex && ta.bestBid(liveBook)) return liveBook;
return depths[i]?.data || null;
});
const closes = candles.map(c => c ? c.close : null);
// 각 밴드의 불균형 비율(%) (양수 = 매수 우세, 음수 = 매도 우세)
const ratios = bounds.map((upper, b) => {
const lower = b === 0 ? 0 : bounds[b - 1];
return candles.map((c, i) => {
const book = books[i];
if (!c || !book) return null;
const bid = ta.sumBids(book, upper, c.close) - ta.sumBids(book, lower, c.close);
const ask = ta.sumAsks(book, upper, c.close) - ta.sumAsks(book, lower, c.close);
const total = bid + ask;
return total > 0 ? ((bid - ask) / total) * 100 : 0;
});
});
// 비율이 강할수록 밴드가 더 불투명해지고, 극단적인 비율은 극단 색상을 사용
const bandOpacity = (ratio) => Math.min(0.95, Math.abs(ratio || 0) / 70 * 0.95);
const bandColor = (ratio) => highlight && Math.abs(ratio || 0) >= 63 ? extremeColor : (ratio >= 0 ? bidColor : askColor);
bounds.forEach((upper, b) => {
const lower = b === 0 ? 0 : bounds[b - 1];
if (from > lower || upper > to) return; // 밴드가 선택한 깊이 범위를 벗어남
const bidUpper = closes.map(c => c != null ? c * (1 - lower / 100) : null);
const bidLower = closes.map(c => c != null ? c * (1 - upper / 100) : null);
const askLower = closes.map(c => c != null ? c * (1 + lower / 100) : null);
const askUpper = closes.map(c => c != null ? c * (1 + upper / 100) : null);
if (showRef) {
plot.lineSeries(`Bid ${upper}%`, bidLower, bidColor, 0.4);
plot.lineSeries(`Ask ${upper}%`, askUpper, askColor, 0.4);
}
// 매수 압력: 매수가 최소 비율보다 더 우세한 캔들에서만 밴드를 채움
plot.bandSeries(
`Bid pressure ${lower}-${upper}%`,
bidUpper.map((v, i) => ratios[b][i] >= minBidRatio ? v : null),
bidLower,
(upper, lower, i) => bandColor(ratios[b][i]),
(upper, lower, i) => bandOpacity(ratios[b][i])
);
// 매도 압력
plot.bandSeries(
`Ask pressure ${lower}-${upper}%`,
askUpper.map((v, i) => ratios[b][i] <= -minAskRatio ? v : null),
askLower,
(upper, lower, i) => bandColor(ratios[b][i]),
(upper, lower, i) => bandOpacity(ratios[b][i])
);
// 최신 캔들에 대한 알림
const latestRatio = ratios[b][lastIndex];
if (latestRatio != null && Math.abs(latestRatio) >= alertMinRatio) {
alert.fire(`Pressure ${lower}-${upper}%`, `ratio ${latestRatio.toFixed(1)}%`, candles[lastIndex].startTime);
}
});