外观
执行模型
约 3952 字大约 13 分钟
概述
Mine Script® 运行时的执行模型与 Mine Script 的时间序列和类型系统紧密相关。理解这三者是充分利用 Mine Script 强大功能的关键。
执行模型决定了脚本在图表上的执行方式,从而决定了您在脚本中编写的代码如何工作。如果没有 Mine Script 的运行时,您的代码将无法执行任何操作。Mine Script 的运行时会在代码编译完成后启动,并在图表上执行,因为 触发脚本执行的事件之一已经发生。
当 Mine 脚本加载到图表上时,它会在每个历史柱形图上执行一次,使用每个柱形图可用的 OHLCV(开盘价、最高价、最低价、收盘价、成交量)值。脚本执行到数据集中最右侧的柱形图后,如果图表交易品种当前正在进行交易,则 Mine 脚本指标将在每次发生更新(例如价格或成交量变化)时执行一次。默认情况下,Mine 脚本 策略仅在最右侧柱形图收盘时执行,但也可以像指标一样配置为每次更新时执行。
所有交易品种/时间框架对都拥有一个包含有限数量柱状图的数据集。当您向左滚动图表查看数据集的早期柱状图时,相应的柱状图会加载到图表上。当特定交易品种/时间框架对不再有柱状图,或已加载到您的账户类型允许的最大柱状图数量时,加载过程将停止 。您可以向左滚动图表,直到看到数据集的第一个柱状图,其索引值为 0(参见 bar_index)。
当脚本首次在图表上运行时,数据集中的所有条形图均为 历史条形图,除非交易时段处于活跃状态,否则最右侧的条形图除外。当最右侧的条形图处于活跃状态时,它被称为 实时条形图。当检测到价格或交易量变化时,实时条形图会更新。当实时条形图关闭时,它将变为已过去的实时条形图,并打开一个新的实时条形图。
根据历史柱计算
让我们采用一个简单的脚本并跟踪其在历史柱上的执行情况:
Mine Script®
已复制
在历史柱线上,脚本会在相当于该柱线收盘价的位置执行,此时该柱线的所有 OHLCV 值均已知。在某个柱线上执行脚本之前,内置变量(例如open、 high、low、close、volume和time)会设置为与该柱线对应的值。脚本会在每个历史柱线上执行一次。
我们的示例脚本首先在索引 0 处的数据集的第一根柱线上执行。每个语句都使用当前柱线的值执行。因此,在数据集的第一根柱线上,执行以下语句:
Mine Script®
已复制
src使用第一个柱状图的值初始化变量close,并依次执行接下来的每一行。由于脚本只对每个历史柱状图执行一次,因此脚本始终会使用相同的close值来计算特定的历史柱状图。
脚本中每一行的执行都会产生计算,进而生成指标的输出值,这些输出值随后可以绘制在图表上。我们的示例在脚本末尾使用了plot和plotshape调用来输出一些值。对于策略而言,计算结果可用于绘制数值或指示要下达的订单。
在第一个柱状图上执行并绘制后,脚本将在数据集的第二个柱状图上执行,该柱状图的索引为 1。然后重复该过程,直到处理完数据集中的所有历史柱状图,并且脚本到达图表最右边的柱状图。

根据实时柱状图计算
Mine 脚本在实时柱状图上的行为与在历史柱状图上的行为截然不同。回想一下,当图表交易品种处于活跃状态时,实时柱状图是图表最右侧的柱状图。另外,回想一下,策略在实时柱状图上可以有两种不同的行为方式。默认情况下,它们仅在实时柱状图关闭时执行,但 可以将声明语句calc_on_every_tick的参数strategy设置为 true 来修改策略的行为,使其像指标一样在每次实时柱状图更新时执行。因此,此处描述的指标行为仅适用于使用 的策略calc_on_every_tick=true。
在历史柱和实时柱上执行脚本最重要的区别在于:它们在历史柱上只执行一次,而脚本在实时柱期间每次发生更新时都会执行。这意味着,诸如high、low和close之类的内置变量在历史柱上永远不会改变,但在实时柱中每次脚本迭代时都会发生变化。脚本计算中使用的内置变量的变化反过来会导致计算结果的变化。这是脚本跟踪实时价格行为所必需的。因此,同一个脚本在实时柱期间每次执行时可能会产生不同的结果。
注意:在实时柱状图中,close变量 始终代表当前价格。同样,内置变量high和low代表实时柱状图自开始以来达到的最高价和最低价。Mine Script 的内置变量仅代表实时柱状图上次更新时的最终值。
让我们在实时栏中遵循我们的脚本示例。
当脚本到达实时柱时,它会首次执行。它使用内置变量的当前值来生成一组结果,并在需要时绘制它们。在下一次更新发生时脚本再次执行之前,其用户定义变量将重置为已知状态,该状态对应于上一个柱结束时的最后一次提交的状态。如果由于每个柱都初始化而未对变量进行提交,则它们将重新初始化。在这两种情况下,它们最后计算的状态都会丢失。绘制的标签和线条的状态也会重置。在实时柱中每次新的脚本迭代之前重置脚本的用户定义变量和绘图称为回滚。其效果是将脚本重置为实时柱打开时的已知状态,因此实时柱中的计算始终从干净状态执行。
随着实时柱状图中价格或交易量的变化,脚本的值会不断重新计算,这可能会导致c我们示例中的变量因交叉而变为真,因此脚本最后一行绘制的红色标记会出现在图表上。如果在下一次价格更新时,价格发生了变动,导致该 close值由于不再有交叉而不再产生计算c结果为真,那么之前绘制的标记将会消失。
当实时柱线关闭时,脚本将执行最后一次。通常,变量会在执行前回滚。但是,由于本次迭代是实时柱线上的最后一次迭代,因此计算完成后,变量将提交为该柱线的最终值。
总结一下实时条形图的流程:
- 脚本在实时栏打开时执行,然后每次更新时执行一次。
- 每次实时更新之前变量都会回滚。
- 变量在收盘价更新时提交一次。
触发脚本执行的事件
当发生以下事件之一时,将在图表上的完整条形图集上执行脚本:
- 图表上加载了新的符号或时间范围。
- 从 Mine 脚本编辑器或图表的“指标和策略”对话框中,将脚本保存或添加到图表中。
- 在脚本的“设置/输入”对话框中修改了一个值。
- 在策略的“设置/属性”对话框中修改值。
- 检测到浏览器刷新事件。
当交易处于活动状态时,在实时栏上执行脚本:
- 上述情况之一发生,导致脚本在实时柱开盘时执行,或
- 由于检测到价格或数量变化,实时条形图会更新。
请注意,如果市场活跃时图表未进行任何操作,则一系列已开盘并随后平仓的实时柱状图将落后于当前实时柱状图。虽然这些已过去的实时柱状图由于其变量已全部提交而得到确认,但脚本尚未在其历史状态下执行,因为脚本上次在图表数据集上运行时,这些柱状图并不存在。
更多信息
- 内置
barstate.*变量提供有关 脚本执行的K线类型或事件的信息。记录这些变量的页面还包含一个脚本,例如,它允许您可视化实时柱线和历史柱线之间的差异。 - 策略页面解释了策略计算的细节,这些细节与指标的计算细节并不相同。
函数的历史值
Mine 中的每个函数调用都会留下一系列历史值,脚本可以使用 [] 运算符在后续柱状图上访问这些历史值。函数的历史序列依赖于连续调用来记录每个柱状图的输出。如果脚本没有在每个柱状图上都调用函数,则可能会产生不一致的历史记录,这可能会影响计算和结果,也就是说,当脚本依赖于历史序列的连续性来按预期运行时。在这种情况下,编译器会警告用户,无论是内置函数还是用户自定义函数,其值都可能具有误导性。
为了演示,我们编写一个脚本来计算当前柱的索引,并在每隔一个柱上输出该值。在下面的脚本中,我们定义了一个calcBarIndex()函数,该函数在每个柱上将其内部index变量的先前值加 1。该脚本在每个condition返回true的柱上(每隔一个柱)调用该函数来更新customIndex值。它将这个值与内置bar_index一起绘制以验证输出:

Mine Script®
已复制
注意:
检查图表后,我们发现两条曲线差异很大。造成这种现象的原因是,脚本calcBarIndex()每隔一条柱线就在一个if结构范围内调用一次,导致历史输出与bar_index序列不一致。当每两根柱线调用一次该函数时,内部引用的先前值index会获取两根柱线之前的值,即函数执行的最后一个柱线的值。这种行为导致customIndex值为内置的bar_index的一半。
为了使calcBarIndex()输出与bar_index对齐,我们可以将函数调用移至脚本的全局范围。这样,该函数将在每根柱线上执行,从而允许记录和引用其整个历史记录,而不是仅记录其他每个柱线的结果。在下面的代码中,我们在全局范围内定义了一个变量globalScopeBarIndex,并将其赋值给calcBarIndex()的返回值,而不是在本地调用该函数。脚本在condition发生时将customIndex的值设置为globalScopeBarIndex:

Mine Script®
已复制
这种行为还会对内部引用历史记录的内置函数产生根本性影响。例如,ta.sma()函数会“在后台”引用其过去的值。如果脚本有条件地调用此函数,而不是在每个柱状图上都调用,则计算中的值可能会发生显著变化。我们可以通过将 ta.sma()赋值给全局变量,并根据需要引用该变量的历史记录,来确保计算的一致性。
以下示例计算三个 SMA 系列:controlSMA、 localSMA和globalSMA。该脚本在全局范围和if结构的局部范围内分别进行计算controlSMA和localSMA。在 if 结构中,它还使用controlSMA值更新了的globalSMA值。我们可以看到,globalSMA和controlSMA系列的值一致,而系列localSMA与其他两个系列不同,因为它使用了不完整的历史记录,这影响了其计算:

Mine Script®
已复制
为什么会有这种行为?
这种行为是必需的,因为强制在每个柱状图上执行函数会导致那些产生副作用的函数(即除了返回值之外还执行其他操作的函数)出现意外结果。例如, label.new() 函数会在图表上创建一个标签,因此,即使该函数位于 if 结构内,强制在每个柱状图上调用它也会创建逻辑上不应出现的标签。
例外
并非所有内置函数都会在计算中使用其先前的值,这意味着并非所有函数都需要在每个柱状图上执行。例如, math.max()会比较传入的所有参数并返回最大值。这类函数不以任何方式与历史记录交互,因此无需特殊处理。
如果在条件块中使用函数不会引起编译器警告,则可以安全使用,并且不会影响计算。否则,请将函数调用移至全局范围以强制执行一致性执行。即使出现警告,如果仍将函数调用保留在条件块中,请至少确保输出正确,以避免出现意外结果。