外观
重绘
约 5321 字大约 18 分钟
简介
我们将重绘定义为:导致历史与实时计算结果或绘图表现不同的脚本行为。
重绘行为普遍存在,多种因素可导致该现象。根据我们的定义,估计超过95%的现有指标都存在某种形式的重绘行为。例如常用指标如MACD和RSI,在历史K线上显示确认值,但在未确认的实时K线上会持续波动直至K线闭合。因此它们在历史和实时状态下表现不同。
并非所有重绘行为本质上都无用或具有误导性,这种行为也不妨碍有经验的交易者使用相关指标。例如,谁会仅因成交量分布指标在实时K线上更新值就否定其价值?
根据脚本计算内容的不同,使用者可能会遇到以下重绘形式:
普遍但通常可接受:
- 脚本使用随实时价格更新的未确认K线值。例如在未闭合K线上使用close变量计算时,其值会反映该K线的最新价格,但脚本仅在K线闭合后才向历史序列提交新数据点。
- 使用request.security()在实时K线上获取更高时间周期数据(如其他时间周期和数据页面的历史与实时行为章节所述)。与图表时间周期的未确认K线类似,该函数可追踪更高时间周期上下文的未确认值,导致脚本重启执行后出现重绘。
- 只要理解其工作原理,此类脚本通常仍可使用。但若用于生成警报或交易指令,必须清楚其实时与历史行为的差异。
可能具有误导性:
- 向过去绘制数值的脚本
- 在实时K线上产生无法在历史K线复现结果的脚本
- 重定位历史事件的脚本,例如:一目均衡表、大多数基于枢轴点的脚本、使用
calc_on_every_tick = true的策略、request.security()在实时K线表现异常的脚本、使用varip的脚本、使用timenow的脚本,以及部分使用barstate.*变量的脚本。
不可接受:
- 将未来信息泄漏到过去的脚本
- 使用实时内部K线生成警报或指令的脚本
- 这些会产生严重误导性重绘。
不可避免:
- 数据提供商对数据流的修订
- 图表历史起始K线的变化
- 可能导致脚本不可避免的重绘。
前两类重绘在以下情况下完全可以接受:
- 您知晓该行为
- 您能接受该行为
- 您能规避该行为
现在应该明确,并非所有重绘行为都是错误且必须不惜代价避免的。许多情况下,某些重绘形式可能正是脚本所需。重要的是知道何时重绘行为不符合需求。为避免不可接受的重绘,关键在于理解工具原理或设计自建工具的准则。若发布脚本,请确保在发布说明中提及其潜在误导行为及其他限制。
对于脚本使用者而言
若理解重绘行为特性且该行为符合分析需求,完全可以选用存在重绘的指标。请勿效仿某些新手,仅因脚本存在重绘就随意在公开脚本标注"重绘"试图贬低其价值,这种行为恰恰暴露了对基础知识的匮乏。
笼统询问"脚本是否重绘"毫无意义——鉴于某些重绘行为在脚本中完全可接受。此类泛泛之问无法获得有效解答。应当针对具体重绘行为提出精确问题,例如:
- 脚本在历史K线与实时K线上的计算/显示逻辑是否一致?
- 脚本生成的警报是否等待实时K线结束后触发?
- 脚本显示的交易信号标记是否等待实时K线闭合后出现?
- 脚本是否向历史K线位置绘制数值?
- 策略是否启用
calc_on_every_tick = true参数? - 脚本的request.security()调用是否在历史K线上泄漏未来信息?
关键在于理解所用工具的工作原理,并判断其行为(无论是否存在重绘)是否符合您的目标。正如本页内容所示,重绘是复杂议题——具有多重表现形态和成因机制。即使不编写Mine Script®代码,理解这些成因也能帮助您与脚本开发者展开更有价值的专业讨论。
针对Mine Script程序员
如前述讨论,并非所有重绘行为都必须不计代价地避免,也并非所有潜在重绘行为都可完全规避。希望本页内容能帮助您更好地理解其中的运行机制,从而在设计交易工具时充分考虑这些行为特性。本文内容可使您意识到导致误导性重绘结果的常见编码错误。
无论您做出何种设计决策,若公开发布脚本,请向交易者详细说明脚本行为特性,确保其理解运作原理。
本页涵盖三大类重绘成因:
历史与实时计算
动态数据值
历史数据不包含K线内部的中间价格变动记录,仅包含开盘价、最高价、最低价和收盘价(OHLC)。
然而,在实时K线(市场交易时运行的K线)上,最高价、最低价和收盘价并非固定值——它们会在实时K线闭合前多次变动,属于动态值。这导致脚本在历史数据和实时环境中的表现可能存在差异,其中只有开盘价在K线持续期间保持不变。
任何在实时环境中使用最高价、最低价或收盘价的脚本,其计算结果可能在历史K线上无法复现,从而产生重绘。
请看以下简单脚本示例,它检测收盘价(在实时K线上对应品种的当前价格)上穿和下穿EMA的情况:

Mine Script®
已复制
需要注意:
- 脚本使用bgcolor()函数在收盘价上穿EMA时将背景设为绿色,下穿时设为红色。
- 屏幕截图显示该脚本在30秒图表上的实时运行情况。已检测到EMA上穿,因此实时K线背景为绿色。
- 问题在于无法保证该状态会持续到实时K线结束。箭头指向的计时器显示实时K线还剩21秒,在此期间任何情况都可能发生。
- 我们正在观察一个会重绘的脚本。
为避免这种重绘,必须修改脚本使其不使用实时K线上波动的值。这需要使用已结束K线(通常是前一根K线)的值,或使用不会在实时K线中变动的开盘价。
可通过多种方式实现。以下方法在交叉检测中添加了barstate.isconfirmed条件,要求脚本在K线闭合、价格确认的最后一次迭代时执行。这是避免重绘的简单方法:
Mine Script®
已复制
此版本使用在前一根K线上检测到的交叉信号:
Mine Script®
已复制
此版本仅使用已确认的收盘价和EMA值进行计算:
Mine Script®
已复制
此版本检测实时K线开盘价与前一根K线EMA值之间的交叉。需要注意的是,由于EMA基于收盘价计算,仍存在重绘风险。为确保使用已确认值进行交叉检测,必须在交叉检测逻辑中使用ma[1](上一周期EMA值):
Mine Script®
已复制
所有这些方法都有一个共同点:在防止重绘的同时,信号触发会比重绘脚本更晚。这是避免重绘必然要付出的代价,鱼与熊掌不可兼得。
request.security()调用导致的重绘
request.security()函数在历史K线和实时K线上表现不同。在历史K线上,它仅返回请求上下文中已确认的值,而在实时K线上可能返回未确认的值。当脚本重启执行时,原本处于实时状态的K线转为历史K线,因此只会包含这些K线上已确认的值。如果request.security()返回的值在实时K线上波动但未获上下文确认,脚本重启执行时将会重绘这些值。详见其他时间周期和数据页面的历史与实时行为章节的详细说明。
通过以下方法可确保高时间周期数据请求在所有K线(无论K线状态)上仅返回确认值:使用历史引用运算符[]将expression参数至少偏移1根K线,并在request.security()调用中将lookahead参数设为barmerge.lookahead_on(如本文所述)。
下方脚本展示了重绘与非重绘HTF数据请求的区别。它包含两个request.security()调用:第一个函数调用不加额外限定地从higherTimeframe请求close数据;第二个调用请求相同的序列但带有偏移和barmerge.lookahead_on。
在所有实时K线(橙色背景)上可见:repaintingClose包含未经higherTimeframe确认的波动值,意味着脚本重启执行时会重绘;而nonRepaintingClose在实时K线和历史K线上表现一致,即仅当新的确认数据可用时才改变其值:

Mine Script®
已复制
请注意:
- 我们使用 plotshape() 函数在较高时间框架发生变化时标记图表。
- 如果较高时间框架低于图表的时间框架,此脚本会产生运行时错误。
- 在历史K线上,
repaintingClose在每个时间框架结束时有一个新值,nonRepaintingClose在每个时间框架开始时有一个新值。
为了便于重用,下面是一个简单的 noRepaintSecurity() 函数,可以应用于脚本中以请求不重涂的较高时间框架值:
Mine Script®
已复制
请注意:
- 对序列使用
[1]偏移量以及lookahead = barmerge.lookahead_on的设置是相互依存的。移除其中任何一个都会影响函数的完整性。 - 与普通的 request.security() 调用不同,此封装函数不能接受元组表达式参数。对于多元素用例,可以传递一个用户自定义类型,其字段包含需要请求的元素。
在较低时间框架使用 request.security()
部分脚本会使用 request.security() 从比图表时间框架更低的周期请求数据。当专门设计用于处理较低时间框架内K线的函数被传递到该周期时,这种做法可能有用。但这类用户自定义函数若需要检测内K线的首根K线(多数情况如此),则该技术仅能在历史K线上生效。这是因为实时内K线尚未完成排序。其后果是:此类脚本无法在实时行情中复现其在历史K线上的行为。例如,任何生成警报的逻辑都将存在缺陷,且需要持续刷新才能将已发生的实时K线作为历史K线重新计算。
若在低于图表时间框架的周期使用 request.security(),且未配备能区分内K线的专用函数时,该函数仅会返回图表K线扩张周期内最后一根内K线的值——这通常毫无意义,且同样无法在实时行情中复现,从而导致重绘问题。
基于以上原因,除非您深刻理解在低于图表时间框架周期使用 request.security() 的微妙之处,否则应尽量避免在此类周期使用该函数。高质量的脚本会内置逻辑来检测此类异常,并阻止显示使用较低时间框架时可能产生的无效结果。
如需更可靠的较低时间框架数据请求,请使用 request.security_lower_tf(),具体说明参见其他时间框架与数据页面的相关章节。
未来泄漏问题与 request.security()
当 request.security() 与 lookahead = barmerge.lookahead_on 配合使用,且未通过 [1] 偏移序列来获取价格时,该函数会在历史K线上返回来自未来的数据——这种危险行为将导致严重误导。
虽然历史K线会神奇地显示本应未知的未来价格,但在实时行情中不可能实现前瞻(正如应有的逻辑),因为未来尚不可知,自然也不存在未来K线。
示例如下:

Mine Script®
已复制
请注意较高时间框架线如何在对应高点实际出现前就显示了该值。避免此影响的解决方案是如本节所示范的方式使用该函数。
如其他时间框架与数据页面的前瞻章节所述,在发布的脚本中使用前瞻功能制造误导性结果属于禁止行为。采用这种误导性技术的脚本出版物将受到审核处理。
varip
使用 varip 声明模式的变量(详见我们关于 varip 的章节)会在实时更新中保存信息,而这些信息无法在仅包含 OHLC 数据的历史 K 线上复现。此类脚本可能在实时行情中有用(包括生成警报),但其逻辑无法进行回测,且历史 K 线上的绘图也无法反映实时计算的结果。
K线状态内置变量
使用K线状态变量的脚本可能会重绘,也可能不会。如前一节所述,使用 barstate.isconfirmed 实际上是避免重绘的一种方法,且能在历史K线上复现(历史K线始终处于“已确认”状态)。然而,使用其他K线状态变量(如 barstate.isnew)将导致重绘问题。原因在于:在历史K线上,barstate.isnew 在K线收盘时为真,而在实时行情中,它在K线开盘时为真。使用其他K线状态变量通常会导致历史K线与实时K线之间的行为差异。
timenow
内置变量 timenow 返回当前时间。使用此变量的脚本无法保持历史数据与实时行为的一致性,因此必然会产生重绘问题。
策略
使用 calc_on_every_tick = true 的策略会在每次实时更新时执行,而策略在历史K线收盘时运行。这很可能导致订单成交不一致,从而产生重绘问题。请注意,当这种情况发生时,也会使回测结果失效,因为它们无法代表策略在实时行情中的真实表现。
在过去位置绘图
检测到枢轴点后延迟5根K线才绘图的脚本,通常会回溯到实际枢轴点位置(即5根K线之前)标注枢轴水平或数值。这往往导致查看历史K线绘图的交易者误以为:当枢轴点在实时行情中形成时,相关标记会立即出现在枢轴点位置,而非实际检测完成时才显示。
以下脚本示例通过将高价枢轴点的价格标注在检测完成时(枢轴点形成后的第5根K线)进行展示:

Mine Script®
已复制
请注意:
- 此脚本存在重绘问题,因为一根原本未显示价格的实时K线,若在枢轴点实际形成5根K线后被识别为枢轴点,则可能被追加价格标记。
- 虽然显示效果出色,但可能产生误导。
开发供他人使用的脚本时,最佳解决方案是默认采用无偏移绘图,但通过输入参数让脚本用户自主选择是否开启回溯绘图功能。这样能确保用户明确知晓脚本的运行机制,例如:
Mine Script®
已复制
数据集差异
起始点
脚本从图表的第一根历史K线开始执行,随后按顺序在每根K线上运行(如本手册Mine脚本执行模型章节所述)。若起始K线发生变化,脚本的计算结果往往与之前数据集起始点不同时存在差异。
影响图表显示K线数量及起始点的因素包括:
- 账户类型
- 数据供应商提供的历史数据
- 数据集对齐要求(决定起始点)
各账户类型的K线数量限制如下:
- Premium方案:20,000根历史K线
- 其他方案:5,000根历史K线
起始点根据图表时间框架按以下规则确定:
- 1-14分钟:对齐至当周起始点
- 15-29分钟:对齐至当月起始点
- 30-1439分钟:对齐至当年起始点
- 1440分钟及以上:对齐至最早可用的历史数据点
随时间推移,这些因素会导致图表历史数据的起始点发生变化。由于早期K线的计算结果会传导至后续所有K线,这将影响脚本运算。例如使用ta.valuewhen()、ta.barssince()或ta.ema()等函数时,结果会随早期历史数据变化而波动。
历史数据修订
交易所/经纪商通过两种数据源构建K线:历史数据和实时数据。当实时K线闭合后,交易所/经纪商有时会对K线价格进行微调并写入历史数据。刷新图表或重新执行脚本时,这些已闭合的实时K线将采用可能包含价格修订的历史数据重新计算。
历史数据也可能因其他原因修订(例如股票分拆)。