重新粉刷
介绍
我们将重绘定义为:脚本行为导致历史与实时计算或绘图表现不同。
重绘行为很普遍,许多因素都可能导致重绘。根据我们的定义,我们估计现有指标中超过 95% 都表现出某种形式的重绘行为。例如,常用的指标(如 MACD 和 RSI)在历史条形图上显示已确认的值,但在实时、未确认的图表条形图上会波动,直到收盘。因此,它们 在历史和实时状态下的行为不同。
并非所有重绘行为本质上都是无用或误导性的,而且此类行为也不会阻止知识渊博的交易者使用具有此类行为的指标。例如,谁会仅仅因为成交量概况指标在实时条形图上更新其值而怀疑它呢?
人们可能会在所使用的脚本中遇到以下任何形式的重绘,具体取决于脚本的计算内容:
- 广泛但通常可以接受:脚本可以使用随未确认条形图上的实时价格变化而更新的值。例如,如果在打开的图表条形图上执行的计算中使用 close 变量,则其值将反映条形图中的最新价格。但是,脚本只会在条形图关闭后将新数据点提交到其历史系列。另一个常见情况是使用 request.security() 获取实时条形图上的更高时间范围数据,如 其他时间范围和数据页面的 历史和实时行为部分所述。与图表时间范围内的未确认图表条形图一样, request.security() 可以跟踪实时条形图上更高时间范围上下文中的未确认值,这可能导致脚本重新启动执行后重新绘制。使用此类脚本通常没有错,只要您了解它们的工作原理即可。但是,在选择使用此类脚本发出警报或交易订单时,重要的是要了解它们的实时行为和历史行为之间的差异,并自行决定它是否能满足您的需求。
- 潜在的误导性:绘制过去值、计算无法在历史条上复制的实时条形图结果或重新定位过去事件的脚本可能会产生误导。例如,Ichimoku、大多数基于枢轴的脚本、大多数使用 的策略
calc_on_every_tick = true
、使用 request.security() 的脚本( 当它在实时条形图上表现不同时)、许多使用 varip 的脚本、许多使用timenow 的脚本 以及一些使用barstate.*
变量的脚本都可能表现出误导性的重绘行为。 - 不可接受:将未来信息泄露到过去的脚本、在 非标准图表上执行的策略以及使用实时内部条形图生成警报或订单的脚本,都是可能产生严重误导的重绘行为的例子。
- 不可避免的是:来自提供商的数据馈送的修订和图表历史记录的起始条的变化可能会导致脚本中不可避免的重绘行为。
如果符合以下情况,前两种类型的重新涂漆是完全可以接受的:
- 您已经意识到了这种行为。
- 你可以忍受它,或者
- 你可以规避它。
现在应该清楚的是,并非所有的重绘行为都是错误的,需要不惜一切代价避免。在许多情况下,某些形式的重绘可能正是脚本所需要的。重要的是要知道重绘行为何时不符合个人需求。为了避免不可接受的重绘,了解工具的工作原理或如何设计构建的工具非常重要。如果您 发布脚本,请确保在出版物的描述中提及任何可能误导的行为以及脚本的其他限制。
对于脚本用户
如果了解重绘行为,并确定该行为是否符合分析要求,则可以决定使用重绘指标。不要成为那些在已发布的脚本上贴上“重绘”句子以试图诋毁它们的新手之一,因为这样做表明你缺乏该主题的基础知识。
简单地询问脚本是否重绘是相对没有意义的,因为脚本中存在完全可以接受的重绘行为形式。因此,这样的问题不会产生有意义的答案。我们应该问一些关于脚本潜在重绘行为的具体问题,例如:
- 脚本在历史和实时条形图上的计算/显示方式是否相同?
- 脚本发出的警报是否会等待实时条结束后才触发?
- 脚本显示的信号标记是否要等待实时条结束后才显示?
- 脚本是否绘制/描绘过去的值?
- 该策略是否使用
calc_on_every_tick = true
? - 脚本的 request.security() 调用是否会将未来信息泄露到历史条形图中的过去?
重要的是,您要了解所使用的工具是如何工作的,以及它们的行为是否与您的目标(重绘或不重绘)兼容。如果您阅读本页,您就会发现,重绘是一个复杂的问题。它有很多方面,也有很多原因。即使您不使用 Pine Script™ 编程,本页也将帮助您了解可能导致重绘的一系列原因,并希望能够与脚本作者进行更有意义的讨论。
对于 Pine Script™程序员
如上所述,并非所有形式的重绘行为都必须不惜一切代价避免,也并非所有潜在的重绘行为都一定可以避免。我们希望本页能帮助您更好地了解其中的动态,以便您在设计交易工具时考虑到这些行为。本页的内容应该有助于您了解导致误导性重绘结果的常见编码错误。
无论您的设计决策是什么,如果您 发布了脚本,请向交易者解释该脚本,以便他们了解其行为方式。
本页涵盖了重新粉刷的三大类原因:
历史计算与实时计算
流体数据值
历史数据不包括条形图上的中间价格变动记录;仅包括 开盘价、 最高价、 最低价和 收盘 价(OHLC)。
然而,在实时柱状图(工具市场开放时运行的柱状图)上, 最高价、 最低价和 收盘 价并不固定;它们可以在实时柱状图关闭之前多次更改值,并且其 HLC 值是固定的。它们是流动的。这导致脚本在历史数据和实时数据上的工作方式有时不同,其中只有开盘价 在 柱状图期间不会改变。
任何使用 实时最高、 最低和 收盘价等值的脚本 都可能产生在历史条形图上无法重复的计算——因此需要重新绘制。
让我们看一下这个简单的脚本。它检测 收盘 价(在实时栏中,这对应于工具的当前价格)高于和低于 EMA 的交叉:
//@version=5
indicator("Repainting", "", true)
ma = ta.ema(close, 5)
xUp = ta.crossover(close, ma)
xDn = ta.crossunder(close, ma)
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 80) : xDn ? color.new(color.fuchsia, 80) : na)
注意:
- 当收盘价超过 EMA 时,脚本使用 bgcolor() 将背景颜色设为绿色 ,当收盘价低于EMA 时,脚本使用 bgcolor()将背景颜色设为红色。
- 屏幕快照在 30 秒图表上实时显示脚本。已检测到 EMA 交叉,因此实时条形图的背景为绿色。
- 这里的问题是,没有什么可以保证这种情况会一直持续到实时条结束。箭头指向的计时器显示实时条剩余 21 秒,在此之前任何事情都可能发生。
- 我们正在见证一个重新粉刷的剧本。
为了防止这种重绘,我们必须重写脚本,使其不使用实时柱线期间波动的值。这将需要使用已过去的柱线(通常是前一个柱线)的值,或 实时不变化的开盘价。
我们可以通过多种方式实现这一点。此方法
and barstate.isconfirmed
为我们的交叉检测添加了一个条件,要求脚本在柱状图的最后一次迭代(即柱状图关闭且价格确认时)上执行。这是一种避免重绘的简单方法:
//@version=5
indicator("Repainting", "", true)
ma = ta.ema(close, 5)
xUp = ta.crossover(close, ma) and barstate.isconfirmed
xDn = ta.crossunder(close, ma) and barstate.isconfirmed
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 80) : xDn ? color.new(color.fuchsia, 80) : na)
这使用在前一根柱线上检测到的交叉:
//@version=5
indicator("Repainting", "", true)
ma = ta.ema(close, 5)
xUp = ta.crossover(close, ma)[1]
xDn = ta.crossunder(close, ma)[1]
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 80) : xDn ? color.new(color.fuchsia, 80) : na)
这仅使用确认的 收盘 价和 EMA 值进行计算:
//@version=5
indicator("Repainting", "", true)
ma = ta.ema(close[1], 5)
xUp = ta.crossover(close[1], ma)
xDn = ta.crossunder(close[1], ma)
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 80) : xDn ? color.new(color.fuchsia, 80) : na)
这将检测实时柱的
开盘价
与前几根柱的 EMA 值之间的交叉。请注意,EMA 是使用
close计算的,因此它会重新绘制。我们必须确保使用确认的值来检测交叉,因此ma[1]
在交叉检测逻辑中:
//@version=5
indicator("Repainting", "", true)
ma = ta.ema(close, 5)
xUp = ta.crossover(open, ma[1])
xDn = ta.crossunder(open, ma[1])
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 80) : xDn ? color.new(color.fuchsia, 80) : na)
所有这些方法都有一个共同点:虽然它们可以防止重绘,但它们也会在重绘脚本之后触发信号。如果想避免重绘,这是一个不可避免的妥协。鱼与熊掌不可兼得。
重新绘制 `request.security()`调用
request.security()函数 在历史和实时条形图上的行为不同。在历史条形图上,它仅返回来自其请求上下文的已确认值,而在实时条形图上它可以返回未确认值。当脚本重新启动执行时,具有实时状态的条形图将成为历史条形图,因此将仅包含它在这些条形图上确认的值。如果 request.security()返回的值 在实时条形图上波动而没有来自上下文的确认,则脚本将在重新启动执行时重新绘制它们。 有关详细说明,请参阅其他时间范围和数据页面的 历史和实时行为部分。
可以确保更高时间范围的数据请求仅返回所有条形图上的确认值,而不管条形图状态如何,方法是
expression
使用历史引用运算符
[]将参数偏移至少一个条形图
,并使用
barmerge.lookahead_on
作为request.security()lookahead
调用中的参数
,如此处
所述。
下面的脚本演示了重绘和非重绘 HTF 数据请求之间的区别。它包含两个
request.security()
调用。第一个函数调用请求
来自的关闭
数据,higherTimeframe
无需额外指定,第二个调用请求带有偏移量和
barmerge.lookahead_on 的同一系列。
正如我们在所有
实时
柱(具有橙色背景的柱)上看到的, 包含repaintingClose
未经 确认而波动的值
,这意味着当脚本重新启动执行时higherTimeframe
它将重新绘制nonRepaintingClose
。另一方面, 在实时和历史柱上的行为相同,即,它仅在有新的确认数据可用时更改其值:
//@version=5
indicator("Repainting vs non-repainting `request.security()` demo", overlay = true)
//@variable The timeframe to request data from.
string higherTimeframe = input.timeframe("30", "Timeframe")
if timeframe.in_seconds() > timeframe.in_seconds(higherTimeframe)
runtime.error("The 'Timeframe' input is smaller than the chart's timeframe. Choose a higher timeframe.")
//@variable The current `close` requested from the `higherTimeframe`. Fluctuates without confirmation on realtime bars.
float repaintingClose = request.security(syminfo.tickerid, higherTimeframe, close)
//@variable The last confirmed `close` requested from the `higherTimeframe`.
// Behaves the same on historical and realtime bars.
float nonRepaintingClose = request.security(
syminfo.tickerid, higherTimeframe, close[1], lookahead = barmerge.lookahead_on
)
// Plot the values.
plot(repaintingClose, "Repainting close", color.new(color.purple, 50), 8)
plot(nonRepaintingClose, "Non-repainting close", color.teal, 3)
// Plot a shape when a new `higherTimeframe` starts.
plotshape(timeframe.change(higherTimeframe), "Timeframe change marker", shape.square, location.top, size = size.small)
// Color the background on realtime bars.
bgcolor(barstate.isrealtime ? color.new(color.orange, 60) : na, title = "Realtime bar highlight")
注意:
-
当图表发生变化时,我们使用
plotshape()
函数来标记
。
higherTimeframe
-
如果低于图表的时间范围,该脚本将产生运行时错误。
higherTimeframe
- 在历史条形图上,在每个时间范围结束
repaintingClose
时都有一个新值 ,并且在每个时间范围开始时都有一个新值。nonRepaintingClose
为了便于重复使用,下面是一个简单的
noRepaintSecurity()
函数,可以在脚本中应用该函数来请求非重绘的更高时间范围的值:
//@function Requests non-repainting `expression` values from the context of the `symbol` and `timeframe`.
noRepaintSecurity(symbol, timeframe, expression) =>
request.security(symbol, timeframe, expression[1], lookahead = barmerge.lookahead_on)
注意:
- 系列的偏移量
[1]
和使用lookahead = barmerge.lookahead_on
是相互依赖的。 如果不损害功能的完整性,就无法删除任何一个。 - 与普通的
request.security()
调用不同,此包装函数不能接受元组
expression
参数。对于多元素用例,可以传递 用户定义的类型,其字段包含要请求的所需元素。
在较短的时间范围内使用 `request.security()`
一些脚本使用 request.security() 来请求低于图表时间范围的时间范围内的数据。当专门设计用于处理较低时间范围内的 intrabars 的函数被发送到时间范围内时,这可能很有用。当这种类型的用户定义函数需要检测 intrabars 的第一个 bar(大多数情况下都是这样)时,该技术仅适用于历史 bar。这是因为实时 intrabars 尚未排序。其影响是此类脚本无法实时重现它们在历史 bar 上的行为。例如,任何生成警报的逻辑都会有缺陷,并且需要不断刷新才能将过去的实时 bar 重新计算为历史 bar。
当在比图表更低的时间范围内使用且没有能够区分内部条形图的专门函数时, request.security()将仅返回图表条形图扩张中最后一个内部条形图 的值,这通常没用,也不会实时重现,因此导致重新绘制。
出于所有这些原因,除非您了解在比图表更低的时间范围内使用request.security()的微妙之处 ,否则最好避免在这些时间范围内使用该函数。更高质量的脚本将具有检测此类异常的逻辑,并防止显示在使用较低时间范围内无效的结果。
对于更可靠的较低时间范围数据请求,请使用 request.security_lower_tf(),如 其他时间范围和数据页面的本节 中所述。
`request.security()`的未来泄漏
当
使用request.security()lookahead = barmerge.lookahead_on
来获取价格而不偏移系列时[1]
,它将返回历史条形图上的未来数据,这具有危险的误导性。
尽管历史条形图会在人们应该知道之前神奇地显示未来的价格,但由于未来是未知的,所以不可能实时预测,因此不存在未来的条形图。
以下是一个例子:
// FUTURE LEAK! DO NOT USE!
//@version=5
indicator("Future leak", "", true)
futureHigh = request.security(syminfo.tickerid, "1D", high, lookahead = barmerge.lookahead_on)
plot(futureHigh)
请注意,较高的时间范围线在发生之前如何显示时间范围的 高值。避免这种影响的解决方案是使用本节 中演示的函数 。
脚本发布中不允许使用前瞻技术产生误导性结果,如 其他时间范围和数据页面的 前瞻部分所述。使用这种误导性技术的脚本发布 将受到审核。
`varip`
使用varip变量声明模式的脚本 (有关详细信息,请参阅有关varip 的部分 )会保存实时更新的信息,而这些信息无法在只有 OHLC 信息可用的历史条形图上重现。此类脚本可能在实时情况下有用,包括生成警报,但它们的逻辑无法进行回溯测试,它们在历史条形图上的绘图也无法反映将实时进行的计算。
内置状态栏
使用条形状态的脚本
可能会或可能不会重绘。正如我们在上一节中看到的,使用
barstate.isconfirmed
实际上是避免在历史条形图上重现重绘的一种方法,历史条形图始终是“已确认的”。但是,使用其他条形状态(例如
barstate.isnew)将导致重绘。原因是在历史条形图上,
barstate.isnew
位于true
条形图的收盘
价,但在实时中,它位于true
条形图的
开盘价。使用其他条形状态变量通常会导致历史条形图和实时条形图之间出现某种类型的行为差异。
`timenow`
timenow 内置函数返回当前时间。使用此变量的脚本无法显示一致的历史和实时行为,因此它们必须重新绘制。
策略
策略使用calc_on_every_tick = true
在每次实时更新时执行,而策略在
历史条形图收盘时运行
。它们很可能不会生成相同的订单执行,因此会重新绘制。请注意,当发生这种情况时,它也会使回溯测试结果无效,因为它们不能代表策略的实时行为。
过去的阴谋
在 5 个柱线过去后检测枢轴的脚本通常会回溯到过去,绘制 5 个柱线前实际枢轴上的枢轴水平或值。这通常会导致毫无戒心的交易者在查看历史柱线上的绘图时推断,当枢轴实时发生时,与检测到枢轴时相比,枢轴上会出现相同的绘图。
让我们看一个脚本,该脚本通过将价格置于检测到枢轴点后的 5 个条形图上来显示高枢轴点的价格:
//@version=5
indicator("Plotting in the past", "", true)
pHi = ta.pivothigh(5, 5)
if not na(pHi)
label.new(bar_index[5], na, str.tostring(pHi, format.mintick) + "\n🠇", yloc = yloc.abovebar, style = label.style_none, textcolor = color.black, size = size.normal)
注意:
- 此脚本会重新绘制,因为如果一个已过去的实时条形图被识别为枢轴点,且在实际枢轴点出现后的 5 个条形图上没有显示价格,则它可能会被赋予价格。
- 显示效果很好,但可能会产生误导。
为他人开发脚本时,解决此问题的最佳方法是默认绘图时不使用偏移,但为脚本用户提供通过输入打开过去绘图的选项,因此他们必然知道脚本正在做什么,例如:
//@version=5
indicator("Plotting in the past", "", true)
plotInThePast = input(false, "Plot in the past")
pHi = ta.pivothigh(5, 5)
if not na(pHi)
label.new(bar_index[plotInThePast ? 5 : 0], na, str.tostring(pHi, format.mintick) + "\n🠇", yloc = yloc.abovebar, style = label.style_none, textcolor = color.black, size = size.normal)
数据集变化
起点
脚本从图表的第一个历史条形图开始执行,然后按顺序在每个条形图上执行,如本手册的 Pine Script™ 执行模型页面中所述。如果第一个条形图发生变化,则脚本通常不会像数据集在不同时间点开始时那样计算。
以下因素会影响您在图表上看到的条形数量及其起点:
- 您持有的账户类型
- 数据供应商提供的历史数据
- 数据集的对齐要求,决定了它的 起点
以下是特定帐户的限制标准:
- 高级计划包含 20,000 个历史条形图。
- Pro 和 Pro+ 计划的 10000 个历史条形图。
- 其他计划的 5000 个历史条形图。
起点根据以下规则确定,具体取决于图表的时间范围:
- 1、5、10、15、30 秒:与一天的开始对齐。
- 1 - 14 分钟:与一周的开始一致。
- 15 - 29 分钟:与月初一致。
- 30 – 1439 分钟:与一年的开始一致。
- 1440 分钟及更高:与第一个可用的历史数据点对齐。
随着时间的推移,这些因素会导致图表的历史记录从不同的时间点开始。这通常会对脚本计算产生影响,因为早期条形图的计算结果变化可能会影响数据集中的所有其他条形图。例如,使用 ta.valuewhen()、 ta.barssince() 或 ta.ema()等函数将产生随早期历史记录而变化的结果。
历史数据修订
历史和实时条形图使用交易所/经纪商提供的两种不同的数据馈送来构建:历史数据和实时数据。实时条形图过时,交易所/经纪商有时会对条形图价格进行通常很小的调整,然后将其写入其历史数据。刷新图表或重新执行这些过时的实时条形图上的脚本时,它们将使用历史数据构建和计算,历史数据将包含通常很小的价格修订(如果有的话)。
历史数据也可能因为其他原因而被修改,例如股票分割。