MobChartDocs
Abrir MobChart

API de indicadores personalizados

Escribe tus propios indicadores en JavaScript, ejecútalos en un sandbox seguro junto al gráfico y compártelos con otros usuarios. La API te da datos de mercado (velas, libros de órdenes, clústeres, liquidaciones, datos de derivados), primitivas de dibujo, funciones de análisis técnico y alertas.

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ámetroTipoDescripción
titlestringNombre del indicador
descriptionstringDescripción breve
type'onchart' | 'offchart'Ubicación del indicador (en el gráfico o en un panel aparte)
isPublictrue | falseSi el indicador es público
isPublicCodetrue | falseSi el código fuente es público
optionsobject (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étodoDescripció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ámetroDescripción
exchange, ticker, timeframePor defecto, los del gráfico en el que está abierto el indicador. Pasa otros valores para obtener datos de otro mercado.
lookbackNú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):

AjustePor defectoDescripción
tickerstodos los tickerssolo estos tickers del exchange
exclude[]tickers que se omiten
type"all""all", "bids" o "asks"
nearness5distancia máxima al precio actual, en porcentaje
size100000tamaño mínimo de la orden en la moneda de cotización
lifespan0minutos mínimos que la orden lleva en el libro
touches, currentTouches0toques mínimos del nivel / de la orden actual
filled0parte ejecutada mínima de la orden, en porcentaje
strength, diversity0fuerza (en porcentaje) y diversidad mínimas, como en el screener
continuousOnlyfalseomite las órdenes que desaparecieron y volvieron
volume1h0volumen mínimo de 1h del ticker en la moneda de cotización
spotAndFuturesfalsesolo tickers que existen tanto en el mercado spot como en el de futuros
limit500nú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 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);
}
}

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étodoDescripció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–299
status: 200, // HTTP status, 0 when the request was not sent
headers: { "content-type": "application/json", ... },
data: { ... }, // response body parsed as JSON, null when the body is not JSON
text: "...", // raw response body
error: 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

VariableDescripciónEjemplo
currentExchangeExchange actual del gráficobinance, binanceFutures, coinbase, ...
currentTickerTicker actual del gráfico (el formato depende del exchange)BTCUSDT, ADA-USD, BTC/USDC, ...
currentTimeframeTemporalidad actual del gráfico1m, 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étodoDescripció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étodoDescripció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ámetroTipo de datoDescripción
titlestringNombre del elemento (identificador único en el gráfico)
date1number (timestamp, ms)Marca de tiempo inicial
price1numberPrecio inicial (coordenada del eje Y)
date2number (timestamp, ms)Marca de tiempo final (para líneas y rectángulos)
price2numberPrecio final (para líneas y rectángulos)
sizenumberTamaño de la forma (en píxeles)
radiusnumberRadio del círculo (10 por defecto)
fillstring (hex, p. ej., "#FF0000")Color de relleno de la forma
opacitynumber (0–1)Opacidad del relleno
strokestring (hex, p. ej., "#000000")Color del trazo
strokeOpacitynumber (0–1)Opacidad del trazo
strokeWidthnumberGrosor del trazo (en píxeles)
strokeDasharrayarray (p. ej., [5,5])Estilo de línea discontinua
fontSizenumberTamaño del texto (en píxeles)
valuestringValor 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étodoDescripció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ámetroTipo de datoDescripción
values, upperValues, lowerValues(number \| null)[]Un valor por vela, null oculta la vela
fill, strokestring (hex) o functionColor, o una función que devuelve el color de la vela: (value, i) para lineSeries, barSeries, candleSeries y areaSeries, (upper, lower, i) para bandSeries
opacitynumber (0–1) o functionOpacidad del relleno, una función (value, i) / (upper, lower, i) para areaSeries / bandSeries
basenumber | nullNivel base de areaSeries, null = la parte inferior del gráfico
stepbooleanEstilo 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 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
);

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ónPor defectoDescripción
steptamaño de tick del gráficoaltura 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"
width30fila 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
opacity0.6opacidad de las barras
deltafalsedibuja buy - sell en lugar de la suma, solo pares
poctruelínea en la fila con el valor más grande
valueArea70porcentaje del total alrededor del POC dibujado con opacidad completa, false para desactivarlo
valueAreaOpacity0.3opacidad de las filas fuera del área de valor
labelstruetotales 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 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" });

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 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 });

Ejemplo, tus propios niveles:

// 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 });

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ónPor defectoDescripción
position"top-right"esquina del panel: "top-right", "top-left", "bottom-right", "bottom-left"
headerfalseprimera fila en negrita sobre una franja sombreada
fontSize11tamaño del texto en px, de 8 a 16
color, bg, bordersegún el temacolores por defecto del texto, del panel y de la cuadrícula; una celda puede sobrescribir color y bg
opacity0.85opacidad 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étodoDescripció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étodoDescripció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étodoDescripció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étodoDescripció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étodoDescripció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étodoDescripció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étodoDescripció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étodoDescripció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étodoDescripció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, message o date se 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).
  • date es el startTime de 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 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);
});

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 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);
});
}
}

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 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 (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 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);
}
});