如何获取K线编号?(bar_index 变量详解)
几乎每个TradingView脚本的计算都会引用之前的价格K线。但实际上,指标和策略也可以直接使用K线自身的编号。接下来就让我们一探究竟。
在TradingView Pine中获取K线编号:bar_index
尽管在TradingView图表上不可见,但每一根K线都有一个特定的编号。脚本可以利用这个编号来放置绘图对象,或者查看图表总共有多少根K线。那么,指标和策略要如何访问这个值呢?
TradingView的 bar_index 内置变量能够返回当前K线的编号。它返回的是一个整数值。
bar_index 的值是从零开始的 (zero-based)。这意味着,对于图表上的第1根K线,它返回 0;对第2根K线,返回 1,以此类推。这也意味着,图表上K线的总数量等于 bar_index + 1。
如果我们想知道之前某根K线的编号,可以将 bar_index 与历史引用运算符 ([]) 结合使用。例如,bar_index[10] 返回的是10根K线前的那个编号。或者,我们也可以简单地从当前K线的编号中做减法:bar_index - 10 会得到相同的值。
特别说明
bar_index变量在TradingView Pine中是相对较新的。如果您的脚本使用的是Pine第3版或更早的版本,您需要使用n变量来访问K线编号。
在TradingView Pine中使用K线编号的几种方式
指标和策略可以通过多种方式来利用 bar_index 变量:
- 我们经常在图表上放置绘图对象时使用
bar_index。例如,趋势线和标签等绘图对象,其x轴(时间)坐标默认使用的就是K线编号。 - 当某个特定条件满足时,如果我们将当时的
bar_index值储存在一个变量中,我们便可以在之后将当前的K线编号与储存的值进行比较,从而得知从那时起经过了多少根K线。 - 有时
bar_index被用来限制策略的回测范围。例如,我们可能只希望在bar_index大于5000时才生成新的交易。然而,这种方法并不理想,因为不是每个图表都有相同数量的K线,不同的时间周期其K线数量也不同。一个更好的方法是使用开始和结束日期来限定回测区间。 - 在图表的最后一根K线上,
bar_index + 1的值就代表了当前图表总共有多少根K线。(我们可以通过barstate.islast变量来判断脚本当前是否正在处理这最后一根K线。)
示例:使用 bar_index 计数K线并定位标签
让我们通过一个TradingView脚本实例,来看看如何使用 bar_index 变量。下方的指标利用这个变量来计算图表上的K线总数,并且用它来定位一个标签。
该指标的完整代码如下:
//@version=5
indicator("Example of bar_index variable", overlay=true)
// 使用var声明,确保标签只被创建一次
var label myLabel = label.new(bar_index, high + ta.tr,
textcolor=color.white, color=color.blue)
// 在图表的最后一根K线上,显示K线总数
if barstate.islast
// 设置标签的文本内容
label.set_text(myLabel, text="Bars onnthe chart:n" +
str.tostring(bar_index + 1))
// 更新标签的位置
label.set_x(myLabel, bar_index)
label.set_y(myLabel, high + ta.tr)
我们首先使用 indicator() 函数来配置指标的基本设置。
然后,我们创建一个标签:
// 使用var声明,确保标签只被创建一次
var label myLabel = label.new(bar_index, high + ta.tr,
textcolor=color.white, color=color.blue)
这里,我们使用TradingView的 label.new() 函数来创建一个标签。我们将其初始位置设定在当前K线的编号 (bar_index) 和当前K线最高价加上真实波幅 (high + ta.tr) 的位置。其文本颜色为白色 (color.white),背景色为蓝色 (color.blue)。
我们将创建的标签对象储存在 myLabel 变量中以备后用。因为我们使用了 var 关键字来声明,这段代码在整个图表的生命周期中只会执行一次,从而确保只创建一个标签。
接下来,我们更新这个标签:
// 在图表的最后一根K线上,显示K线总数
if barstate.islast
// 设置标签的文本内容
label.set_text(myLabel, text="Bars onnthe chart:n" +
str.tostring(bar_index + 1))
// 更新标签的位置
label.set_x(myLabel, bar_index)
label.set_y(myLabel, high + ta.tr)
这个 if 语句检查 barstate.islast 变量。该变量只有在脚本处理图表上最后一根K线时才为 true,在其他所有K线上均为 false。
在这最后一根K线上,我们首先设定标签的文本。我们调用 label.set_text() 函数,通过 myLabel 变量指定要修改的标签。然后,我们将标签的文本设为一个描述性字符串 ("Bars onnthe chart:n"),并拼接上图表的K线总数 (bar_index + 1)。由于后者是数值,我们使用 str.tostring() 函数将其转换为文本。
接着,我们将标签的位置移动到这最后一根K线上。label.set_x() 函数将其时间坐标更新为当前的K线编号 (bar_index),而 label.set_y() 函数则将其价格坐标更新到当前K线的最高价之上 (high + ta.tr)。
这就是该指标及其标签在图表上的最终效果:
从上图我们可以看到,这张欧洲斯托克50指数 (EuroStoxx 50 CFD) 的图表总共有6,885根K线。我们正是从 bar_index 变量获得了这个信息。同时,这个变量也帮助我们设定了标签的时间坐标。
内容总结
TradingView脚本通过 bar_index 变量来访问K线编号。该变量从图表的第一根K线(编号为 0)开始计数,随后每增加一根K线,其值便加一。在图表的最后一根K线上,bar_index + 1 的值就等于当前图表的总K线数量。
我们经常使用 bar_index 来在图表上定位绘图对象(例如趋势线和标签)。它也可以用来计算两个事件之间相隔了多少根K线。当然,它最直接的功能就是告诉我们当前图表加载了多少历史数据。
TradingView图表能加载多少根K线?
当我们打开一个图表时,TradingView会加载一定数量的K线。这个K线数量决定了图表所能覆盖的日期范围,也决定了我们能获得的历史数据量。更多的K线意味着更多的历史数据和更广的时间跨度。但具体能加载多少根呢?让我们一探究竟。
账户类型决定K线数量上限
图表上能够显示的K线数量,取决于您的TradingView账户等级:
- 免费版 (Free) 用户,图表可加载超过5,000根K线。
- Pro和Pro+ 用户,图表可加载超过10,000根K线。
- 而 Premium用户,图表可加载超过20,000根K线。
除了升级账户,我们无法自行增加K线的加载数量。一个特定图表究竟能显示多少根K线,是由TradingView来决定的。
值得注意的是,上述的K线数量限制并非精确值。不同交易品种之间,K线数量可能会有数千根的差异。此外,TradingView也在不断地增加数据量(有时新的上限需要一段时间后才会公布)。在撰写本文时,Premium用户的图表上,通常已经能看到超过30,000根K线。
除了TradingView的账户限制,还有另外两个因素会影响K线的数量。第一个是交易品种的年龄。一个品种的交易历史越长,其价格数据自然就越多。而对于去年才首次公开募股(IPO)的股票,其数据量自然就很少。
另一个重要因素是该品种的交易所或数据源。让我们来更深入地探讨这一点。
历史数据量取决于交易所
在TradingView上,不同交易品种的历史数据量各不相同。当我们讨论两种不同的交易品种时(例如BTCUSD和ETHUSD),这很好理解。但即使是同一个交易品种,这种情况也时有发生。
这是因为并非每个数据源都拥有同样多的历史数据。在TradingView中,对于同一个交易品种,一些交易所可能只提供很少的数据,而另一些则可能提供非常详尽的数据。
这种现象不仅发生在像加密货币这样相对较新的品种上,也存在于那些已有数十年历史的传统金融品种中。
以 EURUSD 为例。欧元于1999年1月1日正式开始交易。这应该意味着它有足够多的历史数据,对吗?答案是不一定。
如果我们从 CAPITALCOM 交易所加载EURUSD图表,TradingView只会显示回溯至2017年的K线:
但如果我们从 IDC 交易所加载同一个EURUSD品种,数据甚至可以追溯到1971年1月(这是基于对美元价格进行历史回溯调整得出的):
由此可见,对于同一个交易品种,不同的数据源所提供的历史数据量可能天差地别。这意味着:
当我们计划进行历史图表分析或策略回测时,一个明智的做法是,先尝试从不同的交易所加载同一个交易品种,比较并找出哪个数据源提供了最长的历史数据。否则,我们可能会在不知不觉中错失了好几年的宝贵数据。
在图表上显示K线数量
以上的讨论引出了一个重要问题:如果每个交易品种和数据源的K线数量都不同,我们如何才能知道当前图表究竟有多少根K线呢?
这个信息并未直接显示在TradingView的用户界面中,但我们可以编写一个自定义脚本来告诉我们答案。下面的这个Pine Script指标就能创建一个文本标签,用以显示当前图表上的K线总数:
//@version=5
indicator(title="Display number of bars on chart", overlay=true)
// 创建一个标签,用于显示图表的K线总数
var barsLabel = label.new(x=na, y=na, style=label.style_label_left,
color=color.teal, textcolor=color.white, size=size.large)
// 在图表的最后一根K线上,更新标签内容
if barstate.islast
labelText = "This " + syminfo.ticker + " chart hasn" +
str.tostring(bar_index + 1, "##,###") + " price bars"
label.set_text(id=barsLabel, text=labelText)
label.set_xy(id=barsLabel, x=bar_index + 1, y=close)
这段代码主要做了四件事:定义脚本设置、创建一个标签、生成标签文本,然后在最后一根K线上更新这个标签。让我们逐一解析。
首先,我们使用 indicator() 函数来配置指标,这行代码为脚本命名,并将其叠加显示在主图的交易品种上。
然后,我们使用 label.new() 函数来创建一个标签:
// 使用var声明,确保标签只被创建一次
var barsLabel = label.new(x=na, y=na, style=label.style_label_left,
color=color.teal, textcolor=color.white, size=size.large)
这个文本标签的箭头指向左边 (label.style_label_left)。它采用青色 (color.teal) 背景和白色 (color.white) 文字。size.large 会使标签及其字体比常规尺寸更大。
标签的初始坐标暂时设为 na(不可用),我们稍后会将其更新到图表上的一个具体位置。
我们将创建的标签对象储存在 barsLabel 变量中。该变量使用了 var 关键字声明,这意味着这个变量只会被初始化一次,从而确保我们的标签在整个图表的生命周期中也只被创建一次。
接下来,我们生成标签所需的文本内容:
if barstate.islast
labelText = "This " + syminfo.ticker + " chart hasn" +
str.tostring(bar_index + 1, "##,###") + " price bars"
这个 if 语句检查 barstate.islast 变量。该变量只有在脚本处理图表上的最后一根K线时才为 true。在这最后一根K线上,我们才能准确地知道图表总共有多少根K线。
在最后一根K线上,我们创建了 labelText 变量,并将其设为一个拼接起来的字符串。我们使用 + 运算符将几个文本片段连接起来。其中,我们通过 syminfo.ticker 变量来获取当前品种的交易代码(例如EURUSD或MSFT)。
我们还用到了 bar_index 变量,它能返回当前K线的编号。在最后一根K线上,其值加 1 (bar_index + 1) 就等于图表的总K线数(加1是因为 bar_index 从0开始计数)。
由于K线编号是数值类型,我们使用 str.tostring() 函数将其转换为文本。通过提供 "##,###" 这个格式化字符串,我们可以让转换后的数字带有千位分隔符(,),例如 22,238 或 1,823,这使得数字更易于阅读。
代码的最后一部分用于更新标签:
if barstate.islast
// ...
label.set_text(id=barsLabel, text=labelText)
label.set_xy(id=barsLabel, x=bar_index + 1, y=close)
这里我们做了两件事。首先,调用 label.set_text() 函数,将标签的文本更新为我们刚刚生成的字符串变量 labelText。
然后,我们调用 label.set_xy() 函数来更新标签的位置。新的位置被设定在当前K线右侧一根K线处 (bar_index + 1),其价格坐标则与当前收盘价 (close) 对齐。
示例图表
让我们看看这个指标在图表上的实际效果。在一张GBPUSD的日线图上,脚本告诉我们该图表有超过7,300根K线:
当图表的时间周期切换到30分钟时,这张GBPUSD图表(在Premium账户下)则拥有超过31,000根K线:
您可以亲手试试这个脚本,看看您最关注的那些交易品种有多少历史K线。
内容总结
- 一个图表拥有多少K线,取决于您的TradingView账户等级。免费用户可获得超过5,000根,Pro和Pro+ 用户超过10,000根,而Premium用户则能拥有超过20,000根K线。
- 并非每个交易所都提供同样多的历史数据。对于同一个交易品种,一些数据源的数据量可能远少于其他数据源。
- Pine Script可以通过
bar_index变量来获知图表上的K线数量。该变量返回的是当前K线的编号。
close 变量总是返回K线的收盘价吗?(并不是!)
TradingView提供了几个用于返回K线数据的内置变量。例如 close 用于获取收盘价,volume 用于获取成交量,low 用于获取最低价。但您是否知道,这些变量返回的,并不总是该K线最终的收盘价、成交量和最低价?接下来,就让我们一探究竟。
当TradingView的 close 变量不代表收盘价时
TradingView提供了若干变量来返回K线信息,例如 open(开盘价)、high(最高价)、low(最低价)、volume(成交量),以及 close(收盘价)。
这看起来显而易见且合乎逻辑。但这些K线变量有一个与直觉相悖的特性:它们返回的并不总是K线的最终值。因此,close 变量不一定返回K线的收盘价,同理,high、low 和 volume 也不总是返回K线最终的最高价、最低价和总成交量。这到底是为什么呢?
当一根K线尚未收盘时,每一个传入的报价(tick)都会更新这根K线。当一根新的K线形成,其最高价、最低价和成交量逐渐成形时,我们就能实时观察到这个过程。所有这些微小的价格更新,共同构成了K线的形成过程。但这些更新同样也能触发脚本的计算。由于我们的指标或策略需要数据来进行计算,TradingView便会将这根K线的临时值提供给脚本。
这样一来,close 变量就简单地代表了最新的一笔报价,而 high 和 low 则告诉脚本这根K线到目前为止所出现的最高和最低价。让我们通过更深入的分析来理解这一行为。
TradingView K线数据的最终值与临时值
一个K线变量既可以返回该K线的最终值,也可以在该K线尚未收盘时,返回一个临时值。它具体返回哪种值,取决于脚本的计算时机:
- 当一个指标或策略在某根K线已经收盘后进行计算时,K线变量返回的是该K线的最终数据。
- 但如果我们的脚本在某根K线仍在交易时进行计算,那么K线变量返回的则是该K线到目前为止的数值。
更准确地说,根据K线的状态,我们可以从TradingView的K线变量中得到如下的值:
| 变量 | 活跃交易中的K线(未收盘) | 已收盘的K线 |
|---|---|---|
high |
盘中已达到的最高价 | K线最终的最高价 |
low |
盘中已达到的最低价 | K线最终的最低价 |
close |
最新一次报价(即最后一个tick的价格) | K线最终的收盘价 |
volume |
盘中累计的成交量 | K线的总成交量 |
特别说明
high、low、volume和close变量并不总是返回K线的最终值。当我们的脚本处理一根仍在交易中的K线时,这些变量只返回该K线当前的最高价、最低价、成交量和最新报价。
我们可以这样来理解:TradingView K线变量的作用,是返回该K线的最新值。对于一根已经收盘的K线来说,它的最新值就是它最终的最高价、最低价、收盘价或成交量。
而当一根K线仍在接收实时数据时,它的最新值只是一个个临时的快照。在这种情况下,high、low、close 和 volume 返回的就是我们在该K线生命周期中,到目前为止所观察到的价格或成交量。
TradingView脚本在何时处理临时或最终的价格数据?
我们已经知道,TradingView的K线变量既可以返回一根K线的临时值,也可以返回其最终数据。但具体在什么情况下,high、low、close 和 volume 这些变量返回的数据会产生差异呢?
在以下三种场景中,这些K线变量返回的并非K线的最终数据:
- 当一个指标在实时数据上运行时。
- 当一个策略在实时数据上运行,并且启用了“在每个Tick上重新计算”(Recalculate On Every Tick) 的设置时。
- 当一个策略有订单成交,并且开启了“订单成交后重新计算”(Recalculate After Order Filled) 的设置时。
让我们来逐一深入探讨。
运行在实时数据上的指标脚本
当一个指标运行在我们的图表上时,它会处理每一个传入的实时价格更新(tick)。最直观的例子就是,当最新报价传来时,我们看到脚本绘制的指标值也随之更新。脚本的其他行为,例如触发警报或监控价格穿越,同样也是在最后一根K线上逐个tick地进行计算。
然而,这种逐个tick更新的行为并不会发生在历史K线上。在历史K线上,TradingView指标每根K线只在收盘时计算一次。因此,对于历史K线,high、low、close 和 volume 变量总是指向该K线最终的、已确认的数据。
启用了“在每个Tick上重新计算”的策略
与指标类似,TradingView策略也可以处理图表最后一根K线上的实时价格更新。但这需要我们启用策略设置中的“在每个Tick上重新计算”(Recalculate On Every Tick) 选项。启用后,当交易品种活跃交易时,像 high 和 close 这样的K线变量所代表的,就不仅是K线的最终值,也包括了K线盘中的临时值。
特别说明
如果不开启“在每个Tick上重新计算”设置,那么无论是在历史数据还是实时数据上,策略都遵循默认行为:每根K线只在收盘时计算一次。因此,在默认情况下,K线变量总是返回该K线最终的价格。
“在每个Tick上重新计算”不会改变TradingView策略处理历史K线的方式。在历史K线上,计算仍然是每根K线在收盘时执行一次。因此,对于历史K线,high、low、close 和 volume 都指向K线的最终数据——除非我们启用了“订单成交后重新计算”设置。接下来我们分析这个选项。
启用了“订单成交后重新计算”的策略
当我们启用“订单成交后重新计算”(Recalculate After Order Filled) 设置时,每当有订单成交,TradingView策略就会执行一次额外的K线内计算。在这次计算期间,K线变量(如 high 和 close)并不指向K线的最终数据,而是返回它们在这次K线内计算发生那一刻所具有的值。
由于大多数策略并非在每根K线上都有订单成交,所以“订单成交后重新计算”只会在某些特定的K线上改变策略的行为。在其他没有订单成交的K线上,策略仍然是每根K线在收盘时计算一次。因此,在大多数时候,像 close 和 volume 这样的变量返回的仍然是已收盘K线的数据。
K线的最终收盘价与临时收盘价有何影响?
综上所述,close 变量既可能返回K线的最终收盘价,也可能返回最新一次价格更新的值。但这个差异在实际应用中真的很重要吗?
答案取决于您的脚本代码。对于像绘制一条10周期简单移动平均线 (SMA) 这样的基础应用来说,几乎没有影响。无论 close 变量代表的是最终收盘价还是最新的tick报价,移动平均线都能在图表上正常显示。
但当我们基于K线变量 (high, low, close, volume) 编写交易条件时,情况就变得复杂了。假设我们的脚本需要判断收盘价是否高于过去20根K线的最高点。我们可能会这样编写代码:
close > ta.highest(high, 20)[1]
此时,我们的脚本何时计算以及 close 变量的确切含义就变得至关重要了:
- 如果我们的脚本在K线已收盘后计算,
close获取的就是这根K线的最终收盘价。在这种情况下,close > ta.highest(high, 20)[1]这段代码确实是在判断“收盘价是否高于过去20根K线的最高点”。 - 但如果我们的脚本在K线内进行计算,那么
close返回的就是最新的报价。在这种情况下,我们的代码片段检查的其实是“最新的tick价格是否高于过去20根K线的最高点”。
第二种情况的问题就在于:当下一个tick(或者该K线最终的收盘价)低于过去20根K线的最高点时,我们的条件在K线内的某次计算中仍然成立过 (true)。
因此,如果我们基于这个条件发送了一个市价单,订单会立即成交。但当这根K线最终收盘时,那个触发条件可能已经不复存在了。这常常会让我们感到困惑:“为什么这个订单会成交?收盘价明明没有高于过去20根K线的最高点啊!”
这种困惑是完全正确的,这根K线最终确实没有收盘在20周期最高点之上。但我们的脚本确实在K线内进行了计算,并且在其中一次计算中,有一个tick的价格曾经高于了20周期的最高点,从而导致我们的条件在那一刻变为了 true。
我们能否检查 close 变量返回的是否为K线的最终收盘价?
幸运的是,我们可以通过代码来检查 close 变量返回的究竟是K线的最终价格(即收盘价),还是盘中某个tick更新的临时值。为此,我们使用 barstate.isconfirmed 变量。
当我们的脚本在一根价格K线的最后一次(即收盘)更新时进行计算,barstate.isconfirmed 这个内置变量会返回 true。如果我们的脚本在一个尚未收盘的K线上计算,barstate.isconfirmed 则会返回 false。
假设我们的脚本要求在K线收盘价高于过去5根K线的最高点时执行某个动作。我们可能会这样编写代码:close > ta.highest(high, 5)[1]。然而,当盘中的某个报价高于过去5根K线的最高点时,这个比较条件同样会成立 (true)。但如果我们将它与 barstate.isconfirmed 变量结合起来,那么只有当这根K线真正收盘在5周期最高点之上时,这个比较条件才会为 true。
下面的示例脚本展示了如何应用 barstate.isconfirmed:
//@version=5
indicator(title="Highest high breakout", overlay=true)
// 计算最高高点并判断突破
hiHighs = ta.highest(high, 5)[1]
hiBreakout = barstate.isconfirmed and
close > hiHighs
// 绘制最高高点
plot(hiHighs, color=color.orange)
// 当发生突破时为背景上色
bgcolor(hiBreakout ? color.green : na)
这里,我们使用TradingView的 ta.highest() 函数来计算最高高点,并将其值储存在 hiHighs 变量中。
然后,我们进行一个逻辑比较。我们要求 barstate.isconfirmed 和 close > hiHighs 这两个条件同时为 true。当它们都满足时,我们就让 bgcolor() 函数将图表背景染成绿色。否则,我们使用 na 值来禁用背景色。
下面是这个指标在图表上的实际表现:
在这张图上,最后一根K线的价格突破了过去5根K线的最高点。但那根K线尚未收盘(还剩下54秒)。因此,我们的指标还未用绿色背景来高亮这次突破。
现在,假设我们不使用 barstate.isconfirmed,而只是简单地判断 close 变量是否高于5周期最高点:
//@version=5
indicator(title="Highest high breakout", overlay=true)
// 计算最高高点并判断突破
hiHighs = ta.highest(high, 5)[1]
hiBreakout = close > hiHighs
// 绘制最高高点
plot(hiHighs, color=color.orange)
// 当发生突破时为背景上色
bgcolor(hiBreakout ? color.green : na)
现在,只要K线的最新价格移动到5周期高点之上,我们的指标就会立即为背景上色:
图表上的最后一根K线尚未收盘,但指标已经将其背景染成了绿色。这是因为,对于一根未收盘的K线,close 变量返回的是最新的tick报价。在这里,那个最新的价格已经高于了5周期的最高点。
因此,借助 barstate.isconfirmed 变量,我们就能确保脚本只在K线确认收盘时,才去评估某些特定的条件,而不是在 close 变量只代表最新tick价格时就执行操作。
内容总结
- 当一根价格K线收盘时,TradingView的K线变量返回的是该K线的最终值。但当一根K线仍在交易时,它们返回的是该K线到目前为止的数据。因此,
close变量并不总是代表收盘价:在K线内的脚本计算中,这个变量返回的是最新的tick报价。 - 有两个因素会影响这种行为:脚本运行在何种价格数据上,以及脚本自身的设置。当一个指标运行在实时数据上时,它总是在每个实时价格更新时进行计算。这意味着对于图表的最后一根K线,
close指的是最新的tick。只有在这根K线收盘的那个tick上,close的值才会与该K线最终的收盘价相匹配。 - 策略默认只在K线收盘时计算一次。因此,对于策略脚本,
close通常指的是K线的收盘价。但当我们启用“订单成交后重新计算”或“在每个Tick上重新计算”的策略设置时,情况就会改变。这些选项会使策略也执行K线内的计算,从而也使得close变量有可能指向最新的tick价格。 - 要判断一个脚本是否在已收盘的K线上计算,我们可以使用
barstate.isconfirmed变量。当K线已经收盘时(此时K线变量返回的是最终值),该变量返回true。当barstate.isconfirmed返回false时,close、high、low和volume返回的则是K线的临时值。









