MobChartDocs
Ouvrir MobChart

API d'indicateurs personnalisés

Écrivez vos propres indicateurs en JavaScript, exécutez-les dans un bac à sable sécurisé à côté du graphique et partagez-les avec les autres utilisateurs. L'API vous donne accès aux données de marché (chandeliers, carnets d'ordres, clusters, liquidations, données des dérivés), à des primitives de dessin, à des fonctions d'analyse technique et aux alertes.

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ètreTypeDescription
titlestringNom de l'indicateur
descriptionstringCourte description
type'onchart' | 'offchart'Emplacement de l'indicateur (sur le graphique ou dans un panneau séparé)
isPublictrue | falseSi l'indicateur est public
isPublicCodetrue | falseSi le code source est public
optionsobject (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éthodeDescription
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 graphique
const 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ètreDescription
exchange, ticker, timeframePar 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é.
lookbackNombre 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églageDéfautDescription
tickerstous les tickersseulement ces tickers de l'exchange
exclude[]tickers à ignorer
type"all""all", "bids" ou "asks"
nearness5distance max au prix courant, en pourcent
size100000taille min de l'ordre dans la devise de cotation
lifespan0minutes min pendant lesquelles l'ordre est resté
touches, currentTouches0touches min du niveau / de l'ordre courant
filled0part min exécutée de l'ordre, en pourcent
strength, diversity0force (pourcent) et diversité min, comme sur le screener
continuousOnlyfalseignorer les ordres qui ont disparu puis sont revenus
volume1h0volume 1h min du ticker dans la devise de cotation
spotAndFuturesfalseseulement les tickers présents sur le spot et les futures
limit500nombre 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 lifespan
const candles = await source.ohlcv();
const lastDate = candles.at(-1).startTime;
const levels = await source.levels(currentExchange, { tickers: [currentTicker], nearness: 3, size: 300000, lifespan: 30 });
const own = levels[currentTicker] || { asks: {}, bids: {} };
for (const [type, color] of [["asks", "#ef5350"], ["bids", "#26a69a"]]) {
for (const [price, level] of Object.entries(own[type])) {
plot.horizontalLine(`${type} ${price}`, +price, color, 0.8, Math.min(4, 1 + level.quantityUSDT / 1e6));
plot.text(`${type} ${price} label`, `${(level.quantityUSDT / 1e6).toFixed(2)}M · ${level.duration}m`, lastDate, +price, 10, color);
}
}

keyLevels(exchange, settings?)

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ètreDéfautDescription
tickerstous les tickersseulement ces tickers de l'échange
exclude[]tickers à ignorer
periodstoutes"daily", "prevDay", "weekly", "prevWeek", "monthly", "prevMonth", "yearly", "prevYear"
fieldstous"open", "high", "low" : les niveaux à renvoyer
distanceFrom, distanceToaucuneplage 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 %
limit200nombre 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 lines
const candles = await source.ohlcv();
const lastDate = candles.at(-1).startTime;
const rows = await source.keyLevels(currentExchange, { tickers: [currentTicker], periods: ["daily", "prevDay", "weekly"] });
const own = rows[currentTicker];
if (own) {
for (const period of ["daily", "prevDay", "weekly"]) {
const level = own[period];
if (!level) continue;
for (const [field, color] of [["high", "#ef5350"], ["low", "#26a69a"], ["open", "#90a4ae"]]) {
if (level[field] == null) continue;
plot.horizontalLine(`${period} ${field}`, level[field], color, 0.8, 1);
plot.text(`${period} ${field} label`, `${period} ${field.toUpperCase()}`, lastDate, level[field], 10, color);
}
}
}

levelsMetrics(exchange, settings?)

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ètrePar défautDescription
tickerstous les tickersseulement ces tickers de la plateforme
exclude[]tickers à ignorer
hours24période en heures (max 168 ; 24 sans abonnement)
minDuration30durée minimale de l'ordre en minutes : 30, 60, 120 ou 180
minStrength80force minimale de l'ordre en pourcentage : 80, 90 ou 100
maxDistanceaucunedistance maximale de l'ordre au prix actuel en pourcentage, vérifiée dans chaque instantané
stableOnlyfalsetrue : seulement les ordres dont la quantité n'a jamais sauté (« exclure les ordres aux quantités instables »)
limit200nombre maximal de niveaux dans levels (max 1000 ; 100 sans abonnement)

Exemple :

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

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éthodeDescription
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–299
status: 200, // statut HTTP, 0 si la requête n'a pas été envoyée
headers: { "content-type": "application/json", ... },
data: { ... }, // corps de la réponse analysé en JSON, null si le corps n'est pas du JSON
text: "...", // corps brut de la réponse
error: 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

VariableDescriptionExemple
currentExchangeÉchange actuel sur le graphiquebinance, binanceFutures, bybit, bybitFutures, okx, okxFutures, coinbase, bitfinex, bitfinexFutures, hyperliquid, hyperliquidFutures, asterFutures
currentTickerTicker actuel sur le graphique (le format dépend de l'échange)BTCUSDT, ADA-USD, BTC/USDC, ...
currentTimeframePériode actuelle sur le graphique1m, 5m, 15m, ...
exchangesNoms de toutes les plateformes (tableau figé)["binance", "binanceFutures", ...]
tickersTickers de toutes les plateformes : tickers[currentExchange] est la liste de la plateforme du graphique (figé)tickers.binance = ["BTCUSDT", "ETHUSDT", ...]
heatmapStepPas 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 ETHUSDT
const depths = source.depths("binanceFutures", "ETH-USD");
// le ticker d'un graphique Binance sur Coinbase : ETHUSDT devient ETH-USD
const spot = source.ohlcv("coinbase", currentTicker, currentTimeframe);

Journalisation

La classe logger sert à afficher des informations et à déboguer.

MéthodeDescription
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éthodeDescription
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ètreType de donnéeDescription
titlestringNom de l'élément (identifiant unique sur le graphique)
date1number (timestamp, ms)Horodatage de départ
price1numberPrix de départ (coordonnée sur l'axe Y)
date2number (timestamp, ms)Horodatage de fin (pour les lignes et les rectangles)
price2numberPrix de fin (pour les lignes et les rectangles)
sizenumberTaille de la forme (en pixels)
radiusnumberRayon du cercle (10 par défaut)
fillstring (hex, ex. "#FF0000")Couleur de remplissage de la forme
opacitynumber (0–1)Opacité du remplissage
strokestring (hex, ex. "#000000")Couleur du contour
strokeOpacitynumber (0–1)Opacité du contour
strokeWidthnumberÉpaisseur du contour (en pixels)
strokeDasharrayarray (ex. [5,5])Style de ligne pointillée
fontSizenumberTaille du texte (en pixels)
valuestringValeur 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éthodeDescription
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ètreType de donnéeDescription
values, upperValues, lowerValues(number \| null)[]Une valeur par chandelier, null masque le chandelier
fill, strokestring (hex) ou functionCouleur, ou fonction retournant la couleur du chandelier : (value, i) pour lineSeries, barSeries, candleSeries, areaSeries, (upper, lower, i) pour bandSeries
opacitynumber (0–1) ou functionOpacité du remplissage, une fonction (value, i) / (upper, lower, i) pour areaSeries / bandSeries
basenumber | nullNiveau de base de areaSeries, null = le bas du graphique
stepbooleanStyle 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 it
plot.lineSeries("Close", closes, (v, i) => v > ema[i] ? colorBuy : colorSell);
plot.barSeries("Premium %", premiumPercents, ((d) => d > 0 ? color1 : color2), 1);
plot.candleSeries(
"Candles",
opens, closes, highs, lows,
(v, i) => {
return opens?.[i] > closes?.[i] ? colorBuy : colorSell
}
);
plot.areaSeries("Volume", volumes, "#00C8FF", 0.3);
// area colored per candle, more opaque on the bigger candles
plot.areaSeries("Delta", deltas, (v, i) => v >= 0 ? colorBuy : colorSell, (v, i) => Math.min(1, Math.abs(v) / maxDelta));
// pressure band between the close and 1% below it, colored and shaded by the imbalance ratio of the candle
plot.bandSeries(
"Bid pressure",
closes,
closes.map(c => c * 0.99),
(upper, lower, i) => ratios[i] > 0 ? bidColor : askColor,
(upper, lower, i) => Math.abs(ratios[i]) / 100
);

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.

OptionDéfautDescription
steptick du graphiquehauteur 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"
width30ligne 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
opacity0.6opacité des barres
deltafalsedessiner buy - sell au lieu de la somme, paires seulement
poctrueligne sur la ligne de plus grande valeur
valueArea70pourcentage du total autour du POC dessiné en pleine opacité, false pour désactiver
valueAreaOpacity0.3opacité des lignes hors de la value area
labelstruetotaux 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 tool
const rows = await source.clusters();
plot.profile("Volume", rows.map((r) => r?.data ?? null), { step: 5 });
// only large trades, delta coloured, on the left edge
const minSize = input.number("Min trade size", 50, 0, 100000, 1);
const big = rows.map((r) => {
if (!r?.data) return null;
const out = {};
for (const [price, c] of Object.entries(r.data)) {
if (price === "totalBuy" || price === "totalSell") continue;
if (c.buy + c.sell >= minSize) out[price] = { buy: c.buy, sell: c.sell };
}
return out;
});
plot.profile("Large trades", big, { step: 5, delta: true, orient: "left" });

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 colour
const book = await source.orderbook();
const rows = {};
for (const [price, qty] of Object.entries(book.bids)) rows[price] = { buy: qty };
for (const [price, qty] of Object.entries(book.asks)) rows[price] = { sell: qty };
plot.histogram("Order book", rows, { step: 5, width: 25, valueArea: false, poc: false });

Exemple, vos propres niveaux :

// your own histogram: cluster volume of the last 100 candles, recent candles weigh more
const rows = await source.clusters();
const weighted = {};
rows.slice(-100).forEach((r, i, arr) => {
if (!r?.data) return;
const weight = (i + 1) / arr.length;
for (const [price, c] of Object.entries(r.data)) {
if (price === "totalBuy" || price === "totalSell") continue;
const cell = weighted[price] || (weighted[price] = { buy: 0, sell: 0 });
cell.buy += (c.buy || 0) * weight;
cell.sell += (c.sell || 0) * weight;
}
});
plot.histogram("Weighted volume", weighted, { step: 5, orient: "left", delta: true });

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.

OptionDéfautDescription
position"top-right"coin du panneau : "top-right", "top-left", "bottom-right", "bottom-left"
headerfalsepremière ligne en gras sur un bandeau ombré
fontSize11taille du texte en px, de 8 à 16
color, bg, borderselon le thèmecouleurs par défaut du texte, du panneau et de la grille ; une cellule peut redéfinir color et bg
opacity0.85opacité 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éthodeDescription
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éthodeDescription
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éthodeDescription
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éthodeDescription
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éthodeDescription
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éthodeDescription
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éthodeDescription
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éthodeDescription
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éthodeDescription
fire(title, message, date)Déclenche une notification d'alerte
  • Les trois paramètres sont obligatoires — un appel sans title, message ou date est 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).
  • date est le startTime du 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 date
plot.box(...).alert("High volume", `vol ${candle.volume}`);
// callback: return nothing to skip, [title, message] to fire
plot.box(...).alert((shape) => {
if (candle.volume > alertVolumeThreshold)
alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);
});

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 close
plot.lineSeries("Closes", closes, color).alert((close, date) => {
if (close > alertCloseThreshold) alert.fire("Close above threshold", `close ${close}`, latestCandle.startTime);
});
for (let i = 0; i < candles.length; i++) {
const candle = candles[i];
if (candle.volume > volumeThreshold) {
const right = i < candles.length - 1 ? candles[i + 1].startTime : candle.startTime + 60000;
// shape alert: checked against the user's alert threshold
plot.box(`Volume ${i}`, candle.startTime, candle.high, right, candle.low, "#00C8FF", 0.3)
.alert(() => {
if (candle.volume > alertVolumeThreshold)
alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);
});
}
}

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 candles
const candles = source.ohlcv();
const colorUp = style.color("Color 1", "#2BB462"); // Greenish color for rising prices
const colorDown = style.color("Color 2", "#FB4C51"); // Reddish color for falling prices
const volumes = candles.map(candle => candle.volume);
plot.barSeries("Volume", volumes, (value, index) => {
const currentCandle = candles[index];
return currentCandle.close >= currentCandle.open ? colorUp : colorDown;
});
define('Coinbase Premium Index', 'Coinbase Premium Index', 'offchart', true, false);
const color1 = style.color("Color 1", "#2BB462");
const color2 = style.color("Color 2", "#FB4C51");
// Get OHLC candles
const cbCandles = source.ohlcv('coinbase', 'BTC-USD');
const binanceCandles = source.ohlcv('binance', 'BTCUSDT');
// Extract closes
const cbCloses = cbCandles.map(c => c.close);
const binanceCloses = binanceCandles.map(c => c.close);
// --- Coinbase Premium Index calculation ---
const premiumPercent = cbCloses.map((cb, i) => {
const b = binanceCloses[i];
return ((cb - b) / b) * 100;
});
// Plot premium index
plot.barSeries("Premium %", premiumPercent, ((d) => d > 0 ? color1 : color2)) // Percentage difference
const title = "Candles";
const description = "Simple candles draw";
const indicatorType = "offchart";
const isPublic = true;
const isPublicCode = true;
define(title, description, indicatorType, isPublic, isPublicCode);
const colorBuy = style.color("Buy Color", "#2BB462");
const colorSell = style.color("Sell Color", "#7a3d3f");
const candles = source.ohlcv();
const closes = candles.map(c => c.close);
const opens = candles.map(c => c.open);
const highs = candles.map(c => c.high);
const lows = candles.map(c => c.low);
plot.candleSeries(
"Candles",
opens, closes, highs, lows,
(v, i) => {
return opens?.[i] > closes?.[i] ? colorBuy : colorSell
}
);

Order Book Pressure (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 close
const from = parseFloat(depthFrom);
const to = parseFloat(depthTo);
const candles = source.ohlcv();
const depths = source.depths();
const liveBook = source.orderbook();
// orderbook of every candle: depths()[i] is the snapshot of candles[i] (the biggest limit orders), the latest candle uses the live book
const lastIndex = candles.length - 1;
const books = candles.map((c, i) => {
if (!c) return null;
if (i === lastIndex && ta.bestBid(liveBook)) return liveBook;
return depths[i]?.data || null;
});
const closes = candles.map(c => c ? c.close : null);
// imbalance ratio of every band in % (positive = bids dominate, negative = asks dominate)
const ratios = bounds.map((upper, b) => {
const lower = b === 0 ? 0 : bounds[b - 1];
return candles.map((c, i) => {
const book = books[i];
if (!c || !book) return null;
const bid = ta.sumBids(book, upper, c.close) - ta.sumBids(book, lower, c.close);
const ask = ta.sumAsks(book, upper, c.close) - ta.sumAsks(book, lower, c.close);
const total = bid + ask;
return total > 0 ? ((bid - ask) / total) * 100 : 0;
});
});
// a stronger ratio -> a more opaque band, extreme ratios get the extreme color
const bandOpacity = (ratio) => Math.min(0.95, Math.abs(ratio || 0) / 70 * 0.95);
const bandColor = (ratio) => highlight && Math.abs(ratio || 0) >= 63 ? extremeColor : (ratio >= 0 ? bidColor : askColor);
bounds.forEach((upper, b) => {
const lower = b === 0 ? 0 : bounds[b - 1];
if (from > lower || upper > to) return; // the band is outside of the selected depth range
const bidUpper = closes.map(c => c != null ? c * (1 - lower / 100) : null);
const bidLower = closes.map(c => c != null ? c * (1 - upper / 100) : null);
const askLower = closes.map(c => c != null ? c * (1 + lower / 100) : null);
const askUpper = closes.map(c => c != null ? c * (1 + upper / 100) : null);
if (showRef) {
plot.lineSeries(`Bid ${upper}%`, bidLower, bidColor, 0.4);
plot.lineSeries(`Ask ${upper}%`, askUpper, askColor, 0.4);
}
// bid pressure: the band is filled only on the candles where bids dominate by more than the min ratio
plot.bandSeries(
`Bid pressure ${lower}-${upper}%`,
bidUpper.map((v, i) => ratios[b][i] >= minBidRatio ? v : null),
bidLower,
(upper, lower, i) => bandColor(ratios[b][i]),
(upper, lower, i) => bandOpacity(ratios[b][i])
);
// ask pressure
plot.bandSeries(
`Ask pressure ${lower}-${upper}%`,
askUpper.map((v, i) => ratios[b][i] <= -minAskRatio ? v : null),
askLower,
(upper, lower, i) => bandColor(ratios[b][i]),
(upper, lower, i) => bandOpacity(ratios[b][i])
);
// alert on the latest candle
const latestRatio = ratios[b][lastIndex];
if (latestRatio != null && Math.abs(latestRatio) >= alertMinRatio) {
alert.fire(`Pressure ${lower}-${upper}%`, `ratio ${latestRatio.toFixed(1)}%`, candles[lastIndex].startTime);
}
});