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

Pine Script(219):设置基于百分比的追踪止损

#Pine Script入门教学

如何设置基于百分比的追踪止损

移动止损(Trailing Stop)能以一个随着价格向有利方向移动而不断优化的价格来平仓。虽然TradingView支持移动止损,但它无法直接基于百分比来设置。不过幸运的是,我们可以自己编写代码来实现这一功能。让我们看看具体怎么做。

移动止损的原理是,先在市价下方(对于多头)或上方(对于空头)设置一个初始的止损价。然后,当市场价格向对我们有利的方向移动时,只要能获得一个更优的执行价格,止损位就会随之更新。这不仅能限制交易风险,理想情况下,止损位还能移动足够多的距离来锁定部分利润。

我们通常使用 strategy.order()strategy.exit() 函数来提交平仓的止损单。虽然 strategy.exit() 功能最灵活,但它也只能发送基于固定价格和固定点数的移动止损。然而,基于百分比的止损在很多时候也同样非常有用。

试想一下,如果我们进行一个长期的历史回测,期间交易品种的价格已经发生了巨大的涨跌。在这种情况下,一个固定点数的移动止损就显得不太实用了。因为当品种价格很高时,这个固定点数的止损可能离市价太近,容易被正常波动扫掉;而当品种价格很低时,同样点数的止损又可能离市价太远,起不到有效的保护作用。

一个可行的解决方案就是使用基于百分比的移动止损。这种止损能始终与市价保持一个固定的相对距离。下面我们来看看如何在PineScript中实现它。

实现百分比移动止损的步骤

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

第一步,通过输入选项设置移动止损百分比。创建输入选项,可以让我们在策略设置界面中方便地调整移动止损的百分比,而无需每次都去修改策略的源代码。为了能够独立地配置多头和空头的止损,我们分别为它们创建输入选项:

// 通过输入选项配置移动止损水平(可选)
longTrailPerc = input.float(3, title="多头移动止损 (%)",
     minval=0.0, step=0.1) * 0.01

shortTrailPerc = input.float(3, title="空头移动止损 (%)",
     minval=0.0, step=0.1) * 0.01

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

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

(如果你的策略不需要这些输入,只需知道在本文后续部分,longTrailPercshortTrailPerc 分别代表多头和空头的移动止损百分比(小数形式)即可。)

第二步,确定多头和空头的移动止损价格。有了以变量形式存储的移动止损百分比,我们就可以用它们来计算出移动止损单应该设置的具体价格了。为此,我们分两段代码来处理。首先计算多头的移动止损价格:

// 确定移动止损价格
longStopPrice = 0.0

longStopPrice := if strategy.position_size > 0
    stopValue = close * (1 - longTrailPerc)
    math.max(stopValue, longStopPrice[1])
else
    0

这段代码的逻辑有些复杂,我们来分解一下。首先,我们声明了 longStopPrice 变量,然后通过一个 if/else 语句为其赋予实际的值。

if 的条件是判断策略当前是否持有多头仓位(strategy.position_size > 0)。如果是,我们就需要确定多头的止损价,这分为两步:

首先,我们计算出一个临时的止损价 stopValue,它的值是当前K线的收盘价乘以 (1 - longTrailPerc)。用1减去止损百分比,是因为多头止损的初始位置应该在当前市价的下方。例如,如果 longTrailPerc 是0.07(7%),那么 stopValue 就是 close * 0.93

当然,我们需要的不仅仅是一个静态的百分比止损,它还需要能够移动。math.max() 函数的作用就在于此。它会比较刚刚计算出的 stopValuelongStopPrice 在前一根K线上的值(longStopPrice[1]),并返回两者中较大的那个。

通过这种方式,与前一根K线相比,我们的止损价只能提高或保持不变,绝不会降低。由于这个结果在每根K线上都会被重新赋值给 longStopPrice 变量,就实现了多头止损只上不下的移动效果。

当策略没有多头仓位时,else 部分的代码会执行,将 longStopPrice 重置为0,以便下一次开多仓时能重新开始计算。

第二段代码用于计算空头的移动止损价格:

// 确定移动空头价格
shortStopPrice = 0.0

shortStopPrice := if strategy.position_size < 0
    stopValue = close * (1 + shortTrailPerc)
    math.min(stopValue, shortStopPrice[1])
else
    999999

这段代码的逻辑与多头非常相似。if 条件判断策略是否持有空头仓位。如果是,我们计算的 stopValueclose * (1 + shortTrailPerc),因为空头止损的初始位置应在市价的上方。

接着,我们使用 math.min() 函数来比较当前的 stopValue 和前一根K线的 shortStopPrice,并取其较小值。这样,空头的移动止损就只会向下移动或保持不变。

当策略没有空头仓位时,else 部分会将 shortStopPrice 重置为一个非常大的数999999。这样做是为了确保在下一次开空仓时,math.min() 能够正确地取到新计算出的 stopValue 作为初始止损价,而不是被一个过小的前期值(如0)所干扰。

注意:在本文中,我们是基于收盘价(close)来移动止损的。你也可以根据自己的需要,使用K线的最低价(low)来移动多头止损,或使用最高价(high)来移动空头止损,只需替换相应的变量即可。

第三步,提交策略的移动止损订单。计算出多空移动止损的具体价格后,我们就可以通过 strategy.exit() 函数来提交实际的止损订单了:

// 为移动止损价格提交平仓订单
if strategy.position_size > 0
    strategy.exit("XL TRL STP", stop=longStopPrice)

if strategy.position_size < 0
    strategy.exit("XS TRL STP", stop=shortStopPrice)

第一个 if 语句检查策略是否持有多头仓位。如果是,就调用 strategy.exit() 发送一个名为 “XL TRL STP” 的止损单,其价格就是我们动态计算的 longStopPrice。第二个 if 语句则处理空头仓位,提交一个价格为 shortStopPrice 的止损单。

通过这种方式,虽然 strategy.exit() 函数本身不支持基于百分比的移动止损,但我们通过自己计算出百分比对应的具体价格,然后利用该函数基于价格的 stop 参数,巧妙地实现了这一功能。

注意:我们也可以使用 strategy.order() 函数来提交止损单。但该函数需要我们自己指定订单数量,并且可能会意外地开立反向仓位。为了方便起见,本文统一使用 strategy.exit() 函数。

示例策略:使用基于百分比的移动止损进行交易

下面的示例策略整合了我们上面讨论的所有三个步骤。该脚本基于两条移动平均线的交叉进行交易,当快线上穿慢线时做多,下穿时则做空。

该策略有两种平仓方式:一是当均线反向交叉时,仓位会反转;二是通过移动止损退出。我们初始将移动止损设在距离市价3%的位置。当价格向不利方向移动时,止损位会触发,以防止更大的亏损。

策略的完整代码如下:

//@version=5
strategy(title="Trailing stop loss (% of instrument price)",
     overlay=true, pyramiding=3)

// 步骤 1:
// 通过输入选项配置移动止损水平(可选)
longTrailPerc = input.float(3, title="多头移动止损 (%)",
     minval=0.0, step=0.1) * 0.01

shortTrailPerc = 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:
// 确定移动止损价格
longStopPrice = 0.0
shortStopPrice = 0.0

longStopPrice := if strategy.position_size > 0
    stopValue = close * (1 - longTrailPerc)
    math.max(stopValue, longStopPrice[1])
else
    0

shortStopPrice := if strategy.position_size < 0
    stopValue = close * (1 + shortTrailPerc)
    math.min(stopValue, shortStopPrice[1])
else
    999999

// 绘制止损位以供确认
plot(strategy.position_size > 0 ? longStopPrice : na,
     color=color.fuchsia, style=plot.style_cross,
     linewidth=2, title="多头移动止损位")
plot(strategy.position_size < 0 ? shortStopPrice : na,
     color=color.fuchsia, style=plot.style_cross,
     linewidth=2, title="空头移动止损位")

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

// 步骤 3:
// 为移动止损价格提交平仓订单
if strategy.position_size > 0
    strategy.exit("XL TRL STP", stop=longStopPrice)
if strategy.position_size < 0
    strategy.exit("XS TRL STP", stop=shortStopPrice)

让我们看看这个策略的实际表现。下图中的标普500 CFD空头交易发生在下跌趋势接近尾声时。但随着价格继续下跌,移动止损也随之向下移动。

在交易过程中,市场的最低收盘价达到了2,804.3。此后价格开始缓慢回升,移动止损在2,888.5被触发。从那个最低收盘价算起,这次止损导致了84.2点的回撤(或-3.0025%)。幸运的是,止损位已经向对我们有利的方向移动了不少,使得最终的平仓价位仍在开仓价附近。

再来看另一个例子。下图中,策略使用了2%的移动止损。在一个漂亮的上涨趋势开始前,脚本建立了多头仓位。在趋势消退后,价格出现了显著的回调。幸运的是,移动止损保护了我们的一部分利润。

交易过程中的最高收盘价是2,816.8,而止损最终在2,760.4成交。虽然相对于开仓价来说这笔交易是盈利的,但与最高点相比,利润回撤了56.4点,即2.0022%。

赞(0)
未经允许不得转载:图道交易 » Pine Script(219):设置基于百分比的追踪止损
分享到

评论 抢沙发

登录

找回密码

注册