为策略订单编写日期范围过滤器
当我们将一个TradingView策略加载到图表时,它默认会对图表上所有可用的历史数据进行回测。这能让我们的交易思路在尽可能多的数据上得到检验。但有时,自定义回测的开始和结束日期也大有裨益。
通过自定义日期,我们可以测试策略在特定市场阶段的表现。同时,这也方便我们在未来使用完全相同的参数重复进行某次回测,以确保结果的可比性。此外,这也是一个限制交易总数的好方法,因为TradingView的回测并非允许无限量的交易。
那么,就让我们在策略中加入一个日期范围过滤器吧!
实现回测日期过滤器的步骤
要实现一个日期范围过滤器,我们需要完成四件事:创建用于指定开始和结束日期的输入选项;判断当前的K线是否落在我们设定的日期范围之内;确保只有在日期范围内才提交开仓订单;当日期范围结束时,平掉所有持有的仓位。下面我们来逐一探讨每个步骤。
第一步,创建日期范围输入选项。为策略添加开始和结束日期的输入选项,可以让我们在不修改代码的情况下,方便地配置日期过滤器。同时,再增加一个用于启用或禁用日期过滤功能的复选框也会很有用。要创建这些输入选项,我们可以将以下代码添加到策略脚本中:
// 步骤 1. 创建用于配置回测日期范围的输入选项
useDateFilter = input.bool(true, title="启用回测日期范围过滤",
group="回测时间段")
backtestStartDate = input.time(timestamp("1 Jan 2021"),
title="开始日期", group="回测时间段",
tooltip="此开始日期基于图表交易品种所在交易所的时区。" +
"它不使用图表或您电脑设置的时区。")
backtestEndDate = input.time(timestamp("1 Jan 2022"),
title="结束日期", group="回测时间段",
tooltip="此结束日期基于图表交易品种所在交易所的时区。" +
"它不使用图表或您电脑设置的时区。")
这段代码创建了三个输入选项。首先,input.bool() 函数创建了一个复选框,用于控制是否启用日期过滤,我们用 useDateFilter 变量来跟踪它的状态。
接着,input.time() 函数创建了两个时间输入框,分别用于设置回测的开始和结束日期。我们使用 timestamp() 函数和日期字符串来设定它们的默认值,你可以根据需要自由修改。这两个输入框的值分别存储在 backtestStartDate 和 backtestEndDate 变量中。
为了让设置界面更美观,我们使用了 group 参数来为这些选项创建一个分组标题,并用 tooltip 为时间输入框添加了详细的提示说明。这些输入选项在设置界面中看起来是这样的:
第二步,判断当前是否在交易窗口内。有了开始和结束日期,我们现在需要判断当前K线是否处于这个设定的时间范围内。如果是,策略才被允许进行交易。我们可以通过下面这段代码来实现这个判断:
// 步骤 2. 判断当前K线是否落在日期范围内
inTradeWindow = not useDateFilter or (time >= backtestStartDate and
time < backtestEndDate)
这段代码创建了一个布尔(真/假)变量 inTradeWindow。在两种情况下它的值会是 true:一是用户没有勾选”启用回测日期范围过滤”的复选框,那么 not useDateFilter 的结果就是 true,这会使得 inTradeWindow 在每根K线上都为 true,策略可以不受限制地进行交易;二是用户勾选了复选框,那么策略只有在当前K线的时间位于设定的日期范围内时才能交易——我们通过 time >= backtestStartDate and time < backtestEndDate 来进行判断,第一个条件检查当前K线的开盘时间是否晚于或等于开始日期,第二个条件则检查其是否早于结束日期。
值得注意的是,内置变量 time 和时间输入框使用的都是交易品种所在交易所的时区。正因如此,我们可以直接对它们进行比较。然而,如果你图表上显示的时区与交易所时区不同,你可能会觉得策略的开始和结束时间看起来有偏差,这是正常的时区差异所致。
第三步,过滤开仓订单。现在,我们有了一个变量 inTradeWindow,它能告诉我们脚本当前是否可以交易。开仓订单的逻辑也需要遵循这个规则,否则它们就会在指定的日期范围之外被触发。所以,我们需要将 inTradeWindow 变量整合到策略的开仓条件中。它看起来可能像这样:
// 步骤 3. 将日期过滤器整合到开仓条件中
if inTradeWindow and high > highestHigh
strategy.entry("Enter Long", strategy.long)
if inTradeWindow and low < lowestLow
strategy.entry("Enter Short", strategy.short)
这里我们有两个 if 语句,分别用于开立多头和空头仓位。下单的条件由一个交易规则(例如 high > highestHigh)和我们上一步创建的 inTradeWindow 变量共同决定。因为我们要求 inTradeWindow 必须为 true 才能生成订单,所以策略就不会在指定的日期范围之外开仓了。
你的策略代码可能与上面的例子完全不同,但这没关系。你只需将 inTradeWindow 变量加入到你的交易条件中,确保开仓订单只在该变量为 true 时才被触发即可。
顺便一提,PineScript中有两个函数可以开仓: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线上才生成平仓订单的,所以实际的平仓时间不会精确地落在你设定的结束日期和时间点上。但由于PineScript无法预知下一根K线的情况,这种等待时间窗口关闭后再行动的方式是最可靠的实现方法。
示例策略
让我们看一个使用日期范围过滤器的完整策略。下面的脚本基于20周期高低点突破进行交易,当价格穿越高低点通道的中线时则退出市场。我们整合了上面讨论的所有步骤,使得这个策略只在特定的日期范围内进行交易。
策略的完整代码如下:
//@version=5
strategy(title="Backtest specific date range", overlay=true)
// 步骤 1. 创建用于配置回测日期范围的输入选项
useDateFilter = input.bool(true, title="启用回测日期范围过滤",
group="回测时间段")
backtestStartDate = input.time(timestamp("1 Jan 2021"),
title="开始日期", group="回测时间段",
tooltip="此开始日期基于图表交易品种所在交易所的时区。")
backtestEndDate = input.time(timestamp("1 Jan 2022"),
title="结束日期", group="回测时间段",
tooltip="此结束日期基于图表交易品种所在交易所的时区。")
// 步骤 2. 判断当前K线是否落在日期范围内
inTradeWindow = not useDateFilter or (time >= backtestStartDate and
time < backtestEndDate)
// 计算并绘制高低点通道及中线
highestHigh = ta.highest(high, 20)[1]
lowestLow = ta.lowest(low, 20)[1]
midPoint = (highestHigh + lowestLow) / 2
plot(highestHigh, color=color.green, title="Highest High")
plot(lowestLow, color=color.red, title="Lowest Low")
plot(midPoint, color=color.blue, title="Middle Line")
// 步骤 3. 将日期过滤器整合到开仓条件中
if inTradeWindow and high > highestHigh
strategy.entry("Enter Long", strategy.long)
if inTradeWindow and low < lowestLow
strategy.entry("Enter Short", strategy.short)
// 生成平仓交易
if ta.crossunder(close, midPoint)
strategy.close("Enter Long", comment="Exit Long")
if ta.crossover(close, midPoint)
strategy.close("Enter Short", comment="Exit Short")
// 步骤 4. 当回测日期范围结束时,平掉所有持有的
// 仓位并取消所有未成交的挂单
if not inTradeWindow and inTradeWindow[1]
strategy.cancel_all()
strategy.close_all(comment="日期范围结束退出")
我们首先用 strategy() 函数命名脚本,然后创建了三个输入选项来配置回测的时间段。接着,我们判断当前K线是否处于该时间窗口内,并将结果存入 inTradeWindow 变量。
之后,代码计算并绘制了20周期的高低点通道及其中线。接下来是开仓逻辑(步骤3),两个 if 语句分别处理多头和空头的突破开仓,并且都包含了 inTradeWindow 作为必要条件。然后是常规的平仓逻辑,当价格反向穿越通道中线时,平掉相应的仓位。策略的最后是一个 if 语句,它实现了步骤4:监控日期范围的结束,并在结束时取消所有挂单并平掉所有持仓。
在图表上,我们可以看到该策略的交易活动没有进入2022年——因为默认的结束日期是2021年12月31日:
简单总结一下:默认情况下,TradingView策略会对整个图表的数据进行回测。通过一个自定义的日期过滤器,我们可以精确地控制脚本进行交易的时间段。实现这一点需要四个步骤:首先创建输入选项来配置日期范围,然后判断当前K线是否处于该时间窗口内,之后确保只在该时间段内提交开仓订单,最后当时间段结束时平掉所有持有的仓位。




