保持敬畏之心
交易是一场持久战

Pine Script(102):图表极值与历史最高最低价,后台加载日线数据实战

#Pine Script入门教学

如何获取整个图表范围内的最高值?

TradingView图表通常会加载一个很长的时间周期,K线数量可达数万根。这使得我们用肉眼很难找出图表上的最高价。那么,我们该如何让Pine Script帮我们找到这个值呢?

要用代码实现获取图表的最高值,我们通常遵循以下两个步骤:

  1. 创建一个变量,用于持续存储最高值。
  2. 当出现一个比已存储值更高的数值时,就用这个新的最高值来更新我们的变量。

通过在每次图表达到更高值时都更新这个变量,当脚本运行到图表末尾时,我们也就收集到了整个图表范围内的最高值。接下来看看实现这一目标所需的代码。


实现图表最高值的自定义函数

下面的这个自定义函数,能够返回整个图表加载范围内的最高值:

// GetChartHighest() 函数返回给定序列在整个图表上的最高值。
// `dataSeries` 参数是可选的,如果未提供,则默认使用K线的最高价。
// 注意:为保证计算的一致性,请在每一根K线上都调用此函数。
// 此外,函数的输出取决于TradingView在当前图表上加载的K线数量。
GetChartHighest(dataSeries = high) =>
    var chartHighest = dataSeries
    if dataSeries > nz(chartHighest, -1e10)
        chartHighest := dataSeries
    chartHighest

这个自定义的 GetChartHighest() 函数有一个参数:dataSeries。它用于指定我们要从哪个数据序列中寻找图表的最高值。

这个函数的妙处在于,它不限制我们传入的数据类型。GetChartHighest() 可以处理最高价、收盘价、移动平均线值、成交量值,或者任何我们想从中寻找最高值的数据。如果我们不告诉函数要使用什么数据,它会默认使用K线的最高价 (high)。

在函数内部,我们首先创建了 chartHighest 变量。该变量使用了 var 关键字来声明,这使得它的值可以在不同的K线之间保持不变(即持久化),即使我们不在每一根K线上都更新它。当这个变量在图表的第一根K线上被创建时,它会取用 dataSeries 参数当时的那个值作为其初始默认值。

接下来,一个 if 语句会判断当前的 dataSeries 值是否高于已经存储在 chartHighest 变量中的值。这个检查会在每一根K线上都发生,这正是我们收集图表最高值的方式。

为了处理 chartHighest 变量在初始阶段可能出现的 na(不可用)值,我们使用了 nz() 函数。当该变量确实为 na 时,nz() 会返回一个我们指定的默认值,这里是 -1e10(即-100亿)。这样可以确保即使在初始阶段,我们的比较逻辑也能正常工作,因为几乎任何有效的数值都会比这个极小值要大。

dataSeries 的值大于 (>) chartHighest 变量(或-100亿)时,if语句就会执行,将 chartHighest 变量更新为当前 dataSeries 的值。通过这种方式,该变量会随着每一个新高的出现而更新。

GetChartHighest() 函数的最后一行只是 chartHighest 变量本身,这意味着函数会将该变量的当前值作为计算结果返回。

重要提示:图表最高值 ≠ 历史最高价

该函数返回的是当前图表日期范围内的最高值。这个范围取决于图表加载了多少根K线。并非每个图表和时间周期都能显示某个交易品种的全部历史数据。

因此,图表的最高值并不等同于该品种的历史最高价 (all-time high)。虽然有时人们会这样称呼,但这只在图表恰好包含了该品种完整历史的情况下才准确,而这种情况在日内图表上几乎从未发生。

如何使用该函数

让我们看看如何应用上面创建的函数。要获取整个图表的最高高点,我们无需提供任何参数,直接调用 GetChartHighest() 即可:

// 返回图表的最高高点
chartHigh = GetChartHighest()

该函数也可以返回任何变量的最高值。假设我们计算了一个振荡器指标,并将其值储存在 myOscillator 变量中。要获取这个变量在整个图表上的最高值,我们可以像这样使用 GetChartHighest()

// 返回 'myOscillator' 变量
// 在图表上曾达到的最高值
oscChartHigh = GetChartHighest(myOscillator)

不仅如此,GetChartHighest() 函数的参数也不限于变量,我们还可以直接传入一个函数(只要该函数返回的是一个时间序列数据)。GetChartHighest() 随后会返回该函数计算结果的最高值。

例如,要获取图表上20周期指数移动平均线的历史最高值,我们可以这样做:

// 返回图表上20周期EMA的最高值
emaChartHigh = GetChartHighest(ta.ema(close, 20))

重要提示:必须在每根K线上执行

为了得到准确的结果,GetChartHighest() 函数必须在每一根K线上都执行。如果它在某根K线上没有运行,它就会错过那根K线的数据。假如那根K线恰好创下了新高,函数将对此一无所知,这就会导致函数返回不正确的结果。

为了确保函数在每根K线上都执行,我们不应将其放置在 if 语句、switch 语句或循环的内部。相反,我们应该从代码的顶层(即没有缩进的代码块)调用它。


完整示例脚本

让我们在一个完整的脚本中应用 GetChartHighest() 函数。下方的指标使用 GetChartHighest() 函数来获取图表的最高高点最高EMA以及周五的最高收盘价。我们在图表上将这些价格水平绘制出来。

该脚本的完整代码如下:

//@version=5
indicator(title="Highest value of chart", overlay=true)

// GetChartHighest() 函数返回给定序列在整个图表上的最高值。
// `dataSeries` 参数是可选的,如果未提供,则默认使用K线的最高价。
// 注意:为保证计算的一致性,请在每一根K线上都调用此函数。
// 此外,函数的输出取决于TradingView在当前图表上加载的K线数量。
GetChartHighest(dataSeries = high) =>
    var chartHighest = dataSeries
    if dataSeries > nz(chartHighest, -1e10)
        chartHighest := dataSeries
    chartHighest

// 显示图表的最高高点
plot(series=GetChartHighest(), color=color.green, 
     linewidth=2, title="Highest High Chart")

// 显示最高的20周期EMA值
emaValue = ta.ema(close, 20)
plot(series=emaValue, color=color.orange, title="EMA")
plot(series=GetChartHighest(emaValue), color=color.fuchsia,
     linewidth=2, title="Highest EMA Chart")

// 绘制在周五达到的最高收盘价
closeFriday = dayofweek == dayofweek.friday ? close : na
plot(series=GetChartHighest(closeFriday), color=color.teal, 
     linewidth=2, title="Highest Friday Close Chart")

代码解析

首先,代码使用 indicator() 函数来配置指标,这行代码为脚本命名,并将其叠加显示在主图的交易品种上。

然后,我们包含了上文讨论过的 GetChartHighest() 自定义函数。

接下来,我们绘制图表的最高高点:

// 显示图表的最高高点
plot(series=GetChartHighest(), color=color.green, 
     linewidth=2, title="Highest High Chart")

这里,plot() 函数在图表上显示 GetChartHighest() 的返回值。由于我们没有向函数提供任何数据序列,它会默认使用图表的最高价 (high)。这些最高价以一条绿色 (color.green) 的常规线图形式显示。

接着,我们绘制图表上指数移动平均线的历史最高值:

// 显示最高的20周期EMA值
emaValue = ta.ema(close, 20)
plot(series=emaValue, color=color.orange, title="EMA")
plot(series=GetChartHighest(emaValue), color=color.fuchsia,
     linewidth=2, title="Highest EMA Chart")

这里,ta.ema() 函数首先计算了20周期的指数移动平均线。我们将计算结果存入 emaValue 变量。然后,第一个 plot() 函数将这条EMA本身绘制在图表上。

为了获取这条移动平均线的历史最高值,我们调用 GetChartHighest() 并将 emaValue 变量作为参数传入。我们再用一个 plot() 函数将返回的图表最高EMA值,以一条紫红色 (color.fuchsia) 的线显示出来。

接下来,我们获取只在周五这一天达到的历史最高收盘价:

// 绘制在周五达到的最高收盘价
closeFriday = dayofweek == dayofweek.friday ? close : na
plot(series=GetChartHighest(closeFriday), color=color.teal, 
     linewidth=2, title="Highest Friday Close Chart")

代码解析

这段代码首先创建了 closeFriday 变量。它的值是通过条件运算符 (?:) 来设定的。该运算符会检查当前K线的星期 (dayofweek) 是否等于 (==) 星期五 (dayofweek.friday)。

  • 如果条件为真(即当前K线是周五),运算符会返回当天的收盘价 close
  • 对于一周中的其他日子,运算符则返回 na(代表不可用或空值)。

顺便一提,选择周五这个条件是随机的。我们完全可以选择一周中的任何其他日子,或者使用一个完全不同的条件。这段代码的重点在于演示,即使一个数据序列中包含 na 值的断点,GetChartHighest() 函数依然能够正确地跟踪其最高值。

最后,为了显示这个周五最高收盘价,我们调用 plot() 函数,它会将 GetChartHighest()closeFriday 这个特殊序列中找到的最高值绘制出来。

当这个指标在图表上运行时,它会同时绘制出最高高点、最高EMA和周五的最高收盘价。效果如下:

如何获取整个图表范围内的最低值?

TradingView图表通常会覆盖很广的日期范围,K线数量可达数万根。在如此海量的数据中,用肉眼很难找出图表上的最低点。那么,我们该如何让Pine Script代码帮我们找到这个值呢?

要获取图表的最低值,我们本质上需要做两件事:

  1. 创建一个变量,用于持续存储最低值。
  2. 当出现一个比已存储值更低的数值时,就用这个新的最低值来更新我们的变量。

通过在每次图表达到更低值时都更新这个变量,当脚本运行到图表末尾时,我们也就得到了整个图表范围内的最低值。接下来看看实现这一目标所需的代码。


实现图表最低值的自定义函数

要获取图表的最低值,我们可以使用下面的这个自定义函数:

// GetChartLowest() 函数返回给定序列在整个图表上的最低值。
// `dataSeries` 参数是可选的,如果未提供,则默认使用K线的最低价。
// 注意:为保证计算的一致性,请在每一根K线上都调用此函数。
// 此外,函数的输出取决于TradingView在当前图表上加载的K线数量。
GetChartLowest(dataSeries = low) =>
    var chartLowest = dataSeries
    if dataSeries < nz(chartLowest, 1e10)
        chartLowest := dataSeries
    chartLowest

这个自定义的 GetChartLowest() 函数有一个参数:dataSeries。它用于指定我们要从哪个数据序列中寻找最低值。

这个函数的实用之处在于,它不限制我们传入的数据类型。GetChartLowest() 可以处理最低价、收盘价、移动平均线值、成交量值,或任何我们想要分析的数据。即便是包含 na 值的序列也没有问题。如果我们不告诉函数要使用什么数据,它会默认使用K线的最低价 (low)。

函数内部的代码首先创建了 chartLowest 变量。这个变量用于跟踪函数参数的最低值。它是一个 var 变量,这意味着它的值可以在不同的K线之间保持不变(即持久化)。该变量的初始值是 dataSeries,它在图表的第一根K线上从函数的参数中获取这个初始值。

然后,一个 if 语句会判断当前的 dataSeries 值是否小于 (<) chartLowest 变量。为了处理 chartLowest 变量在初始阶段可能出现的 na(不可用)值,我们使用了 nz() 函数。当它确实为na时,nz() 会返回一个我们指定的默认值,这里是 1e10(即100亿)。通过使用一个极大的数作为默认值,可以确保任何有效的 dataSeries 数值都比它小,从而在第一次比较时成功捕获初始最低值。

当参数的值确实小于图表的最低值(或100亿)时,我们就将 chartLowest 变量更新为当前 dataSeries 的值。通过这种方式,该变量会随着每一个新低的出现而更新。

GetChartLowest() 函数的最后一行只是 chartLowest 变量本身,这意味着函数会将该变量的当前值作为计算结果返回。

重要提示:图表最低值 ≠ 历史最低价

该函数返回的是当前图表日期范围内的最低值。这个范围取决于图表加载了多少根K线。并非每个图表和时间周期都能包含某个交易品种的全部历史数据。

因此,图表的最低值并不等同于该品种的历史最低价 (all-time low)。

虽然有时人们会将图表的最低低点称为历史最低价,但这通常是不准确的。只有在日线及以上的时间周期,图表才更有可能包含完整的价格历史。

如何使用该函数

让我们看看如何应用上面创建的函数。如果我们想要图表的最低低点,我们只需直接调用 GetChartLowest() 而不提供任何参数即可:

// 返回图表的最低低点
chartLow = GetChartLowest()

该函数也可以返回任何变量的最低值。假设我们计算了一条移动平均线,并将其值储存在 myAverage 变量中。要获取这条移动平均线在整个图表上的历史最低值,我们可以这样做:

// 返回 'myAverage' 变量
// 在图表上曾达到的最低值
avgChartLow = GetChartLowest(myAverage)

不仅如此,GetChartLowest() 函数的参数也不限于变量,我们还可以直接传入一个函数(只要该函数返回的是一个时间序列数据)。GetChartLowest() 随后会返回该函数计算结果的最低值。

例如,要获取整个图表上20周期指数移动平均线的历史最低值,我们可以这样做:

// 返回图表上20周期EMA的最低值
emaChartLow = GetChartLowest(ta.ema(close, 20))

重要提示:必须在每根K线上执行

为了得到准确的结果,GetChartLowest() 函数必须在每一根K线上都执行。如果它在某根K线上没有运行,它就会错过那根K线的数据。假如那根K线恰好创下了新低,函数将对此一无所知,这就会导致函数返回不正确的结果。

为了确保函数在每根K线上都执行,我们不应将其放置在 if 语句、switch 语句或循环的内部。相反,我们应该从代码的顶层(即没有缩进的代码块)调用它。


完整示例脚本

让我们看看一个脚本如何应用上面的函数。下方的指标使用 GetChartLowest() 函数来获取图表的最低低点最低EMA以及周五的最低收盘价。我们在图表上将这些最低水平绘制出来。

该指标的完整代码如下:

//@version=5
indicator(title="Lowest value of chart", overlay=true)

// GetChartLowest() 函数返回给定序列在整个图表上的最低值。
// `dataSeries` 参数是可选的,如果未提供,则默认使用K线的最低价。
// 注意:为保证计算的一致性,请在每一根K线上都调用此函数。
// 此外,函数的输出取决于TradingView在当前图表上加载的K线数量。
GetChartLowest(dataSeries = low) =>
    var chartLowest = dataSeries
    if dataSeries < nz(chartLowest, 1e10)
        chartLowest := dataSeries
    chartLowest

// 显示图表的最低低点
plot(series=GetChartLowest(), color=color.red,
     linewidth=2, title="Lowest Low Chart")

// 显示图表的最低20周期EMA值
emaValue = ta.ema(close, 20)
plot(series=emaValue, color=color.orange, title="EMA")
plot(series=GetChartLowest(emaValue), color=color.fuchsia,
     linewidth=2, title="Lowest EMA Chart")

// 绘制图表在周五达到的最低收盘价
closeFriday = dayofweek == dayofweek.friday ? close : na
plot(series=GetChartLowest(closeFriday), color=color.teal, 
     linewidth=2, title="Lowest Friday Close Chart")

代码解析

这个脚本首先使用 indicator() 函数来配置其设置,这行代码命名了指标并将其叠加显示在主图的交易品种上。

然后,我们包含了上文讨论过的 GetChartLowest() 自定义函数。

接下来,我们绘制图表的最低低价:

// 显示图表的最低低点
plot(series=GetChartLowest(), color=color.red,
     linewidth=2, title="Lowest Low Chart")

这段代码使用 plot() 函数创建了一个绘图。绘制的数据来源于 GetChartLowest() 的返回值。由于没有提供参数,该函数默认返回图表的最低低点。plot() 函数将这个价格以一条红色 (color.red) 的常规线图形式显示出来。

然后,我们显示最低的指数移动平均线:

// 显示图表的最低20周期EMA值
emaValue = ta.ema(close, 20)
plot(series=emaValue, color=color.orange, title="EMA")
plot(series=GetChartLowest(emaValue), color=color.fuchsia,
     linewidth=2, title="Lowest EMA Chart")

首先,ta.ema() 函数计算了20周期的EMA,并将其结果存入 emaValue 变量。第一个 plot() 函数将这条EMA本身绘制成一条橘色 (color.orange) 的线图。

为了显示图表上EMA的历史最低值,我们创建了第二个绘图。这次,我们绘制的是 GetChartLowest() 函数的返回值,该函数在这里处理 emaValue 变量。那条紫红色 (color.fuchsia) 的线告诉我们最低的移动平均线值是多少。

代码的最后一部分,用于显示图表上只在周五这一天达到的历史最低收盘价:

// 绘制图表在周五达到的最低收盘价
closeFriday = dayofweek == dayofweek.friday ? close : na
plot(series=GetChartLowest(closeFriday), color=color.teal, 
     linewidth=2, title="Lowest Friday Close Chart")

代码解析

这段代码首先创建了 closeFriday 变量。它的值是通过条件运算符 (?:) 来设定的。该运算符会检查当前K线的星期 (dayofweek) 是否等于 (==) 星期五 (dayofweek.friday)。

  • 如果条件为真(即当前K线是周五),运算符会返回当天的收盘价 close
  • 对于一周中的其他日子,运算符则返回 na(代表不可用或空值)。

顺便一提,选择周五这个条件是随机的,周五的收盘价本身并无特殊之处。这段代码的重点在于演示,即使一个数据序列中包含 na 值的断点,GetChartLowest() 函数依然能够正确地跟踪其最低值。

为了显示这个周五最低收盘价,我们调用 plot() 函数,它会将 GetChartLowest()closeFriday 这个特殊序列中找到的最低值绘制出来。这些最低值以一条青色 (color.teal) 的线图形式显示。

当这个指标在图表上运行时,它会同时显示图表的最低低点、最低移动平均线以及周五的最低收盘价。对于比特币 (BTCUSD) 来说,这些价格与当前价格相距甚远:

如何获取品种的历史最高价(All-Time High)?

TradingView图表覆盖了广泛的历史数据,某些交易品种(如SPX,即标普500指数)的数据甚至可以追溯到120多年前。然而,图表本身有K线数量的加载上限。这使得在日内图表上,我们常常无法加载到足够的历史数据来看到该品种的真正历史最高价。但事实果真如此吗?

借助一小段代码,TradingView脚本可以在后台加载更高时间周期的数据。当我们的代码分析这些数据时,便能找到该品种的历史最高价(All-Time High)。接下来,就让我们一探究竟!


实现历史最高价的自定义函数

下面的这两个自定义函数,能够返回一个交易品种的历史最高价:

// GetChartHighest() 函数返回给定序列在当前图表加载范围内的最高值。
// `dataSeries` 参数是可选的,如果未提供,则默认使用K线的最高价。
// 注意:为保证计算的一致性,请在每一根K线上都调用此函数。
// 此外,函数的输出取决于TradingView在当前图表上加载的K线数量。
GetChartHighest(dataSeries = high) =>
    var chartHighest = dataSeries
    if dataSeries > nz(chartHighest, -1e10)
        chartHighest := dataSeries
    chartHighest

// GetAllTimeHigh() 函数返回给定序列的历史最高价。
// 为保证计算的一致性,请在每一根K线上都调用此函数。
// 函数的结果取决于TradingView为该品种和交易所组合提供了多少历史数据。
GetAllTimeHigh(dataSeries = high) =>
    highestHTF = request.security(syminfo.tickerid, "D", 
         GetChartHighest(dataSeries)[1], lookahead=barmerge.lookahead_on)
    highestChartTF = GetChartHighest(dataSeries)
    math.max(highestHTF, highestChartTF)

这段代码中的第一个函数是 GetChartHighest()。它用于返回给定数据序列在当前图表加载范围内的最高值。我们已在获取图表的最高值一文中详细讨论过,因此本节课我们将重点关注另一个函数。

GetAllTimeHigh() 函数接受一个参数:dataSeries。它用于指定我们要从哪个数据序列中获取历史最高价。例如,我们可以传入收盘价、成交量值或一条移动平均线,函数便会返回这些值的历史最高。如果我们不设置这个参数,函数将默认使用当前品种的最高价 (high)。

在函数内部,我们首先使用 request.security() 函数从更高的时间周期获取数据。我们向该函数传递了几个参数:

  • 第一个参数 syminfo.tickerid,代表了当前图表的交易代码及其交易所,确保函数加载的是完全相同的品种。
  • 第二个参数 "D",告诉函数去获取日线数据。
  • 第三个参数是对 GetChartHighest() 函数的调用。这使得该函数在 request.security() 加载的日线数据环境中进行计算,从而找到日线级别的最高值。

为了防止 request.security() 函数产生重绘(repainting),我们做了两件事:在 GetChartHighest() 后面使用历史引用运算符 [1],这使我们获取的是截至前一个交易日的数据;而 lookahead=barmerge.lookahead_on 则将日线数据的时间与当前图表的K线进行同步。

request.security() 完成工作后,highestHTF 变量就持有了来自日线时间周期的最高值。然后,我们创建另一个变量 highestChartTF,通过再次调用 GetChartHighest() 函数来获取当前图表时间周期内的最高值。

函数的最后一行,让 math.max() 函数返回日线周期最高值 (highestHTF) 和当前图表最高值 (highestChartTF) 中的较大者。因为我们加载的日线数据截至昨天,所以今天有可能会创出新的历史新高。通过取两个时间周期的最高值,我们就考虑到了这种可能性。


如何使用该函数

让我们看看如何应用这个函数。如果我们想获取历史最高价,我们无需提供任何参数,直接调用 GetAllTimeHigh() 即可:

// 获取历史最高价
allTimeHigh = GetAllTimeHigh()

如果我们想获取该品种有史以来的最高收盘价,可以这样做:

// 获取历史最高收盘价
allTimeCloseHigh = GetAllTimeHigh(close)

因为 GetAllTimeHigh() 结合了日线数据和当前图表数据,所以这个历史最高收盘价不一定等于最高的日线收盘价。如果今天创下了新的历史新高,那么它返回的就是当前图表的最高收盘价。

我们也可以从一个自定义变量中获取最高值。例如,下面的代码获取了20周期移动平均线的历史最高值:

// 计算20周期指数移动平均线
emaValue = ta.ema(close, 20)

// 获取EMA的历史最高值(基于日线数据)
allTimeEmaHigh = GetAllTimeHigh(emaValue)

完整示例脚本

让我们看看如何在完整的指标中应用上述两个函数。下面的指标会在图表上绘制出该品种的历史最高价:

//@version=5
indicator(title="All-time high", overlay=true)

// GetChartHighest() 函数返回给定序列在当前图表加载范围内的最高值。
// `dataSeries` 参数是可选的,如果未提供,则默认使用K线的最高价。
// 注意:为保证计算的一致性,请在每一根K线上都调用此函数。
// 此外,函数的输出取决于TradingView在当前图表上加载的K线数量。
GetChartHighest(dataSeries = high) =>
    var chartHighest = dataSeries
    if dataSeries > nz(chartHighest, -1e10)
        chartHighest := dataSeries
    chartHighest

// GetAllTimeHigh() 函数返回给定序列的历史最高价。
// 为保证计算的一致性,请在每一根K线上都调用此函数。
// 函数的结果取决于TradingView为该品种和交易所组合提供了多少历史数据。
GetAllTimeHigh(dataSeries = high) =>
    highestHTF = request.security(syminfo.tickerid, "D", 
         GetChartHighest(dataSeries)[1], lookahead=barmerge.lookahead_on)
    highestChartTF = GetChartHighest(dataSeries)
    math.max(highestHTF, highestChartTF)

// 获取并绘制历史最高价
plot(series=GetAllTimeHigh(), color=color.green, linewidth=2, 
     title="All-Time High")

首先,我们使用 indicator() 函数来配置指标,这行代码为脚本命名,并将其叠加显示在主图的交易品种上。

然后,我们将上文讨论过的 GetAllTimeHigh()GetChartHighest() 这两个自定义函数复制粘贴到脚本中。

在这之后,我们调用 plot() 函数。我们让 plot() 函数在图表上显示 GetAllTimeHigh() 函数的返回值。因为我们没有指定要分析哪个数据序列,GetAllTimeHigh() 会默认使用该品种的最高价 (high)。我们用一条绿色 (color.green) 的常规线图来显示这些值。


图表示例

让我们通过几张图表来看看这个指标的实际效果。在这张JSC (纳斯达克) 股票的5分钟图上,指标显示其历史最高价为24.00:

有趣的是,24.00这个高点本身并没有出现在这张5分钟图表上。这是因为我们的脚本在后台加载了日线数据,因此它能访问到比当前图表所显示的更多的数据。

如果我们切换到日线图,就会发现24.00这个高点其实发生在20多年前,即2000年3月10日互联网泡沫时期:

对于其他交易品种,该脚本同样能成功地找到其历史最高价。以可口可乐 (KO) 为例,在一张3分钟图表上,我们看到其历史最高价为60.13:

这个高点同样没有出现在这张日内图表上。不过,对于可口可乐的股东来说幸运的是,这个高点发生在不算太久的2020年2月21日:

当然,如果一个交易品种最近才刚刚创下其历史新高,那同样也会被我们的脚本准确地捕捉到:

重要提示:数据源的选择至关重要

并非每个交易品种和交易所的组合都拥有同样多的历史数据。有时,同一个TradingView品种,在某个特定交易所的数据会比在其他交易所的要丰富得多。

例如,在撰写本文时,比特币 (BTCUSD) 在 BITSTAMP 交易所的数据可以追溯到2011年8月。但 COINBASE 的BTCUSD数据仅从2014年12月才开始。而 INDEX:BTCUSD 的数据则从2010年7月开始。

因此,要分析真正的历史最高价(或历史最低价),一个明智的做法是,先比较并找出哪个数据源在图表上提供了最广的数据范围和最长的历史数据。否则,我们可能会在不知不觉中错失了好几年的宝贵数据。

如何获取品种的历史最低价(All-Time Low)?

TradingView图表拥有海量的价格数据,图表的时间周期越大,其覆盖的日期范围也越广。某些交易品种的数据甚至可以追溯到100多年前!

然而,在日内图表上我们是看不到这么多数据的。因为TradingView在图表上只会显示一定数量的K线,所以价格数据的总量是有限的。这意味着我们常常无法看到一个交易品种的真正历史最低价。

幸运的是,Pine Script可以在后台加载数据。这让我们能够从当前图表访问到更高时间周期的数据。借此,交易品种的历史最低价(All-Time Low) 便触手可及。接下来,就让我们看看如何实现。


实现历史最低价的自定义函数

下面的这两个自定义函数,能够返回一个交易品种的历史最低价:

// GetChartLowest() 函数返回给定序列在当前图表加载范围内的最低值。
// `dataSeries` 参数是可选的,如果未提供,则默认使用K线的最低价。
// 注意:为保证计算的一致性,请在每一根K线上都调用此函数。
// 此外,函数的输出取决于TradingView在当前图表上加载的K线数量。
GetChartLowest(dataSeries = low) =>
    var chartLowest = dataSeries
    if dataSeries < nz(chartLowest, 1e10)
        chartLowest := dataSeries
    chartLowest

// GetAllTimeLow() 函数返回给定序列的历史最低价。
// 为保证计算的一致性,请在每一根K线上都调用此函数。
// 函数的结果取决于TradingView为该品种和交易所组合提供了多少历史数据。
GetAllTimeLow(dataSeries = low) =>
    lowestHTF = request.security(syminfo.tickerid, "D", 
         GetChartLowest(dataSeries)[1], lookahead=barmerge.lookahead_on)
    lowestChartTF = GetChartLowest(dataSeries)
    math.min(lowestHTF, lowestChartTF)

这里的第一个函数是 GetChartLowest()。它用于返回当前图表加载范围内的最低值。我们已在获取图表的最低值一文中详细讨论过,因此本节课我们将重点关注另一个函数。

GetAllTimeLow() 函数接受一个参数:dataSeries。它用于指定我们要从哪个数据序列中获取历史最低价。我们可以传入收盘价、成交量值,甚至是移动平均线。如果我们不设置这个参数,函数将默认使用当前品种的最低价 (low),从而得到历史最低价。

函数内部做的第一件事,就是使用 request.security() 函数来加载更高时间周期的数据。我们向该函数传递了几个参数:

  • 第一个参数 syminfo.tickerid,代表了当前图表的交易代码及其交易所,确保函数加载的是完全相同的品种。
  • 第二个参数 "D",告诉函数去获取日线数据。
  • 第三个参数是对 GetChartLowest() 函数的调用。这使得该函数在 request.security() 加载的日线数据环境中进行计算,从而找到日线级别的最低值。

为了防止 request.security() 函数产生重绘(repainting),我们做了两件事:在 GetChartLowest() 后面使用历史引用运算符 [1],这使我们获取的是截至前一个交易日的数据;而 lookahead=barmerge.lookahead_on 则将日线数据的时间与当前图表的K线进行同步。

request.security() 完成工作后,lowestHTF 变量就持有了来自日线时间周期的历史最低值。然后,我们创建另一个变量 lowestChartTF,通过再次调用 GetChartLowest() 函数来获取当前图表时间周期内的最低值。

函数的最后一行,让 math.min() 函数返回日线周期最低值 (lowestHTF) 和当前图表最低值 (lowestChartTF) 中的较小者。因为我们加载的日线数据截至昨天,所以今天有可能会创出新的历史新低。通过取两个时间周期的最低值,我们就考虑到了这种可能性。

如何使用该函数

让我们看看如何应用这个函数。如果我们想获取历史最低价,我们无需提供任何参数,直接调用 GetAllTimeLow() 即可:

// 获取历史最低价
allTimeLow = GetAllTimeLow()

如果我们对该品种有史以来的最低收盘价感兴趣,可以这样做:

// 获取历史最低收盘价(基于日线数据)
allTimeCloseLow = GetAllTimeLow(close)

由于 GetAllTimeLow() 结合了日线数据和当前图表数据,所以这个历史最低收盘价不一定等于最低的日线收盘价。如果今天创下了新的历史新低,那么它返回的就是当前图表的最低收盘价。

从一个自定义变量中获取最低值也是可行的。例如,获取一条20周期移动平均线的历史最低值:

// 计算20周期指数移动平均线
emaValue = ta.ema(close, 20)

// 获取EMA的历史最低值(基于日线数据)
allTimeEmaLow = GetAllTimeLow(emaValue)

完整示例脚本

让我们看看如何在完整的脚本中应用 GetAllTimeLow() 函数。下面的指标会在图表上绘制出该品种的历史最低价:

//@version=5
indicator(title="All-time low", overlay=true)

// GetChartLowest() 函数返回给定序列在当前图表加载范围内的最低值。
// `dataSeries` 参数是可选的,如果未提供,则默认使用K线的最低价。
// 注意:为保证计算的一致性,请在每一根K线上都调用此函数。
// 此外,函数的输出取决于TradingView在当前图表上加载的K线数量。
GetChartLowest(dataSeries = low) =>
    var chartLowest = dataSeries
    if dataSeries < nz(chartLowest, 1e10)
        chartLowest := dataSeries
    chartLowest

// GetAllTimeLow() 函数返回给定序列的历史最低价。
// 为保证计算的一致性,请在每一根K线上都调用此函数。
// 函数的结果取决于TradingView为该品种和交易所组合提供了多少历史数据。
GetAllTimeLow(dataSeries = low) =>
    lowestHTF = request.security(syminfo.tickerid, "D", 
         GetChartLowest(dataSeries)[1], lookahead=barmerge.lookahead_on)
    lowestChartTF = GetChartLowest(dataSeries)
    math.min(lowestHTF, lowestChartTF)

// 获取并绘制历史最低价
plot(series=GetAllTimeLow(), color=color.red, 
	 linewidth=2, title="All-Time Low")

代码解析

首先,我们使用 indicator() 函数来设置指标属性。这行代码为脚本命名,并将其叠加显示在主图的交易品种上。

然后,我们包含了上文提到的 GetChartLowest()GetAllTimeLow() 这两个自定义函数。

接下来,plot() 函数在图表上显示 GetAllTimeLow() 函数的返回值。因为我们没有向 GetAllTimeLow() 提供要处理的数据序列,它会默认使用该品种的最低价 (low)。这个绘图以一条红色 (color.red) 的常规线图形式显示。


图表示例

让我们看看上面的指标实际效果如何。当我们将它添加到特斯拉 (TSLA) 的15分钟图表上时,它会告诉我们,其历史最低低点是3.00:

有趣的是,3.00这个数值,与这张日内图表上显示的所有数据都相去甚远。这正是因为我们的脚本在后台加载了更高时间周期的数据,从而访问到了远超当前图表范围的价格信息。

如果我们切换到特斯拉的月线图,就会发现这个历史最低价其实发生在2010年7月:

类似的情况也发生在其他交易品种上。以标普500指数 (SPX) 为例,在一张5分钟图表上,该品种报告的历史最低价为2.73:

这个低点同样从未出现在这张日内图表上。所以让我们切换到更高的时间周期,看看这个低点究竟发生在何时。在日线图上,我们发现这个历史最低价可以追溯到1877年6月1日:

重要提示:数据源的选择至关重要

并非每个交易品种和交易所的组合都拥有同样多的历史数据。有时,同一个TradingView品种,在某个特定交易所的数据会比在其他交易所的要少得多。

在我撰写本文时,比特币 (BTCUSD) 在 BITSTAMP 交易所的数据可以追溯到2011年8月。但 COINBASE 的BTCUSD数据仅从2014年12月才开始。而 INDEX:BTCUSD 的数据则从2010年7月开始。

如果我们想找到比特币真正的历史最低价,我们就不能使用BITSTAMP和COINBASE这两个交易所,因为它们根本没有提供足够的历史数据。

这表明,在分析前,比较并找出哪个数据源在图表上提供了最广的数据范围和最长的历史数据,是至关重要的一步。否则,我们可能会在不知不觉中错失了好几年宝贵的数据。

赞(0)
未经允许不得转载:图道交易 » Pine Script(102):图表极值与历史最高最低价,后台加载日线数据实战
分享到

评论 抢沙发

登录

找回密码

注册