English Русский Español Deutsch 日本語 Português
preview
简单均值回归交易策略

简单均值回归交易策略

MetaTrader 5交易 | 29 一月 2024, 09:45
1 449 1
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

概述

均值回归是一种逆势交易,交易者预估价格将返回到某种形式的均衡点位,通常依据均值或其它向心趋势统计值来衡量。 本文讨论一个非常简单的均值回归交易策略。


均值回归快速介绍

行情通常以无规律的周期波动。 这意味着,当我们查看图表时,我们往往会看到上升、下降和相对平坦的阶段。 交易和投资的关键是能够判定这些阶段的变化,这些阶段也称为行情制度。

均值回归可以采用移动平均线的形式,如果行情离它太远,它可能会回退到其区域附近。


什么是均值回归?

均值回归是一个金融术语,即假设资产价格随时间推移,趋向聚拢到平均价格。

使用均值回归作为时机策略涉及辨别证券的交易范围,以及利用量化方法计算平均价格。 均值回归是一种现象,在大量的金融时序数据中均有所表现,来自价格数据、收益数据、和账面数值。

当现行价格低于过去的平均价格时,可认为该证对买方具有吸引力,并预估价格会上涨。 当现行价格高于过去的平均价格时,则预估行情价格会下跌。 换言之,预计自平均价格的偏差将退回至平均值。 这些知识可当作多交易策略的基石。

股票报告服务通常提供周期为 50 天和 100 天的移动平均线。 虽然报告服务提供了平均值,但仍然需要辨别所研究期间的最高价和最低价。

与图表相比,均值回归似乎是一种更科学地选择股票买卖点的方法,因为精确的数值衍生自历史数据,可识别买入/卖出价位,而非试图使用图表(图表,也称为技术分析)来解释价格走势,尽管新兴的尝试是利用 RSI 指标和平均真实范围(ATR)来捕捉这种系统形态。

许多资产类型,甚至汇率,都观测到均值回归;不过,这个过程也许会持续数年,因此对短线投资者并无价值。

均值回归应当展现出一种对称形式,因为一只股票高于或低于其历史平均水平的频率大致相仿。

历史均值回归模型不会收容证券价格的全部实际行为。 例如,也许会出现新信息,永久性影响标的股票的长期估值。 在破产的情况下,它也许会完全停止交易,永远无法恢复到曾经的历史平均水平。

在金融领域中,“均值回归”一词与统计学中的“回归或回归均值”的含义略有不同。杰里米·西格尔(Jeremy Siegel)使用“均值回报率”一词来描述一般原则,即一个金融时序,其中“在短期内回报可能非常不稳定,但从长期来看非常稳定”。按量化来讲,平均年回报率的标准差下降速度快于持有期的倒数,这意味着该过程不是随机游走,而是在低回报率区间之后迎来高回报的补偿期,例如季节性商业。


下图勾勒该示例。

买入

但是我们如何衡量“太远”呢? 我们尝试一种非常简单的方式,仅基于价格相对于移动平均线的位置。


设计策略

现在,我们取行情与其 200-周期移动平均线的间距,据其得到一个 50-周期的常规化均值,我们准备遵照以下交易信号编码:

  • 每当常规化指数自等于 100 后回落,而当前收盘价低于 5-周期前的收盘价,且低于 200-周期移动平均线时,就会生成做多(买入)信号。
  • 每当常规化指数自等于 100 后回落,而当前收盘价高于 5-周期前的收盘价,且高于 200-周期移动平均线时,就会生成做空(卖出)信号。

如此,在给定条件下,或许这并非最简单的策略,但尽管如此,它还是非常直观和直接的。 信号功能如下:

自上一章节,我们已有明确的目标来开始设计策略:

  • 每当行情远低于其移动平均线时,就会生成做多(买入)信号,寄希望于它回退到较高的均值。
  • 每当行情远高于其移动平均线时,就会生成做空(卖出)信号,寄希望于它回退到较低的均值。

我将对策略进行一些修改,尝试在策略作用不佳时(当净值走低)获得更好的结果。 我将修改这个策略:

- 取代查看最后的收盘价,我会查看最高价和最低价的走高,以便尝试筛选更好的开单。

开单不会太频繁。

修改后的代码如下:

if(previousValue==100)
        {
         if(Normalizado<100 && array_ma[0]>tick.bid  && rates[5].high < rates[1].low )
           {
            Print("Open Order Buy");
            Alert(" Buying");
            Orden="Buy";
            sl=NormalizeDouble(tick.ask - ptsl*_Point,_Digits);
            tp=NormalizeDouble(tick.bid + pttp*_Point,_Digits);
            //trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp);
            trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy");
           }
         if(Normalizado<100 && array_ma[0]<tick.ask  && rates[5].low > rates[1].high )
           {
            Print("Open Order Sell");
            Alert(" Selling");
            Orden="Sell";
            sl=NormalizeDouble(tick.bid + ptsl*_Point,_Digits);
            tp=NormalizeDouble(tick.ask - pttp*_Point,_Digits);
            //trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp);
            trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,get_lot(tick.ask),tick.ask,sl,tp,"Sell");

           }
        }

因之这些修改,策略获胜较少,但看起来更稳定(止损 = 4000 pts)。

4500 止损 30 分钟


4500 止损 30 分钟的数据


EA 必须要优化才能获得更好的结果。 您还可以更改代码。 您可以看到策略在止损为 300 持续 30 分钟的示例。 只需根据所用的品种和周期搜索最拟合的。

350 止损 30 分钟


350 止损 30 分钟的数据


该图形看似更稳定,止损为 300 点,但止损为 4000 点时更有利可图。 我们应该针对止损进行优化,以便找到 30 分钟的平衡点。 但是这份工作我会留给你。



代码

这是输入


input ENUM_LOT_TYPE        inp_lot_type               = LOT_TYPE_FIX;              // type of lot
input double               inp_lot_fix                = 0.01;                        // fix lot
input double               inp_lot_risk               = 0.01;      
input bool     InpPrintLog          = false;       // Print log
ulong                    Expert_MagicNumber       =66777;            //
bool                     Expert_EveryTick         =false;            //
input ENUM_TIMEFRAMES my_timeframe=PERIOD_CURRENT;                  // Timeframe
int    handle_iMA;
input int                  Inp_MA_ma_period     = 200;          // MA: averaging period
input int                  Inp_MA_ma_shift      = 5;           // MA: horizontal shift
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_SMA;    // MA: smoothing type
input ENUM_APPLIED_PRICE   Inp_MA_applied_price = PRICE_CLOSE; // MA: type of price
int shift = 49; // loockback normalization
input int ptsl = 350; // points for stoploss
input int pttp = 5000; // points for takeprofit

第一行设置所要用的手数类型,即固定手数。 下一行设置固定手数的大小,后跟风险手数的大小。 下一行设置一个布尔值,指示是否要打印日志。 下一行设置智能系统的魔幻数字。 下一行设置一个布尔值,指示智能系统是否要在每次跳价时执行。 下一行为智能系统设置时间帧 接下来的几行设置移动平均线指标的参数,例如平均周期、水平偏移、平滑类型、和价格类型。 下一行设置回溯常规化的移位。 接下来的两行设置止损和止盈的点数。

在 OnInit() 中:

int OnInit()
  {
//---
   handle_iMA=iMA(_Symbol,my_timeframe,Inp_MA_ma_period,Inp_MA_ma_shift,
                  Inp_MA_ma_method,Inp_MA_applied_price);

// Initialize the variable here if needed
   previousValue = 0.0;

//---
   return(INIT_SUCCEEDED);
  }

此代码是用 MQL5 语言编写的,用于初始化变量。 代码第一行为 iMA 函数创建一个句柄,其用于计算给定品种在指定时间帧内的移动平均线。 iMA 函数的参数是为了给输入变量 Inp_MA_ma_period、Inp_MA_ma_shift、Inp_MA_ma_method、和 Inp_MA_applied_price 设置初值。 代码第二行初始化变量 previousValue 为 0.0。 代码最后一行返回值 INIT_SUCCEEDED,表示初始化成功。

在 OnTick() 中:

MqlTick tick;
   double last_price = tick.ask;
   SymbolInfoTick(_Symbol,tick);

以及

   if(SymbolInfoTick(_Symbol,tick))
      last=tick.last;

   double Last = NormalizeDouble(last,_Digits);

此代码是用 MQL5 语言编写的,比较交易品种的当前要价(ask)与最后成交价(last)。 第一行创建一个名为 'tick' 的变量,类型为 MqlTick。 第二行将最后成交价存储在变量 “last_price” 之中。 第三行提取变量 “_Symbol” 中指定的交易品种的跳价信息,并将其存储在变量 “tick” 之中。 第四行检查当前要价是否大于变量 “last_price” 中存储的最后成交价。 如果是,则完成一些事情。

此代码计算给定交易品种的点差百分比。 它首先使用 SymbolInfoTick() 函数提取交易品种的最后成交价。 然后,将最后的成交价常规化到 _Digits 参数指定的小数位。 如果常规化的最后成交价大于 0,则提取并常规化交易品种的要价(ask)和出价(bid)。 点差的计算方法是从常规化要价(ask)中减去常规化出价(bid)。 然后将点差除以交易品种的点值(使用 Pow() 函数计算),就得到以点数为单位的点差。 最后,以点数为单位的点差除以常规化最后成交价,再乘以 100 得到点差百分比。 如果点差百分比小于或等于 Max_Spread 参数,则会采取一些动作。

对于 MA,我们将这样用它:

   handle_iMA=iMA(_Symbol,my_timeframe,Inp_MA_ma_period,Inp_MA_ma_shift,
                  Inp_MA_ma_method,Inp_MA_applied_price);
//---
   double array_ma[];
   ArraySetAsSeries(array_ma,true);
   int start_pos=0,count=3;
   if(!iGetArray(handle_iMA,0,start_pos,count,array_ma))
      return;
   string text="";
   for(int i=0; i<count; i++)
      text=text+IntegerToString(i)+": "+DoubleToString(array_ma[i],Digits()+1)+"\n";
//---
   Comment(text);

bool iGetArray(const int handle,const int buffer,const int start_pos,
               const int count,double &arr_buffer[])
  {
   bool result=true;
   if(!ArrayIsDynamic(arr_buffer))
     {
      //if(InpPrintLog)
      PrintFormat("ERROR! EA: %s, FUNCTION: %s, this a no dynamic array!",__FILE__,__FUNCTION__);
      return(false);
     }
   ArrayFree(arr_buffer);
//--- reset error code
   ResetLastError();
//--- fill a part of the iBands array with values from the indicator buffer
   int copied=CopyBuffer(handle,buffer,start_pos,count,arr_buffer);
   if(copied!=count)
     {
      //--- if the copying fails, tell the error code
      //if(InpPrintLog)
      PrintFormat("ERROR! EA: %s, FUNCTION: %s, amount to copy: %d, copied: %d, error code %d",
                  __FILE__,__FUNCTION__,count,copied,GetLastError());
      //--- quit with zero result - it means that the indicator is considered as not calculated
      return(false);
     }
   return(result);
  }

此代码计算和显示给定时间帧内给定品种的移动平均线(MA)。 iMA() 函数计算 MA,iGetArray() 函数从指标缓冲区中提取 MA 数值。 ArraySetAsSeries() 函数设置数组作为序列,IntegerToString() 和 DoubleToString() 函数将数组值转换为字符串。 最后,Comment() 函数在图表注释中显示 MA 数值。


为了在开单时不出现交易量错误,我们将使用以下方法:

//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
double get_lot(double price)
  {
   if(inp_lot_type==LOT_TYPE_FIX)
      return(normalize_lot(inp_lot_fix));
   double one_lot_margin;
   if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,one_lot_margin))
      return(inp_lot_fix);
   return(normalize_lot((AccountInfoDouble(ACCOUNT_BALANCE)*(inp_lot_risk/100))/ one_lot_margin));
  }
//+------------------------------------------------------------------+
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
double normalize_lot(double lt)
  {
   double lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   lt = MathFloor(lt / lot_step) * lot_step;
   double lot_minimum = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   lt = MathMax(lt, lot_minimum);
   return(lt);
  }

此代码计算买入订单的手数。 第一个函数 get_lot() 以价格作为参数,并返回手数。 手数大小由手数类型决定,手数类型可以是固定的,也可以基于风险百分比。 如果手数类型是固定的,则该函数返回常规化的手数大小。 如果手数类型基于风险百分比,则该函数将计算一手的保证金,根据余额和风险百分比计算手数,并返回常规化的手数大小。 第二个函数 normalize_lot() 以手数作为参数,并返回常规化的手数大小。 标准化手数的计算方法是将手数大小除以交易量步长,然后乘以交易量步长。 然后将常规化的手数与最小手数进行比较,并返回两者的最大值。

开单时我们要用到它

trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy");

此代码是用 MQL5 编写的,用于在市场上开新仓。 第一个参数是要交易的资产品种。 第二个参数是订单类型,在本例中为做多订单。 第三个参数是手数,它是调用 get_lot() 函数和当前出价计算得出的。 第四个参数是当前出价(bid)。 第五个和第六个参数分别是止损和止盈水平。 最后一个参数是将会添加到订单中的注释。

根据初始策略,开始将是这样的,只是针对高低点改变收盘条件(以获得更稳健的结果)。

   if(0<=Normalizado<=100 )
     {
      //------------------------------------------------------------------------------
      if(previousValue==100)
        {
         if(Normalizado<100 && array_ma[0]>tick.bid  && rates[5].high < rates[1].low )
           {
            Print("Open Order Buy");
            Alert(" Buying");
            Orden="Buy";
            sl=NormalizeDouble(tick.ask - ptsl*_Point,_Digits);
            tp=NormalizeDouble(tick.bid + pttp*_Point,_Digits);
            //trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp);
            trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy");
           }
         if(Normalizado<100 && array_ma[0]<tick.ask  && rates[5].low > rates[1].high )
           {
            Print("Open Order Sell");
            Alert(" Selling");
            Orden="Sell";
            sl=NormalizeDouble(tick.bid + ptsl*_Point,_Digits);
            tp=NormalizeDouble(tick.ask - pttp*_Point,_Digits);
            //trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp);
            trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,get_lot(tick.ask),tick.ask,sl,tp,"Sell");
           }
        }
     }
   previousValue = Normalizado;
   if(Orden=="Sell" && rates[0].low <array_ma[0])
     {
      trade.PositionClose(_Symbol,5);
      Print("cerro sell");
      return;
     }
   if(Orden=="Buy" && rates[0].high >array_ma[0])
     {
      trade.PositionClose(_Symbol,5);
      Print("cerro buy");
      return;
     }
  }

此代码是用 MQL5 语言编写的,用于在外汇市场中开仓和平仓。 该代码首先检查变量 “Normalizado” 的值是否介于 0 和 100 之间。 如果是,则检查 “Normalizado” 的先前值是否为 100。 如果是,则检查 “Normalizado” 的值是否小于 100,数组 array_ma[0] 的值是否大于当前买入价,以及最后 5 个汇率的最高价是否小于第一个汇率的最低价。 如果满足所有这些条件,则代码将打印 “Open Order Buy”,提醒用户它正在买入,将 “Orden” 变量设置为 “Buy”,设置止损和止盈水平,并按指定参数开立买单。

如果 “Normalizado” 的值小于 100,并且 array_ma[0] 的值小于当前要价,并且最后 5 个汇率的最低价大于第一个汇率的最高价,则代码打印 “Open Order Sell”,提醒用户它正在卖出,将 “Orden” 变量设置为 “Sell”, 设置止损和止盈水平,并按指定参数开立卖单。 此后,代码将 “Normalizado” 的先前值设置为当前值。 最后,代码检查 “Orden” 变量是否设置为 “Sell”,以及当前汇率的低点是否小于 array_ma[0] 的值。 如果满足这些条件,则代码将卖单平仓,并打印 “cerro sell”。 类似地,如果 “Orden” 变量设置为 “Buy”,并且当前汇率的最高点大于 array_ma[0] 值,则代码将买单平仓,并打印 “cerro buy”。



结束语

该策略必须经过优化才能应用于市场,但其本意是提出一种思考市场分析的均值回归方式。



本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/12830

最近评论 | 前往讨论 (1)
lj1616
lj1616 | 24 2月 2024 在 08:33
我无法回测这个策略
MQL5中的ALGLIB数值分析库 MQL5中的ALGLIB数值分析库
本文简要介绍了ALGLIB 3.19数值分析库、它的应用以及可以提高金融数据分析效率的新算法。
Heiken-Ashi指标与移动平均指标组合能够提供好的信号吗? Heiken-Ashi指标与移动平均指标组合能够提供好的信号吗?
策略的组合可能会提供更好的机会,我们可以把指标和形态一起使用,或者更进一步,多个指标和形态一起,这样我们可以获得额外的确认因子。移动平均帮我们确认和驾驭趋势,它们是最为人所知的技术指标,这是因为它们的简单性和为分析增加价值的良好记录。
在 MQL4 和 MQL5 框架下开发 OpenAI  的 ChatGPT 功能 在 MQL4 和 MQL5 框架下开发 OpenAI 的 ChatGPT 功能
在本文中,我们将尝鲜来自 OpenAI 的 ChatGPT,从而了解它在降低开发智能系统、指标、和脚本的时间和劳动强度方面的能力。我将引导您快速通览这项技术,并尝试向您展示如何正确地使用它在 MQL4 和 MQL5 中进行编程。
神经网络变得轻松(第四十六部分):条件导向目标强化学习(GCRL) 神经网络变得轻松(第四十六部分):条件导向目标强化学习(GCRL)
在本文中,我们要看看另一种强化学习方式。 它被称为条件导向目标强化学习(GCRL)。 按这种方式,代理者经过训练,可以在特定场景中达成不同的目标。