用strategy.cancel()取消未成交的订单
当一个PineScript策略生成一个基于价格的订单时(如限价单或止损单),这个模拟订单通常不会立即成交。strategy.cancel() 函数的作用就是让策略取消一个特定的未成交订单。通过这个函数,我们就可以删除一个待处理的(pending)策略订单。
我们之所以需要使用 strategy.cancel(),是因为在PineScript中,一个基于价格的挂单一旦被提交,就会一直保持激活状态,直到它成交或被明确取消。
由于这些待处理的订单会持续有效,及时取消那些入场或出场条件已不再满足的订单就变得至关重要。否则,它们可能会在很久之后意外触发一笔我们不希望的交易。因此,我们执行 strategy.cancel() 来清理那些我们不再需要的挂单。
标准语法格式
该订单函数的标准语法格式如下:
strategy.cancel(id, when)
id:一个字符串,用于指定要取消哪个订单。我们必须提供与待处理订单完全相同的订单ID。例如,如果我们之前提交了一个ID为"Long TP"的多头止盈单,那么使用"long TP"、"long tp"或"Long Tp"都无法取消该订单——ID必须完全匹配,包括大小写。when:一个布尔值,用于条件性地决定是否执行取消操作。此参数的默认值为true,意味着如果不设置它,该函数会无条件地执行。正因如此,我们通常会将它放在一个if语句中来精确控制其执行时机。
strategy.cancel()示例
现在我们来看看如何在实践中使用 strategy.cancel() 函数。
要取消一个特定的订单,我们需要将该函数的第一个参数设置为要取消的订单的ID;同时,为了避免在每根K线上都取消订单,我们将 strategy.cancel() 放在一个 if 语句中,通过 if 的条件来判断何时执行取消操作。示例如下:
// 当价格上穿30周期EMA时,取消名为 'Enter Long' 的限价挂单
if ta.crossover(close, ta.ema(close, 30))
strategy.cancel("Enter Long")
这个 if 语句判断收盘价是否上穿了指数移动平均线。如果条件满足,strategy.cancel() 函数就会执行,取消ID为 “Enter Long” 的订单。如果市场中确实存在一个同名的待处理订单,策略就会将其删除,使其无法再成交。
strategy.cancel() 函数并不关心它所取消的订单类型,所以我们用完全相同的方式来撤销一个空头订单:
// 当价格下穿20周期SMA时,取消名为 'Enter Short' 的限价挂单
if ta.crossunder(close, ta.sma(close, 20))
strategy.cancel("Enter Short")
要同时取消多个订单(即在同一次脚本计算中),我们只需多次调用 strategy.cancel() 函数即可。每次调用时,我们都为函数提供一个要取消的订单的ID。通过这种方式,这个本身设计为一次只取消一个订单的函数,也能实现批量删除订单的效果。示例如下:
// 当价格跌破加权移动平均线(WMA)时,
// 取消两个多头止盈挂单
if ta.crossunder(close, ta.wma(close, 30))
strategy.cancel("Long Exit - Profit #1")
strategy.cancel("Long Exit - Profit #2")
因为 strategy.cancel() 不区分订单类型,所以取消空头挂单的方式也完全一样:
// 如果价格上穿简单移动平均线(SMA),
// 取消两个空头平仓挂单
if ta.crossover(close, ta.sma(close, 10))
strategy.cancel("Short Exit - Stop")
strategy.cancel("Short Exit - Limit")
注意:如果想取消所有待处理的订单,使用
strategy.cancel_all()函数会更方便,它无需我们处理具体的订单ID。
strategy.cancel()的行为特性
了解了如何使用 strategy.cancel() 后,我们再来看看这个订单函数的一些重要特性。
strategy.cancel() 函数用于取消(停用)未成交的、基于价格的订单,包括限价单、止损单,以及止损限价单。这些订单通常由 strategy.entry()、strategy.exit() 和 strategy.order() 函数生成。
有趣的是,strategy.cancel() 也能取消市价单。这种情况发生在以下特定场景中:策略生成一个市价单,然后在同一次脚本计算中,strategy.cancel() 函数以与该市价单相同的ID被执行。由于市价单也需要一个极短的时间来等待下一个价格的成交,如果在它成交前的这个短暂窗口期内,strategy.cancel() 被执行,那么这个市价单就会在成交前被取消。
这个特性可能会导致策略出现令人困惑的行为,因为我们很少希望在生成市价单的同时就取消它。为避免这种情况,我们可以采取以下几种方法:一是使用不同的订单ID,不要用同一个ID来既下市价单又取消挂单,例如可以根据订单类型来区分命名(如 "Exit Long Stop" 和 "Exit Long TP #1");二是检查逻辑冲突,如果代码中出现了用同一个条件既下市价单又取消订单的情况,这可能意味着策略逻辑本身存在问题;三是调整代码顺序,将 strategy.cancel() 函数放在生成市价单的代码之前,这样策略会先取消旧的挂单,然后再用同一个ID生成新的市价单。
与其它订单函数类似,我们可以通过 when 参数或 if 语句来条件性地执行 strategy.cancel()。两者在功能上没有区别,但我个人更推荐使用 if 语句,因为它能让代码结构更清晰易读。
还有几点一般特性值得知道:
strategy.cancel()没有返回值,所以我们无法通过它来检查订单是否真的被取消了。- 如果提供的ID不对应任何待处理的订单,那么函数不会执行任何操作,也不会报错。因此,在不确定某个订单是否还存在时,我们仍然可以安全地调用此函数。
- 在PineScript中,我们不通过先取消后下单的方式来修改一个待处理的订单。正确的做法是,直接用相同的订单ID提交一个带有新参数的订单,TradingView会自动用新订单更新旧的挂单。
- 一旦一个订单被
strategy.cancel()取消,它就永远消失了,无法被重新激活。 strategy.cancel()取消的是模拟订单,无法作用于真实的经纪商账户或手动下的订单。- 如果你的目标是一个订单成交后取消其他几个相关的订单,使用OCA(One-Cancels-All)订单组会是比手动调用
strategy.cancel()更高效、更可靠的选择。 - 策略本身无法获取一个当前所有待处理订单的列表,我们的代码逻辑必须能够自己追踪应该在何时取消哪个订单。如果想取消所有订单,最简单的方法是调用
strategy.cancel_all()。
简单总结一下:strategy.cancel() 函数用于取消指定的待处理订单,我们取消订单是为了防止它们在信号失效后意外成交。它可以取消止损单、限价单和止损限价单,也可以取消在同一次脚本计算中生成的、尚未成交的市价单。
用strategy.cancel_all()取消所有未成交的策略订单
TradingView策略可以生成各种基于价格的挂单(停止单、限价单和停止-限价单)。通常这些订单不会立即成交,而是保持挂起状态,等待市场价格触及。strategy.cancel_all() 函数的作用就是取消(删除)策略当前所有尚未成交的挂单。
为什么要使用它?在PineScript中,所有基于价格的挂单会一直保持有效,直到它们被成交或被明确取消。因此,当策略的某些挂单因为市场变化而变得不再有意义时,我们就需要主动将它们撤销。否则,它们可能会在很久之后,在一个我们完全不希望的时间点被意外触发。
默认语法结构
该订单函数的标准语法非常简单:
strategy.cancel_all(when)
when:一个布尔值,用于决定是否应该执行全部撤单这个操作。这个参数的默认值是true,这意味着如果我们不设置它,函数会无条件地执行。这个特性使得将它放入if语句中来控制执行时机变得非常方便。
strategy.cancel_all()示例
要取消策略的所有未成交订单,我们只需确定一个需要清扫所有挂单的场景,然后将 strategy.cancel_all() 放入一个以该场景为条件的 if 语句中。这是一个例子:
// 当相对强弱指数(RSI)下穿20时,
// 取消所有挂单
if ta.crossunder(ta.rsi(close, 7), 20)
strategy.cancel_all()
这个 if 语句判断7周期的RSI是否下穿了20。如果条件成立,我们便调用 strategy.cancel_all() 函数,取消掉所有尚未成交的挂单。
它的效果是什么?就是删除每一个未成交的停止单、限价单和停止-限价单。并且,它还会顺便取消掉在同一次脚本计算中刚刚生成的市价单。
strategy.cancel_all() 的一个优点是它的通用性。无论策略当前是持多仓还是空仓,无论挂起的是止损单还是止盈单,我们都用同样的方式调用它。这个函数不在乎要取消的是哪种类型的挂单,它的任务就是清扫全场。
strategy.cancel_all()的特性
现在我们知道了如何使用 strategy.cancel_all(),再来深入了解一下它的几个重要特性。
它可以取消所有基于价格的挂单,包括限价单、停止单和停止-限价单。这些订单通常是由 strategy.entry()、strategy.exit() 和 strategy.order() 函数生成的。
一个令人有些意外但极其重要的特性是:strategy.cancel_all() 同样会取消市价单。由于市价单通常会很快成交,所以只有在一种非常特定的情况下才会发生:在同一次脚本计算中,策略先生成了一个市价单(例如通过 strategy.close_all()),紧接着在后面的代码中又调用了 strategy.cancel_all()。此时,策略会取消所有尚未成交的订单,其中就包括那个几毫秒前刚刚生成的市价单!
这种行为是可能的,因为市价单在被提交后,到它在下一个报价(tick)上成交前,存在一个极其短暂的挂起状态。就在这个微小的时间窗口内,策略可以自己取消掉自己刚发的市价单。这个特性可能会导致策略出现令人困惑的行为:明明图表上出现了开仓或平仓信号,但交易却没有执行。
为了避免 strategy.cancel_all() 意外地取消我们的市价单,我们可以不在生成市价单的相同条件下执行它,或者在生成市价单的代码之前先调用它——先清扫掉所有旧的挂单,然后再提交新的市价单。第二种方法通常是最佳实践。例如,我最近犯过的一个错误:
// 错误示例:本意是在回测周期结束时平仓并撤销所有挂单
if not inTradeWindow and inTradeWindow[1]
strategy.close_all(comment="周期结束平仓") // 先提交了市价单
strategy.cancel_all() // 然后立即把它取消了
正确的做法是调换顺序,先撤后平:
// ✅ 正确示例:先撤销所有旧挂单,再提交新的平仓市价单
if not inTradeWindow and inTradeWindow[1]
strategy.cancel_all()
strategy.close_all(comment="周期结束平仓")
与其它订单函数一样,我们可以通过 when 参数或 if 语句来控制 strategy.cancel_all() 的执行时机:
// 使用 when 参数
strategy.cancel_all(when = ta.cross(close, ta.ema(close, 20)))
// 使用 if 语句
if ta.cross(close, ta.ema(close, 20))
strategy.cancel_all()
两种写法的效果完全相同。我个人更推荐使用 if 语句,因为它能让条件逻辑更突出,代码结构也更清晰易读。
还有几点一般特性:
- 防止重复挂单:当你使用
strategy.cancel_all()时,需要考虑你的挂单逻辑,避免在刚撤销后又立刻重新挂上同样的订单。 - 修改与取消:要修改一个挂单,只需用相同的ID提交一个新订单即可,无需先撤销再提交。
- 不可恢复:一旦订单被取消,它就永远消失了,无法恢复。
- 安全调用:如果你不确定当前是否有挂单但想确保万无一失,可以直接调用
strategy.cancel_all()。当没有挂单时,它什么也不会做,不会报错。 - 与OCA组的比较:如果你需要实现一个订单成交、其他几个订单自动取消的逻辑,使用OCA(One-Cancels-All)订单组是更高效、更简洁的选择。
- 模拟行为:
strategy.cancel_all()取消的都是模拟订单,它不能用于真实交易账户或纸上交易,也不能取消你手动下的订单。
简单总结一下:strategy.cancel_all() 函数用于取消策略当前所有尚未成交的挂单,我们用它来清理那些因市场变化而不再有效的挂单,防止它们在不希望的时间点被触发。它可以取消所有类型的未成交订单:停止单、限价单、停止-限价单,甚至是在同一计算周期内生成的市价单。


