
作为技术分析工具的 MTF 指标
简介
大多数交易者都同意,当前的市场状态分析从评估更高的图表时间框架开始。该分析向下执行,以缩短执行交易的时间范围。这种分析方法似乎是成功交易的专业方法的强制性部分。要执行多时间段分析,如果使用同一组工具,用户通常必须打开几个窗口或在图表时间段之间切换,这样还要进行预先分析。
下一步怎么办?是否必须忽略较旧的时间框架或在窗口和时间段之间不断切换?当工作时间框架为H1或以上时,用户有时间仔细评估一般情况。但是,在处理M1-M15时间段时,没有时间从更高的时间段检查所需信息。而这些信息有时是至关重要的。此外,这些数据现在应该在这里可用,而不是在另一个选项卡或其他任何地方。这对于基于同时评估不同时间框架的MTF策略尤其重要,例如 Wolfe Waves 或 Elder 的三重滤网。
因此,使用这些策略的交易者往往处于精神紧张状态。当在较低的时间框架上进行交易时,可能会错过较高时期的盈利信号,这可能导致决策混乱、头寸提前平仓或市场反转点缺失。后果可能非常令人不安。在这些情况下,用户只能依靠他们的经验或信息访问速度。
这个问题的解决方案是一个指标,它接收来自不同时间段或多个交易符号的信息,并在屏幕上显示综合数据,以便用户能够有效地评估市场状态。除了基本信息外,这些指标还可以显示市场趋势的真实状态,并推荐进一步的交易活动。
算法特点
与经典指标的主要区别在于,MTF指标可以处理所有时间间隔或金融工具的广义信息,然后将数据传递到当前图表。任何指标(振荡指标,趋势指标,交易量等)或者它们的组合都可以在多个时间框架中运行。它们根据基本算法计算,并根据设置中指定的时间间隔传递信息。
与传统的MTF版本相比,这个MTF版本不需要特殊的设置,除了指定时间间隔或使用数据的符号列表的附加参数(或参数组)。结果可以在主图表窗口中输出,也可以单独输出,并在根据工具类型组合组时显示为信号。
多时间段指标分类
这些指标在所有标准类中都有介绍,而大多数是复杂的类型,即结合计算和图形元素。指标组如下所示:
1. 信息类:此类指标显示数据和附加信息,无信号或图形。典型的例子是 MultiTimeFrame (多时间框架)指标,它显示每个时间段的烛形关闭时间、所选货币对的询价和出价、烛形状态(向上、向下、DOJI)和交易量。指标屏幕上充满了大量有用的信息,但在交易中很难使用,只能查看。
图 1. 信息指标
该指标组还拥有独立的工具,可用于制定交易决策。它们显示多个标准指标的分析结果,无需在图表上安装这些指标(自动执行计算),并生成交易建议。
图 2. 信息指标的信号
图 3. 信息指标的信号
2. 图形指标在不同的时间框架上显示相同工具的图形。这里是来自三个不同时间框架下的标准包络线 MA(13)
图 4. 图形指标
另一种图形结构是一组具有不同计算周期的图表,这种方法是基于纯数学的。即M5上的随机振荡指标(5.3.3)的参数为M15的(15.3.9)和M30的(30.3.18)。
图 5. 使用不同计算周期数的图形指标
上面的版本可以在保留的情况下引用MTF类,这种方法并不总是可能实现的,但是这种解决方案的缺点可能非常严重,以至于使用它是不适当的。我们将详细讨论该方法适用的情况以及其优缺点。
一个单独的组由所谓的信号指标组成。为了避免工作区的图形结构过多,指标形成信号线或图形块,反映趋势方向或其他参数。参见标准 MTF_Coral 指标及上述 MTF 解决方案:
图 6. 信号指标
另一组可以称为“窗口中的窗口(Window in a window)”,不同时间表或指标的图表显示在主图表窗口中。
图 7. "Window in window" 指标
图 7.1. "Window in window" 指标
另一个 All_Woodies CCI 的例子.
图 7.1. "Window in window" 指标 All_Woodies CCI
还有一个独立的组包含了 MTF 波动性指标,这些指标包含了 MTF Candles.
图 8. 波动性指标 MTF Candles
实现方法
我们已经探讨了MTF指标的主要类型,现在让我们看一些简单的例子,这些例子展示了线性实现的主要方法。我们还将分析每个解决方案的特定特性。
多时段指标。
让我们探讨MA指标,并尝试解决以下任务:创建一个计算周期变化的指标版本,以显示三个不同的时间框架。
让我们设置主要参数和变量:
//---- 指标设置 #property indicator_chart_window #property indicator_buffers 3 #property indicator_plots 3 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_type3 DRAW_LINE #property indicator_color1 Blue #property indicator_color2 Red #property indicator_color3 Lime #property indicator_width1 1 #property indicator_width2 1 #property indicator_width3 1 //---- 输入参数 input ENUM_TIMEFRAMES tf1 = 1; // 时间框架 (1) input ENUM_TIMEFRAMES tf2 = 5; // 时间框架 (2) input ENUM_TIMEFRAMES tf3 = 15; // 时间框架 (3) input int maPeriod = 13; // MA 周期数 input int Shift = 0; // 偏移 input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // 移动平均方法 input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; // 应用的价格 //---- 指标缓冲区 double ExtBuf1[]; double ExtBuf2[]; double ExtBuf3[]; //---- 移动平均的 句柄 int ExtHandle1; int ExtHandle2; int ExtHandle3; //--- 所需计算的最小柱数 int ExtBarsMinimum; //--- int period1=0; int period2=0; int period3=0;
现在实现数组数据时,条件是它所附加的时间框架小于或等于变量中指定的时间框架。
void OnInit() { int timeframe; //---- 指标缓冲区映射 SetIndexBuffer(0,ExtBuf1,INDICATOR_DATA); SetIndexBuffer(1,ExtBuf2,INDICATOR_DATA); SetIndexBuffer(2,ExtBuf3,INDICATOR_DATA); //--- timeframe =_Period; //--- if(tf1>=timeframe) { period1=maPeriod*(int)MathFloor(tf1/timeframe); PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,period1-1); //设置要画的第一个柱的索引 PlotIndexSetInteger(0,PLOT_SHIFT,Shift); //画线的偏移 PlotIndexSetString(0,PLOT_LABEL,"MA("+string(period1)+")"); //用于数据窗口的名称 ExtHandle1=iMA(NULL,0,period1,0,InpMAMethod,InpAppliedPrice); //取得 MA 的句柄 } //---- if(tf2>=timeframe) { period2=maPeriod*(int)MathFloor(tf2/timeframe); PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,period2-1); //设置要画的第一个柱的索引 PlotIndexSetInteger(1,PLOT_SHIFT,Shift); //画线时的偏移 PlotIndexSetString(1,PLOT_LABEL,"MA("+string(period2)+")"); //用于数据窗口的名称 ExtHandle2=iMA(NULL,0,period2,0,InpMAMethod,InpAppliedPrice); //取得 MA 的句柄 } //---- if(tf3>=timeframe) { period3=maPeriod*(int)MathFloor(tf3/timeframe); PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,period3-1); //设置要画的第一个柱的索引 PlotIndexSetInteger(2,PLOT_SHIFT,Shift); //画线时的偏移 PlotIndexSetString(2,PLOT_LABEL,"MA("+string(period3)+")"); //用于数据窗口的名称 ExtHandle3=iMA(NULL,0,period3,0,InpMAMethod,InpAppliedPrice); //取得 MA 的句柄 } //--- 设置精确度 IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- 所需计算的最小柱数 int per=MathMax(period3,MathMax(period1,period2)); ExtBarsMinimum=per+Shift; //--- 初始化结束 }
检查初始化和计算的主循环:
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- 检查报价总数 if(rates_total<ExtBarsMinimum) return(0); // 用于计算的柱数不够 //--- 没有计算所有的数据 int calculated=BarsCalculated(ExtHandle1); if(calculated<rates_total&&period1!=0) { Print("ExtHandle1 的数据没有全部计算 (",calculated,"bars ). Error",GetLastError()); return(0); } calculated=BarsCalculated(ExtHandle2); if(calculated<rates_total&&period2!=0) { Print("ExtHandle2 的数据没有全部计算 (",calculated,"bars ). Error",GetLastError()); return(0); } calculated=BarsCalculated(ExtHandle3); if(calculated<rates_total&&period3!=0) { Print("ExtHandle3 的数据没有全部计算 (",calculated,"bars ). Error",GetLastError()); return(0); } //--- 我们可以复制部分数据 int to_copy; if(prev_calculated>rates_total || prev_calculated<0) to_copy=rates_total; else { to_copy=rates_total-prev_calculated; if(prev_calculated>0) to_copy++; } //---- 取得 ma 缓冲区 if(IsStopped()) return(0); //检查停止标志 if(period1!=0) if(CopyBuffer(ExtHandle1,0,0,to_copy,ExtBuf1)<=0) { Print("取得 ExtHandle1 失败!错误 ",GetLastError()); return(0); } if(IsStopped()) return(0); //检查停止标志 if(period2!=0) if(CopyBuffer(ExtHandle2,0,0,to_copy,ExtBuf2)<=0) { Print("取得 ExtHandle2 失败!错误 ",GetLastError()); return(0); } if(IsStopped()) return(0); //检查停止标志 if(period3!=0) if(CopyBuffer(ExtHandle3,0,0,to_copy,ExtBuf3)<=0) { Print("取得 ExtHandle3 失败!错误 ",GetLastError()); return(0); } //--- 返回 prev_calculated 的值用于下次调用 return(rates_total); } //+------------------------------------------------------------------+
让我们看看结果。
图 9. 一个 MTF 指标的实现
我们已经探讨了在不同时间段内使用相同指标使用增加计算周期数方法的示例,稍作改动,我们可以在提供功能同时为每条线设置单独周期数,这种方法似乎效率低下,计算周期和同时应用多个指标非常容易。然而,在某些情况下,尽管存在诸多缺点,但这种方法仍然是最佳的。其中之一是必须在一个窗口中同时观察两(三)个非标准化振荡指标,由于振幅范围的原因,这些振荡器相对于中心线移动,因此解释变得困难。这种方法消除了上述缺点。
多时间框架指标
MQL5 没有使用为人熟知的 iClose(), iHigh(), iLow(), iOpen(), iTime(), iVolume(), 而是提供了 CopyTime(), CopyClose(), CopyHigh(), CopyLow(), CopyOpen(), CopyTime(), CopyVolume() 等函数, 而 iCustom, iMA, iCCI, iMACD 和其它函数是通过 CopyBuffer() 来实现的。它们每个都有自身的优点和缺点。在我们的例子中,我们将只探讨 MQL5. 对于我们的指标,我们需要从M1到MN1的整个时间表列表,即26个版本。此外,如果您需要使用几个交易品种或工具,这个数字会增加很多倍。在大多数情况下,不需要复制整个历史记录。对于大多数信息指标,柱数限制为两个。因此,为了防止代码变得太长,最好将这些命令作为单独的函数编写并重复调用。
对于时间序列函数 CopyClose(),函数如下:
//+------------------------------------------------------------------+ double _iClose(string symbol,int tf,int index) { if(index < 0) return(-1); double buf[]; ENUM_TIMEFRAMES timeframe=TFMigrate(tf); if(CopyClose(symbol,timeframe, index, 1, buf)>0) return(buf[0]); else return(-1); } //+------------------------------------------------------------------+
对于 WPR 调用:
//+------------------------------------------------------------------+ double _iWPR(string symbol, int tf, int period, int shift) { ENUM_TIMEFRAMES timeframe=TFMigrate(tf); int handle=iWPR(symbol,timeframe,period); if(handle<0) { Print("iWPR 对象没有创建: 错误 ",GetLastError()); return(-1); } else return(_CopyBuffer(handle,shift)); } //+------------------------------------------------------------------+ double _CopyBuffer(int handle,int shift) { double buf[]; if(CopyBuffer(handle,0,shift,1,buf)>0) return(buf[0]); return(EMPTY_VALUE); } //+------------------------------------------------------------------+
如果有多条线, _CopyBuffer 可以像下面这样写:
//+------------------------------------------------------------------+ double _CopyBuffer(int handle,int index,int shift) { double buf[]; switch(index) { case 0: if(CopyBuffer(handle,0,shift,1,buf)>0) return(buf[0]); break; case 1: if(CopyBuffer(handle,1,shift,1,buf)>0) return(buf[0]); break; case 2: if(CopyBuffer(handle,2,shift,1,buf)>0) return(buf[0]); break; case 3: if(CopyBuffer(handle,3,shift,1,buf)>0) return(buf[0]); break; case 4: if(CopyBuffer(handle,4,shift,1,buf)>0) return(buf[0]); break; default: break; } return(EMPTY_VALUE); } //+------------------------------------------------------------------+
而 _iWPR 函数将把这一行
return(_CopyBuffer(handle,shift)
改成
return(_CopyBuffer(handle,0,shift)
在两种情况下 TFMigrate() 将看起来如下:
//+------------------------------------------------------------------+ ENUM_TIMEFRAMES TFMigrate(int tf) { switch(tf) { case 0: return(PERIOD_CURRENT); case 1: return(PERIOD_M1); case 5: return(PERIOD_M5); case 15: return(PERIOD_M15); case 30: return(PERIOD_M30); case 60: return(PERIOD_H1); case 240: return(PERIOD_H4); case 1440: return(PERIOD_D1); case 10080: return(PERIOD_W1); case 43200: return(PERIOD_MN1); case 2: return(PERIOD_M2); case 3: return(PERIOD_M3); case 4: return(PERIOD_M4); case 6: return(PERIOD_M6); case 10: return(PERIOD_M10); case 12: return(PERIOD_M12); case 20: return(PERIOD_M20); case 16385: return(PERIOD_H1); case 16386: return(PERIOD_H2); case 16387: return(PERIOD_H3); case 16388: return(PERIOD_H4); case 16390: return(PERIOD_H6); case 16392: return(PERIOD_H8); case 16396: return(PERIOD_H12); case 16408: return(PERIOD_D1); case 32769: return(PERIOD_W1); case 49153: return(PERIOD_MN1); default: return(PERIOD_CURRENT); } } //+------------------------------------------------------------------+
正如我们已经提到的,这种计算类型需要有限数量的元素(柱),但有时计算整个历史是明智的。这里必须小心。不要忘记,较低的时间框架上会比较高的时间框架上有更多的历史柱。在创建这样的工具时,应该考虑到这一事实。最简单的方法是确定最小的柱数,并使用该值进行计算。更困难的方法是分别确定每个时间框架的这个值。经常 (特别是在信息指标中) 只在柱关闭时才需要数据,所以不需要在每个分时重新计算较低时间框架下的旧的数据。如果我们在开发中提供这一方面,这将显著降低代码的复杂性。
信息指标的开发(图1、图2、图3)与经典指标的开发没有什么不同,因此,让我们立即进入图形指标的类别,这是我认为最有趣的一类。信息性指标只需要当前市场状况的数据和使用过的交易品种,图形性指标对图形有附加要求。M5 周期由 5个M1柱组成,M15由3个M5柱组成等。也就是说,当在M5上形成一条线时,从M15开始的一条线在三个柱之间绘制。线位置不固定,可以变化,直到М15烛形关闭。因此,我们需要结合形成烛形开启的时间。让我们用MA的例子来实现这一点。
//---- 指标设置 #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_type1 DRAW_LINE #property indicator_color1 Blue #property indicator_width1 1 //---- 输入参数 input ENUM_TIMEFRAMES tf = 5; // 时间框架 input int maPeriod = 13; // MA 周期数 input int Shift = 0; // 偏移 input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // 移动平均方法 input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; // 使用的价格 input int Bars_Calculated = 500; //---- 指标缓冲区 double ExtMA[]; //---- 移动平均的 句柄 int MA_Handle; //--- 所需计算的最小柱数 int ExtBarsMinimum; ENUM_TIMEFRAMES _tf; int pf; //--- 我们将保留移动平均值指标中的数值 int bars_calculated=0; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ int OnInit() { _tf=tf; ENUM_TIMEFRAMES timeframe; int draw_shift=Shift;// 初始值 PLOT_SHIFT int draw_begin=maPeriod;// 初始值 PLOT_DRAW_BEGIN //--- timeframe=_Period; if(_tf<=timeframe)_tf=timeframe;// 如果时间框架小于或者等于当前, 把它设为 PERIOD_CURRENT pf=(int)MathFloor(_tf/timeframe);// 计算用于 PLOT_DRAW_BEGIN, PLOT_SHIFT 和计算柱数的系数. draw_begin=maPeriod*pf;// 计算 PLOT_DRAW_BEGIN draw_shift=Shift*pf;// 计算 PLOT_SHIFT //---- 指标缓冲区映射 SetIndexBuffer(0,ExtMA,INDICATOR_DATA); //--- PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,draw_begin-pf); //设置开始画的第一个柱的索引 PlotIndexSetInteger(0,PLOT_SHIFT,draw_shift); //设置画线的偏移 PlotIndexSetString(0,PLOT_LABEL,"MA("+string(tf)+" "+string(maPeriod)+")");//用于数据窗口的名称 //--- MA_Handle=iMA(NULL,_tf,maPeriod,0,InpMAMethod,InpAppliedPrice); //取得 MA 的句柄 if(MA_Handle==INVALID_HANDLE) { Print("获取 MA 句柄失败!错误 ",GetLastError()); return(INIT_FAILED); } //--- 设置精确度 IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- 所需计算的最小柱数 ExtBarsMinimum=draw_begin+draw_shift;// 计算所需的最小柱数 //--- 初始化结束 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- 检查报价总数 if(rates_total<ExtBarsMinimum) return(0); // 用于计算的柱数不够 int limit; //--- 对数组元素使用时间序列索引 ArraySetAsSeries(time,true); ArraySetAsSeries(ExtMA,true); //--- 侦测起始位置 //--- 计算所需复制的数据数量 //--- 以及限制重新计算的柱的起始索引 if(prev_calculated>rates_total || prev_calculated<=0|| calculated!=bars_calculated)// 检查计算指标的起始点 { limit=rates_total-ExtBarsMinimum-1; // 用于计算所有柱的起始索引 } else { limit=(rates_total-prev_calculated)+pf+1; // 用于计算新柱的起始索引 } if(Bars_Calculated!=0) limit=MathMin(Bars_Calculated,limit);
我们不会根据柱形索引 (iBarShift()) 搜索, 而是直接根据时间来复制数值。
//--- 主循环 for(int i=limit;i>=0 && !IsStopped();i--) { ExtMA[i]=_CopyBuffer(MA_Handle,time[i]); } //--- bars_calculated=calculated; 这里我们使用上面提到的函数. //+--------- CopyBuffer MA 句柄 ----------------------------------+ double _CopyBuffer(int handle,datetime start_time) { double buf[]; if(CopyBuffer(handle,0,start_time,1,buf)>0) return(buf[0]); return(EMPTY_VALUE); } //+-------------------- END -----------------------------------------+
这里是结果:
图 10. MTF 指标线
该方法适用于各种线性指标,从上面的屏幕截图可以看出主要的缺点:步骤。对于MA来说,这样的行为不是问题,甚至是有用的,因为这样可以清楚地定义支持和阻力水平。但对于我们使用模式的振荡指标来说,这种行为会阻碍识别和绘制此类模式。此外,该解决方案不可接受的WPR,CCI和类似的指标,因为线的外观变化超出了识别。
为了解决这个问题,最后一根柱的计算应考虑到加权系数。让我们添加“interpolate”全局变量,它允许使用这两个方案。
input bool Interpolate = true; //--- 主循环 for(int i=limit;i>=0 && !IsStopped();i--) { int n; datetime t=time[i]; ExtMA[i]=_CopyBuffer(MA_Handle,t); if(!Interpolate) continue; //--- datetime times= _iTime(t); for(n = 1; i+n<rates_total && time[i+n]>= times; n++) continue; double factor=1.0/n; for(int k=1; k<n; k++) ExtMA[i+k]=k*factor*ExtMA[i+n]+(1.0-k*factor)*ExtMA[i]; } //---
在这个变体中,我们需要为当前图表添加 _iTime函数,它将根据打开时间确定柱形编号。
//+------------------------------------------------------------------+ datetime _iTime(datetime start_time) { if(start_time < 0) return(-1); datetime Arr[]; if(CopyTime(NULL,_tf, start_time, 1, Arr)>0) return(Arr[0]); else return(-1); } //+------------------------------------------------------------------+
这看起来像正常的线形了。
图 11. 使用了 _iTime 的 MTF 指标
虽然开发这种复杂的资源密集型系统似乎不合适,但它们有其缺点,甚至是不可或缺的。如果使用经典平均(MA、鳄鱼等),与MTF版本相比,计算周期的增加可能会导致一些延迟。这在小时期尤其明显。
图 12. MTF MA 指标的延迟
图 13. MTF Stochastic 指标的延迟
对于简单的指标,如MA和鳄鱼,这可能不那么重要。但对于由两个或多个MA组成的复杂系统,如MACD、AO等,这可能非常重要。此外,也不可能改变AO或AC的平均周期。对于具有非平滑线的指标(WPR、CCI等),计算周期的微小增加不能提供令人满意的结果,因为它们的噪音非常多。
图 14. MTF WRP 指标
图 15. MTF CCI 指标
图14-15表明,当算法中没有提供这种功能时,它们可以成功地用于平滑。
该类型除了具有直接功能外,还可以弥补 MetaTrader 5 策略测试器中可视化测试模式的不足。在为交易创建 MTF EA 交易 或分析这种策略的有效性时,我们不能同时观察屏幕上不同时间框架中的指标位置。测试后,我们会收到一组选项卡,具体取决于使用的周期数。让我们从Anatoli Kazharski的文章MQL5酷客宝典:基于三重滤网策略开发交易系统框架中查看“三重滤网”策略的示例。该策略的思想是:第一个时间段是最大的时间段,如每周、每天或4小时。它用于确定主要趋势。第二个时间段与第一个时间段有1或2个级别差距。它用于判断修正。第三个时间段又差了一个级别,它用于判断最佳入场点。
在第一重滤网中,它通常使用 M30-W1, 运行 MACD (12,26,1) 和周期数为13的 EMA。在第二重滤网中,М5-D1, 运行随机振荡指标 (5,3,3). 第三重滤网是从 M1 到 H4 用于在主要趋势方向上设置止损订单。
图 16. Elder 的三重滤网
该文章的作者没有坚持使用这种方法,但是保留了“三重滤网”的概念,我们在测试过程中和之后:
图 17. 测试 Elder 的三重滤网策略
这种变体我们没有办法正确分析EA交易(策略)的操作。
让我们使用我们的工具创建自己的EA版本。它将接近经典的策略版本。基于上述指标创建 EA 交易与经典 EA 非常相似,这里是主要代码。
#define EXPERT_MAGIC 1234502 //--- #include <Trade\Trade.mqh> #include <Trade\SymbolInfo.mqh> #include <Trade\PositionInfo.mqh> #include <Trade\AccountInfo.mqh> #include <Trade\OrderInfo.mqh> //+------------------------------------------------------------------+ enum compTF { A, //m1-m5-m15 B, //m5-m15-h1 C, //m15-h1-h4 E //h1-h4-d1 }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ input string s0="//--- 输入参数 手数+跟踪 ---//"; input double InpLots =0.1; // 手数 input int InpTakeProfit =150; // 获利 (in pips) input int InpStopLoss =60; // 止损 (in pips) input int InpLevel_S =20; // 水平信号 input compTF InpTFreg =2; input string s1="//--- MA 输入参数 ---//"; input int Signal_MA_PeriodMA =21; // 平均周期数 input string s2="//--- MACD 输入参数 ---//"; input int Signal_MACD_PeriodFast =12; // 快速 EMA 周期数 input int Signal_MACD_PeriodSlow =26; // 慢速 EMA 周期数 input string s3="//--- Stochastic 输入参数 ---//"; input int Signal_Stoch_PeriodK =5; // K-period input int Signal_Stoch_PeriodD =3; // D-period input string s4="//--- 跟踪止损输入参数 ---//"; //--- 用于跟踪止损的输入参数 input int InpTrailingStop =25; // 跟踪止损水平 (in pips) input int InpOffset =5; // 与价格之间的距离 (in pips) //--- int ExtTimeOut=10; // 交易操作之间的时间间隔 int barsCalculated=3; datetime t=0; datetime time[]; //+------------------------------------------------------------------+ //| AC 示例专家类 | //+------------------------------------------------------------------+ class CSampleExpert { protected: double m_adjusted_point; // 3 或者 5 位小数值 CTrade m_trade; // 交易对象 CSymbolInfo m_symbol; // 交易品种信息 CPositionInfo m_position; // 交易仓位 CAccountInfo m_account; // 账户信息 COrderInfo m_order; // 订单信息 //--- 指标 ENUM_TIMEFRAMES mas_tf[3]; // 时间框架的数组 int handle_MACD; // MACD 指标句柄 int handle_MA; // MA 指标句柄 int handle_Stochastic; // Stochastic 指标句柄 //--- 指标缓冲区 double macd_buff[]; // MACD 指标主缓冲区 double ma_buff[]; // MA 指标主缓冲区 double stoch_buff[]; // Stochastic 指标主缓冲区 //--- double close[]; double open[]; double low[]; double high[]; //--- 要处理的指标数据 double macd_ind_0; double macd_ind_1; double ma_ind; double stoch_ind_0; double stoch_ind_1; int level_stoch; //--- 要处理的跟踪止损数据 double m_traling_stop; double m_take_profit; double m_stop_losse; public: CSampleExpert(void); ~CSampleExpert(void); bool Init(void); void Deinit(void); bool Processing(void); protected: bool InitCheckParameters(const int digits_adjust); bool Copy(void); // bool InitIndicators(void); bool LongModified(void); bool ShortModified(void); bool LongOpened(void); // 检查买入条件 bool ShortOpened(void); // 检查卖出条件 bool OpenSellStop(void); // 设置止损卖出订单 bool OpenBuyStop(void); // 设置止损买入订单 bool OrderModifySellStop(void); // 修改止损卖出订单 bool OrderModifyBuyStop(void); // 修改止损买入订单 bool DellSellStop(void); // 删除止损卖出订单 bool DellBuyStop(void); // 删除止损卖出订单 }; //--- 全局的专家对象 CSampleExpert ExtExpert; //+------------------------------------------------------------------+ //| 构造函数 | //+------------------------------------------------------------------+ CSampleExpert::CSampleExpert(void) : m_adjusted_point(0), handle_MACD(INVALID_HANDLE), handle_Stochastic(INVALID_HANDLE), handle_MA(INVALID_HANDLE), macd_ind_0(0), macd_ind_1(0), stoch_ind_0(0), stoch_ind_1(0), ma_ind(0), m_traling_stop(0), m_take_profit(0) { ArraySetAsSeries(macd_buff,true); } //+------------------------------------------------------------------+ //| 析构函数 | //+------------------------------------------------------------------+ CSampleExpert::~CSampleExpert(void) { } //+------------------------------------------------------------------+ //| 输入参数的初始化和验证 | //+------------------------------------------------------------------+ bool CSampleExpert::Init(void) { //--- 初始化一般信息 m_symbol.Name(Symbol()); // 交易品种 m_trade.SetExpertMagicNumber(EXPERT_MAGIC); // 幻数 m_trade.SetMarginMode(); m_trade.SetTypeFillingBySymbol(Symbol()); //--- 为3位或5位小数做调整 int digits_adjust=1; if(m_symbol.Digits()==3 || m_symbol.Digits()==5) digits_adjust=10; m_adjusted_point=m_symbol.Point()*digits_adjust; //--- 设置用于交易的默认偏差 m_traling_stop =InpTrailingStop*m_adjusted_point; m_take_profit =InpTakeProfit*m_adjusted_point; m_stop_losse =InpStopLoss*m_adjusted_point; //--- 设置交易默认偏差的点数 m_trade.SetDeviationInPoints(3*digits_adjust); //--- int x=InpTFreg; switch(x) { case 0: {mas_tf[0]=PERIOD_M1;mas_tf[1]=PERIOD_M5;mas_tf[2]=PERIOD_M15;} break; case 1: {mas_tf[0]=PERIOD_M5;mas_tf[1]=PERIOD_M15;mas_tf[2]=PERIOD_H1;} break; case 2: {mas_tf[0]=PERIOD_M15;mas_tf[1]=PERIOD_H1;mas_tf[2]=PERIOD_H4;} break; case 3: {mas_tf[0]=PERIOD_H1;mas_tf[1]=PERIOD_H4;mas_tf[2]=PERIOD_D1;} break; } //--- if(!InitCheckParameters(digits_adjust)) return(false); if(!InitIndicators()) return(false); //--- 结果 return(true); } //+------------------------------------------------------------------+ //| 输入参数的验证 | //+------------------------------------------------------------------+ bool CSampleExpert::InitCheckParameters(const int digits_adjust) { //--- 检查源数据 if(InpTakeProfit*digits_adjust<m_symbol.StopsLevel()) { printf("获利必须大于 %d",m_symbol.StopsLevel()); return(false); } if(InpTrailingStop*digits_adjust<m_symbol.StopsLevel()) { printf("跟踪止损必须大于 %d",m_symbol.StopsLevel()); return(false); } //--- 检查手数值 if(InpLots<m_symbol.LotsMin() || InpLots>m_symbol.LotsMax()) { printf("手数必须在范围 %f 和 %f 之间",m_symbol.LotsMin(),m_symbol.LotsMax()); return(false); } if(MathAbs(InpLots/m_symbol.LotsStep()-MathRound(InpLots/m_symbol.LotsStep()))>1.0E-10) { printf("数量没有满足手数步长 %f",m_symbol.LotsStep()); return(false); } //--- warning if(InpTakeProfit<=InpTrailingStop) printf("警告:跟踪止损必须小于获利"); //--- 结果 return(true); } //+------------------------------------------------------------------+ //| 指标的初始化 | //+------------------------------------------------------------------+ bool CSampleExpert::InitIndicators(void) { //--- 创建 MACD 指标 if(handle_MACD==INVALID_HANDLE) { //--- handle_MACD=iCustom(NULL,0,"MTF\\Oscillators\\MTF_MACD",mas_tf[2],Signal_MACD_PeriodFast,Signal_MACD_PeriodSlow); //--- if(handle_MACD==INVALID_HANDLE) { printf("当创建 MACD 指标时出现错误。"); return(false); } } //--- 创建 MA 指标 if(handle_MA==INVALID_HANDLE) { //--- handle_MA=iCustom(NULL,0,"MTF\\Trend\\MA_MultiTF",mas_tf[2],Signal_MA_PeriodMA,0,MODE_EMA,PRICE_CLOSE); //--- if(handle_MA==INVALID_HANDLE) { printf("当创建 MA 指标时出现错误"); return(false); } } //--- 创建 Stochastic 指标 if(handle_Stochastic==INVALID_HANDLE) { //--- handle_Stochastic=iCustom(NULL,0,"MTF\\Oscillators\\MTF_Stochastic",mas_tf[1],Signal_Stoch_PeriodK,Signal_Stoch_PeriodD); //--- if(handle_Stochastic==INVALID_HANDLE) { printf("当创建 Stochastic 指标时出现错误"); return(false); } } //--- 结果 return(true); } //+------------------------------------------------------------------+ //| 修改买入仓位 | //+------------------------------------------------------------------+ bool CSampleExpert::LongModified(void) { bool res=false; //--- 检查跟踪止损 if(InpTrailingStop>0) { if(m_symbol.Bid()-m_position.PriceOpen()>m_adjusted_point*InpTrailingStop) { double sl=NormalizeDouble(m_symbol.Bid()-m_traling_stop,m_symbol.Digits()); double tp=m_position.TakeProfit(); if(m_position.StopLoss()<sl || m_position.StopLoss()==0.0) { //--- 修改仓位 if(m_trade.PositionModify(Symbol(),sl,tp)) printf("将修改 %s 的买入仓位",Symbol()); else { printf("修改 %s 仓位时出错: '%s'",Symbol(),m_trade.ResultComment()); printf("修改参数 : SL=%f,TP=%f",sl,tp); } //--- 已经修改,并且需要退出EA交易 res=true; } } } //--- 结果 return(res); } //+------------------------------------------------------------------+ //| 修改卖出仓位 | //+------------------------------------------------------------------+ bool CSampleExpert::ShortModified(void) { bool res=false; //--- 检查跟踪止损 if(InpTrailingStop>0) { if((m_position.PriceOpen()-m_symbol.Ask())>(m_adjusted_point*InpTrailingStop)) { double sl=NormalizeDouble(m_symbol.Ask()+m_traling_stop,m_symbol.Digits()); double tp=m_position.TakeProfit(); if(m_position.StopLoss()>sl || m_position.StopLoss()==0.0) { //--- 修改仓位 if(m_trade.PositionModify(Symbol(),sl,tp)) printf("将要修改 %s 的卖出仓位",Symbol()); else { printf("修改 %s 仓位时出错: '%s'",Symbol(),m_trade.ResultComment()); printf("修改参数 : SL=%f,TP=%f",sl,tp); } //--- 已经修改,并且需要退出EA交易 res=true; } } } //--- 结果 return(res); } //+------------------------------------------------------------------+ //| 检查用于开启买入仓位的条件 | //+------------------------------------------------------------------+ bool CSampleExpert::LongOpened(void) { bool res=false; level_stoch=InpLevel_S; //--- 检查开启买入仓位的可能性 if(stoch_ind_1<level_stoch && stoch_ind_0>level_stoch && macd_ind_1>macd_ind_0 && ma_ind<close[1] && ma_ind<open[1])//&& ma_ind<close[1] && ma_ind<open[1] { //--- 退出 EA res=true; } //--- 结果 return(res); } //+------------------------------------------------------------------+ //| 开启一个止损买入订单 | //+------------------------------------------------------------------+ bool CSampleExpert::OpenBuyStop(void) { bool res=false; double tp=0,sl=0; //--- if(LongOpened()) { res=true; //--- 声明和初始化交易请求和结果 MqlTradeRequest request={0}; MqlTradeResult result={0}; //--- 设置挂单的参数 request.action =TRADE_ACTION_PENDING; // 交易操作类型 request.symbol =Symbol(); // 交易品种 request.deviation=5; // 允许的价格偏差 request.volume =InpLots; // 交易手数 request.magic =EXPERT_MAGIC; // 订单幻数 double offset=InpOffset; // 当前价格距离订单的点差 double price; // 订单触发价格 double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); // 点的大小 int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); // 小数点位数 (digits) //--- 操作类型 request.type=ORDER_TYPE_BUY_STOP; // 订单类型 price=high[1]+offset*m_adjusted_point; // 开启价格 request.price=NormalizeDouble(price,digits); // 规范化的开启价格 tp=price+m_take_profit; sl=price-m_stop_losse; request.sl =NormalizeDouble(sl,_Digits); // 把新的止损值加到结构中 request.tp =NormalizeDouble(tp,_Digits); // 把新的获利值加到结构中 //--- 发送请求 if(!OrderSend(request,result)) {res=false;printf("OrderSend error %d",GetLastError());} // 如果无法发送请求,输出错误代码 //--- 关于操作的信息 printf("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order); //--- 退出 EA } //--- 结果 return(res); } //+------------------------------------------------------------------+ //| 检查建立卖出仓位的条件 | //+------------------------------------------------------------------+ bool CSampleExpert::ShortOpened(void) { bool res=false; level_stoch=100-InpLevel_S; //--- 检查是否可以建立卖出仓位 (SELL) if(stoch_ind_1>level_stoch && stoch_ind_0<level_stoch && macd_ind_1<macd_ind_0 && ma_ind>close[1] && ma_ind>open[1]) { //--- 退出 EA res=true; } //--- 结果 return(res); } //+------------------------------------------------------------------+ //| 开启一个止损卖出挂单 | //+------------------------------------------------------------------+ bool CSampleExpert::OpenSellStop(void) { bool res=false; double tp=0,sl=0; //--- if(ShortOpened()) { res=true; //--- 声明和初始化交易请求和结果 MqlTradeRequest request={0}; MqlTradeResult result={0}; //--- 设置挂单的参数 request.action =TRADE_ACTION_PENDING; // 交易操作类型 request.symbol =Symbol(); // 交易品种 request.deviation=5; // 允许的价格偏差 request.volume=InpLots; // 交易手数 request.magic=EXPERT_MAGIC; // 订单幻数 int offset=InpOffset; // 当前价格距离挂单的点数距离 double price; // 订单触发价格 int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); // 小数点位数 (digits) request.type=ORDER_TYPE_SELL_STOP; // 订单类型 price=low[1]-offset*m_adjusted_point; // 开启价格 request.price=NormalizeDouble(price,digits); // 规范化的开启价格 tp=price-m_take_profit; sl=price+m_stop_losse; request.sl =NormalizeDouble(sl,_Digits); // 把新的止损值加到结构中 request.tp =NormalizeDouble(tp,_Digits); // 把新的获利值加到结构中 //--- 发送请求 if(!OrderSend(request,result)) {res=false;printf("OrderSend error %d",GetLastError());} // 如果无法发送请求,输出错误代码 //--- 关于操作的信息 printf("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order); //--- 退出 EA } //--- 结果 return(res); } //+------------------------------------------------------------------+ //| 如果有任意仓位正在处理中 | //| 主函数就返回true | //+------------------------------------------------------------------+ bool CSampleExpert::Processing(void) { // MqlDateTime dt; //--- 更新频率 if(!m_symbol.RefreshRates()) return(false); //--- 更新指标 if(BarsCalculated(handle_Stochastic)<barsCalculated) return(false); if(CopyBuffer(handle_Stochastic,0,0,barsCalculated,stoch_buff)!=barsCalculated) return(false); //--- if(BarsCalculated(handle_MACD)<barsCalculated) return(false); if(CopyBuffer(handle_MACD,0,0,barsCalculated,macd_buff)!=barsCalculated) return(false); //--- if(BarsCalculated(handle_MA)<barsCalculated) return(false); if(CopyBuffer(handle_MA,0,0,barsCalculated,ma_buff)!=barsCalculated) return(false); //--- if(!Copy())return(false); //--- 为了简化编程以及提供对 //--- 位于内部数据的快速访问 macd_ind_0 = macd_buff[1]; macd_ind_1 = macd_buff[0]; ma_ind = ma_buff[1]; stoch_ind_0 = stoch_buff[1]; stoch_ind_1 = stoch_buff[0]; //--- 正确退出很重要 ... //--- 首先检查仓位是否存在 - 尝试选择它 bool bord=false,sord=false; ulong ticket; //+--- 有开启的仓位并且启用了跟踪止损 --------+ if(m_position.Select(Symbol()) && PositionsTotal()>0 && m_traling_stop!=0) { if(m_position.PositionType()==POSITION_TYPE_BUY) { bord=true; //--- 修改买入仓位 if(LongModified()) return(true); } else { sord=true; //--- 修改卖出仓位 if(ShortModified()) return(true); } } //+---------止损订单的交易 ------------------------------------+ // 在这个循环中,挨个检查所有的挂单 for(int i=0;i<OrdersTotal();i++) { // 选择每个挂单并取得它的单号 ticket=OrderGetTicket(i); // 处理止损买入订单 if(m_order.OrderType()==ORDER_TYPE_BUY_STOP) { // 设置标志,指出有止损买入订单 bord=true; //--- 正确进入市场是很重要的,必要时移动订单 if(bord)OrderModifyBuyStop(); // 修改 BUYSTOP 订单 } // 处理止损卖出订单 if(m_order.OrderType()==ORDER_TYPE_SELL_STOP) { // 设置标志指出有止损卖出订单 sord=true; //--- 正确进入市场是很重要的,必要时移动订单 if(sord)OrderModifySellStop(); // 修改 SELLSTOP 订单 } } //--- 如果没有订单,就进行设置 --------------------------------+ if(!sord)if(OpenSellStop()){sord=true;return(true);} if(!bord)if(OpenBuyStop()){bord=true;return(true);} //--- 不进行仓位处理而退出 return(false); } //+------------------------------------------------------------------+ //|修改之前设置的止损卖出挂单的参数 | //+------------------------------------------------------------------+ bool CSampleExpert::OrderModifySellStop(void) { bool res=true; ulong ticket; double tp=0,sl=0; double offset=InpOffset; // 当前价格距离订单的点差 double price; // 订单触发价格 int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); // 小数点位数 (digits) // 声明并初始化交易请求和结果 MqlTradeRequest request={0}; MqlTradeResult result={0}; int total=OrdersTotal(); // 挂单的数量 //--- 迭代所有设置的挂单 for(int i=total-1; i>=0; i--) { // 选择每个挂单并取得它的单号 ticket=OrderGetTicket(i); // 处理止损卖出订单 if(m_order.OrderType()==ORDER_TYPE_SELL_STOP) { ulong magic=OrderGetInteger(ORDER_MAGIC); // 订单幻数 //--- 如果幻数匹配 if(magic==EXPERT_MAGIC) { price=low[1]-offset*m_adjusted_point; // 开启价格 if(price>m_order.PriceOpen()) // 检查条件 if(low[1]>low[2]) // 检查条件 { request.action=TRADE_ACTION_MODIFY; // 交易操作类型 request.order = OrderGetTicket(i); // 订单单号 request.symbol =Symbol(); // 交易品种 request.deviation=InpOffset; // 允许的价格偏移 price=low[1]-offset*m_adjusted_point; // 开启价格 request.price=NormalizeDouble(price,digits); // 规范化的开启价格 tp=price-m_take_profit; sl=price+m_stop_losse; request.sl =NormalizeDouble(sl,_Digits); // 把新的止损值加到结构中 request.tp =NormalizeDouble(tp,_Digits); // 把新的获利值加到结构中 //--- 发送请求 if(!OrderSend(request,result)) { // 设置标志指出止损卖出订单没有被删除 res=true; printf("OrderSend 错误 %d",GetLastError()); } // 如果无法发送请求,输出错误代码 //--- 关于操作的信息 printf("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order); } } } } return(res); } //+------------------------------------------------------------------+ //|修改之前设置的止损买入挂单的参数 | //+------------------------------------------------------------------+ bool CSampleExpert::OrderModifyBuyStop(void) { bool res=true; ulong ticket; double tp=0,sl=0; double offset=InpOffset; // 当前价格距离订单的点差 double price; // 订单触发价格 int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); // 小数点位数 (digits) //--- 声明和初始化交易请求和结果 MqlTradeRequest request={0}; MqlTradeResult result={0}; int total=OrdersTotal(); // 挂单的数量 //--- 迭代所有设置的挂单 for(int i=total-1; i>=0; i--) { // 选择每个挂单并取得它的单号 ticket=OrderGetTicket(i); // 处理止损买入订单 if(m_order.OrderType()==ORDER_TYPE_BUY_STOP) { ulong magic=OrderGetInteger(ORDER_MAGIC); // 订单幻数 //--- 如果幻数匹配 if(magic==EXPERT_MAGIC) { price=high[1]+offset*m_adjusted_point; // 开仓价格 if(price<m_order.PriceOpen()) // check the conditions { request.action=TRADE_ACTION_MODIFY; // 交易操作类型 request.symbol =Symbol(); // 交易品种 request.action=TRADE_ACTION_MODIFY; // 交易操作类型 request.order = OrderGetTicket(i); // 订单单号 request.symbol =Symbol(); // 交易品种 request.deviation=InpOffset; // 允许的价格偏移 //--- set the price level, take profit and stop loss request.price=NormalizeDouble(price,digits); // 规范化的开启价格 tp=price+m_take_profit; sl=price-m_stop_losse; request.sl =NormalizeDouble(sl,_Digits); // 把新的止损值加到结构中 request.tp =NormalizeDouble(tp,_Digits); // 把新的获利值加到结构中 //--- 发送请求 if(!OrderSend(request,result)) { // 设置标志指出止损买入挂单没有被删除 res=true; printf("OrderSend 错误 %d",GetLastError()); } // 如果无法发送请求,输出错误代码 //--- 关于操作的信息 printf("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order); } } } } return(res); } //+------------------------------------------------------------------+ //| EA交易初始化函数 | //+------------------------------------------------------------------+ int OnInit(void) { //--- 创建所有所需的对象 if(!ExtExpert.Init()) return(INIT_FAILED); //--- ok return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 分时处理函数 | //+------------------------------------------------------------------+ void OnTick(void) { static datetime limit_time=0; // 上次的处理时间 + 超时 //--- 不处理直到超时 if(TimeCurrent()>=limit_time) { //--- 检查数据 if(Bars(Symbol(),Period())>barsCalculated) { //--- 检查以秒为单位的超时限制时间(如果已处理) if(ExtExpert.Processing()) limit_time=TimeCurrent()+ExtTimeOut; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CSampleExpert::Copy(void) { //--- 要复制的数量 int copied=3; //--- ArrayResize(high,copied); ArrayResize(low,copied); ArrayResize(close,copied); ArrayResize(open,copied); //+------- 设置数组索引方向 ---------------------+ ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(close,true); ArraySetAsSeries(open,true); //--- 复制柱形价格 if(CopyHigh(NULL,mas_tf[0],0,copied,high)<0) {printf("未能复制最高价",GetLastError());return(false);} //--- if(CopyLow(NULL,mas_tf[0],0,copied,low)<0) {printf("未能复制最低价",GetLastError());return(false);} //--- if(CopyClose(NULL,mas_tf[2],0,copied,close)<0) {printf("未能复制收盘价",GetLastError());return(false);} //--- if(CopyOpen(NULL,mas_tf[2],0,copied,open)<0) {printf("未能复制开盘价",GetLastError());return(false);} //--- return(true); } //+------------------------------------------------------------------+
使用本EA更方便:
图 18. 使用所实现的MTF工具进行EA测试
结论
尽管 MQL5 软件模块的作者没有提供创建此类算法的直接可能性,但这些指标可能非常有用。在某些情况下,它们可以在不同的时间框架上同时进行市场状态分析,提高策略测试器的效率,并改进平滑度。所给出的代码示例可能有缺点,因此有很多机会来使用 MQL5 编程语言进一步开发MTF指标。
附件
名称 | 类型 | 位置 |
---|---|---|
MA_MultiPeriod | 趋势指标 | MQL5\Indicators\MA_MultiPeriod.mq5 |
MA_MultiTF | 趋势指标 | MQL5\Indicators\MTF\Trend\MA_MultiTF.mq5 |
MTF_BB | 趋势指标 | MQL5\Indicators\MTF\Trend\MTF_BB.mq5 |
MTF_Envelopes | 趋势指标 | MQL5\Indicators\MTF\Trend\MTF_Envelopes.mq5 |
MTF_ParabolicSAR | 趋势指标 | MQL5\Indicators\MTF\Trend\MTF_ParabolicSAR.mq5 |
MTF_StdDev | 趋势指标 | MQL5\Indicators\MTF\Trend\MTF_StdDev.mq5 |
MTF_CCI | 振荡指标 | MQL5\Indicators\MTF\Oscillators\MTF_CCI.mq5 |
MTF_Force_Index | 振荡指标 | MQL5\Indicators\MTF\Oscillators\MTF_Force_Index.mq5 |
MTF_MACD | 振荡指标 | MQL5\Indicators\MTF\Oscillators\MTF_MACD.mq5 |
MTF_Momentum | 振荡指标 | MQL5\Indicators\MTF\Oscillators\MTF_Momentum.mq5 |
MTF_RSI | 振荡指标 | MQL5\Indicators\MTF\Oscillators\MTF_RSI.mq5 |
MTF_RVI | 振荡指标 | MQL5\Indicators\MTF\Oscillators\MTF_RVI.mq5 |
MTF_Stochastic | 振荡指标 | MQL5\Indicators\MTF\Oscillators\MTF_Stochastic.mq5 |
MTF_WPR | 振荡指标 | MQL5\Indicators\MTF\Oscillators\MTF_WPR.mq5 |
三重滤网交易系统 1.0 | EA 交易 |
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/2837


