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

Pine Script(222):百分比止盈与限价单成交假设

#Pine Script入门教学

如何用基于百分比的止盈目标退出交易

通过止盈订单,我们可以预先设定一个价格目标,然后在价格达到该水平时通过限价单退出仓位。虽然TradingView提供了多种订单函数,但没有一个能直接支持基于百分比的止盈目标。不过,我们可以自己编写代码来实现这一功能。让我们看看具体怎么做。

止盈订单(Take Profit Order)的作用是,在高于开仓价(对于多头)或低于开仓价(对于空头)的某个价位预设一个平仓指令。当市场价格达到这个水平时,限价单便会成交,从而将我们期望的利润收入囊中。

在TradingView中,要通过限价单平仓,我们可以使用 strategy.order()strategy.exit() 函数。尽管 strategy.exit() 功能灵活,但它也只支持基于固定价格和固定点数的止盈目标,无法直接创建基于百分比的止盈单。

然而,百分比止盈实际上非常有用,尤其是在我们回测的交易品种价格随时间发生了巨大变化的情况下。在这种场景下,一个固定点数的止盈目标有时会离当前价格太近,有时又太远。而一个基于百分比的止盈目标则能始终与我们的开仓价保持一个固定的相对距离。

幸运的是,实现百分比止盈只需几个简单的步骤。

实现百分比止盈的步骤

要让策略能够生成基于百分比的止盈订单,我们需要执行三步:先(可选地)创建一个用户输入框,用于灵活配置止盈的百分比;然后使用这个百分比计算出实际的止盈价格;最后根据计算出的价格,提交具体的限价平仓订单。下面,我们来逐一审视这些步骤及其所需的代码。

第一步,创建配置止盈百分比的输入框。为了能够方便地配置止盈目标,我们可以创建输入选项。这样,我们就可以在不修改策略代码的情况下,随时调整止盈的百分比。为了获得最大的灵活性,我们分别为多头和空头仓位创建输入选项:

// 创建用于设置止盈百分比的输入选项(可选)
longProfitPerc = input.float(3, title="多头止盈 (%)",
     minval=0.0, step=0.1) * 0.01

shortProfitPerc = input.float(3, title="空头止盈 (%)",
     minval=0.0, step=0.1) * 0.01

我们使用 input.float() 函数创建了两个浮点数输入框。第一个名为”多头止盈 (%)”,默认值为3(即3%),其最小值(minval)设为0,以防意外输入负值。我们将这个输入框的当前值存储在 longProfitPerc 变量中。另一个输入框”空头止盈 (%)”也同样默认从3%开始,且最小值也为0,它的值由 shortProfitPerc 变量来跟踪。

请注意,我们将每个输入框的值都乘以了0.01。这是因为,虽然我们在界面上设置的是百分比(如3%或15%),但在后续的计算中,使用小数形式(如0.03和0.15)会更加方便。

第二步,根据百分比计算实际的目标价格。知道了止盈的百分比后,我们就可以将它们转换成实际的交易品种价格了:

// 计算出止盈价格
longExitPrice  = strategy.position_avg_price * (1 + longProfitPerc)
shortExitPrice = strategy.position_avg_price * (1 - shortProfitPerc)

为了计算出止盈价格,我们使用了 strategy.position_avg_price 这个内置变量,它能返回当前持仓的平均开仓价格。

对于多头止盈,我们用 strategy.position_avg_price 乘以 (1 + longProfitPerc)。这里的 longProfitPerc 就是上一步中定义的输入变量,我们用1加上这个盈利百分比,来计算出价格向上的目标位。

举个例子:假设开仓价是2,480.75,我们的止盈目标是7%(即小数0.07)。那么 1 + 0.07 等于1.07,用开仓价乘以1.07,我们得到2,654.4025。这个价格正好比我们的开仓价高7%,也就是我们多头止盈单应该设置的位置。

对于空头止盈,计算方式类似。我们用1减去 shortProfitPerc,然后乘以 strategy.position_avg_price。这样计算出的结果就是一个比我们开仓价低某个百分比的价格,也就是我们空头止盈单的目标位置。

我们将计算出的实际止盈价位分别存储在 longExitPriceshortExitPrice 变量中,以备下一步使用。

注意:使用 strategy.position_avg_price 有一个非常实用的好处。当策略进行加仓或减仓操作时,这个变量的值会自动更新,以反映最新的持仓平均成本。由于我们的止盈目标是基于这个值计算的,它们也会随之自动调整,从而确保止盈总能在一个正确的盈利水平上触发,无论中间发生了多少次进出场。

第三步,在计算出的目标价格提交限价订单。现在,我们有了具体的目标价格,可以提交实际的平仓订单了。我们使用 strategy.exit() 函数来实现:

// 基于止盈价格提交平仓订单
if strategy.position_size > 0
    strategy.exit("XL TP", limit=longExitPrice)

if strategy.position_size < 0
    strategy.exit("XS TP", limit=shortExitPrice)

第一个 if 语句检查策略当前是否持有多头仓位(strategy.position_size > 0)。如果是,我们就调用 strategy.exit() 提交一个名为 “XL TP”(Exit Long Take Profit)的平仓订单,其价格就是我们上一步计算出的 longExitPrice。同样,第二个 if 语句检查策略是否持有空头仓位,如果是,就提交一个价格为 shortExitPrice 的 “XS TP” 订单。

虽然 strategy.exit() 函数本身并不直接支持百分比止盈,但好消息是,我们仍然可以实现这个功能。这是因为该函数提供了一个 limit 参数,可以接受一个具体的限价单价格。因此,只要我们自己先计算出百分比目标对应的具体价格,就可以通过 strategy.exit() 来发送这个订单。

注意:本文为了方便起见,统一使用了 strategy.exit() 函数。虽然 strategy.order() 函数也能发送止盈订单,但该函数可能会意外地在平仓的同时开立一个反向的新仓位,并且我们还必须自己计算正确的订单数量。因此,strategy.exit() 在这种场景下使用起来更简单。

示例策略:使用百分比止盈交易均线交叉

下面的示例策略整合了我们上面讨论的所有三个步骤。该脚本基于两条简单移动平均线(SMA)的交叉进行交易。如果快线上穿慢线,我们做多;反之则做空。

这些交易有两种平仓方式。第一种是当均线再次反向交叉时,策略会反转仓位。另一种则是通过基于百分比的止盈目标来平仓。止盈目标被设在距离开仓价3%的位置(多头向上3%,空头向下3%)。为了方便观察这些价位,我们用圆点图将它们绘制在图表上。

策略的完整代码如下:

//@version=5
strategy(title="Take profit (% of instrument price)",
     overlay=true, pyramiding=3)

// 步骤 1:
// 创建用于设置止盈百分比的输入选项(可选)
longProfitPerc = input.float(3, title="多头止盈 (%)",
     minval=0.0, step=0.1) * 0.01

shortProfitPerc = input.float(3, title="空头止盈 (%)",
     minval=0.0, step=0.1) * 0.01

// 计算移动平均线
fastSMA = ta.sma(close, 20)
slowSMA = ta.sma(close, 60)

// 计算交易条件
enterLong  = ta.crossover(fastSMA, slowSMA)
enterShort = ta.crossunder(fastSMA, slowSMA)

// 绘制移动平均线
plot(fastSMA, color=color.teal)
plot(slowSMA, color=color.orange)

// 步骤 2:
// 计算出止盈价格
longExitPrice  = strategy.position_avg_price * (1 + longProfitPerc)
shortExitPrice = strategy.position_avg_price * (1 - shortProfitPerc)

// 绘制止盈位以供确认
plot(strategy.position_size > 0 ? longExitPrice : na,
     color=color.green, style=plot.style_circles,
     linewidth=3, title="多头止盈位")
plot(strategy.position_size < 0 ? shortExitPrice : na,
     color=color.red, style=plot.style_circles,
     linewidth=3, title="空头止盈位")

// 提交开仓订单
if enterLong
    strategy.entry("EL", strategy.long)
if enterShort
    strategy.entry("ES", strategy.short)

// 步骤 3:
// 基于止盈价格提交平仓订单
if strategy.position_size > 0
    strategy.exit("XL TP", limit=longExitPrice)
if strategy.position_size < 0
    strategy.exit("XS TP", limit=shortExitPrice)

让我们来看看这个策略的实际表现。在下方的图表中,脚本在标准普尔500 CFD的2,890.70价位做空。这笔交易回报颇丰:几天后,仓位在2,803.90被名为 “XS STP” 的订单平掉。这带来了86.8点的利润,即3.0027%,策略在此处成功触及了其止盈目标。

现在,让我们将多头的止盈百分比设为6%。下图展示了其效果。这里,2019年初的多头开仓单在2,489.2成交。止盈目标随后在2,638.6被触发,带来了149.4点的利润,即比我们的开仓价高出6.0019%。

策略如何验证限价单?成交假设详解

策略的 backtest_fill_limits_assumption 设置(及其在UI上等效的”Verify Price for Limit Orders”选项)能够让限价单的回测结果更贴近现实。但这个TradingView的功能究竟是如何运作的呢?

我们来一探究竟!首先,我们将了解 backtest_fill_limits_assumption 的特性,然后通过几个图表示例来加深理解。

限价单成交假设的特性

这个设置有几个核心特性:只有当价格穿过限价一定幅度后,订单才会成交;它模拟真实的订单队列效应;以相同的方式影响所有限价单;并且可以通过一个公式来精确计算成交条件。让我们逐一详细了解。

首先是以优于限价的价格成交。启用 backtest_fill_limits_assumption 设置后,一个限价单只有在K线的市场价格优于其指定价格达到一定点数(ticks)后,才会被认为成交。这意味着:买入限价单只有当市场价格低于其限价达到指定点数后才会成交;卖出限价单只有当市场价格高于其限价达到指定点数后才会成交。

如果K线的价格波动范围未能满足这个穿透条件,那么限价单就不会成交,而是继续保持挂单状态,直到条件满足或被策略撤销。

例如,假设我们将 backtest_fill_limits_assumption 的值设为3。在这种情况下,买入限价单只有当市场价低于限价3个点时才会成交,卖出限价单只有当市场价高于限价3个点时才会成交。

这个设置的本质,是模拟一个订单队列。它模拟了这样一种真实场景:我们的订单在订单簿中的排队位置并不靠前,或者在限价价位上的成交量很小。

在没有启用此设置时,限价单只要被市场价格触及就会立即成交。这是一种非常乐观的假设,它默认我们的订单排在队列的最前面,并且有充足的流动性来确保成交。而启用这个设置后,我们就不再做这种乐观假设。现在,订单只有在价格穿透其价位后才能成交。当这种情况在真实交易中发生时,我们的订单几乎肯定能够成交。

这个设置会以相同的方式影响所有类型的限价单,包括买入和卖出、开仓和平仓的限价单,以及常规限价单和停止限价单中的限价部分。在代码层面,它会影响由 strategy.entry()strategy.exit()strategy.order() 函数生成的限价单。市价单和停止单则不受此设置的影响。

它的工作原理也可以用一个公式来精确表达。对于买入限价单,成交条件为:

市场价格 <= 订单限价 - (假设值 * 最小变动单位)

对于卖出限价单,成交条件为:

市场价格 >= 订单限价 + (假设值 * 最小变动单位)

公式中,市场价格是K线实际达到的价格,订单限价是我们设定的限价,假设值是 backtest_fill_limits_assumption 设置的数值(单位为ticks),最小变动单位(tick size)则是该交易品种的最小价格波动单位。

来看一个买入限价的例子。假设策略在以下场景中提交了一个买入限价单:品种当前价格为4,280.0,买入限价单挂在4,278.50,最小变动单位是0.50,backtest_fill_limits_assumption 设为4个ticks。根据公式,成交条件为:市场价格必须达到4,278.50 – (4 * 0.50) = 4,276.50。因此,当价格跌至或低于4,276.50时,TradingView会在4,278.50这个价位上成交该限价单;如果价格未能达到4,276.50,则该限价单不会成交。

再看一个卖出限价的例子:品种当前价格为16,395.75,卖出限价单挂在16,398.50,最小变动单位是0.25,backtest_fill_limits_assumption 设为8个ticks。根据公式,成交条件为:市场价格必须达到16,398.50 + (8 * 0.25) = 16,400.50。当价格升至或高于16,400.50时,TradingView会在16,398.50这个价位上成交该限价单;如果价格未能达到16,400.50,则该限价单不会成交。

限价单成交假设的影响

了解了 backtest_fill_limits_assumption 的工作原理后,让我们探讨一下这个设置在实践中会带来哪些影响。

第一个非常重要的好处是,它能有效避免订单在不切实际的位置成交,例如K线的精确最高价或最低价。因为它要求价格必须穿透限价位,所以那种完美的极限成交将不再发生。

例如,在不启用此设置时,限价单可能会在K线的精确最高价成交:

这种成交在真实交易中几乎是不可能复制的。为了让回测更真实,我们将 backtest_fill_limits_assumption 设为2。这会改变策略的行为:

即使是一个只有2个ticks的保守值,也能让限价单的成交位置变得更加贴近现实。

第二个影响是可能导致订单在K线范围外成交。启用此设置后,限价单会在价格穿透限价位时成交,但成交价格本身并不会改变,仍然是你最初设定的限价。当市场需要不止一根K线才能满足这个穿透条件时,就可能出现订单在K线范围之外成交的奇特现象。以下图为例:

这里的蓝线是卖出限价单的价格,橙色线是市场价格必须达到的穿透价位。几根K线后,市场价格终于触及了橙色线,此时TradingView判定成交条件满足,并以原始的限价(蓝线价格)成交了该订单。由于成交时的K线范围并未包含这个原始限价,所以成交标记被画在了K线的最低价下方。

如果我们不了解这个机制,可能会误以为这是策略的一个bug。但实际上,这正是这个设置的工作特性。

第三个实际影响是,限价单成交的概率会降低。毕竟,它为成交增加了一个额外的条件。如果价格触及了限价,但未能进一步穿透指定的距离,订单就不会成交。由于这种额外的移动并非总能发生,一些在标准回测中能成交的订单,在启用此设置后可能永远不会执行。

以下图的多头交易为例。蓝线是限价,但橙色线是实际的成交触发线:

由于价格未能触及橙色线,箭头所指的这次入场机会便被错过了。如果我们减小 backtest_fill_limits_assumption 的值,那么TradingView就能成交这笔限价单了:

启用此设置后,仅仅触及限价是不够的,价格必须穿透它。由于这个条件更苛刻,成交的概率自然也就更低了。

简单总结一下:backtest_fill_limits_assumption 设置让限价单只有在市场价格优于其限价达到指定点数后才会成交。这个功能通过模拟订单队列来避免订单在不切实际的价格(如K线的精确最高或最低价)成交,从而让回测更真实,它会影响所有类型的限价单。启用此设置后,可能会导致某些订单无法成交,也可能导致某些成交标记出现在K线范围之外。

赞(0)
未经允许不得转载:图道交易 » Pine Script(222):百分比止盈与限价单成交假设
分享到

评论 抢沙发

登录

找回密码

注册