API d'indicateurs personnalisés
Générer des indicateurs avec l'IA
L'ensemble de l'API est également disponible sous forme d'un seul fichier texte pour les modèles de langage : AI-scripting.txt. Joignez-le à ChatGPT, Claude, Gemini ou tout autre LLM et décrivez l'indicateur que vous souhaitez.
Exemple de requête :
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.
Bac à sable et limites
Développement d'indicateurs
Le développement d'indicateurs se fait dans un widget séparé, qui peut être placé à côté du graphique sur lequel l'indicateur sera activé.

Créer un indicateur
define(title, description, type, isPublic, isPublicCode, options)
Enregistre l'indicateur dans le système.
| Paramètre | Type | Description |
|---|---|---|
title | string | Nom de l'indicateur |
description | string | Courte description |
type | 'onchart' | 'offchart' | Emplacement de l'indicateur (sur le graphique ou dans un panneau séparé) |
isPublic | true | false | Si l'indicateur est public |
isPublicCode | true | false | Si le code source est public |
options | object (optionnel) | { fullRange, warmup }, voir ci-dessous |
Exemple :
define("My Indicator", "Shows EMA line", "onchart", true, false);
Plage de calcul. Par défaut un script ne calcule que la partie du graphique qui en a besoin : les chandeliers chargés en remontant dans l'historique, ou les derniers chandeliers lors d'un rafraîchissement, plus warmup chandeliers d'historique avant eux (500 par défaut), nécessaires aux calculs mais non retournés. Déclarez warmup si votre indicateur a besoin de plus ou moins d'historique, par ex. { warmup: 200 } pour une EMA(200). Un script dont les valeurs ou les formes dépendent de toute la plage visible (VWAP depuis le premier chandelier visible, delta cumulé, un libellé au maximum de la fenêtre, des niveaux tracés sur tout le graphique) doit déclarer { fullRange: true }, sinon ses résultats sont faux après le chargement d'historique. Le mode est lu dans define() à chaque enregistrement : un script enregistré sans fullRange est calculé partiellement, ajoutez donc le drapeau aux scripts cumulatifs existants lorsque vous les modifiez. Les scripts partiels sont aussi chargés par morceaux : quand vous en activez un sur un long historique, le graphique demande d'abord les 500 derniers chandeliers (ou warmup chandeliers si c'est plus), puis les parties plus anciennes une requête à la fois, de sorte que la partie visible apparaît immédiatement. Les scripts avec fullRange sont toujours calculés en une seule requête sur toute la plage.
define("Window max", "Label at the highest visible high", "onchart", true, true, { fullRange: true });
Emplacement de l'indicateur pour onchart et offchart :

Paramètres d'entrée
Les classes input et style permettent de créer des paramètres configurables par l'utilisateur (similaires à input() dans PineScript).
| Méthode | Description |
|---|---|
number(title, defaultValue, min, max, step) | Entrée numérique |
range(title, defaultValue, min, max, step) | Entrée de type plage |
select(title, defaultValue, values) | Sélection dans une liste déroulante |
color(title, defaultValue) | Sélecteur de couleur |
boolean(title, defaultValue) | Entrée booléenne |
tickers(title, defaultValue, exchange) | Sélection multiple des tickers d'une plateforme (par défaut celle du graphique) ; renvoie les tickers choisis sous forme de tableau |
Exemple (input) pour les paramètres principaux :
const length = input.number("Period", 14, 1, 100, 1);const type = input.select("Type", 'sma', ['sma', 'ema']);const color = input.color("Line Color", "#FF0000");// paires à inclure / exclure, choisies parmi les tickers de la plateforme du graphiqueconst include = input.tickers("Include pairs", []);const exclude = input.tickers("Exclude pairs", []);
Ou (style) pour les paramètres visuels :
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");

Sources de données
La classe source récupère les données de marché depuis le serveur. Les méthodes basées sur les chandeliers prennent les mêmes paramètres ; les deux instantanés en direct diffèrent : orderbook() prend un exchange et un ticker, levels() un exchange et un objet de réglages.
| Paramètre | Description |
|---|---|
exchange, ticker, timeframe | Par défaut, ceux du graphique sur lequel l'indicateur est ouvert. Passez d'autres valeurs pour récupérer les données d'un autre marché. |
lookback | Nombre de barres d'historique à partir de la date actuelle, pas plus de 5000. |
ohlcv(exchange, ticker, timeframe, lookback?)
Données des chandeliers.
[{"open": 115886.17,"high": 115911.96,"low": 115764.27,"close": 115815.16,"volume": 228.41528,"startTime": 1757699100000},{...},...]
depths(exchange, ticker, timeframe, lookback?)
Instantanés historiques du carnet d'ordres, un par chandelier. Seuls les plus gros ordres limites sont conservés (les données de la heatmap).
[{"data": {"asks": {"109425": 5.467,"109495": 3.564,...},"bids": {"33000": 3.746,"35000": 16.49,...}},"startTime": 1757699100000},{...},...]
clusters(exchange, ticker, timeframe, lookback?)
Données des clusters (footprints) : volume acheteur et vendeur à chaque niveau de prix du chandelier.
[{"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?)
Volume liquidé à chaque niveau de prix du chandelier (marchés futures uniquement). Même format que 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?)
Données dérivées par chandelier : taux de financement, chandelier d'open interest, ratios long/short (marchés futures uniquement).
[{"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)
Instantané du carnet d'ordres en direct : le carnet complet actuel, non lié aux chandeliers.
{"asks": {"109425": 5.467,"109495": 3.564,...},"bids": {"109400": 1.2,"109390": 0.8,...}}
Les niveaux ne sont pas triés, utilisez les fonctions de carnet d'ordres de ta (ta.bestBid, ta.sumBids, ...) pour travailler avec le carnet.
Les carnets historiques des chandeliers proviennent de depths() : depths()[i].data a le même format, mais seuls les plus grands ordres limites y sont stockés.
Exemple :
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?)
Les niveaux du screener de liquidité : les gros ordres limite au repos d'un exchange, filtrés avec les mêmes réglages que la page du screener. Données en direct, non liées aux chandeliers. Sans plan, la proximité est limitée à 1 %, la durée de vie à 60 minutes et le résultat à 200 niveaux ; avec un plan jusqu'à 2000 niveaux. Les plus gros ordres viennent en premier. Compte comme une requête de source de données.
{"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},...}
Réglages (tous optionnels) :
| Réglage | Défaut | Description |
|---|---|---|
tickers | tous les tickers | seulement ces tickers de l'exchange |
exclude | [] | tickers à ignorer |
type | "all" | "all", "bids" ou "asks" |
nearness | 5 | distance max au prix courant, en pourcent |
size | 100000 | taille min de l'ordre dans la devise de cotation |
lifespan | 0 | minutes min pendant lesquelles l'ordre est resté |
touches, currentTouches | 0 | touches min du niveau / de l'ordre courant |
filled | 0 | part min exécutée de l'ordre, en pourcent |
strength, diversity | 0 | force (pourcent) et diversité min, comme sur le screener |
continuousOnly | false | ignorer les ordres qui ont disparu puis sont revenus |
volume1h | 0 | volume 1h min du ticker dans la devise de cotation |
spotAndFutures | false | seulement les tickers présents sur le spot et les futures |
limit | 500 | nombre max de niveaux, les plus gros en premier (max 2000) |
Exemple :
// 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?)
Les niveaux du screener Key levels : open, high et low du jour, de la semaine, du mois et de l'année en cours et précédents pour chaque ticker d'un échange, avec la distance de chaque niveau au prix actuel. Données en direct, non liées aux bougies. Sans abonnement, le résultat ne contient que les niveaux à ±1 % du prix (la plage fixe de la page du screener) et au plus 100 tickers ; avec un abonnement, n'importe quelle plage de distance et jusqu'à 1000 tickers. Les tickers dont le niveau est le plus proche viennent en premier. Compte comme une requête de source de données.
{"BTCUSDT": {"price": 79776.57, // prix actuel"updatedAt": 1788707880000,"bootstrapped": true, // false tant que l'historique du ticker est encore collecté"nearest": 0.08, // distance du niveau le plus proche, en pourcent"matches": 5, // niveaux dans la plage de distance"daily": { "start": 1788652800000, "open": 79700, "high": 79985, "low": 79710, "openApprox": false,"distance": { "open": -0.1, "high": 0.26, "low": -0.08 } }, // pourcent du prix, signé"prevDay": { ... }, "weekly": { ... }, "prevWeek": { ... },"monthly": { ... }, "prevMonth": { ... }, "yearly": { ... }, "prevYear": null // null : pas encore de données pour la période},...}
Paramètres (tous optionnels) :
| Paramètre | Défaut | Description |
|---|---|---|
tickers | tous les tickers | seulement ces tickers de l'échange |
exclude | [] | tickers à ignorer |
periods | toutes | "daily", "prevDay", "weekly", "prevWeek", "monthly", "prevMonth", "yearly", "prevYear" |
fields | tous | "open", "high", "low" : les niveaux à renvoyer |
distanceFrom, distanceTo | aucune | plage de distance signée au prix, en pourcent (p. ex. -2 et 2) ; seuls les tickers ayant un niveau dans la plage sont renvoyés. Sans abonnement la plage est fixée à ±1 % |
limit | 200 | nombre max de tickers, le niveau le plus proche en premier (max 1000 ; 100 sans abonnement) |
Exemple :
// 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?)
L'historique des niveaux de liquidité forts collecté pour la page Métriques : toutes les 5 minutes, le serveur prend un instantané des niveaux du cribleur de liquidité de chaque plateforme ayant une durée d'au moins 30 minutes et une force d'au moins 80 %, et en conserve une semaine. La source filtre les instantanés d'une période comme la page et renvoie, par instantané, le nombre et la taille en $ des niveaux correspondants, les agrégats par ticker et le cycle de vie de chaque niveau. Données collectées, non liées aux bougies : faites correspondre series[].at aux dates des bougies. Sans abonnement, la période est limitée à 24 heures et la liste à 100 niveaux ; avec un abonnement, jusqu'à 7 jours et 1000 niveaux. Compte pour une requête de source de données.
{"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}
Paramètres (tous facultatifs) :
| Paramètre | Par défaut | Description |
|---|---|---|
tickers | tous les tickers | seulement ces tickers de la plateforme |
exclude | [] | tickers à ignorer |
hours | 24 | période en heures (max 168 ; 24 sans abonnement) |
minDuration | 30 | durée minimale de l'ordre en minutes : 30, 60, 120 ou 180 |
minStrength | 80 | force minimale de l'ordre en pourcentage : 80, 90 ou 100 |
maxDistance | aucune | distance maximale de l'ordre au prix actuel en pourcentage, vérifiée dans chaque instantané |
stableOnly | false | true : seulement les ordres dont la quantité n'a jamais sauté (« exclure les ordres aux quantités instables ») |
limit | 200 | nombre maximal de niveaux dans levels (max 1000 ; 100 sans abonnement) |
Exemple :
// $ 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);
Requêtes HTTP
La classe http envoie des requêtes vers des API HTTP externes et retourne la réponse, ce qui permet à un indicateur d'utiliser des données absentes de source : votre propre serveur, une API REST publique, un fournisseur de métriques on-chain, ...
Comme pour source.*, les appels s'écrivent comme des fonctions synchrones, aucun await n'est nécessaire.
| Méthode | Description |
|---|---|
get(url, options?) | Requête GET |
post(url, body?, options?) | Requête POST. Un objet ou un tableau body est envoyé en JSON (Content-Type: application/json), une chaîne est envoyée telle quelle |
options est { headers: { name: value } } — jusqu'à 10 en-têtes personnalisés (par exemple Authorization ou x-api-key).
Les deux méthodes retournent le même objet de réponse et ne lèvent jamais d'exception :
{ok: true, // statut 200–299status: 200, // statut HTTP, 0 si la requête n'a pas été envoyéeheaders: { "content-type": "application/json", ... },data: { ... }, // corps de la réponse analysé en JSON, null si le corps n'est pas du JSONtext: "...", // corps brut de la réponseerror: null // raison si la requête a échoué : "timeout", "url not allowed", "too many requests", ...}
Valeurs possibles de 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.
Exemple (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");
Exemple (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);
Variables globales
| Variable | Description | Exemple |
|---|---|---|
currentExchange | Échange actuel sur le graphique | binance, binanceFutures, bybit, bybitFutures, okx, okxFutures, coinbase, bitfinex, bitfinexFutures, hyperliquid, hyperliquidFutures, asterFutures |
currentTicker | Ticker actuel sur le graphique (le format dépend de l'échange) | BTCUSDT, ADA-USD, BTC/USDC, ... |
currentTimeframe | Période actuelle sur le graphique | 1m, 5m, 15m, ... |
exchanges | Noms de toutes les plateformes (tableau figé) | ["binance", "binanceFutures", ...] |
tickers | Tickers de toutes les plateformes : tickers[currentExchange] est la liste de la plateforme du graphique (figé) | tickers.binance = ["BTCUSDT", "ETHUSDT", ...] |
heatmapStep | Pas de prix de la heatmap du carnet d'ordres et des clusters au prix actuel (la précision à laquelle le serveur arrondit les prix : 5 au-dessus de 5000, 1 pour 1000-5000, 0.1 pour 100-1000, ...), pris sur la dernière clôture du graphique ; null tant que le prix est inconnu (connu après le premier source.ohlcv() du marché du graphique) | 5, 1, 0.1, 0.001, ... |
Exemple :
const candles = source.ohlcv(currentExchange, currentTicker, currentTimeframe);const closes = candles.map(c => c.close);
Formats de ticker. Chaque méthode source qui prend un échange et un ticker fait passer le ticker par resolveTicker : la monnaie de base est extraite du format fourni (ETHUSDT, ETH-USD, ETH/USDC, ETH:USD, ETHF0:USTF0, ...) et le ticker est reconstruit au format de l'échange demandé. ETH-USD fonctionne donc pour Binance, et le currentTicker d'un graphique Binance fonctionne pour Coinbase. Seule la monnaie est conservée : la paire standard de l'échange est utilisée (USDT sur Binance, USD sur Coinbase, USDC sur Hyperliquid, ...), et une monnaie que l'échange ne liste pas donne un résultat vide avec une ligne dans les logs.
// un ticker au format Coinbase sur Binance Futures : résolu en ETHUSDTconst depths = source.depths("binanceFutures", "ETH-USD");// le ticker d'un graphique Binance sur Coinbase : ETHUSDT devient ETH-USDconst spot = source.ohlcv("coinbase", currentTicker, currentTimeframe);
Journalisation
La classe logger sert à afficher des informations et à déboguer.
| Méthode | Description |
|---|---|
log(...args) | Ajoute une entrée au journal (avec des limites de longueur et de quantité) |
Exemple :
const candles = source.ohlcv();logger.log("First candle:", candles?.[0]);

Visualisation
La classe plot est responsable du dessin des éléments graphiques et des séries de données.
Formes géométriques
| Méthode | Description |
|---|---|
box(title, date1, price1, date2, price2, fill, opacity, stroke, strokeOpacity, strokeWidth) | Rectangle |
square(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) | Carré |
diamond(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) | Losange |
triangle(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) | Triangle |
circle(title, date1, price1, radius = 10, fill, opacity, stroke, strokeOpacity, strokeWidth) | Cercle |
text(title, value, date1, price1, fontSize, fill, opacity) | Texte sur le graphique |
table(title, rows, options) | Tableau épinglé dans un coin du graphique, voir Tableaux |
straightLine(title, date1, price1, date2, price2, stroke, strokeOpacity, strokeWidth, strokeDasharray) | Ligne droite |
horizontalLine(title, price1, stroke, strokeOpacity, strokeWidth, strokeDasharray) | Ligne horizontale sur toute la largeur du graphique |
verticalLine(title, date1, stroke, strokeOpacity, strokeWidth, strokeDasharray) | Ligne verticale sur toute la hauteur du graphique |
| Paramètre | Type de donnée | Description |
|---|---|---|
title | string | Nom de l'élément (identifiant unique sur le graphique) |
date1 | number (timestamp, ms) | Horodatage de départ |
price1 | number | Prix de départ (coordonnée sur l'axe Y) |
date2 | number (timestamp, ms) | Horodatage de fin (pour les lignes et les rectangles) |
price2 | number | Prix de fin (pour les lignes et les rectangles) |
size | number | Taille de la forme (en pixels) |
radius | number | Rayon du cercle (10 par défaut) |
fill | string (hex, ex. "#FF0000") | Couleur de remplissage de la forme |
opacity | number (0–1) | Opacité du remplissage |
stroke | string (hex, ex. "#000000") | Couleur du contour |
strokeOpacity | number (0–1) | Opacité du contour |
strokeWidth | number | Épaisseur du contour (en pixels) |
strokeDasharray | array (ex. [5,5]) | Style de ligne pointillée |
fontSize | number | Taille du texte (en pixels) |
value | string | Valeur du texte (pour la méthode text) |
Exemple :
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);
Pas plus de 3000 formes ne peuvent être affichées sur le graphique.
Séries de données
| Méthode | Description |
|---|---|
lineSeries(title, values, stroke, strokeOpacity, strokeWidth, strokeDasharray) | Graphique en ligne |
barSeries(title, values, fill, opacity) | Graphique en barres |
candleSeries(title, openValues, closeValues, highValues, lowValues, fill, opacity) | Chandeliers |
areaSeries(title, values, fill, opacity, stroke, strokeOpacity, strokeWidth, base) | Zone remplie entre la ligne et base (par défaut : le bas du graphique) |
bandSeries(title, upperValues, lowerValues, fill, opacity, stroke, strokeOpacity, strokeWidth, step) | Bande remplie entre deux séries, chaque chandelier conserve sa propre couleur et opacité |
profile(title, values, options) | Histogramme par prix des chandeliers visibles, comme le profil de volume, voir Profils |
histogram(title, rows, options) | Une carte de prix déjà calculée, dessinée comme un profil, voir Histogrammes |
| Paramètre | Type de donnée | Description |
|---|---|---|
values, upperValues, lowerValues | (number \| null)[] | Une valeur par chandelier, null masque le chandelier |
fill, stroke | string (hex) ou function | Couleur, ou fonction retournant la couleur du chandelier : (value, i) pour lineSeries, barSeries, candleSeries, areaSeries, (upper, lower, i) pour bandSeries |
opacity | number (0–1) ou function | Opacité du remplissage, une fonction (value, i) / (upper, lower, i) pour areaSeries / bandSeries |
base | number | null | Niveau de base de areaSeries, null = le bas du graphique |
step | boolean | Style de bandSeries : true = une colonne par chandelier (par défaut), false = un polygone continu |
Exemple :
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);
Pas plus de 20 séries de données ne peuvent être affichées sur le graphique.
Profils
plot.profile(title, values, options?) dessine un histogramme par prix, comme le profil de volume intégré. values[i] est la carte des prix du chandelier i de ohlcv() : { price: value } ou { price: { buy, sell } } (le data d'un enregistrement clusters() ou liquidations() peut être passé tel quel), ou null pour un chandelier sans lignes. Les lignes des chandeliers visibles sur le graphique sont additionnées par pas de prix dans le graphique lui-même : le profil suit le déplacement, le zoom et le chargement de l'historique sans nouvelle exécution. Les valeurs simples sont dessinées d'une seule couleur (une valeur négative va dans fill2), les paires divisent la barre en parties achat et vente et permettent le mode delta. profile() ne renvoie pas de poignée d'alerte.
| Option | Défaut | Description |
|---|---|---|
step | tick du graphique | hauteur de ligne en unités de prix, les prix sont arrondis vers le bas |
orient | "right" | bord d'où partent les barres, "right" ou "left" |
width | 30 | ligne la plus large en pourcentage de la largeur du graphique |
fill | "#26a69a" | couleur de la valeur, ou de la partie achat |
fill2 | "#ef5350" | couleur de la partie vente |
opacity | 0.6 | opacité des barres |
delta | false | dessiner buy - sell au lieu de la somme, paires seulement |
poc | true | ligne sur la ligne de plus grande valeur |
valueArea | 70 | pourcentage du total autour du POC dessiné en pleine opacité, false pour désactiver |
valueAreaOpacity | 0.3 | opacité des lignes hors de la value area |
labels | true | totaux des lignes sur les barres quand il y a de la place |
Limites : 3 profils par exécution, 300 lignes par chandelier, 200000 lignes par profil. Les lignes au-delà sont ignorées et l'exécution l'indique dans le journal.
Exemple :
// 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" });
Histogrammes
plot.histogram(title, rows, options?) dessine un histogramme que vous avez déjà construit : une seule carte de prix pour tout le graphique, rien n'est additionné depuis les chandeliers. Utilisez profile() quand les lignes appartiennent aux chandeliers et que l'image doit suivre la plage visible, et histogram() quand les lignes sont définitives : un instantané du carnet d'ordres, des niveaux calculés par le script, des données d'un autre marché.
rows a les mêmes formats qu'une entrée de chandelier de profile() : { price: value }, { price: { buy, sell } } ou une liste de lignes [price, buy, sell]. Une valeur simple est dessinée d'une seule couleur (une valeur négative va dans fill2), les paires divisent la barre en parties achat et vente et permettent le mode delta. Les options sont celles de profile(), voir le tableau ci-dessus.
L'histogramme porte la date du dernier chandelier de l'exécution et le graphique garde l'histogramme le plus récent d'un titre, donc les exécutions partielles sur des chandeliers plus anciens (chargement de l'historique) ne remplacent jamais l'actuel. Il est redessiné à chaque rafraîchissement du script, un histogramme du carnet suit donc le carnet en direct. histogram() ne renvoie pas de poignée d'alerte.
Limites : 3 histogrammes par exécution, 5000 lignes. Les lignes au-delà sont ignorées et l'exécution l'indique dans le journal.
Exemple, le carnet d'ordres :
// 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 });
Exemple, vos propres niveaux :
// 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 });
Tableaux
plot.table(title, rows, options?) dessine une petite grille épinglée dans un coin du panneau : résumés multi-unités de temps, statistiques de la plage chargée, tableaux de signaux. rows est un tableau de lignes, une ligne un tableau de cellules, une cellule une chaîne, un nombre ou { text, color, bg, align, bold }. Les tableaux d'un même coin s'empilent dans l'ordre. Un tableau porte la date du dernier chandelier de l'exécution et le graphique garde le tableau le plus récent d'un titre, donc les exécutions partielles sur des chandeliers plus anciens (chargement de l'historique) ne remplacent jamais le tableau courant. Les tableaux ne sont pas dessinés sur les graphiques de moins de 500 px de large. Un tableau peut être déplacé à la souris : la position est enregistrée avec l'espace de travail et réinitialisée quand le script change son option position. table() ne renvoie pas de poignée d'alerte.
| Option | Défaut | Description |
|---|---|---|
position | "top-right" | coin du panneau : "top-right", "top-left", "bottom-right", "bottom-left" |
header | false | première ligne en gras sur un bandeau ombré |
fontSize | 11 | taille du texte en px, de 8 à 16 |
color, bg, border | selon le thème | couleurs par défaut du texte, du panneau et de la grille ; une cellule peut redéfinir color et bg |
opacity | 0.85 | opacité du panneau |
align | "left" | alignement par défaut des cellules, "left", "center" ou "right" |
Limites : 3 tableaux par exécution, 50 lignes, 10 colonnes, 100 caractères par cellule. Un texte plus long est coupé avec des points de suspension, une colonne occupe au plus 30 % de la largeur du graphique.
Exemple :
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 });
Analyse technique
La classe ta implémente les fonctions ta.* de PineScript v5. Pine calcule barre par barre, ici chaque fonction prend des séries entières (tableaux, une valeur par bougie, de la plus ancienne à la plus récente) et renvoie une série de même longueur, directement traçable. Les valeurs sont null tant que la période n'est pas remplie, et les entrées null se propagent comme le na de Pine. Les fonctions Pine qui lisent implicitement high, low, close ou volume reçoivent ces tableaux en arguments.
Les fonctions qui renvoient plusieurs séries (bb, kc, macd, stoch, dmi, supertrend) renvoient un tableau doté de propriétés nommées : const [middle, upper, lower] = ta.bb(closes, 20, 2) ou ta.bb(closes, 20, 2).upper.
Fonctions utilitaires
| Méthode | Description |
|---|---|
highest(values, length) / lowest(values, length) | Valeur la plus haute / basse sur length barres |
highestbars(values, length) / lowestbars(values, length) | Décalage (0 ou négatif) vers la barre la plus haute / basse |
max(values) / min(values) | Plus haut / bas historique jusqu'à chaque barre |
range(values, length) | highest - lowest |
change(values, length = 1) | values[i] - values[i - length] |
mom(values, length) | Momentum |
roc(values, length) | Taux de variation, % |
cum(values) | Somme cumulée |
rising(values, length) / falling(values, length) | Valeur au-dessus / en dessous de chacune des length valeurs précédentes |
variance(values, length, biased = true) | Variance |
stdev(values, length, biased = true) | Écart type |
dev(values, length) | Écart absolu moyen |
median(values, length) | Médiane |
mode(values, length) | Valeur la plus fréquente (la plus petite en cas d'égalité) |
percentile_linear_interpolation(values, length, percentage) | Percentile, interpolation linéaire entre les deux rangs les plus proches |
percentile_nearest_rank(values, length, percentage) | Percentile, méthode du rang le plus proche |
percentrank(values, length) | % des length valeurs précédentes inférieures ou égales à la valeur courante |
correlation(series1, series2, length) | Coefficient de corrélation |
crossover(series1, series2) | Croisement à la hausse (chaque argument peut être un nombre : ta.crossover(rsi, 70)) |
crossunder(series1, series2) | Croisement à la baisse |
cross(series1, series2) | Tout croisement |
barssince(condition) | Barres depuis la dernière fois où condition (tableau de booléens) était vraie |
valuewhen(condition, values, occurrence = 0) | Valeur de values quand condition était vraie il y a occurrence occurrences |
pivothigh(values, leftbars, rightbars) / pivotlow(...) | Valeur du pivot sur la barre de confirmation, le pivot lui-même est rightbars barres plus tôt |
Moyennes mobiles
| Méthode | Description |
|---|---|
sma(values, length) | Moyenne mobile simple |
ema(values, length) | Moyenne mobile exponentielle |
rma(values, length) | Moyenne mobile de Wilder (utilisée par RSI / ATR) |
wma(values, length) | Moyenne mobile pondérée |
hma(values, length) | Moyenne mobile de Hull |
alma(values, length, offset = 0.85, sigma = 6, floor = false) | Moyenne mobile d'Arnaud Legoux |
swma(values) | Moyenne mobile à pondération symétrique sur 4 barres |
vwma(values, volumes, length) | Moyenne mobile pondérée par le volume |
linreg(values, length, offset = 0) | Courbe de régression linéaire |
Oscillateurs
| Méthode | Description |
|---|---|
rsi(values, length) | Indice de force relative |
cmo(values, length) | Chande Momentum Oscillator |
cog(values, length) | Centre de gravité |
mfi(values, volumes, length) | Money Flow Index (values est généralement hlc3) |
tsi(values, shortLength, longLength) | True Strength Index, -1..1 |
wpr(highs, lows, closes, length) | Williams %R |
stoch(highs, lows, closes, period = 14, smoothK = 3, smoothD = 3) | Stochastique [k, d], smoothK = 1 donne la valeur brute |
cci(values, length) / cci(highs, lows, closes, length) | Commodity Channel Index (du prix typique pour la seconde forme) |
macd(values, fast = 12, slow = 26, signalPeriod = 9) | MACD [macd, signal, histogram] |
Bandes et canaux
| Méthode | Description |
|---|---|
bb(values, length = 20, mult = 2) / bollinger(...) | Bandes de Bollinger [middle, upper, lower] |
bbw(values, length, mult) | Largeur des bandes de Bollinger (upper - lower) / middle |
kc(values, highs, lows, closes, length = 20, mult = 2, useTrueRange = true) | Canaux de Keltner [middle, upper, lower] |
kcw(values, highs, lows, closes, length, mult, useTrueRange) | Largeur des canaux de Keltner (upper - lower) / middle |
Tendance et volatilité
| Méthode | Description |
|---|---|
tr(highs, lows, closes, handleNa = false) | True range (handleNa = true donne high - low sur la première barre) |
atr(highs, lows, closes, period = 14) | Average True Range |
dmi(highs, lows, closes, diLength = 14, adxSmoothing = 14) | Mouvement directionnel [plus, minus, adx] |
adx(highs, lows, closes, period = 14) | Average Directional Index |
sar(highs, lows, closes, start = 0.02, inc = 0.02, max = 0.2) | SAR parabolique |
supertrend(highs, lows, closes, factor = 3, atrPeriod = 10) | Supertrend [supertrend, direction], direction -1 = tendance haussière, 1 = baissière |
Volume
| Méthode | Description |
|---|---|
vwap(values, volumes, anchor?) / vwap(highs, lows, closes, volumes, anchor?) | VWAP cumulatif (du prix typique pour la seconde forme), redémarre sur les barres où anchor (tableau de booléens) est vrai |
obv(closes, volumes) | On Balance Volume |
accdist(highs, lows, closes, volumes) | Accumulation / Distribution |
iii(highs, lows, closes, volumes) | Intraday Intensity Index |
pvt(closes, volumes) | Price-Volume Trend |
nvi(closes, volumes) / pvi(closes, volumes) | Negative / Positive Volume Index |
wad(highs, lows, closes) | Williams Accumulation / Distribution |
wvad(opens, highs, lows, closes, volumes) | Williams Variable Accumulation / Distribution |
Fonctions de carnet d'ordres
orderbook est source.orderbook() ou source.depths()[i].data : {asks: {price: quantity}, bids: {price: quantity}}.
| Méthode | Description |
|---|---|
orderbookSide(orderbook, side) | Un côté ('bids' / 'asks') sous forme de [[price, quantity], ...] trié du meilleur prix au moins bon |
bestBid(orderbook) / bestAsk(orderbook) | Meilleur prix bid / ask (null pour un carnet vide) |
sumBids(orderbook, percent, price, inQuote) | Quantité cumulée des bids entre price (par défaut : meilleur bid) et price * (1 - percent / 100), inQuote = true additionne price * quantity |
sumAsks(orderbook, percent, price, inQuote) | Quantité cumulée des asks entre price (par défaut : meilleur ask) et price * (1 + percent / 100) |
Exemple :
const candles = source.ohlcv();const closes = candles.map(c => c.close);const rsiValues = ta.rsi(closes, 14);plot.lineSeries("RSI", rsiValues, "#00C8FF");
Alertes
Les utilisateurs peuvent créer des alertes pour votre indicateur depuis l'onglet Alertes des paramètres de l'indicateur sur le graphique. Les alertes sont vérifiées côté serveur régulièrement, les notifications sont envoyées vers Telegram et/ou une URL de webhook définie par l'utilisateur.
Paramètres d'alerte — alertInput
La classe alertInput déclare les paramètres d'alerte. Elle fonctionne exactement comme input, mais les champs sont affichés dans l'onglet Alertes et chaque utilisateur configure ses propres valeurs pour son alerte.
Les scripts sans déclarations alertInput ne peuvent pas être utilisés pour les alertes.
| Méthode | Description |
|---|---|
number(title, defaultValue, min, max, step) | Entrée numérique |
range(title, defaultValue, min, max, step) | Entrée de type plage |
select(title, defaultValue, values) | Sélection dans une liste déroulante |
color(title, defaultValue) | Sélecteur de couleur |
boolean(title, defaultValue) | Entrée booléenne |
Exemple :
const alertVolumeThreshold = alertInput.number("Volume threshold", 1000, 0, 10000000, 1);
Déclencher des alertes — alert.fire
| Méthode | Description |
|---|---|
fire(title, message, date) | Déclenche une notification d'alerte |
- Les trois paramètres sont obligatoires — un appel sans
title,messageoudateest ignoré (un avertissement est écrit dans le journal). - Gardez le titre de l'alerte constant, ne générez pas de titres uniques. Chaque titre ne se déclenche qu'une fois par exécution (20 titres maximum par exécution).
dateest lestartTimedu chandelier auquel le signal appartient : le même titre ne notifie à nouveau que lorsqu'il se déclenche pour un chandelier plus récent. Les signaux provenant de chandeliers antérieurs au moment où l'utilisateur a créé son alerte sont ignorés (anti-spam).
Exemple :
if (candle.volume > alertVolumeThreshold) {alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);}
Alertes sur les formes — plot.<shape>(...).alert(...)
Toutes les formes géométriques (box, square, diamond, triangle, circle, straightLine, horizontalLine, verticalLine, text) retournent un handle doté d'une méthode .alert().
L'alerte se déclenche automatiquement avec le date1 de la forme, la logique anti-spam fonctionne donc sans code supplémentaire — c'est la méthode recommandée pour les signaux par chandelier.
Deux formes :
// 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);});
Le callback reçoit les données de la forme (date1, price1, date2, price2, ...).
Alertes sur les séries — plot.<series>(...).alert(...)
Les séries de données (lineSeries, barSeries, candleSeries, areaSeries, bandSeries) retournent également le handle .alert().
Le callback reçoit le dernier point de données et la date du dernier chandelier, l'alerte se déclenche avec cette date.
lineSeries/barSeries/areaSeries— le callback reçoit la dernière valeur (un nombre)candleSeries— le callback reçoit le dernier chandelier{open, close, high, low}bandSeries— le callback reçoit le dernier{upper, lower}
plot.lineSeries("RSI", rsiValues, color).alert((rsi, date) => {if (rsi > alertRsiThreshold) alert.fire("RSI high", `RSI ${rsi.toFixed(1)}`, latestCandle.startTime);});
Une condition qui reste vraie notifie une fois par nouveau chandelier. Pour des signaux ponctuels, utilisez des conditions de type croisement (ta.crossover).
Exemple complet
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);});}}
Exemples d'indicateurs
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 (bandes de déséquilibre de profondeur bid / ask autour du prix, comme les superpositions « Order Book Pressure » des terminaux de trading) :
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);}});