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

Pine Script(247):在连败或连胜后停止TradingView策略

#Pine Script入门教学

如何在连续n次亏损后停止TradingView策略?

无论一个交易策略多么优秀,遭遇一连串的连续亏损都只是时间问题。这种情况不仅会损害我们的账户回报,也会极大地打击我们的交易信心。管理这种风险的有效方法之一,便是在发生极端的连败时,果断停止交易。本文将详细介绍如何在TradingView Pine中,从零开始编写这一风控逻辑。

限制交易风险:基于连续亏损笔数停止交易

在经历一轮持续的连败时,一个交易策略似乎无论发出什么信号,结果都是错的。尽管我们的第一反应可能是继续交易以挽回损失,但连败本身已经是一个强烈的信号,说明策略可能暂时与当前市场水土不服。

在TradingView Pine中,有几种方法可以管理连败。其中一种是使用内置的 strategy.risk.max_cons_loss_days() 函数,它会在策略达到设定的连续亏损天数后停止交易。但该函数是基于策略权益而非已平仓交易来衡量亏损的。这意味着一笔盈利的交易,仅仅因为其浮动利润回吐,也可能被计为一个亏损日。另一个更贴近交易者直觉的选项,是基于已平仓交易的实际表现来管理连败。接下来,让我们看看如何自己动手在TradingView中实现这一逻辑。

要让我们的TradingView策略能够基于连续亏损的交易笔数来停止运行,我们需要遵循以下六个步骤:第一步(可选),创建一个输入项,用于灵活配置最大可容忍的连败长度;第二步,判断是否有新的亏损交易发生,从而可能增加连败计数;第三步,计算当前连续亏损的交易笔数;第四步,检查当前的连败长度是否仍在允许的范围内;第五步,只要未达到最大连败上限,就正常提交入场交易;第六步,一旦达到连败上限,立即平掉所有当前持有的仓位。让我们来详细分解这些步骤及其所需的代码。

步骤1.(可选)通过输入项设置连败长度

为了方便地调整最大连败长度,我们可以创建一个(可选的)输入选项:

// 通过输入项来设置最大连败长度
maxLosingStreak = input.int(15, title="最大连败长度", minval=1)

这里,我们使用 input.int() 函数创建了一个整数输入框。我们将其默认值设为15,并通过 minval=1 参数确保输入值始终为正数,这有助于简化后续步骤中的代码逻辑。

我们将这个输入值存入 maxLosingStreak 变量。(如果你的策略不需要输入项,只需在后续步骤中,将 maxLosingStreak 理解为我们设定的固定最大连败长度即可。)

步骤2. 判断是否有新的亏损交易增加了连败计数

接下来,我们需要判断策略是否刚刚完成了一笔亏损的平仓。实现方式有多种,但最直接的方法是利用 strategy.losstrades 变量,该变量返回策略的累计亏损交易总数。

但要延长一次连败,期间不能有任何盈利或保本的交易,因为它们会重置连败计数。因此,我们还需要同时检查 strategy.wintrades 和 strategy.eventrades:

// 检查是否刚刚发生了一笔新的、会增长连败计数的亏损交易
newLoss = strategy.losstrades > strategy.losstrades[1] and
     strategy.wintrades == strategy.wintrades[1] and
     strategy.eventrades == strategy.eventrades[1]

这个布尔变量 newLoss 的值由三个表达式通过 and 运算符组合而成。这意味着,只有当所有三个条件都为 true 时,newLoss 才为 true。首先,我们判断累计亏损笔数(strategy.losstrades)是否比前一根K线增加了,如果是,说明刚有一笔亏损交易平仓。但这还不够,我们还需确保累计盈利笔数(strategy.wintrades)与前一根K线保持不变,同时,累计保本笔数(strategy.eventrades)也必须保持不变。

只有当这三个条件同时满足时,我们才能确定,刚刚发生的这笔亏损交易,是开启或延长了一次连败。(尽管一笔盈利和一笔亏损交易在同一根K线上平仓的可能性不大,但如果K线范围极宽且我们有分批平仓的逻辑,这种情况也是可能发生的。)

请注意,strategy.losstrades、strategy.wintrades 和 strategy.eventrades 这些变量已经考虑了策略的佣金和滑点。这使它们成为衡量策略连败的可靠工具。

步骤3. 计算当前的连败长度

接下来,我们来确定策略当前的连败长度:

// 确定当前的连败长度
streakLen = 0

streakLen := if newLoss
    nz(streakLen[1]) + 1
else
    if strategy.wintrades > strategy.wintrades[1] or
         strategy.eventrades > strategy.eventrades[1]
        0
    else
        nz(streakLen[1])

这里,我们首先声明了一个持久变量 streakLen。然后,一个 if/else 结构赋予了它实际的逻辑。如果步骤2中的 newLoss 为 true,说明连败正在持续,我们便取出 streakLen 在前一根K线的值,并加1。我们用 nz() 函数包裹 streakLen[1],以防止在脚本开始时因没有历史值而出现错误。如果 newLoss 为 false,则进入嵌套的 if/else:如果发生了盈利或保本交易,说明连败被中断,我们将 streakLen 重置为0;如果没有新的亏损,也没有新的盈利或保本,说明连败状态未变(例如,无交易发生),我们便让 streakLen 保持其前一根K线的值。

步骤4. 检查连败是否仍在允许范围内

知道了当前的连败长度后,我们就可以判断是否仍在可容忍的范围内:

// 检查连败长度是否仍在允许的最大值之内
okToTrade = streakLen < maxLosingStreak

这里,我们将步骤3中计算出的 streakLen 与我们设定的最大值 maxLosingStreak 进行比较。如果小于上限,则布尔变量 okToTrade 为 true;否则为 false。

这个 okToTrade 变量将成为我们策略的总开关,告诉我们脚本是应该继续交易(true),还是应该暂停(false)。

步骤5. 在达到上限前正常提交入场订单

现在,我们将 okToTrade 这个总开关整合到策略的入场逻辑中。示例如下:

// 在考虑当前连败的情况下提交订单
if okToTrade and enterLong
    strategy.entry("EL", strategy.long)

if okToTrade and enterShort
    strategy.entry("ES", strategy.short)

在执行 strategy.entry() 之前,我们先检查 okToTrade 是否为 true。只有在连败未达上限时,我们才允许策略根据其原有的 enterLong 或 enterShort 信号建立新的仓位。

一旦策略达到最大连败数,okToTrade 将变为 false,这将导致 if 条件不成立,从而有效阻止策略建立任何新的交易。

请注意,此步骤中的 enterLong 和 enterShort 仅为示例占位符。你需要将 okToTrade 整合到你自己策略的实际入场条件中。核心思想不变:在任何 strategy.entry() 或 strategy.order() 函数执行开仓操作前,都必须先检查 okToTrade 的状态。

步骤6. 达到连败上限时平掉所有持仓

最后一步,我们让策略在达到最大连败上限时,立即平掉所有当前持有的仓位。这实际上就完全停止了策略的后续活动。

// 当达到最大连败时,平掉所有仓位
if not okToTrade
    strategy.close_all()

这个 if 语句使用 not 逻辑运算符来判断 okToTrade 是否为 false。如果是,就意味着策略已触及最大连败数。

此时,我们调用 strategy.close_all() 函数,它会通过一笔市价单来平掉所有当前持仓。(如果策略当时没有持仓,strategy.close_all() 则不执行任何操作。)

需要注意的一点是:strategy.close_all() 发送的这笔市价平仓单,如果运气不佳,其本身也可能是一笔亏损交易。在这种情况下,最终记录的连败长度会比我们设定的最大值多1。

示例策略:在一连串亏损后停止交易

下方的脚本将以上六个步骤整合进了一个完整的策略中。该策略基于两条移动平均线的交叉进行交易。当快线上穿慢线时做多,反之则做空。

这个过程会一直持续,直到策略触及最大连续亏损的上限。一旦发生这种情况,策略将不再发送任何新的入场订单,并会平掉所有当前持仓,从而有效地停止策略。完整的策略代码如下:

//@version=5
strategy(title="在连败后停止", overlay=false, precision=0,
     default_qty_type=strategy.fixed, default_qty_value=5)

// 步骤1:
// 通过输入项来设置最大连败长度
maxLosingStreak = input.int(15, title="最大连败长度", minval=1)

// 计算移动平均线
fastMA = ta.ema(close, 5)
slowMA = ta.ema(close, 25)

// 定义交易条件
enterLong  = ta.crossover(fastMA, slowMA)
enterShort = ta.crossunder(fastMA, slowMA)

// 步骤2:
// 检查是否刚刚发生了一笔新的、会增长连败计数的亏损交易
newLoss = strategy.losstrades > strategy.losstrades[1] and
     strategy.wintrades == strategy.wintrades[1] and
     strategy.eventrades == strategy.eventrades[1]

// 步骤3:
// 确定当前的连败长度
streakLen = 0

streakLen := if newLoss
    nz(streakLen[1]) + 1
else
    if strategy.wintrades > strategy.wintrades[1] or
         strategy.eventrades > strategy.eventrades[1]
        0
    else
        nz(streakLen[1])

// 在图表上显示当前的连败长度及其上限
plot(streakLen, style=plot.style_columns,
     color=streakLen < maxLosingStreak ? color.maroon : color.red)
bgcolor(newLoss ? color.new(color.red, 80) : na)
hline(maxLosingStreak, color=color.red, linestyle=hline.style_solid,
     linewidth=2)
hline(0, linestyle=hline.style_solid, color=color.gray)

// 步骤4:
// 检查连败长度是否仍在允许的最大值之内
okToTrade = streakLen < maxLosingStreak

// 步骤5:
// 在考虑当前连败的情况下提交订单
if okToTrade and enterLong
    strategy.entry("EL", strategy.long)

if okToTrade and enterShort
    strategy.entry("ES", strategy.short)

// 步骤6:
// 当达到最大连败时,平掉所有仓位
if not okToTrade
    strategy.close_all()

该策略会将连败长度绘制出来,以便轻松追踪这一风险指标。在下方的以太坊图表示例中,策略的连败长度缓慢增长至其设定的最大值6。当达到该值时,策略提交了一笔市价单来平掉当前的持仓。不幸的是,这笔平仓交易本身也是亏损的。因此,策略最终以连续7次亏损而停止。

一旦策略达到其最大连续亏损数,它将在剩余的回测期间以及此后的所有实时信号中完全停止。这样,策略的亏损就不会再恶化,但这也意味着策略失去了任何恢复的机会。

要了解更多管理TradingView策略风险的方法,请参阅风险管理分类下的文章。

根据连续获胜次数停止TradingView策略

交易中最大的风险无疑是重大亏损。但一个策略突然表现得异常出色,甚至远超历史回测,这同样值得警惕。一波超长的连胜可能只是运气,但也可能预示着市场发生了根本性变化,使得原有的策略逻辑不再可靠。让我们来看看如何根据这种异常的连胜纪录来暂停一个TradingView策略的交易。

防止非理性繁荣:通过连胜纪录停止交易

极端连胜的问题在于,它们迟早会以一连串的亏损告终。并且,一个远超回测结果的连胜纪录本身也暗示着问题:这意味着该策略未来的连亏纪录很可能也会比历史表现更差。

TradingView标准的风险函数主要用于处理亏损,例如 strategy.risk.max_cons_loss_days() 可以限制策略的连续亏损天数。但对于那些表现好到出乎意料的策略,却没有现成的工具。幸运的是,只需几个步骤,我们就可以编写自定义的Pine Script代码来管理这种连胜风险。

要让我们的TradingView策略在达到设定的连胜次数后自动停止交易,需要完成以下任务:第一步(可选),使用输入选项来设定最大连胜长度;第二步,检测策略是否刚刚平掉一笔盈利的交易,并且这笔交易延续了连胜纪录;第三步,计算当前的连续盈利交易次数;第四步,将当前的连胜次数与设定的上限进行比较;第五步,仅在连胜次数未达到上限时才提交新的开仓订单;第六步,一旦达到最大连胜次数,立即平掉所有当前持有的仓位。让我们逐一分解这些步骤,并看看需要哪些代码来实现。

步骤1.(可选)使用输入选项设定最大连胜长度

为了方便地调整最大连胜次数,我们可以创建一个输入选项,这样就不必每次都去修改代码。实现代码如下:

// 使用输入项来设定最大连胜长度
maxWinStreak = input.int(15, title="最大连胜长度", minval=1)

我们使用 input.int() 函数来创建一个整数输入框,默认值为15。通过 minval 参数,我们确保这个值至少为1,这为我们后续的代码逻辑提供了保障。我们将这个输入值保存在 maxWinStreak 变量中。(如果你不需要输入选项,只需记住,后续步骤中使用的 maxWinStreak 变量即代表你设定的最大连胜次数。)

步骤2. 判断是否产生了一笔干净的盈利

我们可以用多种方法来判断一笔交易是否盈利。一个简单且可靠的方法是利用 strategy.wintrades 变量,它返回策略的盈利交易总数。如果这个变量相较于上一根K线增加了,说明至少有一笔盈利交易被平仓。

但为了精确地追踪连胜,我们不仅要检查盈利交易数(strategy.wintrades)是否增加,还要同时确认亏损交易数(strategy.losstrades)和保本交易数(strategy.eventrades)均未增加。只有这样,我们才能确定这笔盈利确实是延续了连胜,而不是发生在一次盈亏交替中。

// 判断是否有一笔新的、能够延续连胜的盈利交易产生
newWin = strategy.wintrades > strategy.wintrades[1] and
     strategy.losstrades == strategy.losstrades[1] and
     strategy.eventrades == strategy.eventrades[1]

newWin 变量只有在上述三个条件同时满足时才为 true。

重要提示:TradingView的 strategy.wintrades、strategy.losstrades 和 strategy.eventrades 变量在计算时已包含了佣金和滑点成本,这使得它们成为衡量策略连胜长度的可靠依据。

步骤3. 计算当前连胜长度

现在,我们有了一个能判断干净盈利是否发生的变量。我们可以利用它来计算当前的连胜长度:

// 计算当前的连胜长度
streakLen = 0

streakLen := if newWin
    nz(streakLen[1]) + 1
else
    if strategy.losstrades > strategy.losstrades[1] or
         strategy.eventrades > strategy.eventrades[1]
        0
    else
        nz(streakLen[1])

我们定义了一个 streakLen 变量来记录连胜长度。其更新逻辑是:如果 newWin 为 true,说明产生了一笔新的连胜,我们将 streakLen 的前一个值加1;否则,如果发生了亏损或保本交易,说明连胜中断,我们将 streakLen 重置为0;如果以上情况都未发生(即没有新的平仓),则 streakLen 的值保持不变。(nz() 函数用于处理变量在初始时可能为空(na)的情况,将其视作0来计算。)

步骤4. 判断是否已达到最大连胜上限

有了当前的连胜长度,我们就可以将其与设定的上限进行比较了:

// 判断连胜次数是否仍在限制范围内
okToTrade = streakLen < maxWinStreak

okToTrade 是一个布尔变量。如果当前的连胜长度 streakLen 小于我们设定的上限 maxWinStreak(默认为15),okToTrade 就为 true,否则为 false。

步骤5. 根据连胜状态决定是否开仓

现在,我们有了一个明确的交易开关(okToTrade)。我们只需在提交开仓订单前检查这个变量即可:

// 仅当未达到最大连胜上限时才提交订单
if okToTrade and enterLong
    strategy.entry("EL", strategy.long)
if okToTrade and enterShort
    strategy.entry("ES", strategy.short)

当策略达到最大连胜上限时,okToTrade 会变为 false,从而阻止策略提交任何新的开仓订单。

重要提示:请确保在你策略中所有可能产生新仓位的代码(包括 strategy.entry() 和 strategy.order())前都加入了 okToTrade 这个条件判断,否则此风控逻辑将无法生效。

步骤6. 达到上限后立即平仓

最后一步,一旦达到最大连胜次数,我们就平掉所有剩余的持仓,从而彻底停止策略的交易活动。

// 一旦达到最大连胜,立即清空所有仓位
if not okToTrade
    strategy.close_all()

我们使用 not okToTrade 来判断 okToTrade 是否为 false。一旦为 false,就调用 strategy.close_all() 函数,它会发送一个市价单来平掉所有当前持仓。

请注意,strategy.close_all() 发出的市价平仓单本身也可能是一笔盈利的交易。如果发生这种情况,最终的连胜次数会比我们设定的最大值多一次。

示例策略:实现连胜后停止交易

下方的完整策略脚本融合了以上所有步骤。它基于均线交叉进行交易,并带有止损和止盈。但核心在于,当策略达到设定的最大连胜次数后,交易将完全停止。

//@version=5
strategy(title="连胜后停止",
     overlay=false, precision=0)

// 步骤 1: 设定最大连胜长度
maxWinStreak = input.int(15, title="最大连胜长度", minval=1)

// 计算指标和交易条件
fastMA = ta.ema(close, 5)
slowMA = ta.ema(close, 25)
isFlat     = strategy.position_size == 0
enterLong  = isFlat and ta.crossover(fastMA, slowMA)
enterShort = isFlat and ta.crossunder(fastMA, slowMA)

// 步骤 2: 判断是否为“干净”的盈利
newWin = strategy.wintrades > strategy.wintrades[1] and
     strategy.losstrades == strategy.losstrades[1] and
     strategy.eventrades == strategy.eventrades[1]

// 步骤 3: 计算连胜长度
streakLen = 0
streakLen := if newWin
    nz(streakLen[1]) + 1
else
    if strategy.losstrades > strategy.losstrades[1] or
         strategy.eventrades > strategy.eventrades[1]
        0
    else
        nz(streakLen[1])

// 在图表上可视化连胜状态
plot(streakLen, style=plot.style_columns,
     color=streakLen < maxWinStreak ? color.green : color.gray)
bgcolor(newWin ? color.lime : na)
hline(maxWinStreak, color=color.orange, linestyle=hline.style_solid,
     linewidth=2)
hline(0, linestyle=hline.style_solid, color=color.gray)

// 步骤 4: 判断是否可以交易
okToTrade = streakLen < maxWinStreak

// 步骤 5: 提交开仓订单
if okToTrade and enterLong
    strategy.entry("EL", strategy.long)
if okToTrade and enterShort
    strategy.entry("ES", strategy.short)

// 计算并设置止盈止损
highestHighLong = ta.highest(high, 15)[1]
lowestLowLong   = ta.lowest(low, 30)[1]
highestHighShort = ta.highest(high, 30)[1]
lowestLowShort   = ta.lowest(low, 15)[1]
if strategy.position_size > 0
    strategy.exit("XL", stop=lowestLowLong, limit=highestHighLong)
if strategy.position_size < 0
    strategy.exit("XS", stop=highestHighShort, limit=lowestLowShort)

// 步骤 6: 达到上限后平仓
if not okToTrade
    strategy.close_all()

这个策略还会将当前的连胜次数绘制成柱状图。在下方的EUR/USD图表中,我们可以看到连胜次数随着交易的进行而缓慢增加。当最后一笔空头交易以盈利平仓后,策略达到了15次连胜的上限。从那一刻起,便再也没有新的交易产生。

要触发止损,必须是一系列连续的盈利。任何一次亏损都足以将连胜计数器重置为零,从而给策略更多的时间去交易。在下方的SPY(标普500 ETF)图表中,策略最初取得了3连胜,但随后的一次亏损将连胜纪录清零了。

管理交易连续性的另一种思路是在连续n次亏损后停止交易。要了解更多管理TradingView策略风险的方法,可以参阅相关的风险管理主题分类。

赞(0)
未经允许不得转载:图道交易 » Pine Script(247):在连败或连胜后停止TradingView策略
分享到

评论 抢沙发

登录

找回密码

注册