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)
Регистрирует индикатор в системе.
| Параметр | Тип | Описание |
|---|---|---|
title | string | Название индикатора |
description | string | Краткое описание |
type | 'onchart' | 'offchart' | Расположение индикатора (на графике или в отдельной панели) |
isPublic | true | false | Индикатор доступен всем |
isPublicCode | true | false | Исходный код индикатора доступен всем |
options | object (необязательно) | { 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–299status: 200, // HTTP-статус, 0 если запрос не был отправленheaders: { "content-type": "application/json", ... },data: { ... }, // тело ответа, разобранное как JSON, null если тело не JSONtext: "...", // сырое тело ответа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) | Вертикальная линия через весь график |
| Параметр | Тип данных | Описание |
|---|---|---|
title | string | Название элемента (уникальный идентификатор на графике) |
date1 | number (timestamp, ms) | Начальная временная метка |
price1 | number | Начальная цена (координата по оси Y) |
date2 | number (timestamp, ms) | Конечная временная метка (для линий и прямоугольников) |
price2 | number | Конечная цена (для линий и прямоугольников) |
size | number | Размер фигуры (в пикселях) |
radius | number | Радиус круга (по умолчанию 10) |
fill | string (hex, например "#FF0000") | Цвет заливки фигуры |
opacity | number (0–1) | Прозрачность заливки |
stroke | string (hex, например "#000000") | Цвет обводки |
strokeOpacity | number (0–1) | Прозрачность обводки |
strokeWidth | number | Толщина линии обводки (в пикселях) |
strokeDasharray | array (например [5,5]) | Стиль пунктирной линии |
fontSize | number | Размер текста (в пикселях) |
value | string | Текстовое значение (для метода 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, stroke | string (hex) или function | Цвет или функция, возвращающая цвет свечи: (value, i) для lineSeries, barSeries, candleSeries, areaSeries, (upper, lower, i) для bandSeries |
opacity | number (0–1) или function | Прозрачность заливки, функция (value, i) / (upper, lower, i) для areaSeries / bandSeries |
base | number | null | Базовый уровень areaSeries, null = низ графика |
step | boolean | Стиль 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 itplot.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 candlesplot.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 candleplot.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 dateplot.box(...).alert("High volume", `vol ${candle.volume}`);// callback: return nothing to skip, [title, message] to fireplot.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 closeplot.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 thresholdplot.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 candlesconst candles = source.ohlcv();const colorUp = style.color("Color 1", "#2BB462"); // Greenish color for rising pricesconst colorDown = style.color("Color 2", "#FB4C51"); // Reddish color for falling pricesconst 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 candlesconst cbCandles = source.ohlcv('coinbase', 'BTC-USD');const binanceCandles = source.ohlcv('binance', 'BTCUSDT');// Extract closesconst 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 indexplot.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 closeconst 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 bookconst 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 colorconst 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 rangeconst 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 ratioplot.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 pressureplot.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 candleconst latestRatio = ratios[b][lastIndex];if (latestRatio != null && Math.abs(latestRatio) >= alertMinRatio) {alert.fire(`Pressure ${lower}-${upper}%`, `ratio ${latestRatio.toFixed(1)}%`, candles[lastIndex].startTime);}});