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

Pine Script(224):设置回测的开始与结束日期

#Pine Script入门教学

设置策略回测的开始日期和时间

默认情况下,一个TradingView策略会对图表上所有可用的历史数据进行回测,这能让我们的交易思路在尽可能多的数据上得到检验。但有时,自定义回测的开始日期也大有裨益。

它能让我们在几周后还能重复进行完全相同的回测,而如果让TradingView自动决定开始日期,这一点就无法保证。我们还可以测试策略如果从某个特定日期开始运行,其表现会有何不同。此外,我们还可以通过这种方式减少回测的订单总数,考虑到TradingView的订单限制,这有时是必要的。

下面,就让我们来看看如何让一个策略从指定的日期开始运行。

设置回测开始日期的步骤

要让一个策略从特定日期开始运行,需要完成三件事:创建用于指定开始日期的输入选项;判断当前K线的时间是否晚于或等于设定的开始日期;确保只有在达到或超过该时间点后,才提交开仓订单。让我们来逐一探讨。

第一步,创建开始日期输入选项。为策略添加一个开始日期的输入选项,可以让我们在不修改代码的情况下,方便地调整这个日期。同时,再增加一个用于启用或禁用日期过滤功能的复选框也会很有用。要创建这两个输入选项,我们可以将以下代码添加到策略中:

// 步骤 1. 创建用于配置回测开始日期的输入选项
useDateFilter = input.bool(true, title="从指定日期开始回测",
     group="回测时间段")
backtestStartDate = input.time(timestamp("1 Jan 2021"), 
     title="开始日期", group="回测时间段",
     tooltip="此开始日期基于图表交易品种所在交易所的时区。" + 
     "它不使用图表或您电脑设置的时区。")

这里,input.bool() 函数创建了一个复选框,用于控制策略是否需要等待到指定的开始日期才开始交易,它默认是启用的(true)。我们用 useDateFilter 变量来跟踪它的状态。

input.time() 函数则创建了一个日期和时间输入框。我们通过 timestamp() 函数和日期字符串将其默认值设为2021年1月1日。当然,你可以根据需要自由修改。这个输入框的值被存储在 backtestStartDate 变量中。

为了让设置界面更美观,我们使用了 group 参数来为这两个选项创建一个分组标题,并用 tooltip 为开始日期输入框添加了详细的提示说明。这些输入选项在设置界面中看起来是这样的:

第二步,判断当前是否在交易窗口内。设置好开始日期后,我们现在需要判断当前K线的时间是否已经达到或超过了这个日期。如果是,策略才被允许进行交易,否则就不应下单。我们可以通过下面这段代码来实现这个判断:

// 步骤 2. 判断当前K线是否在开始日期或之后
inTradeWindow = not useDateFilter or time >= backtestStartDate

这段代码创建了一个布尔(真/假)变量 inTradeWindow。在两种情况下它的值会是 true:一是用户没有勾选”从指定日期开始回测”的复选框,那么 not useDateFilter 的结果就是 true,这会使得 inTradeWindow 在每根K线上都为 true,策略可以不受限制地进行交易;二是用户勾选了复选框,那么 time >= backtestStartDate 这个条件就必须为 true——这个表达式判断当前K线的开盘时间(time)是否大于或等于我们设定的开始日期(backtestStartDate),如果条件满足,inTradeWindow 也会是 true,策略被允许交易。

值得注意的是,内置变量 time 和时间输入框使用的都是交易品种所在交易所的时区,因此我们可以直接对它们进行比较。如果你图表上显示的时区与交易所时区不同,你可能会觉得策略的开始日期看起来有偏差,这是正常的时区差异所致。

第三步,过滤开仓订单。现在,我们有了一个变量 inTradeWindow,它能告诉我们脚本当前是否可以交易。最后一步,就是将这个变量与我们的开仓逻辑关联起来。它看起来可能像这样:

// 步骤 3. 将日期过滤器整合到开仓条件中
if inTradeWindow and ta.crossover(fastEMA, medEMA) and close > slowEMA
    strategy.entry("Enter Long", strategy.long)

if inTradeWindow and ta.crossunder(fastEMA, medEMA) and close < slowEMA
    strategy.entry("Enter Short", strategy.short)

这两个 if 语句通过 strategy.entry() 函数来生成开仓订单。每个 if 的条件都包含了 inTradeWindow 变量。这样,当 inTradeWindowfalse 时(即日期未到),整个条件也为 false,订单也就无法生成。

你的策略代码可能与上面的例子完全不同,但这没关系。你只需将 inTradeWindow 变量作为附加条件加入到你的交易逻辑中,确保开仓只在该变量为 true 时才发生即可。

如果你不熟悉一段策略代码,可以先搜索 strategy.entry()strategy.order() 这两个函数出现的位置,找到它们的触发条件,然后将 inTradeWindow 变量作为附加条件加入其中。

示例策略

让我们看一个完整的策略如何从指定的日期开始回测。下面的脚本基于指数移动平均线(EMA)的交叉进行交易,并使用第三条慢速均线作为过滤。该策略整合了我们上面讨论的所有步骤,使其只在一个特定的日期之后才开始交易。

完整的策略代码如下:

//@version=5
strategy(title="Backtest since date", overlay=true)

// 步骤 1. 创建用于配置回测开始日期的输入选项
useDateFilter = input.bool(true, title="从指定日期开始回测",
     group="回测时间段")
backtestStartDate = input.time(timestamp("1 Jan 2021"), 
     title="Start Date", group="Backtest Time Period",
     tooltip="此开始日期基于图表交易品种所在交易所的时区。")

// 步骤 2. 判断当前K线是否在开始日期或之后
inTradeWindow = not useDateFilter or time >= backtestStartDate

// 计算并绘制 EMA
fastEMA = ta.ema(close, 10)
medEMA  = ta.ema(close, 30)
slowEMA = ta.ema(close, 80)
plot(fastEMA, color=color.orange, title="Fast EMA")
plot(medEMA, color=color.blue, title="Medium EMA")
plot(slowEMA, color=color.teal, linewidth=2, title="Slow EMA")

// 步骤 3. 将日期过滤器整合到开仓条件中
if inTradeWindow and ta.crossover(fastEMA, medEMA) and close > slowEMA
    strategy.entry("Enter Long", strategy.long)
if inTradeWindow and ta.crossunder(fastEMA, medEMA) and close < slowEMA
    strategy.entry("Enter Short", strategy.short)

我们首先用 strategy() 函数命名脚本,然后创建了两个输入选项来配置回测的开始日期(步骤1)。接着,我们判断当前K线是否已经到达开始日期,并将结果存入 inTradeWindow 变量(步骤2)。之后,代码计算并绘制了三条EMA均线。

策略的开仓逻辑由两个 if 语句控制。这两个 if 的条件都包含了 inTradeWindow 变量,这确保了只有在日期过滤器允许的情况下,策略才会根据均线的交叉信号以及慢速均线的过滤条件来开立多头或空头仓位。

让我们看看这个策略在图表上的表现。由于默认的开始日期是2021年1月1日,该策略在21年的第一个月才开始进行交易:

简单总结一下:默认情况下,一个TradingView策略会对图表上的全部历史数据进行回测。但通过一个开始日期过滤器,我们可以指定它应该从何时开始。这需要三个步骤:首先创建输入选项来配置开始日期,然后判断当前K线是否已经达到或晚于该日期,如果是,我们才允许策略提交开仓订单。通过这种方式,策略会一直等待,直到达到我们设定的开始日期才开始交易。

让回测运行到特定日期为止

默认情况下,一个TradingView策略会对图表上所有可用的历史数据进行回测。这非常好,因为它能提供尽可能多的性能数据。但是,能够自定义回测的结束日期同样大有裨益。

它能确保我们重复进行的回测总是使用相同的结束日期,即便TradingView已经为图表增加了新的历史数据。我们也能方便地观察策略在某个特定时间点之前的表现,而无需自己从交易列表中费力计算。此外,它还能防止策略生成的订单数量超出TradingView的限制。

下面,就让我们来看看如何为回测设置一个结束日期吧!

设置回测结束日期的步骤

要让一个策略交易到某个特定的日期和时间为止,需要完成四个步骤:通过输入选项来设置结束日期;判断当前K线的时间是否早于设定的结束日期;确保只有在结束日期之前的K线上才提交开仓订单;当达到结束日期时,平掉所有持有的仓位。下面我们来逐一探讨。

第一步,创建结束日期输入选项。通过输入选项,我们可以在不修改代码的情况下,方便地配置策略的结束日期。同时,再增加一个用于启用或禁用日期过滤功能的复选框也会很有用。要创建这两个输入选项,我们可以将以下代码添加到策略中:

// 步骤 1. 创建用于配置回测结束日期的输入选项
useDateFilter = input.bool(true, title="在指定日期停止回测",
     group="回测时间段")
backtestEndDate = input.time(timestamp("1 Jan 2022"),
     title="结束日期", group="回测时间段",
     tooltip="此结束日期基于图表交易品种所在交易所的时区。" + 
     "它不使用图表或您电脑设置的时区。")

这里 input.bool() 函数创建了一个名为”在指定日期停止回测”的复选框,它默认是启用的(true)。我们用 useDateFilter 变量来跟踪它的状态。

接着,input.time() 函数创建了一个名为”结束日期”的日期和时间输入框。我们通过 timestamp() 函数将其默认值设为2022年1月1日,其值由 backtestEndDate 变量跟踪。为了让设置界面更美观,我们使用了 grouptooltip 参数。

这些输入选项在设置界面中看起来是这样的:

第二步,判断当前是否在交易窗口内。设置好结束日期后,我们现在需要判断当前K线的时间是否已经超过了这个日期。如果还没超过,策略就可以进行交易;反之,则不允许再开新仓。我们可以通过下面这段代码来实现这个判断:

// 步骤 2. 判断当前K线是否在回测结束日期之前
inTradeWindow = not useDateFilter or time < backtestEndDate

这段代码创建了一个布尔(真/假)变量 inTradeWindow。在两种情况下它的值会是 true:一是用户没有勾选”在指定日期停止回测”的复选框,那么 not useDateFilter 的结果就是 true,这会使得 inTradeWindow 在每根K线上都为 true,策略可以不受限制地进行交易;二是用户勾选了复选框,那么 time < backtestEndDate 这个条件就必须为 true——这个表达式判断当前K线的开盘时间(time)是否早于我们设定的结束日期(backtestEndDate),如果条件满足,inTradeWindow 也会是 true

所以,策略要么一直可以交易(当复选框关闭时),要么只在结束日期之前交易(当复选框开启时)。

需要说明的是,内置变量 time 和时间输入框使用的都是交易品种所在交易所的时区,因此我们可以直接对它们进行比较。如果你图表上显示的时区与交易所时区不同,你可能会觉得策略停止的日期看起来有偏差,这是正常的时区差异所致。

第三步,过滤开仓订单。现在,我们有了一个变量 inTradeWindow,它能告诉我们脚本当前是否可以交易。接下来就是将这个变量与我们的开仓逻辑关联起来,否则它们仍会在结束日期之后继续下单。它看起来可能像这样:

// 步骤 3. 在开仓条件中应用日期过滤器
if inTradeWindow and stDirection < 0 and stDirection[1] > 0
    strategy.entry("Enter Long", strategy.long)

if inTradeWindow and stDirection > 0 and stDirection[1] < 0
    strategy.entry("Enter Short", strategy.short)

这两个 if 语句通过 strategy.entry() 函数来生成开仓订单。每个 if 的条件都包含了 inTradeWindow 变量。这样,当 inTradeWindowfalse 时(即日期已过),整个条件也为 false,订单也就无法生成。

你的策略代码可能与上面的例子完全不同,但这没关系。你只需将 inTradeWindow 变量作为附加条件加入到你的交易逻辑中即可。如果你不熟悉一段策略代码,可以先搜索 strategy.entry()strategy.order() 这两个函数出现的位置,找到它们的触发条件,然后将 inTradeWindow 变量作为附加条件加入其中。

第四步,在日期范围结束时平仓。最后一步,是在达到结束日期后,平掉策略所有持有的仓位。我们可以用下面这段代码来实现:

// 步骤 4. 当回测日期范围结束时,平掉所有持有的
// 仓位并取消所有未成交的挂单
if not inTradeWindow and inTradeWindow[1]
    strategy.cancel_all()
    strategy.close_all(comment="日期范围结束退出")

这个 if 语句用于监测 inTradeWindow 变量从 true 变为 false 的那一刻。这个条件(not inTradeWindow and inTradeWindow[1])只会在日期范围结束后的第一根K线上成立。

在这根K线上,我们执行两个操作:首先,调用 strategy.cancel_all() 来取消所有未成交的挂单;然后,调用 strategy.close_all() 来平掉所有已有的仓位,确保策略不会在结束日期之后还持有头寸。

请注意,这种实现方式并非完美无瑕,它存在一根K线的延迟:我们是在结束日期之后的第一根K线上才生成平仓订单的。但由于PineScript无法预知未来K线的日期,这种方法是在不同时间周期和图表类型上最可靠的实现方式。

示例策略

让我们看一个使用结束日期过滤器的完整策略。下面的脚本基于10周期的超级趋势(SuperTrend)指标进行交易。我们整合了上面讨论的所有步骤,使得这个策略只在一个特定的结束日期前进行交易。

完整的策略代码如下:

//@version=5
strategy(title="Backtest until date", overlay=true)

// 步骤 1. 创建用于配置回测结束日期的输入选项
useDateFilter = input.bool(true, title="在指定日期停止回测",
     group="回测时间段")
backtestEndDate = input.time(timestamp("1 Jan 2022"),
     title="结束日期", group="Backtest Time Period",
     tooltip="此结束日期基于图表交易品种所在交易所的时区。")

// 步骤 2. 判断当前K线是否在回测结束日期之前
inTradeWindow = not useDateFilter or time < backtestEndDate

// 获取超级趋势的方向
[_, stDirection] = ta.supertrend(3.0, 10)

// 步骤 3. 在开仓条件中应用日期过滤器
if inTradeWindow and stDirection < 0 and stDirection[1] > 0
    strategy.entry("Enter Long", strategy.long)
if inTradeWindow and stDirection > 0 and stDirection[1] < 0
    strategy.entry("Enter Short", strategy.short)

// 步骤 4. 当回测日期范围结束时,平掉所有持有的
// 仓位并取消所有未成交的挂单
if not inTradeWindow and inTradeWindow[1]
    strategy.cancel_all()
    strategy.close_all(comment="日期范围结束退出")

我们首先用 strategy() 函数命名脚本,然后创建了两个输入选项来配置回测的结束日期(步骤1)。接着,我们判断当前K线是否早于结束日期,并将结果存入 inTradeWindow 变量(步骤2)。之后,代码计算超级趋势的方向。

接下来的两个 if 语句实现了开仓逻辑(步骤3),它们都包含了 inTradeWindow 作为附加条件,以确保只在结束日期前开仓。策略的最后是一个 if 语句,它实现了步骤4:监控日期范围的结束,并在结束时取消所有挂单并平掉所有持仓。

让我们看看策略的表现。在下方的苹果公司图表上,当K线到达2022年1月1日这个回测结束日期时,策略仍持有一个仓位。此时,策略平掉了该仓位,并完全停止了交易:

简单总结一下:默认情况下,TradingView策略会对图表上的所有K线进行回测。但通过一个自定义的日期过滤器,我们可以让策略在一个指定的日期停止。这需要四段代码:首先创建输入选项来配置结束日期,然后判断当前K线是否发生在那个最终日期之前,接着确保只在那个时间窗口内提交订单,最后当到达结束日期时,停止交易并平掉所有持有的仓位。

赞(0)
未经允许不得转载:图道交易 » Pine Script(224):设置回测的开始与结束日期
分享到

评论 抢沙发

登录

找回密码

注册