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, розраховується частково, тому додайте прапорець до наявних накопичувальних скриптів, коли їх редагуєте. Часткові скрипти й завантажуються частинами: під час увімкнення на довгій історії графік спершу запитує останні 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" |
nearness | 5 | макс. відстань від поточної ціни у відсотках |
size | 100000 | мін. розмір ордера в котирувальній валюті |
lifespan | 0 | мін. кількість хвилин, які стоїть ордер |
touches, currentTouches | 0 | мін. дотики рівня / поточного ордера |
filled | 0 | мін. виконана частина ордера у відсотках |
strength, diversity | 0 | мін. сила (відсотки) і різноманітність, як у скринері |
continuousOnly | false | пропускати ордери, які зникали й поверталися |
volume1h | 0 | мін. обсяг тікера за 1 годину в котирувальній валюті |
spotAndFutures | false | лише тікери, що є і на споті, і на ф'ючерсах |
limit | 500 | макс. кількість рівнів, найбільші першими (макс. 2000) |
Приклад:
// the big orders around the price of the chart market as horizontal lines, labelled with the size and the lifespanconst 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% |
limit | 200 | максимум тикерів, найближчий рівень першим (макс. 1000; 100 без тарифу) |
Приклад:
// the key levels of the chart market as horizontal linesconst 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 | [] | тікери, які треба пропустити |
hours | 24 | період у годинах (максимум 168; 24 без підписки) |
minDuration | 30 | мінімальна тривалість заявки у хвилинах: 30, 60, 120 або 180 |
minStrength | 80 | мінімальна сила заявки у відсотках: 80, 90 або 100 |
maxDistance | немає | максимальна відстань заявки до поточної ціни у відсотках, перевіряється в кожному знімку |
stableOnly | false | true: лише заявки, обсяг яких не стрибав («виключити заявки з нестабільним обсягом») |
limit | 200 | максимум рівнів у levels (максимум 1000; 100 без підписки) |
Приклад:
// $ size of the strong levels of the chart market per candleconst 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 firstlet 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 candleconst 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–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, 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: приводиться до ETHUSDTconst depths = source.depths("binanceFutures", "ETH-USD");// тикер графіка Binance на Coinbase: ETHUSDT стає ETH-USDconst 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) | Вертикальна лінія через весь графік |
| Параметр | Тип даних | Опис |
|---|---|---|
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) | Заливка між двома серіями, у кожної свічки свій колір і прозорість |
profile(title, values, options) | Гістограма за ціною для видимих свічок, як профіль обсягу, див. Профілі |
histogram(title, rows, options) | Готова карта цін, намальована як профіль, див. Гістограми |
| Параметр | Тип даних | Опис |
|---|---|---|
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 серій даних.
Профілі
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" |
width | 30 | найширший рядок у відсотках ширини графіка |
fill | "#26a69a" | колір значення або частини купівель |
fill2 | "#ef5350" | колір частини продажів |
opacity | 0.6 | прозорість стовпчиків |
delta | false | малювати buy - sell замість суми, лише для пар |
poc | true | лінія на рядку з найбільшим значенням |
valueArea | 70 | відсоток суми навколо POC з повною непрозорістю, false — вимкнути |
valueAreaOpacity | 0.3 | прозорість рядків поза value area |
labels | true | суми рядків на стовпчиках, коли є місце |
Обмеження: 3 профілі за запуск, 300 рядків на свічку, 200000 рядків на профіль. Зайві рядки відкидаються, запуск пише про це в лог.
Приклад:
// volume profile of the chart, the same picture as the built-in toolconst rows = await source.clusters();plot.profile("Volume", rows.map((r) => r?.data ?? null), { step: 5 });// only large trades, delta coloured, on the left edgeconst 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 colourconst 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 moreconst 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" |
header | false | перший рядок жирним на затіненій смузі |
fontSize | 11 | розмір тексту в px, від 8 до 16 |
color, bg, border | за темою | кольори тексту, панелі та сітки за замовчуванням; комірка може перевизначити color і bg |
opacity | 0.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 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);}});