MobChartDocs
打开 MobChart

自定义指标 API

用 JavaScript 编写自己的指标,在图表旁的安全沙箱中运行,并与其他用户分享。API 提供市场数据(K线、订单簿、集群、强平、衍生品数据)、绘图原语、技术分析函数和提醒。

使用 AI 生成指标

完整的 API 也以单个文本文件的形式提供给语言模型使用:AI-scripting.txt。将其附加到 ChatGPT、Claude、Gemini 或其他任意 LLM,并描述你想要的指标。

示例提示词:

Read the attached [AI-scripting.txt] https://mobchart.com/docs/public/AI-scripting.txt file and use it as the only reference for the MobChart scripting API.
Create a custom indicator that plots a 20-period EMA and a 50-period EMA on the chart. Color the candles green when EMA20 is above EMA50 and red otherwise.
Use only the documented API. Do not invent functions. Return the complete indicator code.

沙箱与限制

指标开发

指标在一个单独的组件中开发,该组件可以放在启用该指标的图表旁边。

创建指标

define(title, description, type, isPublic, isPublicCode, options)

在系统中注册指标。

参数类型说明
titlestring指标名称
descriptionstring简短描述
type'onchart' | 'offchart'指标位置(图表上或单独面板中)
isPublictrue | false指标是否公开
isPublicCodetrue | false源代码是否公开
optionsobject(可选){ fullRange, warmup },见下文

示例:

define("My Indicator", "Shows EMA line", "onchart", true, false);

计算范围。 默认情况下,脚本只计算图表中需要计算的部分:向左滚动时加载的K线,或刷新时的最新K线,再加上它们之前 warmup 根历史K线(默认 500),这些K线参与计算但不会返回。当指标需要更多或更少的历史数据时,请声明 warmup,例如 EMA(200) 使用 { warmup: 200 }。如果脚本的数值或图形依赖于 整个可见范围(从第一根可见K线开始的 VWAP、累计 delta、窗口最高点处的标签、横跨整个图表的水平位),则必须声明 { fullRange: true },否则在加载更多历史后结果会出错。每次保存脚本时都会从 define() 读取该模式:未声明 fullRange 保存的脚本按部分方式计算,因此编辑现有的累计类脚本时请加上该标志。部分计算的脚本也会分段加载:在较长的历史上启用时,图表会先请求最新的 500 根K线(若 warmup 更大则请求 warmup 根),较早的部分随后逐个请求,因此可见部分会立即显示。带 fullRange 的脚本始终在整个范围上通过单次请求计算。

define("Window max", "Label at the highest visible high", "onchart", true, true, { fullRange: true });

onchart 和 offchart 的指标位置:

输入参数

input 和 style 类用于创建用户可配置的设置(类似 PineScript 中的 input())。

方法说明
number(title, defaultValue, min, max, step)数值输入
range(title, defaultValue, min, max, step)范围输入
select(title, defaultValue, values)下拉选择
color(title, defaultValue)颜色选择器
boolean(title, defaultValue)布尔输入

主要参数示例(input):

const length = input.number("Period", 14, 1, 100, 1);
const type = input.select("Type", 'sma', ['sma', 'ema']);
const color = input.color("Line Color", "#FF0000");

或视觉参数(style):

const length = style.number("Size", 14, 1, 100, 1);
const color = style.select("Background Color", '#FF0000', ["#FF0000", "#AA0000", "#BB0000"]);
const color = style.color("Background Color", "#FF0000");

数据源

source 类从服务器获取市场数据。基于K线的方法使用相同的参数;两个实时快照方法不同:orderbook() 接收交易所和交易对,levels() 接收交易所和设置对象。

参数说明
exchange, ticker, timeframe默认为打开该指标的图表。传入其他值可获取其他市场的数据。
lookback从当前日期起的历史K线数量,不超过 5000。

ohlcv(exchange, ticker, timeframe, lookback?)

K线数据。

[
{
"open": 115886.17,
"high": 115911.96,
"low": 115764.27,
"close": 115815.16,
"volume": 228.41528,
"startTime": 1757699100000
},
{...},
...
]

depths(exchange, ticker, timeframe, lookback?)

历史订单簿快照,每根K线一个。只保存最大的限价单(即热力图背后的数据)。

[
{
"data": {
"asks": {
"109425": 5.467,
"109495": 3.564,
...
},
"bids": {
"33000": 3.746,
"35000": 16.49,
...
}
},
"startTime": 1757699100000
},
{...},
...
]

clusters(exchange, ticker, timeframe, lookback?)

集群(足迹图)数据:K线每个价格档位上的买入和卖出成交量。

[
{
"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?)

K线每个价格档位上的强平成交量(仅限合约市场)。格式与 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?)

每根K线的衍生品数据:资金费率、未平仓合约K线、多空比(仅限合约市场)。

[
{
"data": {
"FR": 0.00003777, // 资金费率
"OI": { // 未平仓合约K线
"open": 85645.708,
"close": 83496.225,
"high": 85801.04,
"low": 83397.918
},
"LS": { "l": 0.5426, "s": 0.4574 }, // 多空比
"taLS": { "l": 0.552, "s": 0.448 }, // 大户账户多空比
"tpLS": { "l": 0.5996, "s": 0.4004 } // 大户持仓多空比
},
"startTime": 1757699100000
},
{...},
...
]

orderbook(exchange, ticker)

实时订单簿快照:当前的完整订单簿,不与K线绑定。

{
"asks": {
"109425": 5.467,
"109495": 3.564,
...
},
"bids": {
"109400": 1.2,
"109390": 0.8,
...
}
}

价格档位未排序,请使用 ta 的订单簿辅助函数(ta.bestBid、ta.sumBids 等)处理订单簿。 K线的历史订单簿来自 depths():depths()[i].data 格式相同,但其中只保存最大的限价单。

示例:

const candles = source.ohlcv(); // exchange、ticker 和 timeframe 参数默认为当前图表。
const closes = candles.map(c => c.close);

levels(exchange, settings?)

流动性筛选器 的价位:某交易所挂着的大额限价单,使用与筛选器页面相同的设置进行过滤。实时数据,不与K线绑定。没有订阅计划时,距离限制为 1%,存续时间限制为 60 分钟,结果限制为 200 个价位;有订阅计划时最多 2000 个价位。最大的订单排在最前。计为一次数据源请求。

{
"BTCUSDT": {
"asks": {
"109500": {
"price": 109500,
"quantity": 42.5, // 以基础资产计的数量
"quantityUSDT": 4653750, // 以计价货币计的数量
"distance": 0.62, // 与当前价格的距离,百分比
"duration": 184, // 订单已挂单的分钟数
"touches": 2,
"currentTouches": 1,
"filledPercent": 12.4,
"strength": 95.5, // 百分比,参见筛选器
"diversity": 11,
"isContinuous": true,
"date": "2026-10-10T06:12:03.000Z"
},
...
},
"bids": { ... },
"volume1hTotal": 183000000
},
...
}

设置(均为可选):

设置默认值说明
tickers所有交易对仅限该交易所的这些交易对
exclude[]要跳过的交易对
type"all""all"、"bids" 或 "asks"
nearness5与当前价格的最大距离,百分比
size100000以计价货币计的最小订单规模
lifespan0订单已挂单的最少分钟数
touches, currentTouches0价位 / 当前订单的最少触及次数
filled0订单已成交部分的最小值,百分比
strength, diversity0最小强度(百分比)和多样性,与筛选器相同
continuousOnlyfalse跳过消失后又重新出现的订单
volume1h0交易对 1 小时成交量的最小值,以计价货币计
spotAndFuturesfalse仅限同时存在于现货和合约市场的交易对
limit500价位的最大数量,最大的在前(最多 2000)

示例:

// 将图表市场价格附近的大单绘制为水平线,并标注数量和存续时间
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);
}
}

HTTP 请求

http 类向外部 HTTP API 发送请求并返回响应,使指标可以使用 source 中没有的数据:你自己的服务器、公共 REST API、链上指标提供商等。 与 source.* 一样,这些调用以同步函数的形式编写,无需 await。

方法说明
get(url, options?)GET 请求
post(url, body?, options?)POST 请求。对象或数组形式的 body 以 JSON 发送(Content-Type: application/json),字符串按原样发送

options 为 { headers: { name: value } } — 最多 10 个自定义请求头(例如 Authorization 或 x-api-key)。

两个方法返回相同的响应对象,且 从不抛出异常:

{
ok: true, // 状态码 200–299
status: 200, // HTTP 状态码,请求未发送时为 0
headers: { "content-type": "application/json", ... },
data: { ... }, // 解析为 JSON 的响应体,响应体不是 JSON 时为 null
text: "...", // 原始响应体
error: null // 请求失败的原因:"timeout"、"url not allowed"、"too many requests" 等
}

可能的 error 值:invalid url、url not allowed、invalid headers、invalid body、body too large、response too large、timeout、too many requests、rate limit、busy、proxy unavailable、network error。

示例(GET):

define("Fear & Greed", "Crypto Fear & Greed index", "offchart", true, true);
const res = http.get("https://api.alternative.me/fng/?limit=30");
if (!res.ok) logger.log("request failed:", res.status, res.error);
// res.data = { data: [{ value: "71", timestamp: "1757635200" }, ...] }
const byDay = {};
(res.data?.data || []).forEach((row) => { byDay[Number(row.timestamp) * 1000] = Number(row.value); });
const candles = source.ohlcv();
const values = candles.map((c) => c ? byDay[c.startTime - (c.startTime % 86400000)] ?? null : null);
plot.lineSeries("Fear & Greed", values, "#00C8FF");

示例(POST):

const candles = source.ohlcv();
const last = candles.at(-1);
const res = http.post(
"https://example.com/api/signals",
{ exchange: currentExchange, ticker: currentTicker, close: last.close },
{ headers: { "x-api-key": "YOUR_KEY" } } // 仅限私有代码
);
logger.log("signal sent:", res.ok, res.status, res.data);

全局变量

变量说明示例
currentExchange图表当前的交易所binance, binanceFutures, coinbase, ...
currentTicker图表当前的交易对(格式取决于交易所)BTCUSDT, ADA-USD, BTC/USDC, ...
currentTimeframe图表当前的周期1m, 5m, 15m, ...

示例:

const candles = source.ohlcv(currentExchange, currentTicker, currentTimeframe);
const closes = candles.map(c => c.close);

日志

logger 类用于输出信息和调试。

方法说明
log(...args)向日志添加一条记录(有长度和数量限制)

示例:

const candles = source.ohlcv();
logger.log("First candle:", candles?.[0]);

可视化

plot 类负责绘制图形元素和数据序列。

几何图形

方法说明
box(title, date1, price1, date2, price2, fill, opacity, stroke, strokeOpacity, strokeWidth)矩形
square(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth)正方形
diamond(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth)菱形
triangle(title, date1, price1, size, fill, opacity, stroke, strokeOpacity, strokeWidth)三角形
circle(title, date1, price1, radius = 10, fill, opacity, stroke, strokeOpacity, strokeWidth)圆形
text(title, value, date1, price1, fontSize, fill, opacity)图表上的文字
table(title, rows, options)固定在图表角落的表格,参见 表格
straightLine(title, date1, price1, date2, price2, stroke, strokeOpacity, strokeWidth, strokeDasharray)直线
horizontalLine(title, price1, stroke, strokeOpacity, strokeWidth, strokeDasharray)横贯整个图表的水平线
verticalLine(title, date1, stroke, strokeOpacity, strokeWidth, strokeDasharray)纵贯整个图表的垂直线
参数数据类型说明
titlestring元素名称(图表上的唯一标识)
date1number(时间戳,毫秒)起始时间戳
price1number起始价格(Y 轴坐标)
date2number(时间戳,毫秒)结束时间戳(用于线和矩形)
price2number结束价格(用于线和矩形)
sizenumber图形尺寸(像素)
radiusnumber圆的半径(默认 10)
fillstring(hex,例如 "#FF0000")图形填充颜色
opacitynumber(0–1)填充不透明度
strokestring(hex,例如 "#000000")描边颜色
strokeOpacitynumber(0–1)描边不透明度
strokeWidthnumber描边宽度(像素)
strokeDasharrayarray(例如 [5,5])虚线样式
fontSizenumber文字大小(像素)
valuestring文字内容(用于 text 方法)

示例:

plot.text("Text object", "Hello world!", 1757699100000, 115000, 10, "#00C8FF", 0.5);
plot.straightLine(`${namePrefix} Open`, level.timeStart, level.open, level.timeEnd, level.open, color);
plot.circle(`${namePrefix} High`, level.timeStart, level.high, 3, color);

图表上最多可显示 3000 个图形。

数据序列

方法说明
lineSeries(title, values, stroke, strokeOpacity, strokeWidth, strokeDasharray)折线图
barSeries(title, values, fill, opacity)柱状图
candleSeries(title, openValues, closeValues, highValues, lowValues, fill, opacity)K线
areaSeries(title, values, fill, opacity, stroke, strokeOpacity, strokeWidth, base)线与 base(默认:图表底部)之间的填充区域
bandSeries(title, upperValues, lowerValues, fill, opacity, stroke, strokeOpacity, strokeWidth, step)两条序列之间的填充带,每根K线保留各自的颜色和不透明度
profile(title, values, options)可见K线的成交量分布式直方图,参见 分布图
histogram(title, rows, options)以分布图形式绘制的现成价格映射,参见 直方图
参数数据类型说明
values, upperValues, lowerValues(number \| null)[]每根K线一个值,null 隐藏该K线
fill, strokestring(hex)或 function颜色,或返回该K线颜色的函数:lineSeries、barSeries、candleSeries 和 areaSeries 为 (value, i),bandSeries 为 (upper, lower, i)
opacitynumber(0–1)或 function填充不透明度,areaSeries / bandSeries 可用函数 (value, i) / (upper, lower, i)
basenumber | nullareaSeries 的基准线,null = 图表底部
stepbooleanbandSeries 样式:true = 每根K线一列(默认),false = 连续多边形

示例:

plot.lineSeries("EMA", ta.ema(closes, 20), "#00C8FF");
// 每根K线保留各自的颜色:收盘价高于 EMA 为绿色,低于为红色
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);
// 按K线着色的区域,K线越大越不透明
plot.areaSeries("Delta", deltas, (v, i) => v >= 0 ? colorBuy : colorSell, (v, i) => Math.min(1, Math.abs(v) / maxDelta));
// 收盘价与其下方 1% 之间的压力带,按该K线的失衡比率着色和设置透明度
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
);

图表上最多可显示 20 条数据序列。

分布图

plot.profile(title, values, options?) 按价格绘制直方图,类似内置的成交量分布。values[i] 是 ohlcv() 第 i 根K线的价格映射:{ price: value } 或 { price: { buy, sell } }(可以直接传入 clusters() 或 liquidations() 记录的 data),没有数据行的K线为 null。图表上可见的K线 的数据行由图表本身按价格步长汇总,因此分布图会跟随平移、缩放和加载更多,无需重新运行。单个值用一种颜色绘制(负值使用 fill2),买卖对会将柱子分为买入和卖出两部分,并支持 delta 模式。profile() 不返回提醒句柄。

选项默认值说明
step图表最小价格变动行高(价格单位),价格向下取整到该值
orient"right"柱子生长的起始边,"right" 或 "left"
width30最宽一行占图表宽度的百分比
fill"#26a69a"数值或买入部分的颜色
fill2"#ef5350"卖出部分的颜色
opacity0.6柱子不透明度
deltafalse绘制 buy - sell 而非总和,仅限买卖对
poctrue在数值最大的一行处画线
valueArea70POC 周围以完全不透明绘制的总量百分比,false 表示禁用
valueAreaOpacity0.3价值区域之外各行的不透明度
labelstrue空间足够时在柱子上显示行合计

限制:每次运行 3 个分布图,每根K线 300 行,每个分布图 200000 行。超出限制的行会被丢弃,运行日志中会记录一条说明。

示例:

// 图表的成交量分布,与内置工具效果相同
const rows = await source.clusters();
plot.profile("Volume", rows.map((r) => r?.data ?? null), { step: 5 });
// 仅大额成交,按 delta 着色,位于左侧边缘
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" });

直方图

plot.histogram(title, rows, options?) 绘制你已经构建好的直方图:整个图表只有一个价格映射,不会从K线中汇总任何数据。当数据行属于各根K线且图形应跟随可见范围时使用 profile();当数据行是最终结果时使用 histogram():订单簿快照、脚本计算出的价位、其他市场的数据。

rows 的格式与 profile() 的单根K线条目相同:{ price: value }、{ price: { buy, sell } } 或 [price, buy, sell] 行的列表。单个值用一种颜色绘制(负值使用 fill2),买卖对会将柱子分为买入和卖出两部分,并支持 delta 模式。选项与 profile() 相同,参见上表。

直方图以本次运行的最后一根K线作为日期,图表保留每个标题 最新 的直方图,因此针对较早K线的部分运行(加载历史时)永远不会替换当前的直方图。每次脚本刷新时都会重绘,因此订单簿直方图会跟随实时订单簿。histogram() 不返回提醒句柄。

限制:每次运行 3 个直方图,5000 行。超出限制的行会被丢弃,运行日志中会记录一条说明。

示例,订单簿:

// 将实时订单簿作为右侧边缘的分布图:买盘用买入颜色,卖盘用卖出颜色
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 });

示例,自定义价位:

// 自定义直方图:最近 100 根K线的集群成交量,越近的K线权重越大
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 });

表格

plot.table(title, rows, options?) 绘制一个固定在图表面板角落的小网格:多周期汇总、已加载范围的统计、信号面板。rows 是行数组,每行是单元格数组,单元格可以是字符串、数字或 { text, color, bg, align, bold }。同一角落的表格按顺序堆叠。表格以本次运行的最后一根K线作为日期,图表保留每个标题 最新 的表格,因此针对较早K线的部分运行(加载历史时)永远不会替换当前的表格。宽度小于 500 px 的图表上不绘制表格。表格可以用鼠标拖动:位置随工作区保存,当脚本更改其 position 选项时重置。table() 不返回提醒句柄。

选项默认值说明
position"top-right"面板角落:"top-right"、"top-left"、"bottom-right"、"bottom-left"
headerfalse第一行加粗并绘制在阴影条上
fontSize11文字大小(px),8 到 16
color, bg, border随主题默认的文字、面板和网格颜色;单元格可以覆盖 color 和 bg
opacity0.85面板不透明度
align"left"默认单元格对齐方式,"left"、"center" 或 "right"

限制:每次运行 3 个表格,50 行,10 列,每个单元格 100 个字符。更长的文本会被截断并显示省略号,一列最多占图表宽度的 30%。

示例:

const candles = await source.ohlcv();
const closes = candles.map((c) => c.close);
const rsi = ta.rsi(closes, 14);
const last = rsi.at(-1);
const change = (closes.at(-1) / closes.at(-25) - 1) * 100;
plot.table("Stats", [
["Metric", "Value"],
["RSI 14", { text: last.toFixed(1), color: last > 70 ? "#ef5350" : last < 30 ? "#26a69a" : undefined }],
["Change 24 bars", { text: change.toFixed(2) + "%", align: "right" }],
["Volume", candles.slice(-24).reduce((s, c) => s + c.volume, 0).toFixed(0)],
], { position: "top-right", header: true });

技术分析

ta 类实现了 PineScript v5 的 ta.* 函数。Pine 逐根K线计算,而这里的每个函数都接收完整的序列(数组,每根K线一个值,从旧到新),并返回相同长度的序列,可直接绘制。在周期尚未填满时值为 null,null 输入会像 Pine 的 na 一样传播。隐式读取 high、low、close 或 volume 的 Pine 函数在这里将这些数组作为参数传入。

返回多条序列的函数(bb、kc、macd、stoch、dmi、supertrend)返回一个同时带有命名属性的数组:const [middle, upper, lower] = ta.bb(closes, 20, 2) 或 ta.bb(closes, 20, 2).upper。

辅助函数

方法说明
highest(values, length) / lowest(values, length)length 根K线内的最高 / 最低值
highestbars(values, length) / lowestbars(values, length)到最高 / 最低K线的偏移量(0 或负数)
max(values) / min(values)截至每根K线的历史最高 / 最低值
range(values, length)highest - lowest
change(values, length = 1)values[i] - values[i - length]
mom(values, length)动量
roc(values, length)变化率,%
cum(values)累计和
rising(values, length) / falling(values, length)值高于 / 低于之前 length 个值中的每一个
variance(values, length, biased = true)方差
stdev(values, length, biased = true)标准差
dev(values, length)平均绝对偏差
median(values, length)中位数
mode(values, length)众数(出现次数相同时取最小值)
percentile_linear_interpolation(values, length, percentage)百分位数,在两个最近排名之间线性插值
percentile_nearest_rank(values, length, percentage)百分位数,最近排名法
percentrank(values, length)之前 length 个值中小于或等于当前值的百分比
correlation(series1, series2, length)相关系数
crossover(series1, series2)向上交叉(任一参数都可以是数字:ta.crossover(rsi, 70))
crossunder(series1, series2)向下交叉
cross(series1, series2)任意交叉
barssince(condition)距 condition(布尔数组)上次为真的K线数
valuewhen(condition, values, occurrence = 0)condition 在第 occurrence 次之前为真时 values 的值
pivothigh(values, leftbars, rightbars) / pivotlow(...)确认K线上的枢轴值,枢轴本身位于 rightbars 根K线之前

移动平均线

方法说明
sma(values, length)简单移动平均线
ema(values, length)指数移动平均线
rma(values, length)Wilder 移动平均线(RSI / ATR 使用)
wma(values, length)加权移动平均线
hma(values, length)Hull 移动平均线
alma(values, length, offset = 0.85, sigma = 6, floor = false)Arnaud Legoux 移动平均线
swma(values)4 根K线的对称加权移动平均线
vwma(values, volumes, length)成交量加权移动平均线
linreg(values, length, offset = 0)线性回归曲线

振荡指标

方法说明
rsi(values, length)相对强弱指数
cmo(values, length)钱德动量摆动指标
cog(values, length)重心指标
mfi(values, volumes, length)资金流量指数(values 通常为 hlc3)
tsi(values, shortLength, longLength)真实强度指数,-1..1
wpr(highs, lows, closes, length)威廉指标 %R
stoch(highs, lows, closes, period = 14, smoothK = 3, smoothD = 3)随机指标 [k, d],smoothK = 1 得到原始值
cci(values, length) / cci(highs, lows, closes, length)顺势指标(第二种形式基于典型价格)
macd(values, fast = 12, slow = 26, signalPeriod = 9)MACD [macd, signal, histogram]

通道与带状指标

方法说明
bb(values, length = 20, mult = 2) / bollinger(...)布林带 [middle, upper, lower]
bbw(values, length, mult)布林带宽度 (upper - lower) / middle
kc(values, highs, lows, closes, length = 20, mult = 2, useTrueRange = true)肯特纳通道 [middle, upper, lower]
kcw(values, highs, lows, closes, length, mult, useTrueRange)肯特纳通道宽度 (upper - lower) / middle

趋势与波动率

方法说明
tr(highs, lows, closes, handleNa = false)真实波幅(handleNa = true 时第一根K线为 high - low)
atr(highs, lows, closes, period = 14)平均真实波幅
dmi(highs, lows, closes, diLength = 14, adxSmoothing = 14)趋向指标 [plus, minus, adx]
adx(highs, lows, closes, period = 14)平均趋向指数
sar(highs, lows, closes, start = 0.02, inc = 0.02, max = 0.2)抛物线 SAR
supertrend(highs, lows, closes, factor = 3, atrPeriod = 10)超级趋势 [supertrend, direction],direction -1 = 上升趋势,1 = 下降趋势

成交量

方法说明
vwap(values, volumes, anchor?) / vwap(highs, lows, closes, volumes, anchor?)累计 VWAP(第二种形式基于典型价格),在 anchor(布尔数组)为真的K线上重新开始
obv(closes, volumes)能量潮
accdist(highs, lows, closes, volumes)累积 / 派发
iii(highs, lows, closes, volumes)日内强度指数
pvt(closes, volumes)量价趋势
nvi(closes, volumes) / pvi(closes, volumes)负 / 正成交量指数
wad(highs, lows, closes)威廉累积 / 派发
wvad(opens, highs, lows, closes, volumes)威廉变异累积 / 派发

订单簿辅助函数

orderbook 为 source.orderbook() 或 source.depths()[i].data:{asks: {price: quantity}, bids: {price: quantity}}。

方法说明
orderbookSide(orderbook, side)一侧('bids' / 'asks')作为 [[price, quantity], ...],按最优价格优先排序
bestBid(orderbook) / bestAsk(orderbook)最优买价 / 卖价(空订单簿为 null)
sumBids(orderbook, percent, price, inQuote)price(默认:最优买价)与 price * (1 - percent / 100) 之间的累计买盘数量,inQuote = true 时累加 price * quantity
sumAsks(orderbook, percent, price, inQuote)price(默认:最优卖价)与 price * (1 + percent / 100) 之间的累计卖盘数量

示例:

const candles = source.ohlcv();
const closes = candles.map(c => c.close);
const rsiValues = ta.rsi(closes, 14);
plot.lineSeries("RSI", rsiValues, "#00C8FF");

提醒

用户可以在图表上指标设置的 提醒 选项卡中为你的指标创建提醒。 提醒会在服务器端定期检查,通知发送到 Telegram 和/或用户定义的 Webhook URL。

提醒设置 — alertInput

alertInput 类声明提醒设置。它的用法与 input 完全相同,但字段显示在 提醒 选项卡中,每个用户都为自己的提醒配置各自的值。

没有 alertInput 声明的脚本无法用于提醒。

方法说明
number(title, defaultValue, min, max, step)数值输入
range(title, defaultValue, min, max, step)范围输入
select(title, defaultValue, values)下拉选择
color(title, defaultValue)颜色选择器
boolean(title, defaultValue)布尔输入

示例:

const alertVolumeThreshold = alertInput.number("Volume threshold", 1000, 0, 10000000, 1);

触发提醒 — alert.fire

方法说明
fire(title, message, date)触发提醒通知
  • 三个参数都是 必需的 — 缺少 title、message 或 date 的调用会被跳过(并在日志中写入警告)。
  • 保持提醒 标题固定,不要生成唯一标题。每个标题每次运行只触发一次(每次运行最多 20 个标题)。
  • date 是信号所属K线的 startTime:同一标题只有在针对 更新的 K线触发时才会再次通知。早于用户创建提醒时刻的K线信号会被忽略(防刷屏)。

示例:

if (candle.volume > alertVolumeThreshold) {
alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);
}

图形提醒 — plot.<shape>(...).alert(...)

所有几何图形(box、square、diamond、triangle、circle、straightLine、horizontalLine、verticalLine、text)都返回一个带有 .alert() 方法的句柄。 提醒会自动使用图形的 date1 触发,因此防刷屏逻辑无需额外代码即可生效 — 这是针对逐根K线信号的推荐方式。

两种形式:

// 简单形式:每当图形针对新日期绘制时触发
plot.box(...).alert("High volume", `vol ${candle.volume}`);
// 回调形式:不返回任何内容则跳过,返回 [title, message] 则触发
plot.box(...).alert((shape) => {
if (candle.volume > alertVolumeThreshold)
alert.fire("High volume", `vol ${candle.volume}`, candle.startTime);
});

回调接收图形数据(date1、price1、date2、price2 等)。

序列提醒 — plot.<series>(...).alert(...)

数据序列(lineSeries、barSeries、candleSeries、areaSeries、bandSeries)同样返回 .alert() 句柄。 回调接收 最新的数据点 和最新K线的日期,提醒以该日期触发。

  • lineSeries / barSeries / areaSeries — 回调接收最后一个 值(数字)
  • candleSeries — 回调接收最后一根K线 {open, close, high, low}
  • bandSeries — 回调接收最后的 {upper, lower}
plot.lineSeries("RSI", rsiValues, color).alert((rsi, date) => {
if (rsi > alertRsiThreshold) alert.fire("RSI high", `RSI ${rsi.toFixed(1)}`, latestCandle.startTime);
});

持续为真的条件在每根 新K线 上通知一次。对于一次性信号,请使用交叉类条件(ta.crossover)。

完整示例

const title = "Volume alerts";
const description = "Shows boxes on high-volume candles";
define(title, description, "onchart", true, true);
const color = style.color("Color", "#00C8FF");
const volumeThreshold = input.number("Volume threshold", 1000, 0, 10000000, 1);
const alertVolumeThreshold = alertInput.number("Alert volume threshold", 5000, 0, 10000000, 1);
const alertCloseThreshold = alertInput.number("Alert close threshold", 100000, 0, 10000000, 1);
const candles = source.ohlcv();
const latestCandle = candles.at(-1);
const closes = candles.map(c => c.close);
// 序列提醒:检查最新收盘价
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;
// 图形提醒:与用户的提醒阈值进行比较
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);
});
}
}

指标示例

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);
// 获取 OHLC K线
const candles = source.ohlcv();
const colorUp = style.color("Color 1", "#2BB462"); // 上涨使用的绿色
const colorDown = style.color("Color 2", "#FB4C51"); // 下跌使用的红色
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");
// 获取 OHLC K线
const cbCandles = source.ohlcv('coinbase', 'BTC-USD');
const binanceCandles = source.ohlcv('binance', 'BTCUSDT');
// 提取收盘价
const cbCloses = cbCandles.map(c => c.close);
const binanceCloses = binanceCandles.map(c => c.close);
// --- Coinbase 溢价指数计算 ---
const premiumPercent = cbCloses.map((cb, i) => {
const b = binanceCloses[i];
return ((cb - b) / b) * 100;
});
// 绘制溢价指数
plot.barSeries("Premium %", premiumPercent, ((d) => d > 0 ? color1 : color2)) // 百分比差值
const title = "Candles";
const description = "Simple candles draw";
const indicatorType = "offchart";
const isPublic = true;
const isPublicCode = true;
define(title, description, indicatorType, isPublic, isPublicCode);
const colorBuy = style.color("Buy Color", "#2BB462");
const colorSell = style.color("Sell Color", "#7a3d3f");
const candles = source.ohlcv();
const closes = candles.map(c => c.close);
const opens = candles.map(c => c.open);
const highs = candles.map(c => c.high);
const lows = candles.map(c => c.low);
plot.candleSeries(
"Candles",
opens, closes, highs, lows,
(v, i) => {
return opens?.[i] > closes?.[i] ? colorBuy : colorSell
}
);

Order Book Pressure(价格周围的买卖盘深度失衡带,类似交易终端中的 "Order Book Pressure" 叠加层):

const title = "Order Book Pressure";
const description = "Bid / ask depth imbalance bands around the price";
define(title, description, "onchart", true, true);
const minBidRatio = input.number("Min bid ratio %", 20, 0, 100, 1);
const minAskRatio = input.number("Min ask ratio %", 20, 0, 100, 1);
const depthFrom = input.select("Depth from", "0%", ["0%", "1%", "2.5%", "5%"]);
const depthTo = input.select("Depth to", "5%", ["1%", "2.5%", "5%", "10%"]);
const bidColor = style.color("Bid fill color", "#a5d6a7");
const askColor = style.color("Ask fill color", "#f1627e");
const extremeColor = style.color("Extreme color", "#ffff00");
const highlight = style.boolean("Highlight extremes", true);
const showRef = style.boolean("Show reference lines", true);
const alertMinRatio = alertInput.number("Alert min |ratio| %", 40, 0, 100, 1);
const bounds = [1, 2.5, 5, 10]; // 距收盘价的深度区间,%
const from = parseFloat(depthFrom);
const to = parseFloat(depthTo);
const candles = source.ohlcv();
const depths = source.depths();
const liveBook = source.orderbook();
// 每根K线的订单簿:depths()[i] 是 candles[i] 的快照(最大的限价单),最新K线使用实时订单簿
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);
// 每个区间的失衡比率,%(正值 = 买盘占优,负值 = 卖盘占优)
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;
});
});
// 比率越强 -> 带越不透明,极端比率使用极端颜色
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; // 该区间不在所选深度范围内
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);
}
// 买盘压力:仅在买盘占优超过最小比率的K线上填充该带
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])
);
// 卖盘压力
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])
);
// 最新K线上的提醒
const latestRatio = ratios[b][lastIndex];
if (latestRatio != null && Math.abs(latestRatio) >= alertMinRatio) {
alert.fire(`Pressure ${lower}-${upper}%`, `ratio ${latestRatio.toFixed(1)}%`, candles[lastIndex].startTime);
}
});