如何在连续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策略风险的方法,可以参阅相关的风险管理主题分类。






