API de indicadores personalizados
Genera indicadores con IA
Toda la API también está disponible como un único archivo de texto para modelos de lenguaje: AI-scripting.txt. Adjúntalo a ChatGPT, Claude, Gemini o cualquier otro LLM y describe el indicador que quieres.
Ejemplo de prompt:
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.
Sandbox y límites
Desarrollo de indicadores
El desarrollo de indicadores se realiza en un widget aparte, que puedes colocar junto al gráfico en el que se activará el indicador.

Crear un indicador
define(title, description, type, isPublic, isPublicCode, options)
Registra el indicador en el sistema.
| Parámetro | Tipo | Descripción |
|---|---|---|
title | string | Nombre del indicador |
description | string | Descripción breve |
type | 'onchart' | 'offchart' | Ubicación del indicador (en el gráfico o en un panel aparte) |
isPublic | true | false | Si el indicador es público |
isPublicCode | true | false | Si el código fuente es público |
options | object (opcional) | { fullRange, warmup }, ver más abajo |
Ejemplo:
define("My Indicator", "Shows EMA line", "onchart", true, false);
Rango de cálculo. Por defecto, un script calcula solo la parte del gráfico que lo necesita: las barras cargadas al desplazarte hacia atrás, o las últimas barras en una actualización, más warmup barras de historial anteriores (500 por defecto) que los cálculos necesitan pero que no se devuelven. Declara warmup cuando tu indicador necesite más o menos historial, p. ej. { warmup: 200 } para una EMA(200). Un script cuyos valores o formas dependen de todo el rango visible (VWAP desde la primera vela visible, delta acumulado, una etiqueta en el máximo de la ventana, niveles dibujados a lo ancho del gráfico) debe declarar { fullRange: true }; de lo contrario, sus resultados serán incorrectos después de cargar más historial. El modo se lee de define() cada vez que se guarda el script: un script guardado sin fullRange se calcula parcialmente, así que añade la opción a tus scripts acumulativos existentes cuando los edites. Los scripts parciales también se cargan por partes: cuando activas uno sobre un historial largo, el gráfico solicita primero las 500 velas más recientes (o warmup velas si es un número mayor) y las partes más antiguas llegan después, una solicitud a la vez, de modo que la parte visible aparece de inmediato. Los scripts con fullRange siempre se calculan en una sola solicitud sobre todo el rango.
define("Window max", "Label at the highest visible high", "onchart", true, true, { fullRange: true });
Ubicación del indicador para onchart y offchart:

Parámetros de entrada
Las clases input y style te permiten crear ajustes configurables por el usuario (similar a input() en PineScript).
| Método | Descripción |
|---|---|
number(title, defaultValue, min, max, step) | Entrada numérica |
range(title, defaultValue, min, max, step) | Entrada de rango |
select(title, defaultValue, values) | Selección desplegable |
color(title, defaultValue) | Selector de color |
boolean(title, defaultValue) | Entrada booleana |
Ejemplo (input) para los parámetros principales:
const length = input.number("Period", 14, 1, 100, 1);const type = input.select("Type", 'sma', ['sma', 'ema']);const color = input.color("Line Color", "#FF0000");
O (style) para los parámetros visuales:
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");

Fuentes de datos
La clase source obtiene datos de mercado del servidor. Los métodos basados en velas reciben los mismos parámetros; las dos instantáneas en vivo son diferentes: orderbook() recibe un exchange y un ticker, levels() un exchange y un objeto de ajustes.
| Parámetro | Descripción |
|---|---|
exchange, ticker, timeframe | Por defecto, los del gráfico en el que está abierto el indicador. Pasa otros valores para obtener datos de otro mercado. |
lookback | Número de barras de historial desde la fecha actual, no más de 5000. |
ohlcv(exchange, ticker, timeframe, lookback?)
Datos de velas.
[{"open": 115886.17,"high": 115911.96,"low": 115764.27,"close": 115815.16,"volume": 228.41528,"startTime": 1757699100000},{...},...]
depths(exchange, ticker, timeframe, lookback?)
Instantáneas históricas del libro de órdenes, una por vela. Solo se guardan las órdenes límite más grandes (los datos detrás del mapa de calor).
[{"data": {"asks": {"109425": 5.467,"109495": 3.564,...},"bids": {"33000": 3.746,"35000": 16.49,...}},"startTime": 1757699100000},{...},...]
clusters(exchange, ticker, timeframe, lookback?)
Datos de clústeres (footprint): volumen de compra y de venta en cada nivel de precio de la vela.
[{"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?)
Volumen liquidado en cada nivel de precio de la vela (solo mercados de futuros). Mismo formato 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?)
Datos de derivados por vela: tasa de financiación, vela de interés abierto, ratios long/short (solo mercados de futuros).
[{"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)
Instantánea en vivo del libro de órdenes: el libro completo actual, no vinculado a las velas.
{"asks": {"109425": 5.467,"109495": 3.564,...},"bids": {"109400": 1.2,"109390": 0.8,...}}
Los niveles no están ordenados; usa las funciones de libro de órdenes de ta (ta.bestBid, ta.sumBids, ...) para trabajar con el libro.
Los libros históricos de las velas provienen de depths(): depths()[i].data tiene el mismo formato, pero allí solo se guardan las órdenes límite más grandes.
Ejemplo:
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?)
Los niveles del Screener de liquidez: las grandes órdenes límite en espera de un exchange, filtradas con los mismos ajustes que la página del screener. Datos en vivo, no vinculados a las velas. Sin plan, la cercanía se limita al 1%, la antigüedad a 60 minutos y el resultado a 200 niveles; con un plan, hasta 2000 niveles. Las órdenes más grandes van primero. Cuenta como una solicitud de fuente de datos.
{"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},...}
Ajustes (todos opcionales):
| Ajuste | Por defecto | Descripción |
|---|---|---|
tickers | todos los tickers | solo estos tickers del exchange |
exclude | [] | tickers que se omiten |
type | "all" | "all", "bids" o "asks" |
nearness | 5 | distancia máxima al precio actual, en porcentaje |
size | 100000 | tamaño mínimo de la orden en la moneda de cotización |
lifespan | 0 | minutos mínimos que la orden lleva en el libro |
touches, currentTouches | 0 | toques mínimos del nivel / de la orden actual |
filled | 0 | parte ejecutada mínima de la orden, en porcentaje |
strength, diversity | 0 | fuerza (en porcentaje) y diversidad mínimas, como en el screener |
continuousOnly | false | omite las órdenes que desaparecieron y volvieron |
volume1h | 0 | volumen mínimo de 1h del ticker en la moneda de cotización |
spotAndFutures | false | solo tickers que existen tanto en el mercado spot como en el de futuros |
limit | 500 | número máximo de niveles, los más grandes primero (máx. 2000) |
Ejemplo:
// 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);}}
Solicitudes HTTP
La clase http envía solicitudes a APIs HTTP externas y devuelve la respuesta, de modo que un indicador puede usar datos que no están disponibles en source: tu propio servidor, una API REST pública, un proveedor de métricas on-chain, ...
Al igual que source.*, las llamadas se escriben como funciones síncronas, no hace falta await.
| Método | Descripción |
|---|---|
get(url, options?) | Solicitud GET |
post(url, body?, options?) | Solicitud POST. Un body de tipo objeto o array se envía como JSON (Content-Type: application/json), una cadena se envía tal cual |
options es { headers: { name: value } }: hasta 10 cabeceras personalizadas (por ejemplo, Authorization o x-api-key).
Ambos métodos devuelven el mismo objeto de respuesta y nunca lanzan excepciones:
{ok: true, // status 200–299status: 200, // HTTP status, 0 when the request was not sentheaders: { "content-type": "application/json", ... },data: { ... }, // response body parsed as JSON, null when the body is not JSONtext: "...", // raw response bodyerror: null // reason when the request failed: "timeout", "url not allowed", "too many requests", ...}
Valores posibles 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.
Ejemplo (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");
Ejemplo (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 | Descripción | Ejemplo |
|---|---|---|
currentExchange | Exchange actual del gráfico | binance, binanceFutures, coinbase, ... |
currentTicker | Ticker actual del gráfico (el formato depende del exchange) | BTCUSDT, ADA-USD, BTC/USDC, ... |
currentTimeframe | Temporalidad actual del gráfico | 1m, 5m, 15m, ... |
Ejemplo:
const candles = source.ohlcv(currentExchange, currentTicker, currentTimeframe);const closes = candles.map(c => c.close);
Registro (logging)
La clase logger se usa para mostrar información y depurar.
| Método | Descripción |
|---|---|
log(...args) | Añade una entrada al registro (con límites de longitud y cantidad) |
Ejemplo:
const candles = source.ohlcv();logger.log("First candle:", candles?.[0]);

Visualización
La clase plot se encarga de dibujar elementos gráficos y series de datos.
Formas geométricas
| Método | Descripción |
|---|---|
box(title, date1, price1, date2, price2, fill, opacity, stroke, strokeOpacity, strokeWidth) | Rectángulo |
square(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) | Cuadrado |
diamond(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) | Rombo |
triangle(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth) | Triángulo |
circle(title, date1, price1, radius = 10, fill, opacity, stroke, strokeOpacity, strokeWidth) | Círculo |
text(title, value, date1, price1, fontSize, fill, opacity) | Texto en el gráfico |
table(title, rows, options) | Tabla fijada en una esquina del gráfico, consulta Tablas |
straightLine(title, date1, price1, date2, price2, stroke, strokeOpacity, strokeWidth, strokeDasharray) | Línea recta |
horizontalLine(title, price1, stroke, strokeOpacity, strokeWidth, strokeDasharray) | Línea horizontal a lo ancho de todo el gráfico |
verticalLine(title, date1, stroke, strokeOpacity, strokeWidth, strokeDasharray) | Línea vertical a lo alto de todo el gráfico |
| Parámetro | Tipo de dato | Descripción |
|---|---|---|
title | string | Nombre del elemento (identificador único en el gráfico) |
date1 | number (timestamp, ms) | Marca de tiempo inicial |
price1 | number | Precio inicial (coordenada del eje Y) |
date2 | number (timestamp, ms) | Marca de tiempo final (para líneas y rectángulos) |
price2 | number | Precio final (para líneas y rectángulos) |
size | number | Tamaño de la forma (en píxeles) |
radius | number | Radio del círculo (10 por defecto) |
fill | string (hex, p. ej., "#FF0000") | Color de relleno de la forma |
opacity | number (0–1) | Opacidad del relleno |
stroke | string (hex, p. ej., "#000000") | Color del trazo |
strokeOpacity | number (0–1) | Opacidad del trazo |
strokeWidth | number | Grosor del trazo (en píxeles) |
strokeDasharray | array (p. ej., [5,5]) | Estilo de línea discontinua |
fontSize | number | Tamaño del texto (en píxeles) |
value | string | Valor del texto (para el método text) |
Ejemplo:
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);
No se pueden mostrar más de 3000 formas en el gráfico.
Series de datos
| Método | Descripción |
|---|---|
lineSeries(title, values, stroke, strokeOpacity, strokeWidth, strokeDasharray) | Gráfico de líneas |
barSeries(title, values, fill, opacity) | Gráfico de barras |
candleSeries(title, openValues, closeValues, highValues, lowValues, fill, opacity) | Velas |
areaSeries(title, values, fill, opacity, stroke, strokeOpacity, strokeWidth, base) | Área rellena entre la línea y base (por defecto: la parte inferior del gráfico) |
bandSeries(title, upperValues, lowerValues, fill, opacity, stroke, strokeOpacity, strokeWidth, step) | Banda rellena entre dos series; cada vela conserva su propio color y opacidad |
profile(title, values, options) | Histograma tipo perfil de volumen de las velas visibles, consulta Perfiles |
histogram(title, rows, options) | Un mapa de precios ya terminado dibujado como un perfil, consulta Histogramas |
| Parámetro | Tipo de dato | Descripción |
|---|---|---|
values, upperValues, lowerValues | (number \| null)[] | Un valor por vela, null oculta la vela |
fill, stroke | string (hex) o function | Color, o una función que devuelve el color de la vela: (value, i) para lineSeries, barSeries, candleSeries y areaSeries, (upper, lower, i) para bandSeries |
opacity | number (0–1) o function | Opacidad del relleno, una función (value, i) / (upper, lower, i) para areaSeries / bandSeries |
base | number | null | Nivel base de areaSeries, null = la parte inferior del gráfico |
step | boolean | Estilo de bandSeries: true = una columna por vela (por defecto), false = un polígono continuo |
Ejemplo:
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);
No se pueden mostrar más de 20 series de datos en el gráfico.
Perfiles
plot.profile(title, values, options?) dibuja un histograma por precio, como el perfil de volumen integrado. values[i] es el mapa de precios de la vela i de ohlcv(): { price: value } o { price: { buy, sell } } (el data de un registro de clusters() o liquidations() se puede pasar tal cual), o null para una vela sin filas. Las filas de las velas visibles en el gráfico se suman por paso de precio en el propio gráfico, por lo que el perfil sigue el desplazamiento, el zoom y la carga de más historial sin una nueva ejecución. Los valores simples se dibujan en un solo color (un valor negativo va a fill2); los pares dividen la barra en una parte de compra y otra de venta y permiten el modo delta. profile() no devuelve ningún manejador de alertas.
| Opción | Por defecto | Descripción |
|---|---|---|
step | tamaño de tick del gráfico | altura de la fila en unidades de precio, los precios se redondean hacia abajo a este valor |
orient | "right" | borde desde el que crecen las barras, "right" o "left" |
width | 30 | fila más ancha como porcentaje del ancho del gráfico |
fill | "#26a69a" | color del valor, o de la parte de compra |
fill2 | "#ef5350" | color de la parte de venta |
opacity | 0.6 | opacidad de las barras |
delta | false | dibuja buy - sell en lugar de la suma, solo pares |
poc | true | línea en la fila con el valor más grande |
valueArea | 70 | porcentaje del total alrededor del POC dibujado con opacidad completa, false para desactivarlo |
valueAreaOpacity | 0.3 | opacidad de las filas fuera del área de valor |
labels | true | totales de las filas sobre las barras cuando hay espacio |
Límites: 3 perfiles por ejecución, 300 filas por vela, 200000 filas por perfil. Las filas que superan los límites se descartan y la ejecución registra una nota.
Ejemplo:
// 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" });
Histogramas
plot.histogram(title, rows, options?) dibuja un histograma que ya has construido: un único mapa de precios para todo el gráfico, no se suma nada a partir de las velas. Usa profile() cuando las filas pertenecen a velas y la imagen debe seguir el rango visible, y histogram() cuando las filas son definitivas: una instantánea del libro de órdenes, niveles calculados por el script, datos de otro mercado.
rows admite los mismos formatos que una entrada de vela de profile(): { price: value }, { price: { buy, sell } } o una lista de filas [price, buy, sell]. Un valor simple se dibuja en un solo color (un valor negativo va a fill2); los pares dividen la barra en una parte de compra y otra de venta y permiten el modo delta. Las opciones son las mismas que para profile(), consulta la tabla anterior.
El histograma se fecha con la última vela de la ejecución y el gráfico conserva el histograma más reciente de cada título, por lo que las ejecuciones parciales sobre velas más antiguas (al cargar historial) nunca sustituyen al actual. Se vuelve a dibujar en cada actualización del script, de modo que un histograma del libro de órdenes sigue al libro en vivo. histogram() no devuelve ningún manejador de alertas.
Límites: 3 histogramas por ejecución, 5000 filas. Las filas que superan el límite se descartan y la ejecución registra una nota.
Ejemplo, el libro de órdenes:
// 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 });
Ejemplo, tus propios niveles:
// 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 });
Tablas
plot.table(title, rows, options?) dibuja una pequeña cuadrícula fijada en una esquina del panel del gráfico: resúmenes multitemporalidad, estadísticas del rango cargado, paneles de señales. rows es un array de filas, una fila es un array de celdas, y una celda es una cadena, un número o { text, color, bg, align, bold }. Las tablas de la misma esquina se apilan en orden. Una tabla se fecha con la última vela de la ejecución y el gráfico conserva la tabla más reciente de cada título, por lo que las ejecuciones parciales sobre velas más antiguas (al cargar historial) nunca sustituyen a la actual. Las tablas no se dibujan en gráficos de menos de 500 px de ancho. Una tabla se puede arrastrar con el ratón: la posición se guarda con el espacio de trabajo y se restablece cuando el script cambia su opción position. table() no devuelve ningún manejador de alertas.
| Opción | Por defecto | Descripción |
|---|---|---|
position | "top-right" | esquina del panel: "top-right", "top-left", "bottom-right", "bottom-left" |
header | false | primera fila en negrita sobre una franja sombreada |
fontSize | 11 | tamaño del texto en px, de 8 a 16 |
color, bg, border | según el tema | colores por defecto del texto, del panel y de la cuadrícula; una celda puede sobrescribir color y bg |
opacity | 0.85 | opacidad del panel |
align | "left" | alineación por defecto de las celdas, "left", "center" o "right" |
Límites: 3 tablas por ejecución, 50 filas, 10 columnas, 100 caracteres por celda. El texto más largo se corta con puntos suspensivos; una columna ocupa como máximo el 30% del ancho del gráfico.
Ejemplo:
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 });
Análisis técnico
La clase ta implementa las funciones ta.* de PineScript v5. Pine funciona barra a barra; aquí cada función recibe series completas (arrays, un valor por vela, de la más antigua a la más reciente) y devuelve una serie de la misma longitud que se puede dibujar directamente. Los valores son null mientras el periodo aún no se ha completado, y las entradas null se propagan como el na de Pine. Las funciones de Pine que leen high, low, close o volume de forma implícita reciben esos arrays como argumentos.
Las funciones que devuelven varias series (bb, kc, macd, stoch, dmi, supertrend) devuelven un array que además tiene propiedades con nombre: const [middle, upper, lower] = ta.bb(closes, 20, 2) o ta.bb(closes, 20, 2).upper.
Funciones auxiliares
| Método | Descripción |
|---|---|
highest(values, length) / lowest(values, length) | Valor máximo / mínimo en length barras |
highestbars(values, length) / lowestbars(values, length) | Desplazamiento (0 o negativo) hasta la barra más alta / más baja |
max(values) / min(values) | Máximo / mínimo histórico hasta cada barra |
range(values, length) | highest - lowest |
change(values, length = 1) | values[i] - values[i - length] |
mom(values, length) | Momentum |
roc(values, length) | Tasa de cambio, % |
cum(values) | Suma acumulada |
rising(values, length) / falling(values, length) | El valor está por encima / por debajo de cada uno de los length valores anteriores |
variance(values, length, biased = true) | Varianza |
stdev(values, length, biased = true) | Desviación estándar |
dev(values, length) | Desviación absoluta media |
median(values, length) | Mediana |
mode(values, length) | Valor más frecuente (el menor en caso de empate) |
percentile_linear_interpolation(values, length, percentage) | Percentil, interpolación lineal entre los dos rangos más cercanos |
percentile_nearest_rank(values, length, percentage) | Percentil, método del rango más cercano |
percentrank(values, length) | % de los length valores anteriores menores o iguales que el actual |
correlation(series1, series2, length) | Coeficiente de correlación |
crossover(series1, series2) | Cruce al alza (cualquiera de los argumentos puede ser un número: ta.crossover(rsi, 70)) |
crossunder(series1, series2) | Cruce a la baja |
cross(series1, series2) | Cualquier cruce |
barssince(condition) | Barras desde la última vez que condition (array de booleanos) fue verdadera |
valuewhen(condition, values, occurrence = 0) | Valor de values cuando condition fue verdadera hace occurrence veces |
pivothigh(values, leftbars, rightbars) / pivotlow(...) | Valor del pivote en la barra de confirmación; el pivote en sí está rightbars barras antes |
Medias móviles
| Método | Descripción |
|---|---|
sma(values, length) | Media móvil simple |
ema(values, length) | Media móvil exponencial |
rma(values, length) | Media móvil de Wilder (usada por RSI / ATR) |
wma(values, length) | Media móvil ponderada |
hma(values, length) | Media móvil de Hull |
alma(values, length, offset = 0.85, sigma = 6, floor = false) | Media móvil de Arnaud Legoux |
swma(values) | Media móvil ponderada simétricamente sobre 4 barras |
vwma(values, volumes, length) | Media móvil ponderada por volumen |
linreg(values, length, offset = 0) | Curva de regresión lineal |
Osciladores
| Método | Descripción |
|---|---|
rsi(values, length) | Índice de fuerza relativa (Relative Strength Index) |
cmo(values, length) | Oscilador de momentum de Chande (Chande Momentum Oscillator) |
cog(values, length) | Centro de gravedad (Center of Gravity) |
mfi(values, volumes, length) | Money Flow Index (values suele ser 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) | Estocástico [k, d], smoothK = 1 da el valor sin suavizar |
cci(values, length) / cci(highs, lows, closes, length) | Commodity Channel Index (del precio típico en la segunda forma) |
macd(values, fast = 12, slow = 26, signalPeriod = 9) | MACD [macd, signal, histogram] |
Bandas y canales
| Método | Descripción |
|---|---|
bb(values, length = 20, mult = 2) / bollinger(...) | Bandas de Bollinger [middle, upper, lower] |
bbw(values, length, mult) | Ancho de las Bandas de Bollinger (upper - lower) / middle |
kc(values, highs, lows, closes, length = 20, mult = 2, useTrueRange = true) | Canales de Keltner [middle, upper, lower] |
kcw(values, highs, lows, closes, length, mult, useTrueRange) | Ancho de los Canales de Keltner (upper - lower) / middle |
Tendencia y volatilidad
| Método | Descripción |
|---|---|
tr(highs, lows, closes, handleNa = false) | Rango verdadero (handleNa = true da high - low en la primera barra) |
atr(highs, lows, closes, period = 14) | Rango verdadero medio (Average True Range) |
dmi(highs, lows, closes, diLength = 14, adxSmoothing = 14) | Movimiento direccional [plus, minus, adx] |
adx(highs, lows, closes, period = 14) | Índice direccional medio (Average Directional Index) |
sar(highs, lows, closes, start = 0.02, inc = 0.02, max = 0.2) | SAR parabólico |
supertrend(highs, lows, closes, factor = 3, atrPeriod = 10) | Supertrend [supertrend, direction], direction -1 = tendencia alcista, 1 = tendencia bajista |
Volumen
| Método | Descripción |
|---|---|
vwap(values, volumes, anchor?) / vwap(highs, lows, closes, volumes, anchor?) | VWAP acumulado (del precio típico en la segunda forma), se reinicia en las barras donde anchor (array de booleanos) es verdadero |
obv(closes, volumes) | On Balance Volume |
accdist(highs, lows, closes, volumes) | Acumulación / Distribución |
iii(highs, lows, closes, volumes) | Intraday Intensity Index |
pvt(closes, volumes) | Price-Volume Trend |
nvi(closes, volumes) / pvi(closes, volumes) | Índice de volumen negativo / positivo |
wad(highs, lows, closes) | Acumulación / Distribución de Williams |
wvad(opens, highs, lows, closes, volumes) | Acumulación / Distribución variable de Williams |
Funciones del libro de órdenes
orderbook es source.orderbook() o source.depths()[i].data: {asks: {price: quantity}, bids: {price: quantity}}.
| Método | Descripción |
|---|---|
orderbookSide(orderbook, side) | Un lado ('bids' / 'asks') como [[price, quantity], ...], ordenado empezando por el mejor precio |
bestBid(orderbook) / bestAsk(orderbook) | Mejor precio bid / ask (null para un libro vacío) |
sumBids(orderbook, percent, price, inQuote) | Cantidad bid acumulada entre price (por defecto: el mejor bid) y price * (1 - percent / 100), inQuote = true suma price * quantity |
sumAsks(orderbook, percent, price, inQuote) | Cantidad ask acumulada entre price (por defecto: el mejor ask) y price * (1 + percent / 100) |
Ejemplo:
const candles = source.ohlcv();const closes = candles.map(c => c.close);const rsiValues = ta.rsi(closes, 14);plot.lineSeries("RSI", rsiValues, "#00C8FF");
Alertas
Los usuarios pueden crear alertas para tu indicador desde la pestaña Alertas en los ajustes del indicador en el gráfico. Las alertas se comprueban regularmente en el servidor; las notificaciones se envían a Telegram y/o a una URL de webhook definida por el usuario.
Ajustes de alertas — alertInput
La clase alertInput declara los ajustes de las alertas. Funciona exactamente igual que input, pero los campos se muestran en la pestaña Alertas y cada usuario configura sus propios valores para su alerta.
Los scripts sin declaraciones alertInput no se pueden usar para alertas.
| Método | Descripción |
|---|---|
number(title, defaultValue, min, max, step) | Entrada numérica |
range(title, defaultValue, min, max, step) | Entrada de rango |
select(title, defaultValue, values) | Selección desplegable |
color(title, defaultValue) | Selector de color |
boolean(title, defaultValue) | Entrada booleana |
Ejemplo:
const alertVolumeThreshold = alertInput.number("Volume threshold", 1000, 0, 10000000, 1);
Disparar alertas — alert.fire
| Método | Descripción |
|---|---|
fire(title, message, date) | Dispara una notificación de alerta |
- Los tres parámetros son obligatorios: una llamada a la que le falte
title,messageodatese omite (se escribe una advertencia en el registro). - Mantén el título de la alerta constante, no generes títulos únicos. Cada título se dispara solo una vez por ejecución (máx. 20 títulos por ejecución).
datees elstartTimede la vela a la que pertenece la señal: el mismo título vuelve a notificar solo cuando se dispara para una vela más reciente. Las señales de velas anteriores al momento en que el usuario creó su alerta se ignoran (antispam).
Ejemplo:
if (candle.volume > alertVolumeThreshold) {alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);}
Alertas de formas — plot.<shape>(...).alert(...)
Todas las formas geométricas (box, square, diamond, triangle, circle, straightLine, horizontalLine, verticalLine, text) devuelven un manejador con un método .alert().
La alerta se dispara automáticamente con el date1 de la forma, de modo que la lógica antispam funciona sin código adicional: es la forma recomendada para señales por vela.
Dos formas:
// 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);});
El callback recibe los datos de la forma (date1, price1, date2, price2, ...).
Alertas de series — plot.<series>(...).alert(...)
Las series de datos (lineSeries, barSeries, candleSeries, areaSeries, bandSeries) también devuelven el manejador .alert().
El callback recibe el último punto de datos y la fecha de la última vela; la alerta se dispara con esa fecha.
lineSeries/barSeries/areaSeries: el callback recibe el último valor (un número)candleSeries: el callback recibe la última vela{open, close, high, low}bandSeries: el callback recibe el último{upper, lower}
plot.lineSeries("RSI", rsiValues, color).alert((rsi, date) => {if (rsi > alertRsiThreshold) alert.fire("RSI high", `RSI ${rsi.toFixed(1)}`, latestCandle.startTime);});
Una condición que sigue siendo verdadera notifica una vez por vela nueva. Para señales únicas usa condiciones de tipo cruce (ta.crossover).
Ejemplo completo
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);});}}
Ejemplos de indicadores
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 (bandas de desequilibrio de profundidad bid / ask alrededor del precio, como las superposiciones "Order Book Pressure" de los terminales 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);}});