MobChartDocs
Открыть MobChart

API для пользовательских индикаторов

Пишите собственные индикаторы на JavaScript, запускайте их в безопасной песочнице рядом с графиком и делитесь ими с другими пользователями. API предоставляет рыночные данные (свечи, стаканы, кластеры, ликвидации, данные деривативов), примитивы для отрисовки, функции технического анализа и алерты.

Генерация индикаторов с помощью ИИ

Весь 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, если индикатору нужно больше или меньше истории, например { warmup: 200 } для EMA(200). Скрипт, значения или фигуры которого зависят от всего видимого диапазона (VWAP от первой видимой свечи, накопленная дельта, подпись у максимума окна, уровни через весь график), должен объявить { fullRange: true }, иначе после подгрузки истории его результаты будут неверными. Режим считывается из define() при каждом сохранении: скрипт, сохранённый без fullRange, рассчитывается частично, поэтому добавьте флаг в существующие накопительные скрипты при их редактировании.

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

Расположение индикаторов при onchart и offchart:

Ввод параметров

Классы input и style позволяют создавать настройки для пользователя (аналог input() в PineScript).

МетодОписание
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 получает рыночные данные с сервера. Все методы принимают одинаковые параметры:

ПараметрОписание
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?)

Данные деривативов по свечам: ставка финансирования, свеча открытого интереса, соотношения long/short (только фьючерсные рынки).

[
{
"data": {
"FR": 0.00003777, // Funding Rate
"OI": { // Open Interest Candle
"open": 85645.708,
"close": 83496.225,
"high": 85801.04,
"low": 83397.918
},
"LS": { "l": 0.5426, "s": 0.4574 }, // Long/Short Ratio
"taLS": { "l": 0.552, "s": 0.448 }, // Top Traders Account Ratio
"tpLS": { "l": 0.5996, "s": 0.4004 } // Top Traders Position Ratio
},
"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(); // The exchange, ticker, and timeframe parameters default to the current chart.
const closes = candles.map(c => c.close);

HTTP-запросы

Класс http отправляет запросы к внешним HTTP API и возвращает ответ, поэтому индикатор может использовать данные, которых нет в source: ваш собственный сервер, публичный REST API, провайдер on-chain метрик, ... Как и 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, null если тело не JSON
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" } } // private code only
);
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)Текст на графике
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 (timestamp, ms)Начальная временная метка
price1numberНачальная цена (координата по оси Y)
date2number (timestamp, 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)Заливка между двумя сериями, у каждой свечи свой цвет и прозрачность
ПараметрТип данныхОписание
values, upperValues, lowerValues(number \| null)[]Одно значение на свечу, null скрывает свечу
fill, strokestring (hex) или functionЦвет или функция, возвращающая цвет свечи: (value, i) для lineSeries, barSeries, candleSeries, areaSeries, (upper, lower, i) для bandSeries
opacitynumber (0–1) или functionПрозрачность заливки, функция (value, i) / (upper, lower, i) для areaSeries / bandSeries
basenumber | nullБазовый уровень areaSeries, null = низ графика
stepbooleanСтиль bandSeries: true = колонка на свечу (по умолчанию), false = непрерывный полигон

Пример:

plot.lineSeries("EMA", ta.ema(closes, 20), "#00C8FF");
// every candle keeps its own color: green where the close is above the EMA, red below it
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);
// area colored per candle, more opaque on the bigger candles
plot.areaSeries("Delta", deltas, (v, i) => v >= 0 ? colorBuy : colorSell, (v, i) => Math.min(1, Math.abs(v) / maxDelta));
// pressure band between the close and 1% below it, colored and shaded by the imbalance ratio of the candle
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 серий данных на графике.

Технический анализ

Класс ta реализует функции ta.* из PineScript v5. Pine считает побарно, а здесь каждая функция принимает целые серии (массивы, одно значение на свечу, от старых к новым) и возвращает серию той же длины, которую можно сразу вывести на график. Пока период не заполнен, значения равны null, а null во входных данных распространяется дальше, как na в Pine. Функции Pine, которые неявно читают high, low, close или volume, принимают эти массивы аргументами.

Функции, возвращающие несколько серий (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 (массив boolean) последний раз был true
valuewhen(condition, values, occurrence = 0)Значение values в баре, где condition был true occurrence раз назад
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)Скользящая средняя Арно Легу
swma(values)Симметрично взвешенная скользящая средняя по 4 барам
vwma(values, volumes, length)Средняя, взвешенная по объёму
linreg(values, length, offset = 0)Кривая линейной регрессии

Осцилляторы

МетодОписание
rsi(values, length)Индекс относительной силы
cmo(values, length)Осциллятор моментума Чанде
cog(values, length)Центр тяжести
mfi(values, volumes, length)Индекс денежного потока (values обычно hlc3)
tsi(values, shortLength, longLength)Индекс истинной силы, -1..1
wpr(highs, lows, closes, length)Williams %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)Истинный диапазон (handleNa = true даёт high - low на первом баре)
atr(highs, lows, closes, period = 14)Средний истинный диапазон
dmi(highs, lows, closes, diLength = 14, adxSmoothing = 14)Индекс направленного движения [plus, minus, adx]
adx(highs, lows, closes, period = 14)Средний индекс направленного движения
sar(highs, lows, closes, start = 0.02, inc = 0.02, max = 0.2)Параболик SAR
supertrend(highs, lows, closes, factor = 3, atrPeriod = 10)Supertrend [supertrend, direction], direction -1 — восходящий тренд, 1 — нисходящий

Объём

МетодОписание
vwap(values, volumes, anchor?) / vwap(highs, lows, closes, volumes, anchor?)Накопительный VWAP (во второй форме — по типичной цене), сбрасывается на барах, где anchor (массив boolean) равен true
obv(closes, volumes)Балансовый объём (OBV)
accdist(highs, lows, closes, volumes)Накопление / распределение
iii(highs, lows, closes, volumes)Индекс внутридневной интенсивности
pvt(closes, volumes)Тренд цены и объёма
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 и/или на Webhook 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 пропускается (в лог записывается предупреждение).
  • Используйте постоянный title, не генерируйте уникальные названия. Каждый title срабатывает только один раз за запуск (максимум 20 названий за запуск).
  • date — это startTime свечи, к которой относится сигнал: тот же title уведомит снова, только когда сработает для более новой свечи. Сигналы со свечей старше момента создания алерта пользователем игнорируются (защита от спама).

Пример:

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

Алерты фигур — plot.<фигура>(...).alert(...)

Все геометрические фигуры (box, square, diamond, triangle, circle, straightLine, horizontalLine, verticalLine, text) возвращают объект с методом .alert(). Алерт срабатывает с date1 фигуры автоматически, поэтому защита от спама работает без дополнительного кода — это рекомендуемый способ для сигналов на конкретных свечах.

Две формы:

// simple: fires every time the shape is plotted for a new date
plot.box(...).alert("High volume", `vol ${candle.volume}`);
// callback: return nothing to skip, [title, message] to fire
plot.box(...).alert((shape) => {
if (candle.volume > alertVolumeThreshold)
alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);
});

Колбэк получает данные фигуры (date1, price1, date2, price2, ...).

Алерты серий — plot.<серия>(...).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);
// series alert: checks the latest 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;
// shape alert: checked against the user's alert threshold
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);
// Get OHLC candles
const candles = source.ohlcv();
const colorUp = style.color("Color 1", "#2BB462"); // Greenish color for rising prices
const colorDown = style.color("Color 2", "#FB4C51"); // Reddish color for falling prices
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");
// Get OHLC candles
const cbCandles = source.ohlcv('coinbase', 'BTC-USD');
const binanceCandles = source.ohlcv('binance', 'BTCUSDT');
// Extract closes
const cbCloses = cbCandles.map(c => c.close);
const binanceCloses = binanceCandles.map(c => c.close);
// --- Coinbase Premium Index calculation ---
const premiumPercent = cbCloses.map((cb, i) => {
const b = binanceCloses[i];
return ((cb - b) / b) * 100;
});
// Plot premium index
plot.barSeries("Premium %", premiumPercent, ((d) => d > 0 ? color1 : color2)) // Percentage difference
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]; // depth bands in % from the close
const from = parseFloat(depthFrom);
const to = parseFloat(depthTo);
const candles = source.ohlcv();
const depths = source.depths();
const liveBook = source.orderbook();
// orderbook of every candle: depths()[i] is the snapshot of candles[i] (the biggest limit orders), the latest candle uses the live book
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);
// imbalance ratio of every band in % (positive = bids dominate, negative = asks dominate)
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;
});
});
// a stronger ratio -> a more opaque band, extreme ratios get the extreme color
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; // the band is outside of the selected depth range
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);
}
// bid pressure: the band is filled only on the candles where bids dominate by more than the min ratio
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])
);
// ask pressure
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])
);
// alert on the latest candle
const latestRatio = ratios[b][lastIndex];
if (latestRatio != null && Math.abs(latestRatio) >= alertMinRatio) {
alert.fire(`Pressure ${lower}-${upper}%`, `ratio ${latestRatio.toFixed(1)}%`, candles[lastIndex].startTime);
}
});