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

Pine Script(213):订单的两个阶段与持仓状态

#Pine Script入门教学

策略脚本的两个订单阶段

要让我们的策略脚本进行交易,只需调用一个TradingView的订单函数来提交订单即可。但在一段代码最终成为一个实际成交的订单之前,TradingView必须经过两个步骤。让我们来一探究竟,看看策略脚本的幕后到底发生了什么。

有几个TradingView订单函数可以让我们的策略脚本根据交易信号开仓、止盈和止损。这些订单看起来像是在图表上被TradingView自动执行了,但实际上,每一个订单在幕后都会经历两个不同的阶段。

理解TradingView是如何处理和执行订单的,能帮助我们更好地把握策略的行为,同时也能避免我们因一些看似bug的现象而感到困惑。

在我们策略脚本生成的订单被提交之前,它会经历以下两个阶段:

  1. 订单生成阶段(Order Generating Phase):在这个阶段,TradingView会检查订单的条件、策略的整体设置以及风险管理规则。根据这些审核的结果,TradingView会决定是批准还是拒绝这个订单请求。
  2. 订单执行阶段(Order Execution Phase):在这个阶段,TradingView会提交实际的订单并监督其执行过程。订单成交后,TradingView会将结果报告给策略。

这两个步骤适用于策略脚本提交的每一个订单,无论是在历史回测、实时模拟测试,还是在真实的实盘交易中。

虽然这两个阶段听起来很理论化,但它们会带来非常实际的后果。举个例子,你知道吗,如果你在同一时间发送多个订单,最终可能会开立一个远超预期的仓位?这个现象背后的原因,正是TradingView的这两个订单处理阶段。所以,让我们来深入了解一下。

订单生成阶段:条件审核与订单批准

当我们的策略脚本执行一个订单函数时,这个订单请求首先会进入订单生成阶段。在这个阶段,系统会审核订单的各项条件,以判断这个订单是否真的应该被创建。只有在这个阶段被批准的订单,才能进入下一阶段(订单执行阶段)。否则,这个订单命令就会被丢弃,我们的订单也就不会被提交到市场。

在订单生成阶段,TradingView除了会考虑我们代码中明确编写的订单条件外,还会审核其他多个方面:

  • 当我们提交一个开仓订单时,策略的金字塔加仓(pyramiding)设置是否允许(再次)开仓?
  • 是否有风险管理函数(例如 strategy.risk.*)禁止当前订单的执行?
  • 当我们使用一个平仓函数时,当前是否真的有持仓可供平掉?
  • 市场中是否已经存在一个具有相同订单ID的待处理订单?如果有,TradingView会修改那个待处理的订单,而不是创建新订单。
  • 当我们使用一个函数来平掉特定开仓单的仓位时(例如 strategy.exit()from_entry 参数),具有该ID的开仓单当前是否真的存在?
  • 当我们使用一个可以反转持仓的订单函数时,当前是否有持仓可供反转?还是应该直接开立一个新仓位?

注意:并非所有的策略订单函数都会受到相同的条件约束。例如,strategy.order() 函数就不受 strategy.risk.allow_entry_in() 风险管理函数的影响,策略的金字塔加仓设置对它也无效。因此,TradingView在批准订单前具体会检查哪些条件,也取决于我们使用了哪个订单函数(当然还有策略的整体设置和风险管理规则)。

只有当上述所有类似条件都满足时,订单才会被放行;只要有一个或多个条件不满足,订单请求就会被拒绝。

所以,在订单生成阶段,并不仅仅是我们的代码逻辑决定订单能否被发送,策略的设置(如金字塔加仓)和当前的上下文(如是否有持仓)同样至关重要。

订单生成阶段有几个非常重要的特性,我们来逐一分析。

TradingView对订单进行独立评估

订单生成阶段最重要的一个特性是:每一个订单都是被独立评估的。这意味着TradingView会拿起一个订单请求,单独检查它是否满足所有条件。如果满足,订单就被批准,然后进入订单执行阶段。

但TradingView不会做的是,评估同时提交的多个订单组合在一起会对策略设置产生什么影响。假设我们的策略中有以下代码,它会在同一时间执行两个开仓订单:

//@version=5
strategy(title="Strategy example", pyramiding=0)

if close > ta.ema(close, 10)
    strategy.entry("EL1", strategy.long)
    strategy.entry("EL2", strategy.long)

在这里,strategy() 函数设置了禁止金字塔加仓(pyramiding=0)。然后,一个 if 语句判断K线收盘价是否高于10周期EMA。如果条件满足,我们连续两次调用 strategy.entry() 来提交两个做多订单。

虽然我们调用了两次 strategy.entry(),但理论上只有一个订单应该成交,因为我们禁止了加仓。然而,实际情况并非如此。因为TradingView在订单生成阶段是独立评估每一个订单的。在评估第一个订单 EL1 时,策略没有持仓,满足开仓条件,所以 EL1 被批准。在评估第二个订单 EL2 时,由于 EL1 还未进入执行阶段,策略的持仓状态仍然是空仓,所以 EL2 也被批准了。最终,TradingView提交了两个订单。

当然,这两个订单的综合效果就是建立了一个比我们预期要大的仓位。然而,TradingView并不会在生成阶段就去考虑如果所有被批准的订单都成交了会怎样。

注意:如果我们的策略已经有了一个持仓,那么在 pyramiding=0 的设置下,任何新的开仓订单都会在生成阶段被拒绝。我们上面描述的问题,主要发生在同时提交多个订单,且每个订单单独来看都符合条件,但它们的组合效果却违背了策略设置的场景中。

我们可以这样来理解这个特性:因为存在两个独立的阶段,一个在订单生成阶段被批准的订单,并不意味着它在进入订单执行阶段后仍然是合规的。

既然我们知道了TradingView会独立批准同时提交的多个订单,即便它们的组合效果会违反策略设置,我们该如何应对呢?

我们无法改变TradingView在订单生成阶段的评估机制。但我们能做到的是,避免在同一时间提交多个订单。例如,与其发送多个开仓订单,我们应该将它们的订单数量合并,通过一次 strategy.entry() 调用来完成。

对于平仓订单,我们需要确保它们的触发条件是互斥的。这样,在每一次脚本计算中,最多只会生成一个平仓订单。否则,我们可能会卖出或买入超预期的数量,导致最终持有一个非预期的反向仓位。

幸运的是,对于平仓订单,我们主要需要关注的是 strategy.order() 函数。其他的平仓函数,如 strategy.exit()strategy.close(),只能从已有的持仓进行平仓,不会开立反向仓位。而 strategy.order() 则有能力在平仓的同时开立一个反向的新仓位。

订单执行阶段:执行订单并反馈结果

在第二个阶段,TradingView会执行订单。执行完毕后,它会将结果报告给策略。这样,我们的策略脚本就可以在下一次计算时使用这些最新的状态信息。

当一个订单到达订单执行阶段时,它已经是被TradingView批准过的。因此,几乎所有进入这个阶段的订单都会被执行。主要有两个例外:属于订单组的订单,以及被我们策略脚本后续取消的待处理订单。

当多个订单被绑定在一个订单组中时(例如OCA——One Cancels Another组),一旦其中一个订单成交,组内其他订单就会被取消或减小数量。当然,这个功能需要TradingView有足够的时间来反应。如果一个OCA组内的几个订单触发价非常接近(例如,一个在 high,另一个在 high + 2 ticks),当一个订单成交时,系统可能来不及在订单执行阶段取消另一个。

一个订单会通过以下两种方式离开订单执行阶段:

  • 订单成交:成交后,TradingView会将成交详情(如价格、数量)报告给策略。
  • 订单被取消:取消操作可以由我们的策略代码发起,也可以由TradingView基于订单组的规则自动执行。

如果我们先取消了一个订单,之后又重新提交了它,那么它会重新从订单生成阶段开始走一遍流程。

总结

当我们的策略脚本执行一个订单函数时,它会提交订单。但在订单最终成交前,它会经历两个阶段:订单生成阶段和订单执行阶段。

在订单生成阶段,TradingView会评估订单的特性、策略的整体设置以及风险管理规则。只有当所有条件都满足时,订单才会被批准进入下一阶段。

在订单执行阶段,TradingView执行订单并反馈结果。在这个阶段,TradingView也可以取消或修改属于订单组的订单。我们的脚本同样可以在这个阶段取消订单。

这些阶段中一个非常重要的特性是:在订单生成阶段,TradingView对每个订单进行独立评估。因此,如果我们同时提交多个开仓订单,而策略又禁止金字塔加仓,那么每个订单都可能会被单独批准。只有当TradingView在下一阶段执行它们时,我们才会发现最终的仓位比预期的要大。但到那时,TradingView已经批准并成交了这些订单。

策略持仓状态如何影响strategy.order()

Pine Script的 strategy.order() 函数用于开仓和平仓。准确地说,这个订单函数可以实现开立一个新仓位、对现有仓位进行加仓、部分平仓、全部平仓,以及将仓位反向。

现在,有趣的地方来了。strategy.order() 具体执行何种操作,并不取决于我们如何调用这个函数,而是完全由策略在调用它那一刻的当前持仓状态所决定。让我们来深入了解。

先看一个快速示例。假设策略当前是空仓状态,没有任何持仓。当策略执行一个 strategy.order() 买入(做多)订单时,Pine Script会开立一个新的多头仓位。

现在,假设策略已经持有多仓。此时,我们执行相同的 strategy.order() 买入订单。由于已存在多头仓位,这个函数的操作就变成了加仓(金字塔式),在原有基础上增加多头头寸。

尽管在这两种情况下,我们都告诉 strategy.order() 去买入,但结果却截然不同:一个是开立新仓,另一个是增加仓位。接下来,我们从多头和空头两个角度来详细分析。

多头strategy.order()订单

假设我们像下面这样执行 strategy.order() 函数:

// 当价格穿越20周期SMA时,买入(做多)5手合约
if ta.cross(close, ta.sma(close, 20))
	strategy.order("Enter Long", strategy.long, qty=5)

这个 if 语句通过 ta.cross() 函数判断收盘价是否穿越了20周期的简单移动平均线。当交叉发生时,strategy.order() 函数会提交一个买入(strategy.long)订单。

这个订单会买入5手合约。但执行后策略的仓位会是怎样的呢?

答案是:我们无法仅从这段代码中得知。因为策略在执行这行代码时,可能已经持有多仓、空仓,或者完全没有仓位。

实际上,这同一个买入订单可以触发五种完全不同的操作:开立新多仓、对多仓加仓、平掉全部空仓、部分平掉空仓(减仓),以及把空仓反手成多仓。具体是哪一种,完全取决于策略当前的持仓状态。

下表详细分析了各种可能性。第一列是 strategy.order() 执行时的持仓状态,第二列是 strategy.order() 的指令,第三列则是最终的仓位变化结果:

当前持仓状态 strategy.order() 指令 结果
空仓(无持仓) 买入5手 开立一个全新的、数量为5手的多头仓位。
持有多仓3手 买入5手 在现有仓位上加仓5手,总仓位变为8手多仓。
持有空仓-5手 买入5手 平掉全部空仓。买入的5手正好对冲掉持有的-5手空仓,最终仓位变为空仓。
持有空仓-8手 买入5手 部分平掉空仓(减仓)。买入的5手使空仓数量从-8手减少到-3手。
持有空仓-3手 买入5手 反手开多仓。买入的5手中,3手用于平掉-3手的空仓,剩下的2手开立了一个新的多头仓位。

请注意,在上表中,strategy.order() 执行的指令始终是同一个:买入5手。但它对策略仓位造成的影响却截然不同,这完全是因为每次执行时的初始持仓状态不同。

空头strategy.order()订单

同样的原理也适用于空头订单:策略当前的持仓状态决定了一个 strategy.order() 卖出(做空)订单会产生什么样的结果。

假设我们的策略中有这样一段代码:

// 当K线最低价低于过去20根K线的最低点时,卖空250股
if low < ta.lowest(low, 20)[1]
	strategy.order("Enter Short", strategy.short, qty=250)

这段代码判断价格是否创下了20周期新低。如果创了新低,strategy.order() 函数就会提交一个卖出(strategy.short)订单。

这个订单会卖空250股。但这会给我们的策略带来怎样的仓位变化呢?同样地,我们无法仅从代码本身得出结论,因为我们不知道策略的初始持仓状态。

这个卖出订单同样可能导致五种不同的结果:开立新空仓、对空仓加仓、平掉全部多仓、部分平掉多仓(减仓),以及把多仓反手成空仓。

下表列出了各种可能性:

当前持仓状态 strategy.order() 指令 结果
空仓(无持仓) 卖出250股 开立一个全新的、数量为-250股的空头仓位。
持有空仓-100股 卖出250股 在现有仓位上加仓-250股,总仓位变为-350股空仓。
持有多仓250股 卖出250股 平掉全部多仓。卖出的-250股正好对冲掉持有的250股多仓,最终仓位变为空仓。
持有多仓400股 卖出250股 部分平掉多仓(减仓)。卖出的-250股使多仓数量从400股减少到150股。
持有多仓100股 卖出250股 反手开空仓。卖出的-250股中,100股用于平掉多仓,剩下的-150股开立了一个新的空头仓位。

strategy.order() 执行的指令每次都完全相同:卖出250股。但最终的结果却因初始持仓状态的不同而千差万别。

获取初始持仓状态

既然策略的初始持仓状态决定了 strategy.order() 的行为,那么我们如何才能在代码中获知这个状态呢?答案是使用 strategy.position_size 变量。

通过检查 strategy.position_size 的值,我们就可以,例如,只在空仓时才执行 strategy.order()。当我们能够精确控制 strategy.order() 的执行情境时,我们就能可靠地预测这个函数将会如何改变我们的策略仓位了。

总结

  • strategy.order() 函数可以发送买入(做多)或卖出(做空)订单。
  • 同一个 strategy.order() 指令可能会产生5种不同的效果:开新仓、加仓、全部平仓、部分平仓(减仓),以及反手开仓。
  • 具体会产生哪种效果,完全取决于 strategy.order() 函数被调用时,策略的当前持仓状态。
  • 我们可以通过内置变量 strategy.position_size 来获取当前的持仓状态,从而在特定情境下才执行 strategy.order(),以此来精确控制和预测它的行为。
赞(0)
未经允许不得转载:图道交易 » Pine Script(213):订单的两个阶段与持仓状态
分享到

评论 抢沙发

登录

找回密码

注册