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, розраховується частково, тому додайте прапорець до наявних накопичувальних скриптів, коли їх редагуєте. Часткові скрипти й завантажуються частинами: під час увімкнення на довгій історії графік спершу запитує останні 500 свічок (або warmup свічок, якщо це більше), а старіші частини — по одному запиту за раз, тож видима частина з'являється одразу. Скрипти з 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)Двійкова змінна
tickers(title, defaultValue, exchange)Множинний вибір тікерів біржі (за замовчуванням біржа графіка); повертає вибрані тікери масивом

Приклад (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");
// пари для включення / виключення, вибираються з тікерів біржі графіка
const include = input.tickers("Include pairs", []);
const exclude = input.tickers("Exclude pairs", []);

Або (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?)

Дані деривативів по свічках: ставка фінансування, свічка відкритого інтересу, співвідношення 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);

levels(exchange, settings?)

Рівні скринера ліквідності: великі лімітні ордери біржі, що стоять у стакані, відфільтровані тими самими налаштуваннями, що й на сторінці скринера. Живі дані, не прив'язані до свічок. Без плану близькість обмежена 1%, час життя 60 хвилинами, а результат 200 рівнями; з планом до 2000 рівнів. Найбільші ордери йдуть першими. Рахується одним запитом джерела даних.

{
"BTCUSDT": {
"asks": {
"109500": {
"price": 109500,
"quantity": 42.5, // size in the base asset
"quantityUSDT": 4653750, // size in the quote currency
"distance": 0.62, // from the current price, percent
"duration": 184, // minutes the order has been standing
"touches": 2,
"currentTouches": 1,
"filledPercent": 12.4,
"strength": 95.5, // percent, see the screener
"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)

Приклад:

// the big orders around the price of the chart market as horizontal lines, labelled with the size and the lifespan
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);
}
}

keyLevels(exchange, settings?)

Рівні скринера Key levels: open, high і low поточного та попереднього дня, тижня, місяця й року для кожного тикера біржі, з відстанню кожного рівня від поточної ціни. Живі дані, не прив'язані до свічок. Без тарифу результат містить лише рівні в межах ±1% від ціни (фіксований діапазон сторінки скринера) і не більше 100 тикерів; з тарифом будь-який діапазон відстані та до 1000 тикерів. Першими йдуть тикери з найближчим рівнем. Рахується як один запит джерела даних.

{
"BTCUSDT": {
"price": 79776.57, // поточна ціна
"updatedAt": 1788707880000,
"bootstrapped": true, // false, поки історія тикера ще збирається
"nearest": 0.08, // відстань до найближчого рівня, у відсотках
"matches": 5, // рівні всередині діапазону відстані
"daily": { "start": 1788652800000, "open": 79700, "high": 79985, "low": 79710, "openApprox": false,
"distance": { "open": -0.1, "high": 0.26, "low": -0.08 } }, // відсоток від ціни, зі знаком
"prevDay": { ... }, "weekly": { ... }, "prevWeek": { ... },
"monthly": { ... }, "prevMonth": { ... }, "yearly": { ... }, "prevYear": null // null: даних за період ще немає
},
...
}

Налаштування (усі необов'язкові):

НалаштуванняТиповоОпис
tickersусі тикерилише ці тикери біржі
exclude[]тикери, які слід пропустити
periodsусі"daily", "prevDay", "weekly", "prevWeek", "monthly", "prevMonth", "yearly", "prevYear"
fieldsусі"open", "high", "low": які рівні повернути
distanceFrom, distanceToнемаєдіапазон відстані від ціни зі знаком, у відсотках (напр. -2 і 2); повертаються лише тикери з рівнем усередині діапазону. Без тарифу діапазон фіксований: ±1%
limit200максимум тикерів, найближчий рівень першим (макс. 1000; 100 без тарифу)

Приклад:

// the key levels of the chart market as horizontal lines
const candles = await source.ohlcv();
const lastDate = candles.at(-1).startTime;
const rows = await source.keyLevels(currentExchange, { tickers: [currentTicker], periods: ["daily", "prevDay", "weekly"] });
const own = rows[currentTicker];
if (own) {
for (const period of ["daily", "prevDay", "weekly"]) {
const level = own[period];
if (!level) continue;
for (const [field, color] of [["high", "#ef5350"], ["low", "#26a69a"], ["open", "#90a4ae"]]) {
if (level[field] == null) continue;
plot.horizontalLine(`${period} ${field}`, level[field], color, 0.8, 1);
plot.text(`${period} ${field} label`, `${period} ${field.toUpperCase()}`, lastDate, level[field], 10, color);
}
}
}

levelsMetrics(exchange, settings?)

Історія сильних рівнів ліквідності, яку збирає сторінка Метрики: кожні 5 хвилин сервер робить знімок рівнів скринера ліквідності кожної біржі з тривалістю від 30 хвилин і силою від 80% та зберігає тиждень таких знімків. Джерело фільтрує знімки за період так само, як сторінка, і повертає по кожному знімку кількість і обсяг ($) відповідних рівнів, зведення за тікерами та історію життя кожного рівня. Це зібрані дані, не прив'язані до свічок: зіставляйте series[].at з датами свічок. Без підписки період не більше 24 годин і в списку не більше 100 рівнів; з підпискою до 7 днів і 1000 рівнів. Рахується як один запит до джерела даних.

{
"exchange": "binanceFutures",
"hours": 24,
"snapshotMinutes": 5,
"latestAt": 1792102098685,
"summary": { "snapshots": 287, "uniqueLevels": 217, "uniqueTickers": 118, "activeNow": 180, "bidsNow": 102, "asksNow": 78, "usdNow": 119294436 },
"series": [ // one point per snapshot, oldest first
{ "at": 1792015822555, "bids": 24, "asks": 21, "bidsUsd": 1851345, "asksUsd": 1228883, "tickers": 20 },
...
],
"tickers": { // per ticker, over the period
"BTCUSDT": { "now": 3, "nowUsd": 95710480, "snapshots": 63, "bids": 3, "asks": 0, "levels": 3,
"maxUsd": 42347880, "maxDuration": 8220, "maxStrength": 100 },
...
},
"levels": [ // the lifecycle of every level, active ones first
{ "ticker": "BTCUSDT", "side": "bids", "price": 52050, "usd": 42347880, "quantity": 813.6, "distance": 19.8,
"duration": 8220, "strength": 100, "continuous": 1, "firstSeen": 1792015822555, "lastSeen": 1792102098685,
"snapshots": 63, "maxDuration": 8220, "maxStrength": 100, "active": true },
...
],
"levelsTotal": 217
}

Налаштування (усі необов'язкові):

НалаштуванняЗа замовчуваннямОпис
tickersусі тікерилише ці тікери біржі
exclude[]тікери, які треба пропустити
hours24період у годинах (максимум 168; 24 без підписки)
minDuration30мінімальна тривалість заявки у хвилинах: 30, 60, 120 або 180
minStrength80мінімальна сила заявки у відсотках: 80, 90 або 100
maxDistanceнемаємаксимальна відстань заявки до поточної ціни у відсотках, перевіряється в кожному знімку
stableOnlyfalsetrue: лише заявки, обсяг яких не стрибав («виключити заявки з нестабільним обсягом»)
limit200максимум рівнів у levels (максимум 1000; 100 без підписки)

Приклад:

// $ size of the strong levels of the chart market per candle
const candles = await source.ohlcv();
const data = await source.levelsMetrics(currentExchange, { tickers: [currentTicker], hours: 24, minDuration: 60, minStrength: 90, stableOnly: true });
const series = data.series; // oldest first
let j = 0;
const buy = [], sell = [];
for (let i = 0; i < candles.length; i++) {
const end = candles[i + 1] ? candles[i + 1].startTime : candles[i].startTime + (candles[1].startTime - candles[0].startTime);
while (j + 1 < series.length && series[j + 1].at < end) j++; // the last snapshot inside the candle
const point = series[j] && series[j].at < end ? series[j] : null;
buy.push(point ? point.bidsUsd : null);
sell.push(point ? point.asksUsd : null);
}
plot.areaSeries("Buy levels ($)", buy, "#26a69a", 0.3, "#26a69a", 1, 1, 0);
plot.areaSeries("Sell levels ($)", sell, "#ef5350", 0.3, "#ef5350", 1, 1, 0);

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, bybit, bybitFutures, okx, okxFutures, coinbase, bitfinex, bitfinexFutures, hyperliquid, hyperliquidFutures, asterFutures
currentTickerПоточний тикер на графіку (формат залежить від біржі)BTCUSDT, ADA-USD, BTC/USDC, ...
currentTimeframeПоточний таймфрейм на графіку1m, 5m, 15m, ...
exchangesНазви всіх бірж (незмінний масив)["binance", "binanceFutures", ...]
tickersТікери всіх бірж: tickers[currentExchange] — список біржі графіка (незмінний)tickers.binance = ["BTCUSDT", "ETHUSDT", ...]
heatmapStepКрок ціни теплової карти стакана та кластерів за поточної ціни (точність, до якої сервер округлює ціни: 5 вище 5000, 1 для 1000-5000, 0.1 для 100-1000, ...), береться з останнього закриття графіка; null, поки ціна невідома (відома після першого source.ohlcv() ринку графіка)5, 1, 0.1, 0.001, ...

Приклад:

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

Формати тикерів. Кожен метод source, що приймає біржу й тикер, пропускає тикер через resolveTicker: з переданого формату (ETHUSDT, ETH-USD, ETH/USDC, ETH:USD, ETHF0:USTF0, ...) береться базова монета, і тикер збирається заново у форматі запитаної біржі. Тому ETH-USD працює для Binance, а currentTicker графіка Binance працює для Coinbase. Зберігається лише монета: використовується стандартна пара біржі (USDT на Binance, USD на Coinbase, USDC на Hyperliquid, ...), а монета, якої немає на біржі, дає порожній результат і рядок у логах.

// тикер у форматі Coinbase на Binance Futures: приводиться до ETHUSDT
const depths = source.depths("binanceFutures", "ETH-USD");
// тикер графіка Binance на Coinbase: ETHUSDT стає ETH-USD
const spot = source.ohlcv("coinbase", currentTicker, currentTimeframe);

Логування

Клас 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 (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)Заливка між двома серіями, у кожної свічки свій колір і прозорість
profile(title, values, options)Гістограма за ціною для видимих свічок, як профіль обсягу, див. Профілі
histogram(title, rows, options)Готова карта цін, намальована як профіль, див. Гістограми
ПараметрТип данихОпис
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 серій даних.

Профілі

plot.profile(title, values, options?) малює гістограму за ціною, як вбудований профіль обсягу. values[i] — карта цін свічки i з ohlcv(): { price: value } або { price: { buy, sell } } (data запису clusters() чи liquidations() можна передати як є), або null для свічки без рядків. Рядки видимих на графіку свічок підсумовуються за кроком ціни прямо на графіку, тому профіль слідує за прокруткою, масштабом і підвантаженням історії без нового запуску. Поодинокі значення малюються одним кольором (від'ємне значення йде у fill2), пари ділять стовпчик на частини купівель і продажів і дозволяють режим дельти. profile() не повертає хендл алерту.

ОпціяТиповоОпис
stepтік графікависота рядка в одиницях ціни, ціни округлюються вниз до неї
orient"right"край, від якого ростуть стовпчики, "right" або "left"
width30найширший рядок у відсотках ширини графіка
fill"#26a69a"колір значення або частини купівель
fill2"#ef5350"колір частини продажів
opacity0.6прозорість стовпчиків
deltafalseмалювати buy - sell замість суми, лише для пар
poctrueлінія на рядку з найбільшим значенням
valueArea70відсоток суми навколо POC з повною непрозорістю, false — вимкнути
valueAreaOpacity0.3прозорість рядків поза value area
labelstrueсуми рядків на стовпчиках, коли є місце

Обмеження: 3 профілі за запуск, 300 рядків на свічку, 200000 рядків на профіль. Зайві рядки відкидаються, запуск пише про це в лог.

Приклад:

// volume profile of the chart, the same picture as the built-in tool
const rows = await source.clusters();
plot.profile("Volume", rows.map((r) => r?.data ?? null), { step: 5 });
// only large trades, delta coloured, on the left edge
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 рядків. Зайві рядки відкидаються, запуск пише про це в лог.

Приклад, стакан:

// the live order book as a profile on the right edge: bids in the buy colour, asks in the sell colour
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 });

Приклад, власні рівні:

// your own histogram: cluster volume of the last 100 candles, recent candles weigh more
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 реалізує функції 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);
}
});