引言
在本文中, 我们要研究一款构建动态价格通道的指标。并基于此通道创建一款智能交易系统。这种系统在趋势区间表现良好, 但在横盘走势中则发出了大量的假信号。因此需要附加的趋势指标。选择一款合适的指标并非易事, 而且选择往往依赖于具体的行情条件。所以, 一个良好的解决方案是提供将任何选定指标快速连接到已有交易系统的可能性。
这就是为什么我们会使用以下方法。我们将创建一个用于 MQL5 向导 的特殊 交易信号模块。此后我们将能够基于任意选定的趋势指标创建一个类似的模块, 以便产生标识趋势存在与否的 Yes/No 信号。创建交易系统时可使用多个模块的组合, 因此我们可以轻松地组合各种指标。
NRTR 指标
NRTR (Nick Rypock Trailing Reverse) 指标是由 Konstantin Kopyrkin 提出的。有趣的信息: 尼克·雷普克 (Nick Rypock) 这个名字源自姓氏 Kopyrkin。
让我们回到指标。这是一个动态价格通道。作者用下图描绘了它的主要思想:
基于 NRTR 指标的交易系统属于突破策略。当价格超过某个周期的前一高点时, 会产生一个买入信号; 当价格跌至前一低点时产生卖出信号。在趋势变化期间, 这种系统也许会使用前一趋势的高点和低点。为了避免这种情况, 计算周期在我们的系统中会动态设置。
作者已经将 NRTR 定义为 动态价格通道突破的趋势指标。
指标操作原则如下: 在上升趋势中, 指标线 (通道) 处于指定时间段内检测到的高位之下的某个价位。下降趋势线处于价格之上, 与某个时间段内的价格低点处于恒定的距离。
用于指标计算的价格通道周期从趋势起点开始动态增加。因此, 前一个计算周期的价格不会影响指标。
从图例中可以看出, 指标首先以的一定距离跟随趋势。指标位于距本地高点 H1 和 H2 固定的距离。本地高点 H3 低于前一个, 则不用于计算。
然后价格在 L3 点突破了通道。这是一个卖出信号。L3 点的值被设置为新的低点。新周期从这一点开始, 即之前的所有价格均被重置, 不再用于将来的计算。随着趋势的发展, 最低值更新为 L3-L4-L5。动态价格通道的周期将延长, 直到趋势发生变化或周期长度达到所允许的最大值。
通道宽度按极值的百分比计算, 或是依据价格波动。这两种方法在本文中均已实现。
买入/卖出信号在价格突破通道边界线时产生。买入信号在价格突破支撑线时形成。如果突破了阻力线, 则形成卖出信号。
现在, 我们需要将指标操作原则的描述转换为 MQL5。我们这就开始。
编写指标: 从简单到复杂
首先, 我们需要定义指标的行为。指标将基于收盘价。基于历史数据的指标值被明确解释。但若是价格突破不完整烛条的支撑/阻力线会怎样呢?在这个版本中, 趋势没有形成, 因此在烛条完整成形之前不会产生信号。一方面, 我们可能会错过走势的一部分。例如, 如果走势开始于一根巨大烛条突破通道, 只有在下一根烛条开盘时才会开仓。另一方面, 这是防止大量假突破所做的保护。
注意: 指标有许多变体, 本文只针对作者提出的原始版本进行描述。
该指标的实现可在代码库找到, 但其周期只是部分为动态的。当趋势发生变化时, 这个周期会被重新设定, 但在理论上可以无止境地延伸。即, 支撑线以前值的 MathMax() 和当前收盘价计算。在这种情况下, 支撑线只能上升, 而阻力线总是下降。在原始版本中, 所有较早的数值均被认定过时, 从而被忽略。此处的最大值/最小值计算为 ArrayMaximum/Minimum(close,i,dynamic_period)。在这种方式下, 支撑线和阻力线都会上涨和下跌。因此, 在动态周期较短的情况下, 支撑线可能在一些舒缓的区间震荡走势情况下深度下移。但是这种平滑的长期趋势很罕见, 两种方法都不是很理想。在本文中, 我们坚持指标作者最初提出的想法。
下一次滞留与时间序列有关。MQL5 中的价格系列 (收盘) 默认值为 ArraySetAsSeries = false。MQL4 中的价格系列带有一个时间序列标志, 它们的 Close[0] 值是最右边柱线的收盘价 (此刻最左边的柱线按规定是不可见的)。请注意, 在本文中 ArraySetAsSeries(close,true)。
现在我们继续实现这个指标。我们将处理四个指标缓冲区: 其中两个用于支撑/阻力线, 另外两个用于买入和卖出信号。
#property indicator_chart_window #property indicator_buffers 4 #property indicator_plots 4 //指标线条样式 #property indicator_type1 DRAW_LINE #property indicator_color1 Green #property indicator_style1 STYLE_DASH #property indicator_type2 DRAW_LINE #property indicator_color2 Red #property indicator_style2 STYLE_DASH #property indicator_type3 DRAW_ARROW #property indicator_color3 Green #property indicator_type4 DRAW_ARROW #property indicator_color4 Red
我们来声明指标缓冲区和指标的外部参数
input int period =12; //动态周期 input double percent =0.2; //缩进百分比 double Buff_Up[],Buff_Dn[]; double Sign_Up[],Sign_Dn[];
指标信号将在图表上显示为箭头。其余参数在 OnInit() 函数中设置。我的 DRAW_ARROW 参数来自 Wingdings 字符集, 编号为 236, 238。"向上" 信号的参数:
SetIndexBuffer(2,Sign_Up,INDICATOR_DATA); PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0.0); PlotIndexSetInteger(2,PLOT_ARROW,236); PlotIndexSetInteger(2,PLOT_LINE_WIDTH,1); ArraySetAsSeries(Sign_Up,true);
在 OnCalculate() 中开始计算时, 我们检查所需数据的可用性以及指标是否首次开始计算。
//+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ 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[]) { int start =0; //计算的起始点 int trend =0; //趋势值, 0 向上 -1 向下 static int trend_prev =0; double value =0; //指标值 static double value_prev =0; int dyn_period =1; //动态周期值 static int curr_period =1; double maxmin =0; //用于计算的技术变量 ArraySetAsSeries(close,true); if(rates_total<period) return(0); if(prev_calculated==0) // 检查指标计算的首次开始 { start=rates_total-1; // 所有柱线的起始计算索引 trend_prev =1; value=close[start]*(1-0.01*percent); } else { start=rates_total-prev_calculated; // 新柱线的起始计算索引 } trend =trend_prev; value =value_prev; dyn_period =curr_period;
主变量也在此定义。为趋势和通道值定义两个变量。其中一个变量是静态的。静态变量会将数值保存到下一个计算周期。这个变量只在一根柱线完整形成时才会更改。如果价格突破不完整柱线上的通道, 则只有本地的非静态变量被改变。如果价格回到通道, 那么之前的趋势将被保留。
现在, 我们考虑到上面的描述来编写主要的计算循环。
trend =trend_prev; value=value_prev; dyn_period =curr_period; //-------------------------------------------------------------------+ // 主要的计算循环 //-------------------------------------------------------------------+ for(int i=start;i>=0;i--) { Buff_Up[i] =0.0; Buff_Dn[i] =0.0; Sign_Up[i] =0.0; Sign_Dn[i] =0.0; if(curr_period>period) curr_period=period; if(dyn_period>period) dyn_period=period; // 如果趋势为升势 if(trend>0) { maxmin =close[ArrayMaximum(close,i,dyn_period)]; value =maxmin*(1-percent*0.01); if(close[i]<value) { maxmin =close[i]; value =maxmin*(1+percent*0.01); trend =-1; dyn_period =1; } } // 如果趋势为降势 else { maxmin =close[ArrayMinimum(close,i,dyn_period)]; value =maxmin*(1+percent*0.01); if(close[i]>value) { maxmin =close[i]; value =maxmin*(1-percent*0.01); trend =1; dyn_period =1; } } // 趋势变化 if(trend>0) Buff_Up[i] =value; if(trend<0) Buff_Dn[i] =value; if(trend_prev<0 && trend>0) { Sign_Up[i] =value; Buff_Up[i] =0.0; } if(trend_prev>0 && trend<0) { Sign_Dn[i] =value; Buff_Dn[i] =0.0; } dyn_period++; if(i) { trend_prev =trend; value_prev =value; if(dyn_period==2)curr_period =2; else curr_period++; } }
该循环中的动态周期被限制在一个指定的数值。如果收盘价突破该通道, 则发现新的支撑和阻力值, 并检查趋势是否已经改变。最后的 if() 操作符检查柱线是否完整形成。只有柱线完整形成, trend_prev 和 value_prev 的值才会更新, 因此可以生成买/卖信号。动态周期也可以在这里重置。
完整的指标代码可以在附加的 NRTR.mq5 文件中找到。
我们在一个图表上启动两个不同参数的 NRTR 来检查指标操作: 第一个 NRTR 的周期为 12, 宽度为 0.1%; 第二个 NRTR 的周期是 120, 宽度是 0.2%。
上图显示, 如果一个周期较小, 支撑线即可能是上升亦或下降。这与价格下降超出动态周期的事实有关。如果周期较大, 支撑线通常不减。
波动性和 NRTR
在以前的方法中, 我们使用固定的价格通道偏差百分比。更合理的解决方案是在波动性增加时扩大通道, 在波动性下降时将通道缩窄。ATR (Average True Range 平均真实范围) 是判定行情波动的流行指标。ATR 的数值可用于设置通道宽度。您可以自行计算 ATR 值, 也可以使用终端的标准技术指标。
我们用 ATR 指标值替换偏差百分比, 以便将通道宽度与行情波动性联系起来。系数仍将用于缩放。我们默认将它设置为 1。我们为 ATR 指标声明一个额外的指标缓冲区: double Buff_ATR[]。百分比参数被替换为系数 K =1。我们创建一个指向 ATR 的指针来接收它的值:
handle_atr =iATR(_Symbol,PERIOD_CURRENT,period);
ATR 周期可以不同于有效的动态周期。一个合乎逻辑的解决方案是令它们相等, 这样的话参数数目不会改变。
这是新增的代码行。
#property indicator_buffers 5 #property indicator_plots 4 ............................. input double K =1; //缩放系数 double Buff_ATR[]; int handle_atr; ............................. SetIndexBuffer(4,Buff_ATR,INDICATOR_CALCULATIONS); ArraySetAsSeries(Buff_ATR,true); handle_atr =iATR(_Symbol,PERIOD_CURRENT,period); ..................................................... int OnCalculate(){ ..................................................... if(CopyBuffer(handle_atr,0,0,start+1,Buff_ATR)==-1) { return(0); Print("复制数据到 ATR 缓冲区失败"); } ..................................................... // 如果趋势为升势 if(trend>=0) { maxmin =close[ArrayMaximum(close,i,dyn_period)]; value =maxmin-K*Buff_ATR[i]; if(close[i]<value) { maxmin =close[i]; value =maxmin+K*Buff_ATR[i]; trend =-1; dyn_period =1; } } }
通道边线值分别使用公式 value = maxmin(+-)K*Buff_ATR[i]。完整的指标代码可在附件 NRTRvolatile.mq5 文件中找到。
我们在一个图表上运行两个相同参数的指标, 并比较它们的行为。
如图所示, 如果波动率较低且 ATR 值较小, 那么 NRTRvolatile 曲线几乎被 "吞噬" 到价格图表中。那么, 当波动性增加时, 这条线就会远离价格。
现在我们继续根据这个指标创建智能交易系统。如上所述, 我们将使用交易信号模块。
用于 MQL5 向导的交易模块
有时候基于已有模块, 使用复制/粘贴方法来编写新模块会更方便。但在我们的情况中, 最好从头开始, 而非替换或修改所有被描述的地方。
以下是 所有模块的通用结构。
- 模块描述符
- 交易参数和参数初始化函数
- 检查输入参数
- 将选定的指标与模块连接
- 交易策略的描述
首先, 我们在信号文件夹中创建一个单独的子文件夹, 以便存储我们自己的信号。例如, Include\Expert\MySignals。右键点击所选文件夹, 并在关联菜单里选择 "新文件"。MQL5 向导打开从菜单里选择 "新类"。将其命名为 NRTRsignal。所有信号都继承自 CExpertSignal 基类, 所以我们要在向导中指明它。
将 CExpertSignal 基类的路径添加到向导生成的代码里: #include "..\ExpertSignal.mqh"
//+------------------------------------------------------------------+ //| SignalNRTR.mqh | //| Orangetree | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Orangetree" #property link "https://www.mql5.com" #property version "1.00" #include "..\ExpertSignal.mqh" // CExpertSignal 类 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class SignalNRTR : public CExpertSignal { private: public: SignalNRTR(); ~SignalNRTR(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ SignalNRTR::SignalNRTR() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ SignalNRTR::~SignalNRTR() { } //+------------------------------------------------------------------+
那只是开始。
现在我们需要创建一个模块描述符, 以便 MQL5 向导可以将这个代码识别为一个信号模块。
// wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Signals of indicator 'NRTR' | //| Type=SignalAdvanced | //| Name=NRTR | //| ShortName=NRTR | //| Class=SignalNRTR | //| Page=???? | //| Parameter=PeriodDyn,int,12,Dynamic channel period | //| Parameter=PercentDev,double,0.1,Channel width in percent | //+------------------------------------------------------------------+ // wizard description end //+------------------------------------------------------------------+ //| Class SignalNRTR. | //| Purpose: Class of generator of trade signals based on | //| the 'NRTR' indicator. | //| Is derived from the CExpertSignal class. | //+------------------------------------------------------------------+
描述符以 "wizard description start" 开始, 并以 "wizard description end" 结束。模块名称和外部参数包含其内。一旦我们将这个模块与描述符一起编译, 它就被添加到向导菜单中: 新文件/智能交易系统 (生成)/通用参数/智能交易系统的信号属性。
我们来添加用于存储外部参数的类变量, 和初始化它们的方法。
用于初始化外部参数的方法名称必须与描述符中写入的外部参数名称相匹配。
class SignalNRTR : public CExpertSignal { protected: int m_period_dyn; // 通道周期 double m_percent_dev; // 通道宽度为价格的百分比 public: SignalNRTR(); ~SignalNRTR(); //--- 设置可调参数的方法 void PeriodDyn(int value) { m_period_dyn=value;} void PercentDev(double value) { m_percent_dev=value;} }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ SignalNRTR::SignalNRTR() : m_period_dyn(12), m_percent_dev(0.1) { //--- 受保护数据的初始化 m_used_series=USE_SERIES_OPEN+USE_SERIES_HIGH+USE_SERIES_LOW+USE_SERIES_CLOSE; }
类成员使用 初始化列表 进行初始化。向导生成的 "private" 方法可以替换为 "protected", 但这不是必需的。
CExpertBase 类中的虚方法 bool ValidationSettings() 能够检查输入的正确性。
我们需要将方法原型添加到我们的类中, 并覆盖它。例如, 我们需要验证周期是否大于 1, 偏差百分比值是正数值。
//+------------------------------------------------------------------+ //| 检查输入参数的方法 | //+------------------------------------------------------------------+ bool SignalNRTR:: ValidationSettings() { // 调用基类方法 if(!CExpertSignal::ValidationSettings()) return(false); // 周期必须大于 1 if(m_period_dyn<2) { Print(周期必须大于 1); return false; } // 通道宽度值必须是正数值 if(m_percent_dev<=0) { Print("通道宽度值必须是正数值"); return false; } return true; }
请注意, 首先调用基类的方法。
接着, 我们使用 InitIndicators() 方法将选定的指标连接到我们的模块。我们在类中创建方法原型: virtual bool InitIndicators(CIndicators *indicators), 然后添加它的描述。我们将使用标准过程检查指标指针, 并在额外的过滤器中初始化指标和时间序列。
//+------------------------------------------------------------------+ //| 创建指标。 | //+------------------------------------------------------------------+ bool SignalNRTR::InitIndicators(CIndicators *indicators) { //--- 检查指针 if(indicators==NULL) return(false); //--- 初始化指标和时间序列的额外过滤器 if(!CExpertSignal::InitIndicators(indicators)) return(false); //--- 创建并初始化 NRTR 指标 if(!InitNRTR(indicators)) return(false); //--- ok return(true); }
我们在 InitNRTR(indicators) 行中创建并初始化指标。还必须添加 InitNRTR(indicators) 函数的原型和描述。
//+------------------------------------------------------------------+ //| 创建 NRTR 指标。 | //+------------------------------------------------------------------+ bool SignalNRTR::InitNRTR(CIndicators *indicators) { //--- 检查指针 if(indicators==NULL) return(false); //--- 将对象加入集合 if(!indicators.Add(GetPointer(m_nrtr))) { printf(__FUNCTION__+": 添加对象出错"); return(false); } //--- 设置 NRTR 参数 MqlParam parameters[3]; //--- parameters[0].type=TYPE_STRING; parameters[0].string_value="Orangetree\\NRTR.ex5"; parameters[1].type=TYPE_INT; parameters[1].integer_value=m_period_dyn; // 周期 parameters[2].type=TYPE_DOUBLE; parameters[2].double_value=m_percent_dev; // 通道宽度 //--- 初始化对象 if(!m_nrtr.Create(m_symbol.Name(),m_period,IND_CUSTOM,3,parameters)) { printf(__FUNCTION__+": 初始化对象出错"); return(false); } //--- ok return(true); }
指标可利用 MqlParam 结构和 Create() 方法创建。
我们已经准备好了所需的代码。现在我们需要编写一个交易算法。我们使用 LongCondition() 和 ShortCondition() 方法。我们来添加接收指标信号的方法。
//--- 获取数据的方法 double UpSignal(int index) { return(m_nrtr.GetData(2,index));} double DnSignal(int index) { return(m_nrtr.GetData(3,index));}
GetData() 函数根据其索引和柱线索引接收指标缓冲区的数值。指标已包含了趋势反转信号。这就是为什么开仓的条件很简单。如果指标产生 "上升" 信号, 我们买入, 如果产生 "下行" 信号, 则卖出。
//+------------------------------------------------------------------+ //| "投票", 价格会增长。 | //+------------------------------------------------------------------+ int SignalNRTR::LongCondition(void) { int idx =StartIndex(); if(UpSignal(idx)) return 100; else return 0; } //+------------------------------------------------------------------+ //| "投票", 价格会下跌。 | //+------------------------------------------------------------------+ int SignalNRTR::ShortCondition(void) { int idx =StartIndex(); if(DnSignal(idx)) return 100; else return 0; }
除了定义交易算法之外, 这些函数还设定了其行为特征。使用 StartIndex() 函数。
virtual int StartIndex() 函数的描述包含以下语句: "如果分析当前柱线的标志设置为 true (从当前柱线分析), 则方法返回 0。如果没有设置标志, 它将返回 1 (从最后一根完成的柱线分析)。"我们的系统只分析完整柱线上的信号。按照我们的策略 StartIndex() 函数默认返回 1。此功能在智能交易系统中通过 Expert_EveryTick 参数设为 false 来反映。
交易信号模块创建步骤完成。
我们使用策略测试器来测试 EA 操作。
另外, 我们来优化交易参数。为了优化, 我们使用 EURUSD 25.09.17 - 18.10.17 的数据和 H1 时间帧。只优化以下指标参数: 通道的周期和宽度。止损和止盈值设为 0。
该图显示了通道周期设为 48, 宽度设为 0.25% 的结果。
NRTR 与不同趋势指标的结合
假设, 我们需要确认我们的指标信号。指标在趋势中运行良好, 所以我们添加趋势指标。在本文中, 我们提供了来自 MQL5 向导的现有信号模块。所以, 我们继续使用相同的方法, 并通过信号模块添加一个趋势指标。
识别趋势并不是一件容易的任务。所以, 我们不会讨论趋势指标的优劣。我们从终端中选择任何标准趋势指标, 因为我们的主要目标是制定一种使用交易模块连接指标的技术。我们对这一步的交易结果不感兴趣。
上一章节介绍了模块创建的所有步骤。因此, 在这里使用 "复制/粘贴" 方法更为方便, 并评估其优点。我们使用一个准备好的模块并替换必要的代码行。
我们从趋势类别中选择 ADX 技术指标。可以使用专门保留的 CiADX 指针来引用指标, 而不是指向自定义指标 CiCustom 的指针。相应地, 通过 Create 方法创建它的方式稍有不同, 不需要指定路径。
protected: CiADX m_adx; // 对象指标 int m_period_adx; //ADX 周期 ....................... 其它代码..................................... //--- 初始化对象 if(!m_adx.Create(m_symbol.Name(),m_period,m_period_adx)) { printf(__FUNCTION__+": 初始化对象出错"); return(false); }
指标只有一个周期参数。它应该反映在模块描述符和参数设置函数中。我们还需要添加获取 ADX 缓冲区数值的函数。
//| Parameter=PeriodADX,int,14,ADX indicator period ..................................................... //--- 设置可调参数的方法 void PeriodADX(int value) { m_period_adx=value;} ..................................................... //--- 获取数据的方法 double MainADX(int index) { return(m_adx.Main(index));} double ValueIDPlus(int index) { return(m_adx.Plus(index));} double ValueIDMinus(int index) { return(m_adx.Minus(index));}
实际上, 交易模块创建部分中所描述的所有步骤我们均已完成。必要时我们用 ADX 替换 NRTR。我们使用 ADX 指标中描述的标准交易条件, 且不检查它们。经典指标提供以下条件:
- 买入, 如果 +DI >-DI 且 ADX 增长。
- 卖出, 如果 +DI <-DI 且 ADX 增长。
//+------------------------------------------------------------------+ //| "投票" 这种趋势是 "向下"。 | //+------------------------------------------------------------------+ int SignalADX::LongCondition(void) { int idx =StartIndex(); if(ValueIDPlus(idx)>ValueIDMinus(idx)&&MainADX(idx)>MainADX(idx+1)) return (100); else return (0); } //+------------------------------------------------------------------+ //| "投票" 这种趋势是 "向上"。 | //+------------------------------------------------------------------+ int SignalADX::ShortCondition(void) { int idx =StartIndex(); if(ValueIDPlus(idx)<ValueIDMinus(idx)&&MainADX(idx)>MainADX(idx+1)) return (100); else return (0); }
这是基本原则。如果我们认为指标呈现上升趋势, 则产生一个买入信号, 若趋势正在下降, 就应该产生一个卖出信号。出于便利, 信号权重设置为 100。
我们再次打开 MQL5 向导。在创建智能交易系统时, 选择两个交易信号, 包括 SignalNTRTR 和 ADXTrendSignal。如果使用多个信号, 则计算其平均值。因此, 我们将权重因子分配给两个信号。开仓的阈值设置为 100。除用于通道以外的所有其它参数都重置为零。我们启动策略测试器以便确保智能交易系统正确运行。
结束语
我们总结一下。我们已讨论了动态价格通道突破的趋势指标 NRTR。已开发了两个版本的指标: 其一距趋势线的偏离为固定的价格极值百分比, 另一个距趋势线的偏离依赖行情波动性。
指标的两个版本都附在下面。交易信号模块基于 NRTR 创建, 并使用 MQL5 向导生成智能交易系统。
已创建了 ADX 指标模块, 作为 NRTR 指标与趋势指标联同工作的直观示例。已在 MQL5 向导中创建了基于 NRTR + ADX 的测试智能交易系统。NRTR + 趋势指标的组合已经过优化, 因为 趋势指标的选择取决于用户, 本文中没有描述。该方法基于模块化和组合的理念。
使用附件文件时, 请确保按照其安装文件夹正确指定指标和交易信号的路径。例如, 对于我的情况 SignalNRTR:
parameters[0].string_value="NRTR.ex5";
根据指标的安装文件夹指定路径。
文件:
# | 名称 | 类型 | 描述 |
---|---|---|---|
1 | NRTR.mq5 | 指标 | 所分析的指标源代码 |
2 | NRTRvolatile.mq5 | 指标 | 价格波动性指标的源代码 |
3 | SignalNRTR.mqh | 交易模块 | 交易信号模块。它用于在 MQL5 向导中生成智能交易系统 |
4 | ADXTrendSignal.mqh | 交易模块 | 趋势指标的测试模块 |
MQL5.zip 文件夹中的文件根据 MetaEditor 目录进行定位。