执行模型

Pine Script™ 运行时的执行模型与 Pine Script™ 的时间序列类型系统密切相关。了解这三者是充分利用 Pine Script™ 功能的关键。

执行模型决定了脚本在图表上的执行方式,从而决定了您在脚本中编写的代码的运行方式。如果没有 Pine Script™ 的运行时,您的代码将无法执行任何操作,因为代码编译后会启动,并在图表上执行,因为 触发脚本执行的事件之一已经发生。

当 Pine 脚本加载到图表上时,它会使用每个条形图的可用 OHLCV(开盘价、最高价、最低价、收盘价、成交量)值在每个历史条形图上执行一次。一旦脚本的执行到达数据集中最右侧的条形图,如果交易当前在图表符号上处于活动状态,则每次发生更新(即价格或成交量变化)时, Pine Script™指标都会执行一次。默认情况下,Pine Script™策略仅在最右侧条形图关闭时执行,但它们也可以配置为在每次更新时执行,就像指标一样。

所有符号/时间范围对都有一个包含有限数量的条形图的数据集。当您向左滚动图表以查看数据集的较早条形图时,相应的条形图会加载到图表上。当该特定符号/时间范围对不再有条形图或已加载您的账户类型允许的最大条形图数量时,加载过程将停止 。您可以将图表向左滚动,直到数据集的第一个条形图,其索引值为 0(参见 bar_index)。

当脚本首次在图表上运行时,数据集中的所有条形图都是 历史条形图,除了最右边的条形图(如果交易时段处于活动状态)。当最右边的条形图处于活动状态时,它被称为 实时条形图。当检测到价格或交易量变化时,实时条形图会更新。当实时条形图关闭时,它将成为已过去的实时条形图,并打开一个新的实时条形图。

根据历史柱计算

我们采用一个简单的脚本并跟踪其在历史柱上的执行情况:

//@version=5
indicator("My Script", overlay = true)
src = close
a = ta.sma(src, 5)
b = ta.sma(src, 50)
c = ta.cross(a, b)
plot(a, color = color.blue)
plot(b, color = color.black)
plotshape(c, color = color.red)

在历史柱上,当该柱的 OHLCV 值全部已知时,脚本将在柱收盘时执行。在柱上执行脚本之前,内置变量(例如openhighlowclosevolume)将设置为与该柱的值相对应的值。每个历史柱time执行一次脚本

我们的示例脚本首先在索引 0 处的数据集的第一个柱上执行。每个语句都使用当前柱的值执行。因此,在数据集的第一个柱上,以下语句:

src = close

src使用第一个柱的值初始化变量close,然后依次执行接下来的每一行。由于脚本只对每个历史柱执行一次,因此脚本将始终使用相同的close值对特定历史柱进行计算。

脚本中每一行的执行都会产生计算,进而生成指标的输出值,然后可以将其绘制在图表上。我们的示例使用脚本末尾的plotplotshape调用来输出一些值。对于策略,计算结果可用于绘制值或指示要下达的订单。

在第一个条形图上执行并绘制之后,脚本将在数据集的第二个条形图上执行,该条形图的索引为 1。然后重复该过程,直到数据集中的所有历史条形图都被处理并且脚本到达图表上的最右边的条形图。

图像

根据实时柱线计算

Pine 脚本在实时条形图上的行为与在历史条形图上的行为截然不同。回想一下,当图表符号上的交易处于活动状态时,实时条形图是图表上最右侧的条形图。另外,回想一下,策略在实时条形图上可以以两种不同的方式运行。默认情况下,它们仅在实时条形图关闭时执行,但 可以将声明语句calc_on_every_tick的参数strategy设置为 true 以修改策略的行为,以便它在每次实时条形图更新时执行,就像指标一样。因此,此处描述的指标行为仅适用于使用 的策略calc_on_every_tick=true

在历史和实时条形图上执行脚本最重要的区别在于,虽然它们在历史条形图上只执行一次,但脚本在实时条形图期间每次发生更新时都会执行。这意味着内置变量(例如,highlowclose 在历史条形图上永远不会改变,但在实时条形图上的每次脚本迭代中都会改变。脚本计算中使用的内置变量的变化反过来会导致这些计算结果的变化。这是脚本跟踪实时价格行为所必需的。因此,同一个脚本在实时条形图期间每次执行时可能会产生不同的结果。

注意:在实时条形图中,close变量始终代表当前价格。同样,highlow内置变量代表自实时条形图开始以来达到的最高点和最低点。Pine Script™ 的内置变量仅代表实时条形图上次更新时的最终值。

让我们在实时栏中关注我们的脚本示例。

当脚本到达实时栏时,它会首次执行。它使用内置变量的当前值来生成一组结果,并在需要时绘制它们。在下一次更新发生时脚本再次执行之前,其用户定义变量将重置为已知状态,对应于上一个栏结束时的最后一次提交的状态。如果由于每个栏都初始化而未对变量进行提交,则它们将重新初始化。在这两种情况下,它们最后计算的状态都会丢失。绘制的标签和线条的状态也会重置。在实时栏中每次新的脚本迭代之前重置脚本的用户定义变量和绘图称为回滚。其效果是将脚本重置为实时栏打开时的已知状态,因此实时栏中的计算始终从干净状态执行。

当实时条形图中的价格或交易量发生变化时,不断重新计算脚本的值可能会导致出现这种情况:c 由于出现交叉,我们示例中的变量变为真,因此脚本最后一行绘制的红色标记将出现在图表上。如果在下一次价格更新时,价格发生了变化,以至于由于不再有交叉,该 close值不再产生计算结果为c真,那么之前绘制的标记将消失。

当实时柱线关闭时,脚本将执行最后一次。通常,变量会在执行前回滚。但是,由于此迭代是实时柱线上的最后一次迭代,因此计算完成后,变量将提交给柱线的最终值。

总结一下实时条形图的流程:

  • 脚本在实时栏打开时执行,然后每次更新时执行一次
  • 每次实时更新之前变量都会回滚
  • 变量在收盘价更新时提交一次

触发脚本执行的事件

当发生以下事件之一时,将在图表上的完整条形图集上执行脚本:

  • 图表上加载了新的符号或时间范围。
  • 从 Pine Script™ 编辑器或图表的“指标和策略”对话框中,将脚本保存或添加到图表中。
  • 在脚本的“设置/输入”对话框中修改了一个值。
  • 在策略的“设置/属性”对话框中修改值。
  • 检测到浏览器刷新事件。

当交易处于活动状态时,会在实时栏上执行脚本:

  • 发生上述情况之一,导致脚本在实时栏开盘时执行,或
  • 由于检测到价格或数量变化,实时条形图会更新。

请注意,当市场活跃时图表未发生任何变化,一系列已打开然后关闭的实时条形图将落后于当前实时条形图。虽然这些已过去的实时条形图将得到确认,因为它们的变量已全部提交,但脚本尚未在它们的历史 状态下执行它们,因为脚本上次在图表数据集上运行时它们并不存在。

当事件触发图表上的脚本执行并导致其在现在已成为历史条形图的条形图上运行时,脚本的计算结果有时会与在相同条形图为实时条形图时上次收盘更新时计算的结果不同。这可能是由于实时条形图收盘时保存的 OHLCV 值与在相同条形图成为历史条形图时从数据馈送中获取的 OHLCV 值之间存在细微差异造成的。此行为是重绘的可能原因之一。

更多信息

  • 内置barstate.*变量提供有关 执行脚本的条形图类型或事件的信息。记录这些变量的页面还包含一个脚本,例如,该脚本可让您直观地看到实时条形图和历史条形图之间的差异。
  • 策略页面解释了策略计算的细节,这些细节与指标的计算细节不同。

函数的历史值

Pine 中的每个函数调用都会留下历史值的痕迹,脚本可以使用 [] 运算符在后续的柱状图上访问这些历史值。历史函数系列依赖于连续调用来记录每个柱状图的输出。当脚本没有在每个柱状图上调用函数时,它可能会产生不一致的历史记录,这可能会影响计算和结果,即当它依赖于历史系列的连续性来按预期运行时。在这些情况下,编译器会警告用户,让他们意识到函数的值(无论是内置的还是用户定义的)可能会产生误导。

为了演示,让我们编写一个脚本来计算当前柱的索引,并在每隔一个柱上输出该值。在下面的脚本中,我们定义了一个calcBarIndex()函数,该函数在每个柱上将其内部变量的先前值加 1。index该脚本在每个返回的柱上调用该函数conditiontrue每隔一个柱)来更新该customIndex值。它将这个值与内置值一起绘制bar_index以验证输出:

图像

//@version=5
indicator("My script")

//@function Calculates the index of the current bar by adding 1 to its own value from the previous bar.
// The first bar will have an index of 0.
calcBarIndex() =>
    int index = na
    index := nz(index[1], replacement = -1) + 1

//@variable Returns `true` on every other bar.
condition = bar_index % 2 == 0

int customIndex = na

// Call `calcBarIndex()` when the `condition` is `true`. This prompts the compiler to raise a warning.
if condition
    customIndex := calcBarIndex()

plot(bar_index,   "Bar index",    color = color.green)
plot(customIndex, "Custom index", color = color.red, style = plot.style_cross)

注意:

  • nz ()函数 用指定值(默认为 0) 替换 na值。在脚本的第一个条形图上,当系列没有历史记录时, na 值将替换为 -1,然后再加 1 以返回初始值 0。replacementindex

检查图表后,我们发现两个图大不相同。出现这种情况的原因是脚本在 每隔一个条形图上都在ifcalcBarIndex()结构 范围内 调用,导致历史输出与系列不一致。每两个条形图调用一次函数时,内部引用的先前值 会获取两个条形图之前的值,即执行函数的最后一个条形图。此行为导致值为内置的一半bar_indexindexcustomIndexbar_index

为了使calcBarIndex()输出与 对齐bar_index,我们可以将函数调用移至脚本的全局范围。这样,函数将在每个条形上执行,从而允许记录和引用其整个历史记录,而不仅仅是每个其他条形的结果。在下面的代码中,我们globalScopeBarIndex在全局范围内定义了一个变量并将其分配给从 的返回,calcBarIndex()而不是在本地调用该函数。脚本在 发生时将 设置customIndex为 的值globalScopeBarIndexcondition

图像

//@version=5
indicator("My script")

//@function Calculates the index of the current bar by adding 1 to its own value from the previous bar.
// The first bar will have an index of 0.
calcBarIndex() =>
    int index = na
    index := nz(index[1], replacement = -1) + 1

//@variable Returns `true` on every second bar.
condition = bar_index % 2 == 0

globalScopeBarIndex = calcBarIndex()
int customIndex = na

// Assign `customIndex` to `globalScopeBarIndex` when the `condition` is `true`. This won't produce a warning.
if condition
    customIndex := globalScopeBarIndex

plot(bar_index,   "Bar index",    color = color.green)
plot(customIndex, "Custom index", color = color.red, style = plot.style_cross)

这种行为还会对内部引用历史记录的内置函数产生重大影响。例如, ta.sma() 函数“在幕后”引用其过去的值。如果脚本有条件地而不是在每个条上调用此函数,则计算中的值可能会发生显著变化。我们可以通过将 ta.sma()分配 给全局范围内的变量并根据需要引用该变量的历史记录来确保计算的一致性。

以下示例计算三个 SMA 系列:controlSMAlocalSMAglobalSMA。该脚本在ifcontrolSMA结构的全局范围和localSMA局部范围内 进行计算 。在 if结构中,它还使用 值 更新的值。我们可以看到,系列的值一致,而系列与其他两个系列不同,因为它使用不完整的历史记录,这会影响其计算:globalSMAcontrolSMAglobalSMAcontrolSMAlocalSMA

图像

//@version=5
indicator("My script")

//@variable Returns `true` on every second bar.
condition = bar_index % 2 == 0

controlSMA = ta.sma(close, 20)
float globalSMA = na
float localSMA  = na

// Update `globalSMA` and `localSMA` when `condition` is `true`.
if condition
    globalSMA := controlSMA        // No warning.
    localSMA  := ta.sma(close, 20) // Raises warning. This function depends on its history to work as intended.

plot(controlSMA, "Control SMA", color = color.green)
plot(globalSMA,  "Global SMA",  color = color.blue, style = plot.style_cross)
plot(localSMA,   "Local SMA",   color = color.red,  style = plot.style_cross)

为什么会有这种行为?

这种行为是必需的,因为强制在每个条形图上执行函数会导致那些产生副作用的函数(即除了返回值之外还执行其他操作的函数)产生意外结果。例如,label.new () 函数会在图表上创建一个标签,因此强制在每个条形图上调用它(即使它在 if 结构内)会创建逻辑上不应该出现的标签。

例外

并非所有内置函数都在计算中使用其先前的值,这意味着并非所有函数都需要在每个柱上执行。例如, math.max() 会比较传递给它的所有参数以返回最大值。这些函数不会以任何方式与其历史记录交互,因此不需要特殊处理。

如果在条件块中使用函数不会引起编译器警告,则可以安全使用而不会影响计算。否则,请将函数调用移至全局范围以强制一致执行。尽管出现警告,但仍在条件块内保留函数调用时,至少要确保输出正确,以避免出现意外结果。

Original text
Rate this translation
Your feedback will be used to help improve Google Translate