为TradingView编写的双重移动平均线趋势跟踪策略
移动平均线是大多数交易者不可或缺的指标。它们能抚平价格毛刺,平滑其他指标的读数,并辅助解读成交量。基础的趋势跟踪系统也常常使用移动平均线来建立多头和空头仓位。但这些基础的策略究竟有效吗?让我们通过在TradingView中编写一个这样的策略来一探究竟。
双移动平均线策略:一种纯粹的趋势跟踪
柯蒂斯·费思(Curtis Faith)在其著作《海龟交易法则》(Way of the Turtle)中,分享了他作为第一代海龟成员所获得的深刻见解。1983年之后,一群被称为海龟的普通人,接受了当时极度成功的交易员理查德·丹尼斯的盈利交易培训。培训结束后,丹尼斯为他们每人提供了高达200万美元的账户进行交易。费思是其中最出色的海龟之一,为丹尼斯赚取了超过3000万美元的利润。
海龟们是坚定的趋势跟踪者。趋势跟踪的核心,是试图从持续数月的大级别价格波段中获利。他们在价格突破历史高点时买入,在价格跌破数月支撑位时做空。这是一种极具挑战性的交易风格,其胜率通常只有30-35%,资金回撤可能非常巨大,错过一到三笔关键的盈利交易,就可能毁掉一整年的努力。
幸运的是,其回报也可能相当可观。费思在其书中分享的策略,据称能达到每年约30-40%的收益。其中之一便是双移动平均线(Dual Moving Average)策略。顾名思义,该策略使用两条移动平均线:一条100日均线和一条350日均线。
双移动平均线策略有几个不寻常之处。它使用非常长期的移动平均线,这使得策略的交易频率相当低。该策略的另一个不寻常之处在于,它始终在市(always in the market)。当两条均线交叉时,它会从多头反转为空头(反之亦然)。接下来,让我们仔细看看该策略的交易规则。
双移动平均线策略的交易规则
该策略包含以下交易规则。做多入场与空头离场:当100日简单移动平均线(SMA)上穿350日移动平均线时,建立一个多头仓位(并平掉所有已有的空头仓位)。做空入场与多头离场:当100日移动平均线下穿350日移动平均线时,建立一个空头仓位(并平掉所有已有的多头仓位)。头寸规模:对于多头和空头头寸,将权益的0.5%除以该市场20周期平均真实波幅(ATR)所代表的美元价值,来确定头寸大小。
双移动平均线策略不使用明确的止损。然而,它包含了一个隐性的止损机制。当价格朝持仓的不利方向移动时,两条移动平均线迟早会再次发生交叉。到那时,策略便会平掉亏损的仓位(从而停止亏损),并向相反的方向开仓。这种方式虽然有止损效果,但我们无法预先知道确切的离场价位。
费思在他的书中分享了双移动平均线策略的盈利回测结果。该回测是在日线数据上,对一系列美国期货投资组合进行的,包括外汇(澳元、英镑、欧元)、大宗商品(黄金、原油、铜、天然气)、软商品(棉花、咖啡、活牛、大豆)以及固定收益期货(美国国库券和国债)。
在TradingView中编写双移动平均线策略
现在,让我们将上述交易规则转化为一个功能完备的TradingView策略脚本。使用模板通常能让策略编写过程更轻松。它将一项大任务分解成更小、更易于管理的工作区块,也能避免我们面对空白脚本时无从下手。此外,模板还能为我们的思路增加一些结构性,减少遗漏。
我们将使用以下模板来编写双移动平均线策略:
//@version=5
// 1. 定义策略设置
// 2. 计算策略所需指标值
// 3. 在图表上输出数据
// 4. 定义多头交易条件
// 5. 定义空头交易条件
// 6. 提交入场订单
// 7. 提交出场订单
如果你想跟随本文一起操作,请在TradingView的Pine编辑器中创建一个新的策略脚本,并将上述模板粘贴进去。
为了让你对我们即将编写的代码有个直观的认识,以下是最终完成的双移动平均线策略在图表上的样子:
现在,让我们开始将上述交易规则,一步步地转化为一个规范的TradingView策略脚本。
步骤1:定义策略设置和输入选项
在第一步,我们需要配置策略的整体属性,并创建用户可调的参数设置。我们用 strategy() 函数作为代码的开端,来配置策略的默认行为:
// 1. 定义策略设置
strategy(title="双均线策略", overlay=true,
pyramiding=0, initial_capital=100000,
commission_type=strategy.commission.cash_per_order,
commission_value=4, slippage=2)
我们通过 strategy() 函数设置策略属性。title 参数为脚本命名。overlay=true 使得策略直接叠加在主图表上。根据交易规则,我们通过 pyramiding=0 禁止加仓。策略的初始资金设为100,000(initial_capital=100000)。在交易成本方面,我们设定每笔单边交易(strategy.commission.cash_per_order)收取4个单位的固定佣金(commission_value=4),并假设市价单和止损单会产生2个最小跳动点的滑点(slippage=2)。这里的成本设置相对悲观,这样可以避免我们高估策略的真实表现。
接下来,我们为策略的各项参数创建输入选项:
fastMALen = input.int(100, title="快线MA周期")
slowMALen = input.int(350, title="慢线MA周期")
usePosSize = input.bool(true, title="启用仓位管理?")
riskPerc = input.float(0.5, title="风险百分比 %", step=0.25)
前两个是整数输入项,通过 input.int() 函数创建,分别用于设置快慢两条移动平均线的计算周期,默认值分别为100和350。
第三个输入项是一个布尔型的复选框,由 input.bool() 创建。我们默认开启它(true)。这个选项可以一键控制是启用我们自定义的仓位管理算法,还是为每笔交易固定交易1个合约。
最后一个是浮点数输入项,由 input.float() 创建,用于设定在仓位管理中我们愿意承担的权益风险比例,默认值为0.5%。
我们将每个输入项的当前值都保存在一个独立的变量中,以便在后续代码中方便地通过变量名来引用用户的当前设置。
步骤2:计算交易策略所需的值
在第二步,我们计算策略运行所需的核心指标值,包括移动平均线和头寸规模。首先,我们计算策略所需的移动平均线:
// 2. 计算策略所需值
fastMA = ta.sma(close, fastMALen)
slowMA = ta.sma(close, slowMALen)
我们使用 ta.sma() 函数来计算简单移动平均线(SMA)。该函数需要两个参数:用于计算的数据序列(这里是收盘价 close)和计算周期。我们分别计算出快线(fastMA)和慢线(slowMA)的值。
然后,我们计算策略的头寸规模:
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = ta.atr(20) * syminfo.pointvalue
posSize = usePosSize ? math.floor(riskEquity / atrCurrency) : 1
我们这里采用的仓位管理算法基于两个核心部分:一部分是策略权益的固定百分比,另一部分是ATR的货币价值。
首先,我们计算出本次交易愿意承担的风险金额 riskEquity。我们将输入的风险百分比 riskPerc 转换为小数(例如,0.5%变为0.005),然后乘以 strategy.equity(策略的当前总权益)。
接着,我们将ATR值转换为具体的货币价值 atrCurrency。我们用 ta.atr() 计算出20周期的ATR值,再乘以 syminfo.pointvalue(当前交易品种每波动一点所代表的货币价值)。
最后,我们通过条件运算符(?:)来确定最终的头寸规模 posSize。如果用户在输入项中勾选了启用仓位管理(usePosSize 为 true),我们就用风险金额 riskEquity 除以每单位风险(即ATR的货币价值 atrCurrency),并用 math.floor() 向下取整。如果未启用,那么 posSize 就固定为1。
然后,我们判断移动平均线的交叉状态:
maCrossover = ta.crossover(fastMA, slowMA)
maCrossunder = ta.crossunder(fastMA, slowMA)
我们使用 ta.crossover() 来判断快线(fastMA)是否向上穿越了慢线(slowMA),并将结果(true 或 false)存入 maCrossover 变量。同理,我们使用 ta.crossunder() 来判断死叉,并将结果存入 maCrossunder 变量。
步骤3:输出数据并可视化信号
接下来,我们输出策略所需的值,以便在图表上直观地验证交易设置。我们使用 plot() 函数来在图表上绘制均线:
// 3. 输出策略数据
plot(fastMA, color=color.teal, linewidth=2, title="快线MA")
plot(slowMA, color=color.orange, linewidth=2, title="慢线MA")
第一行代码绘制了青色的快线MA,并加粗了线条。第二行则绘制了橙色的慢线MA。
我们还可以用背景色来高亮显示均线交叉的时刻:
bgColour = maCrossover ? color.new(color.green, 90) :
maCrossunder ? color.new(color.red, 90) :
na
bgcolor(bgColour)
我们首先定义一个 bgColour 变量来储存背景颜色。通过嵌套的条件运算符,我们判断:如果发生金叉(maCrossover 为 true),就返回一个90%透明度的绿色;否则,如果发生死叉(maCrossunder 为 true),就返回一个90%透明度的红色;如果两者都不成立,则返回 na(即无颜色)。最后,我们调用 bgcolor() 函数,将图表背景设置为 bgColour 变量所代表的颜色。
步骤4:编写多头交易规则
接下来是策略的交易条件。由于双均线策略是一个永远在市的系统,其开仓条件相对简单。多头条件如下:
// 4. 定义多头交易条件
enterLong = maCrossover and
barstate.ishistory
我们在这里定义的 enterLong 变量,只有在两个条件同时满足时才为 true:第一,发生了均线金叉(maCrossover 为 true);第二,脚本当前正运行在历史K线上(barstate.ishistory 为 true),这是一个常用的技巧,用于确保回测结果的干净利落,避免在回测结束时仍有持仓。
步骤5:编写空头交易条件
然后,我们计算策略的空头交易条件:
// 5. 定义空头交易条件
enterShort = maCrossunder and
barstate.ishistory
这段代码的逻辑与多头条件类似。只有当发生均线死叉(maCrossunder)并且脚本运行在历史K线上时,enterShort 才为 true。
步骤6:提交开仓订单
接下来,我们利用上面定义的条件来提交策略的开仓订单:
// 6. 提交开仓订单
if enterLong
strategy.entry("EL", strategy.long, qty=posSize)
if enterShort
strategy.entry("ES", strategy.short, qty=posSize)
第一个 if 语句评估 enterLong 是否为 true。如果是,我们就调用 strategy.entry() 函数提交一个ID为”EL”、数量为 posSize 的多头开仓(strategy.long)订单。
第二个 if 语句同理,当 enterShort 为 true 时,开立一个ID为”ES”的空头仓位(strategy.short)。
请注意,我们没有编写任何明确的反转仓位代码。这是因为 strategy.entry() 函数本身就具备自动反转功能。当我们持有多仓时,一个做空指令会自动先平掉多仓,然后再开立空仓,反之亦然。
步骤7:提交平仓订单
策略代码的最后一部分负责处理仓位的退出:
// 7. 提交平仓订单
if barstate.islastconfirmedhistory
strategy.close_all()
这个 if 语句判断当前K线是否为图表上的最后一根历史K线。如果是,barstate.islastconfirmedhistory 变量就为 true。
在那根K线上,我们调用 strategy.close_all() 函数。这个函数会以市价单平掉所有当前持有的仓位,使策略回归空仓状态。由于我们在历史数据的最后一根K线上执行此操作,这确保了策略在回测结束时是空仓的,所有绩效报告中的交易都是基于已平仓的交易。
双均线策略的表现
让我们先从一个积极的观察开始。和大多数趋势跟踪策略一样,双均线策略在长期的趋势行情中表现出色。
例如,在下方的图表中,该策略成功捕捉了原油期货长达数年的下跌趋势。空头入场点约在$93,最终在$45的水平回补:
但不幸的是,双均线策略也暴露了趋势跟踪策略的共同弱点:当市场进入横盘震荡时,其表现会受到严重影响。
在那种市场环境下,策略会因为趋势无法有效展开,或者趋势行程过短而导致亏损。在后一种情况下,策略往往会在退出信号出现前就回吐掉所有浮动盈利。
例如,在下图这个持续数年的横盘市场中,策略就遭遇了连续数次的亏损:
下表展示了双均线策略在原油期货和E-mini S&P 500期货上的回测表现。关于此策略的表现,有三点需要特别注意。首先,此回测是在未启用仓位管理的情况下进行的,目的是为了确保策略执行每一个交易信号,从而在报告中获得尽可能多的交易样本。其次,交易总数依然过少,不足以对策略的长期表现做出任何确定性的结论。最后,也是最重要的一点,ES期货高达75%的胜率是由极小的样本量导致的统计偏差,完全不可信。典型的趋势跟踪策略胜率通常在30-35%之间。
| 绩效指标 | 原油 (CL) | E-mini S&P500 (ES) |
|---|---|---|
| 首次交易 | 1985-11-05 | 2001-01-02 |
| 最后交易 | 2018-09-17 | 2018-09-17 |
| 时间周期 | 日线 | 日线 |
| 净利润 | -$32,888 | $125,426 |
| 总盈利 | $128,962 | $132,347 |
| 总亏损 | -$161,850 | -$6,920 |
| 最大回撤 | $81,142 | $5,693 |
| 盈利因子 | 0.797 | 19.124 |
| 总交易数 | 31 | 8 |
| 胜率 | 29.03% | 75% |
| 平均每笔交易 | -$1,060 | $15,678 |
| 平均盈利 | $14,329 | $22,057 |
| 平均亏损 | -$7,356 | -$3,460 |
| 盈亏比 | 1.948 | 6.375 |
| 支付佣金 | $128 | $36 |
| 滑点 | 2 跳 | 2 跳 |
改进思路与新策略方向
正如上面的迷你回测所示,这个双均线策略很可能还有很大的优化空间。以下是一些你可能会觉得有价值的探索方向。
增加市场过滤器:该策略是一个永远在市的系统,但市场并非一直适合趋势跟踪。如果能引入有效的市场过滤器,帮助策略避开缓慢的横盘震荡市,其表现有望得到显著提升。
参数优化:原作者费思并未说明他选择这些参数的原因。或许这些参数是最佳的,但也可能只是为了方便而随意选择的。探索其他均线周期组合,或许能让策略表现得更好。
提高策略适应性:100周期和350周期的均线组合非常适合捕捉持续多年的宏大趋势,但这种趋势毕竟是少数。对于大多数中短期趋势,这样的设置显得反应迟钝。尝试缩短均线周期,或引入一条更快速的均线,可能会让策略更具适应性。
增加交易样本量:当前的策略交易次数过少,难以对其表现做出可靠的评估。尽管增加交易频率会带来更高的交易成本,但只有足够多的交易样本,我们才能更好地理解策略的运作模式和真实能力。
引入风险管理:一旦我们有了足够多的交易数据,就可以考虑引入更精细的风险管理措施来改善表现。例如,我们可以限制策略的最大回撤,或者防止单笔仓位过大。此外,也可以参考一个类似的策略:三移动平均线策略。
完整代码:TradingView的双均线策略
以下是双均线策略的完整代码。关于代码的详细解释和更多信息,请参阅上文的深入讨论。
//@version=5
// 1. 定义策略设置
strategy(title="双均线策略", overlay=true,
pyramiding=0, initial_capital=100000,
commission_type=strategy.commission.cash_per_order,
commission_value=4, slippage=2)
fastMALen = input.int(100, title="快线MA周期")
slowMALen = input.int(350, title="慢线MA周期")
usePosSize = input.bool(true, title="启用仓位管理?")
riskPerc = input.float(0.5, title="风险百分比 %", step=0.25)
// 2. 计算策略所需值
fastMA = ta.sma(close, fastMALen)
slowMA = ta.sma(close, slowMALen)
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = ta.atr(20) * syminfo.pointvalue
posSize = usePosSize ? math.floor(riskEquity / atrCurrency) : 1
maCrossover = ta.crossover(fastMA, slowMA)
maCrossunder = ta.crossunder(fastMA, slowMA)
// 3. 输出策略数据并可视化
plot(fastMA, color=color.teal, linewidth=2, title="快线MA")
plot(slowMA, color=color.orange, linewidth=2, title="慢线MA")
bgColour = maCrossover ? color.new(color.green, 90) :
maCrossunder ? color.new(color.red, 90) :
na
bgcolor(bgColour)
// 4. 定义多头交易条件
enterLong = maCrossover and
barstate.ishistory
// 5. 定义空头交易条件
enterShort = maCrossunder and
barstate.ishistory
// 6. 提交开仓订单
if enterLong
strategy.entry("EL", strategy.long, qty=posSize)
if enterShort
strategy.entry("ES", strategy.short, qty=posSize)
// 7. 提交平仓订单
if barstate.islastconfirmedhistory
strategy.close_all()
三重移动平均线策略编码为TradingView Pine脚本
移动平均线是一种常见的技术指标。它们能平滑价格数据,揭示市场的长期行为,帮助我们将当前的价格置于更宏观的背景中进行考量。这一特性也使得它们在趋势跟踪交易者中备受欢迎。本文将探讨一个基于三条移动平均线组合进行交易的策略。
运用三均线策略进行趋势跟踪
柯蒂斯·费思(Curtis Faith)在他的著作《海龟交易法则》中,描述了他作为一名海龟的所学所得。上世纪八十年代,传奇交易员理查德·丹尼斯发起了一个著名的实验,将一群普通人(即海龟)培养成了成功的交易员。培训结束后,丹尼斯交给他们两套成功的交易策略和高达200万美元的真实账户进行交易。在作为海龟的那几年里,费思本人为丹尼斯赚取了超过3000万美元的利润。
海龟们是典型的趋势跟踪者。这种交易风格旨在捕捉市场中持续数月的大级别价格运动。他们在市场创下历史新高时买入,在价格暴跌时做空,其核心假设是价格的动能会延续当前的方向。趋势跟踪是一种能够盈利但执行起来却异常艰难的交易方式。其胜率通常只有30-35%,且可能遭遇巨大的资金回撤。此外,仅仅错过三笔关键的交易,就可能毁掉一整年的努力。
但如果费思书中分享的结果可信,趋势跟踪也能带来高达每年30-40%的回报。他在书中分享的策略之一便是三均线系统。顾名思义,该策略使用三条移动平均线。其中,短期和中期均线用于产生交易的入场和出场信号,而第三条最长周期的均线则充当趋势过滤器。
让我们先来了解一下三均线策略的具体交易规则,然后再着手将其转化为TradingView代码。
三均线策略的交易规则
三均线策略的交易规则如下。多头入场:当150日移动平均线上穿250日移动平均线,并且150日和250日移动平均线同时位于350日移动平均线之上时,开立多头仓位。多头出场:当150日移动平均线下穿250日移动平均线时,平掉多头仓位。空头入场:当150日移动平均线下穿250日移动平均线,并且150日和250日移动平均线同时位于350日移动平均线之下时,开立空头仓位。空头出场:当150日移动平均线上穿250日移动平均线时,回补空头仓位。仓位规模管理:对于多头和空头仓位,其规模由0.5%的账户权益除以市场的20周期ATR的美元价值来决定。
这个三均线策略与我们之前讨论过的双均线策略有些相似。但不同的是,双均线策略是一个永远在市的系统,而三均线策略增加了一个趋势过滤器,只在短期和中期均线都顺从长期趋势方向时才进行交易。
该策略的另一个特点是它没有设置明确的止损订单。但它包含一个隐性的止损。当价格走势对我们的持仓不利时,短期均线迟早会与中期均线发生反向交叉,从而触发策略的平仓信号。我们只是无法预知这个平仓会发生在哪个具体的价格水平。
费思书中报告的盈利回测结果是基于长达10年的日线数据。测试的市场范围广泛,包括外汇期货(澳元、英镑、欧元)、大宗商品(黄金、原油、铜、天然气)、软商品(棉花、咖啡、活牛、大豆)以及固定收益期货(美国国债)。
为TradingView编写三均线策略
现在,让我们开始将上述交易规则转化为一个完整的TradingView策略。在此之前,我们最好先制定一个清晰的行动计划。从零开始编写一个策略可能会很复杂,但如果我们使用一个预设的模板(或清单),就可以将一个大任务分解成若干个更小、更易于管理的部分。模板也有助于我们快速上手。
我们将使用以下这个包含7个步骤的模板来构建三均线策略:
//@version=5
// 1. 定义策略设置
// 2. 计算策略所需值
// 3. 定义多头交易条件
// 4. 编写空头交易条件
// 5. 输出策略数据
// 6. 提交开仓订单
// 7. 提交平仓订单
如果你想跟随本文一起编写代码,可以在TradingView的Pine编辑器中新建一个策略脚本,并将以上模板粘贴进去。
为了让你对我们即将编写的代码有个直观的感受,下面是最终完成的三均线策略在图表上的样子:
现在,让我们开始将上述交易规则,一步步地转化为一个规范的TradingView策略。
步骤1:定义策略设置和输入选项
在第一步,我们需要配置策略的整体属性,并创建用户可调的参数设置。我们像往常一样,用 strategy() 函数作为代码的开端,来配置策略的默认行为:
// 1. 定义策略设置
strategy(title="三均线策略", overlay=true,
pyramiding=0, initial_capital=100000,
commission_type=strategy.commission.cash_per_order,
commission_value=4, slippage=2)
我们通过 strategy() 函数设置策略属性。title 参数为脚本命名。overlay=true 使得策略直接叠加在主图表上。我们通过 pyramiding=0 禁止加仓,并设定初始资金为100,000(initial_capital=100000)。在交易成本方面,我们设定每笔单边交易(strategy.commission.cash_per_order)收取4个货币单位的固定佣金(commission_value=4),并假设市价单和止损单会产生2个最小跳动点的滑点(slippage=2)。这里的成本设置相对悲观,旨在为回测结果增加一层额外的安全边际。
接下来,我们为策略的各项参数创建输入选项:
fastMALen = input.int(150, title="快线MA周期")
medMALen = input.int(250, title="中线MA周期")
slowMALen = input.int(350, title="慢线MA周期")
endMonth = input.int(9, title="回测结束月份")
endYear = input.int(2018, title="回测结束年份")
usePosSize = input.bool(true, title="启用仓位管理?")
riskPerc = input.float(0.5, title="风险百分比 %", step=0.25)
我们在这里创建了多个输入项,以便用户可以方便地手动配置策略所用的参数。
前三个是整数输入项,由 input.int() 创建,分别用于设置快、中、慢三条均线的周期,默认值分别为150、250和350。
接着,我们创建了两个额外的整数输入:回测结束月份和回测结束年份。我们稍后将用它们来指定回测的结束时间,这样可以确保回测报告中没有未平仓的头寸,从而看到所有交易的完整表现。它们的默认值设为9和2018,即2018年9月。
最后两个输入项用于仓位管理。第一个是布尔型的复选框,由 input.bool() 创建,默认勾选(true)。另一个是浮点数输入,由 input.float() 创建,用于设定每笔交易愿意承担的权益风险比例,默认值为0.5%。
我们将每个输入项的当前值都保存在一个独立的变量中,以便在后续代码中方便地通过变量名来引用用户的当前设置。
步骤2:计算交易策略所需的值
第二步,我们计算策略运行所需的数据。首先,我们计算移动平均线:
// 2. 计算策略所需值
fastMA = ta.sma(close, fastMALen)
medMA = ta.sma(close, medMALen)
slowMA = ta.sma(close, slowMALen)
我们使用 ta.sma() 函数来计算简单移动平均线(SMA)。该函数基于收盘价(close)和用户设定的周期(fastMALen、medMALen、slowMALen)来计算,并将结果分别存入 fastMA、medMA 和 slowMA 变量中。
接下来,我们计算策略的头寸规模:
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = ta.atr(20) * syminfo.pointvalue
posSize = usePosSize ? math.floor(riskEquity / atrCurrency) : 1
我们这里采用的仓位管理算法基于两个核心部分:一部分是策略权益的固定百分比,另一部分是ATR的货币价值。
首先,我们计算出本次交易愿意承担的风险金额 riskEquity。然后,我们将ATR值转换为具体的货币价值 atrCurrency。最后,我们通过条件运算符 ?: 来确定最终的头寸规模 posSize。如果用户启用了仓位管理,我们就用风险金额除以ATR的货币价值,并向下取整。如果未启用,posSize 就固定为1。
然后,我们定义策略的回测时间窗口:
tradeWindow = time <= timestamp(endYear, endMonth, 1, 0, 0)
策略在回测结束时持有的任何未平仓交易都不会被计入最终的绩效报告。为了避免这种情况,我们在这里设定一个交易时间窗口。我们使用 timestamp() 函数,根据用户输入的年份和月份,来创建一个具体的时间点。tradeWindow 变量会判断当前K线的时间是否早于这个设定的结束时间。如果是,则为 true,允许交易;否则为 false,禁止交易。
(在这个策略中,我们没有设置回测的开始时间,这意味着策略会从TradingView能提供的最早数据开始回测,以期在报告中获得尽可能多的交易样本。)
步骤3和4:编写多头与空头交易规则
计算完所需数据后,我们开始定义交易条件。多头和空头的交易条件如下:
// 3. 定义多头交易条件
enterLong = ta.crossover(fastMA, medMA) and
fastMA > slowMA and
medMA > slowMA and
tradeWindow
exitLong = ta.crossunder(fastMA, medMA)
// 4. 定义空头交易条件
enterShort = ta.crossunder(fastMA, medMA) and
fastMA < slowMA and
medMA < slowMA and
tradeWindow
exitShort = ta.crossover(fastMA, medMA)
enterLong 变量只有在几个条件同时满足时才为 true:150周期快线MA(fastMA)向上穿越250周期中线MA(medMA);快线MA和中线MA都位于350周期慢线MA(slowMA)之上;并且当前处于我们设定的交易时间窗口内(tradeWindow)。exitLong 变量则简单得多,当快线MA向下跌破中线MA时,它就为 true。
空头条件的逻辑与多头类似。enterShort 变量只有在快线MA下穿中线MA,且两条线都位于慢线MA之下,并且处于交易窗口内时,才为 true。而 exitShort 则在快线MA向上穿越中线MA时为 true。
步骤5:输出数据并可视化信号
接下来,我们输出策略的数据,以便在图表上直观地识别交易信号并验证策略行为。首先,我们把三条均线绘制在图表上:
// 5. 输出策略数据
plot(fastMA, color=color.teal, title="快线MA")
plot(medMA, color=color.orange, title="中线MA")
plot(slowMA, color=color.navy, title="慢线MA", linewidth=2)
我们用 plot() 函数分别绘制了青色的快线、橙色的中线和加粗的海军蓝色的慢线。
为了更清晰地展示信号,我们用背景色来高亮显示它们:
bgColour = if enterLong and strategy.position_size < 1
color.new(color.green, 85)
else if enterShort and strategy.position_size > -1
color.new(color.red, 85)
else if exitLong and strategy.position_size > 0
color.new(color.navy, 85)
else if exitShort and strategy.position_size < 0
color.new(color.orange, 85)
bgcolor(bgColour)
我们定义一个 bgColour 变量,通过一系列 if 语句来判断应使用的颜色。当出现开仓或平仓信号,且当前持仓状态符合逻辑时(例如,做多信号出现时当前不是满仓多头),就赋予相应的带透明度的颜色。如果没有信号,则不应用任何颜色。最后调用 bgcolor() 函数应用该颜色。
步骤6:提交开仓订单
现在,是时候让策略真正开始交易了。首先是开仓逻辑:
// 6. 提交开仓订单
if enterLong
strategy.entry("EL", strategy.long, qty=posSize)
if enterShort
strategy.entry("ES", strategy.short, qty=posSize)
当 enterLong 为 true 时,我们调用 strategy.entry() 开立一个ID为”EL”的多头仓位,数量为 posSize。当 enterShort 为 true 时,则开立ID为”ES”的空头仓位。
步骤7:提交平仓订单
策略代码的最后一部分是平仓逻辑:
// 7. 提交平仓订单
if exitLong
strategy.close("EL")
if exitShort
strategy.close("ES")
if not tradeWindow
strategy.close_all()
当 exitLong 为 true 时,我们调用 strategy.close() 来平掉ID为”EL”的多头仓位。当 exitShort 为 true 时,则平掉ID为”ES”的空头仓位。最后,当交易窗口结束时(not tradeWindow 为 true),我们调用 strategy.close_all() 来清空所有剩余仓位。
三均线策略的表现
让我们先从积极的方面看起。和大多数趋势跟踪策略一样,三均线策略在长期的趋势行情中表现非常出色。
例如,在下图中,该策略成功捕捉了原油期货价格几乎腰斩的一波下跌趋势:
但三均线策略也无法避免趋势跟踪策略的通病:当市场进入横盘震荡时,其表现会显著恶化。
下图就是一个例子。在这里,市场横盘震荡了数年。在此期间,三均线策略表现不佳,只产生了一笔小幅盈利和一笔亏损交易,从风险回报的角度看不值得参与。
下表展示了在两个不同品种上的迷你回测结果。这里有两点需要特别注意。首先,此回测是在未启用仓位管理的情况下进行的,目的是为了确保策略执行每一个交易信号,从而在报告中获得尽可能多的交易样本。其次,即便如此,交易总数依然极端地少。年均不足一笔的交易频率,使得下方的回测结果不具备任何统计意义,我们无法从中得出任何可靠的结论。
| 绩效指标 | 原油 (CL) | 10年期美国国债 (ZN) |
|---|---|---|
| 首次交易 | 1984-10-11 | 2002-09-17 |
| 最后交易 | 2018-09-04 | 2018-09-04 |
| 时间周期 | 日线 | 日线 |
| 净利润 | $24,900 | -$7,275 |
| 总盈利 | $105,422 | $11,483 |
| 总亏损 | -$80,522 | -$18,758 |
| 最大回撤 | $51,780 | $15,903 |
| 盈利因子 | 1.309 | 0.612 |
| 总交易数 | 19 | 9 |
| 胜率 | 42.11% | 44.44% |
| 平均每笔交易 | $1,310 | -$808 |
| 平均盈利 | $13,177 | $2,870 |
| 平均亏损 | -$7,320 | -$3,751 |
| 盈亏比 | 1.80 | 0.765 |
| 支付佣金 | $120 | $72 |
| 滑点 | 2 跳 | 2 跳 |
改进思路与新策略方向
正如上表所示,这个三均线策略显然有极大的改进空间。以下是一些你可能会觉得有趣的探索方向。
调整均线周期:当前使用的均线周期(150、250、350)非常长,尤其是在日线级别上。这不仅导致了交易次数稀少,也使得每一笔交易都至关重要。如果一年只有一次交易机会,那么错过它就意味着毁掉了全年的业绩,这使得策略在心理上难以执行。因此,尝试使用更短的均线周期,看看是否能在保证稳定性的前提下提高交易频率,是一个值得探索的方向。
参数优化:原作者费思并未阐述其选择这些参数的原因。它们可能是最佳设置,也可能只是随意为之。因此,对不同的参数组合进行探索,看看哪些参数在特定的品种、时间框架和历史时期内表现最佳,是很有必要的。
引入止损机制:该策略没有明确的止损机制。这可能是一个缺点,因为知道确切的亏损离场点位能给交易者带来更多的安全感,也使得策略规则更容易被遵守。此外,谁也说不准,引入一个移动止损(trailing stop-loss)或许还能提升策略的整体表现。
反思趋势过滤器:该策略只在两条快线均线都位于最长周期均线之上时才做多(反之做空)。然而,这种过滤条件可能过于严苛,导致绝大多数均线交叉信号都因为不满足趋势方向而被过滤掉,从而极大地限制了交易机会,未必能真正帮助策略提升表现。
增加市场状态过滤器:和大多数趋势跟踪策略一样,当市场进入横盘震荡时,三均线策略的表现会显著恶化。如果我们能增加一个过滤器,以识别并避开市场处于区间震荡时的低概率交易,策略的表现将得到大幅提升。
整合其他风险管理:为了让策略表现得更好,我们也可以从其他维度限制其风险。例如,我们可以限制策略的最大回撤以防止巨额亏损,或者防止单笔仓位过大。
完整代码:三均线TradingView策略
三均线策略的完整代码如下所示。关于代码的详细解释,请参阅前文的讨论。
//@version=5
// 1. 定义策略设置
strategy(title="三均线策略", overlay=true,
pyramiding=0, initial_capital=100000,
commission_type=strategy.commission.cash_per_order,
commission_value=4, slippage=2)
fastMALen = input.int(150, title="快线MA周期")
medMALen = input.int(250, title="中线MA周期")
slowMALen = input.int(350, title="慢线MA周期")
endMonth = input.int(9, title="回测结束月份")
endYear = input.int(2018, title="回测结束年份")
usePosSize = input.bool(true, title="启用仓位管理?")
riskPerc = input.float(0.5, title="风险百分比 %", step=0.25)
// 2. 计算策略所需值
fastMA = ta.sma(close, fastMALen)
medMA = ta.sma(close, medMALen)
slowMA = ta.sma(close, slowMALen)
riskEquity = (riskPerc / 100) * strategy.equity
atrCurrency = ta.atr(20) * syminfo.pointvalue
posSize = usePosSize ? math.floor(riskEquity / atrCurrency) : 1
tradeWindow = time <= timestamp(endYear, endMonth, 1, 0, 0)
// 3. 定义多头交易条件
enterLong = ta.crossover(fastMA, medMA) and
fastMA > slowMA and
medMA > slowMA and
tradeWindow
exitLong = ta.crossunder(fastMA, medMA)
// 4. 定义空头交易条件
enterShort = ta.crossunder(fastMA, medMA) and
fastMA < slowMA and
medMA < slowMA and
tradeWindow
exitShort = ta.crossover(fastMA, medMA)
// 5. 输出策略数据并可视化
plot(fastMA, color=color.teal, title="快线MA")
plot(medMA, color=color.orange, title="中线MA")
plot(slowMA, color=color.navy, title="慢线MA", linewidth=2)
bgColour = if enterLong and strategy.position_size < 1
color.new(color.green, 85)
else if enterShort and strategy.position_size > -1
color.new(color.red, 85)
else if exitLong and strategy.position_size > 0
color.new(color.navy, 85)
else if exitShort and strategy.position_size < 0
color.new(color.orange, 85)
bgcolor(bgColour)
// 6. 提交开仓订单
if enterLong
strategy.entry("EL", strategy.long, qty=posSize)
if enterShort
strategy.entry("ES", strategy.short, qty=posSize)
// 7. 提交平仓订单
if exitLong
strategy.close("EL")
if exitShort
strategy.close("ES")
if not tradeWindow
strategy.close_all()








