1971 年,罗伯特·利维(Robert Levy)首次尝试建立了价格形态系统。 他应用了五点价格波动形态,然后检查它们是否有意义。 他没有达成任何显著绩效,但是 10 年后,亚瑟·美林(Arthur Merrill)继承了他的工作。
他用 M 和 W 字母将形态分为两类。 每个类别包含 16 个形态,以及自己的子类别。 美林着重强调了 6 个子类别:
我们将利用应用程序测试所定义的美林形态与当前行情的相关性。 此外,将这种形态应用于各种类型的数据会很有趣 — 例如收盘价、最高价和最低价,以及振荡器。
为了阐明我们在应用美林形态时如何以及该使用哪些数据,我们需要了解它们的实际含义。 主要的两个类别是类似于字母 M 和 W 的图案。它们被称为 M 和 W 形态。 每个类别包含 16 种形态。
图例 1、示意 16 个 M 形态。 我们可以看到,区别在于构成形态的五个点的相互排列。
图例 1、 М 形态的直观展示
图例 2、 16 个 W 形态的展示。 我们将从价格图表和指标上搜索这两组形态,并研究、评估和寻找可能的规律性。
图例 2、 W 形态的直观展示
任何形态背后的思想都可以归结为以下事实:当出现特定形态时,我们可以预期价格朝某个方向移动,并能从中获利。
为了尽可能清晰地阐明在哪些区域,以及如何研究美林形态,下面列举一些示例。 图例 3、显示一幅通常的线性 USDCAD H1 价格图表。 由于蜡烛和柱线变得越来越流行,因此很少使用这种表现形式。
图例 3、 基于收盘价的线性 USDCAD H1 图表
在此,我们已经可以看到上述几种形态。 这将是第一个要研究的区域 — 应用在基于收盘价的线性图表。 另外,我们将根据开盘价、最高价和最低价排查线性图表。
第二个要研究的区域将由振荡器组成,例如:
我会用到在研究烛台分析技术(第一部分):检查现有形态一文中建议的方法来评估基于价格和上述振荡器的形态。 其背后的思路很简单:
在起手开发之前,我们需要定义应当包含的设置。 该工具应由 分析(Analysis) 和 设置(Settings) 选卡面板组成。 还要用到 EA 设置窗口中的参数。 总之,我们将其划分为三个部分,作为处理形态的工具。 现在,我们来描述每个部分中的设置。
分析(Analysis) 选卡包括:
图例 4、提供上述所有品种和参数的直观实现。
图例 4、 分析(Analysis)选卡
现在我们研究设置(Settings)选卡:
设置选卡外观显示在下面的图例 5 当中:
图例 5、 设置(Settings)选卡
最后一个板块应用“ EA 设置”窗口(F7 热键),并提供“用到的指标”中列出的已应用指标的设置。 图例 6 显示最后部分的设置窗口。
图例 6、 所用指标的设置窗口
在窗口中定义设置时,我们应考虑以下细微差别:
现在,我们研究一下应用程序界面的实现,以及搜索和分析美林形态的方法。
为了开发 GUI,我们使用由 CreateWindow() 方法组成的 CreateGUI() 方法来创建界面主窗口,并调用 CreateWindowSetting1() 对话框窗口选择一个研究时间帧。
//+------------------------------------------------------------------+ //| Create the program GUI | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Create the panel if(!CreateWindow("Merrill Patterns")) return(false); //--- Create the dialog window if(!CreateWindowSetting1("Setting dates")) return(false); //--- Complete GUI creation CWndEvents::CompletedGUI(); return(true); }
现在,我们看看每种方法的组成。 我们首先将注意力集中在界面主窗口上。 它的构成包括“分析(Analysis)”选卡的实现,该选卡由图例 4 中描述的元素组成。
//+------------------------------------------------------------------+ //| Analyze tab | //+------------------------------------------------------------------+ //--- Create the pattern set buttons if(!CreatePatternSet(m_patterns,10,10)) return(false); //--- Timeframe header if(!CreateTFLabel(m_text_labels[1],10,105,0)) return(false); //--- Create the timeframe set buttons if(!CreateTimeframeSet(m_timeframes,10,125,0)) return(false); //--- Field for searching the symbol filter if(!CreateSymbolsFilter(m_symb_filter1,m_request1,10,180,0)) return(false); //--- Create the button for selecting a date range if(!CreateDateRange(m_request3,280,180,0)) return(false); //--- Create the field for entering the profit threshold value if(!CreateThresholdValue(m_threshold1,400,180,100,0)) return(false); //--- Create the symbol table if(!CreateSymbTable(m_symb_table1,10,225,0)) return(false); //--- Create the result table if(!CreateTable1(m_table1,120,225,0)) return(false);
以及图例 5 中描述的“设置(Settings)”选卡。
//+------------------------------------------------------------------+ //| Settings tab | //+------------------------------------------------------------------+ //--- if(!CreateButtonsGroup1(10,50)) return(false); //--- Text labels if(!CreateTextLabel(m_text_labels[0],10,100)) return(false); if(!CreateTextLabel(m_text_labels[3],10,10)) return(false); //--- Input fields if(!CreateCoef(m_coef1,10,140,"K1",1)) return(false); if(!CreateCoef(m_coef2,100,140,"K2",0.5)) return(false); if(!CreateCoef(m_coef3,200,140,"K3",0.25)) return(false); if(!CreateLanguageSetting(m_lang_setting,10,180,1)) return(false); //--- Status bar if(!CreateStatusBar(1,26)) return(false); //--- return(true); }
可以在随附的源代码中找到每种应用方法添加接口元素的更详细实现。
实现对话框窗口以便设置临时样本的方法如下所示:
//+---------------------------------------------------------------------------------+ //| Create the dialog window for selecting the range of dates in the Analysis tab | //+---------------------------------------------------------------------------------+ bool CProgram::CreateWindowSetting1(const string caption_text) { //--- Add the window pointer to the window array CWndContainer::AddWindow(m_window[2]); //--- Coordinates int x=m_request3.X(); int y=m_request3.Y()+m_request3.YSize(); //--- Properties m_window[2].XSize(372); m_window[2].YSize(230); m_window[2].WindowType(W_DIALOG); //--- Create the form if(!m_window[2].CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- if(!CreateCalendar(m_calendar1,m_window[2],10,25,D'01.01.2019',1)) return(false); if(!CreateCalendar(m_calendar2,m_window[2],201,25,m_calendar2.Today(),1)) return(false); //--- if(!CreateTimeEdit(m_time_edit1,m_window[2],10,200,"Time",1)) return(false); if(!CreateTimeEdit(m_time_edit2,m_window[2],200,200,"Time",1)) return(false); //--- return(true); }
现在,我们将注意力转向搜索、研究和评估形态的方法。 为达此目标,我们需要跟踪算法动作的整个序列。 首先,查看此算法开始的 MerrillPatterns.mq5 文件。
//--- Include the application class #include "Program.mqh" CProgram program; //+------------------------------------------------------------------+ //| EA inputs | //+------------------------------------------------------------------+ input ENUM_APPLIED_PRICE Inp_Price1 = PRICE_CLOSE; // Applied price input int Inp_ATR_Peroid = 5; // ATR Period input int Inp_CCI_Peroid = 5; // CCI Period input int Inp_DeM_Peroid = 5; // DeMarker Period input int Inp_ForcePeriod = 13; // ForceIndex Period input ENUM_MA_METHOD Inp_ForceMAMethod = MODE_SMA; // ForceIndex MA method input ENUM_APPLIED_PRICE Inp_ForceAppliedPrice = PRICE_CLOSE; // ForceIndex Applied price input ENUM_APPLIED_VOLUME Inp_ForceAppliedVolume = VOLUME_TICK; // ForceIndex Volumes input int Inp_WPR_Period = 5; // WPR Period input int Inp_RSI_Period = 5; // RSI Period //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(void) { //--- program.OnInitEvent(); //--- Set the trading panel if(!program.CreateGUI()) { ::Print(__FUNCTION__," > Failed to create GUI!"); return(INIT_FAILED); } //--- program.InitializePrice(Inp_Price1); program.InitializeATR(Inp_ATR_Peroid); program.InitializeCCI(Inp_CCI_Peroid); program.InitializeDeM(Inp_DeM_Peroid); program.InitializeForce(Inp_ForcePeriod,Inp_ForceMAMethod,Inp_ForceAppliedPrice,Inp_ForceAppliedVolume); program.InitializeWPR(Inp_WPR_Period); program.InitializeRSI(Inp_RSI_Period); return(INIT_SUCCEEDED); }
除了指标输入之外,在 OnInit() 部分中进行搜索图形外壳,然后在“属性”窗口中初始化数据集。所有方法都会将外部设置传递给内部变量。
//--- void InitializePrice(ENUM_APPLIED_PRICE price) { m_applied_price=price; } void InitializeATR(int period) { m_atr_period=period; } void InitializeCCI(int period) { m_cci_period=period; } void InitializeDeM(int period) { m_dem_period=period; } void InitializeWPR(int period) { m_wpr_period=period; } void InitializeRSI(int period) { m_rsi_period=period; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::InitializeForce(int period,ENUM_MA_METHOD ma_method,ENUM_APPLIED_PRICE price,ENUM_APPLIED_VOLUME volume) { m_force_period=period; m_force_ma_method=ma_method; m_force_applied_price=price; m_force_applied_volume=volume; } //+-----------------------------------------------------------------
之后,该应用程序准备就绪,而其余设置会被传递到所创建的图形界面。 我已经提到过,所执行计算会按从品种表中选择的货币品种来启动 (图例 4 位置 6)。 设置“趋势阈值”后也执行此操作 (图例 4 位置 3)。 这两个事件都会启动 ChangeSymbol1() 方法,以便开始收集检测到的数据,并准备进行分析。
//+------------------------------------------------------------------+ //| Select a symbol in the Analysis tab | //+------------------------------------------------------------------+ bool CProgram::ChangeSymbol1(const long id) { //--- Check the element ID if(id!=m_symb_table1.Id()) return(false); //--- Exit if the string is not highlighted if(m_symb_table1.SelectedItem()==WRONG_VALUE) { //--- Show full description of a symbol in the status bar m_status_bar.SetValue(0,"Symbol for analysis not selected"); m_status_bar.GetItemPointer(0).Update(true); return(false); } //--- Get a selected symbol string symbol=m_symb_table1.GetValue(0,m_symb_table1.SelectedItem()); //--- Show the full symbol description in the status bar string val=(m_lang_index==0)?"Выбранный символ: ":"Selected symbol: "; m_status_bar.SetValue(0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION)); m_status_bar.GetItemPointer(0).Update(true); //--- GetResult(symbol); return(true); }
它的工作实质是从交易品种表中定义一个选定的交易品种,并将其值传递给状态栏和 GetResult() 方法。 我们来更深入地考察该方法,因为所有主要操作i都在其中进行。
//+------------------------------------------------------------------+ //| Handle pattern search results | //+------------------------------------------------------------------+ bool CProgram::GetResult(const string symbol) { //--- Structure for evaluating pattern efficiency RATING_SET m_coef[]; //--- Figure types PATTERN_TYPE pattern_types[]; //--- ArrayResize(pattern_types,33); for(int i=0;i<33;i++) { if(i==16) pattern_types[i]=-1; if(i<16) pattern_types[i]=PATTERN_TYPE(i); if(i>16) pattern_types[i]=PATTERN_TYPE(i-1); } //--- Define selected timeframes GetTimeframes(m_timeframes,m_cur_timeframes); int total=ArraySize(m_cur_timeframes); //--- Check for at least one selected timeframe if(total<1) { if(m_lang_index==0) MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("You have not selected working timeframe!","Error",MB_OK); return(false); } int count=0; m_total_row=0; //--- Remove all strings m_table1.DeleteAllRows(); //--- Get date range datetime start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00"); datetime end=StringToTime(TimeToString(m_calendar2.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit2.GetHours()+":"+(string)m_time_edit2.GetMinutes()+":00"); //--- Check selected dates if(start>end || end>TimeCurrent()) { if(m_lang_index==0) MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("Incorrect date range selected!","Error",MB_OK); return(false); } //--- for(int k=0;k<33;k++) { if(k==16) continue; //--- Get selected patterns for analysis if(m_patterns[k].IsPressed()) { ArrayResize(m_m_total,total); ArrayResize(m_coef,total); ZeroMemory(m_m_total); ZeroMemory(m_coef); count++; //--- Calculate by timeframes for(int j=0;j<total;j++) { double arr[]; //--- Get data for analysis int copied=GetData(m_buttons_group1.SelectedButtonIndex(),symbol,m_cur_timeframes[j],start,end,arr); //--- if(copied<9) MessageBox("Insufficient data for analysis","Error",MB_OK); for(int i=0;i<copied;i++) { if(i>copied-9) continue; //--- Pattern search condition double A=arr[i]; double B=arr[i+1]; double C=arr[i+2]; double D=arr[i+3]; double E=arr[i+4]; if(GetPatternType(A,B,C,D,E)==pattern_types[k]) { m_m_total[j]++; GetCategory(symbol,i+5,m_coef[j],m_cur_timeframes[j],m_threshold_value1); } } //--- Add the result to the table AddRow(m_table1,m_patterns[k].LabelText(),m_coef[j],m_m_total[j],m_cur_timeframes[j]); } } } //--- if(count>0) { //--- m_table1.DeleteRow(m_total_row); //--- Update the table m_table1.Update(true); m_table1.GetScrollVPointer().Update(true); } else { if(m_lang_index==0) MessageBox("Вы не выбрали паттерн!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("You have not chosen a pattern!","Error",MB_OK); } return(true); }
首先,我需要在方法的开头解释输入的变量类型。第一个是 RATING_SET 结构。
struct RATING_SET { int a_uptrend; int b_uptrend; int c_uptrend; int a_dntrend; int b_dntrend; int c_dntrend; };
它包含 6 个 int 类型变量,这是必需要添加的数据,用来衡量确定形态后,价格移向指定方向的概率,以及价格会多快触及。 例如,假设我们有一个上行趋势,并且将趋势阈值按 5 位小数设置为 100 点,而价格在单个蜡烛中覆盖了该值。 在此情况下,a_uptrend 变量将接到数值 1。 如果价格在 2 根蜡烛内达到 100 点,则将该值传递给 b_uptrend 变量。 我们将在自己的方法中使用 m_coef[] 结构数组。
第二个变量类型是 PATTERN_TYPE。 这是一个收集所有美林形态类型的枚举。
//+------------------------------------------------------------------+ //| Figure type | //+------------------------------------------------------------------+ enum PATTERN_TYPE { M1,M2,M3,M4,M5,M6,M7,M8, M9,M10,M11,M12,M13,M14,M15,M16, W1,W2,W3,W4,W5,W6,W7,W8, W9,W10,W11,W12,W13,W14,W15,W16 };
方法中用到了 pattern_types[] 枚举数组。 接着进行检查 — 应用程序中选择哪个时间帧来操作。 此数据由 GetTimeframes() 方法处理。
//+------------------------------------------------------------------+ //| Get the array of selected timeframes | //+------------------------------------------------------------------+ void CProgram::GetTimeframes(CButton &buttons[],ENUM_TIMEFRAMES &timeframe[]) { string tf[22]= { "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30", "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN" }; int j=0; ArrayResize(timeframe,22); for(int i=0;i<22;i++) { if(buttons[i].IsPressed()) { timeframe[j]=StringToTimeframe(tf[i]); j++; } } ArrayResize(timeframe,j); }
该方法将其写入预先设置的 m_cur_timeframes[] 时间帧数组。 接着,获取操作时间范围。
在第一次循环中,我们开始检查是否按下了负责形态类型选定的按钮,并定义一组被研究形态。 在下一次循环中,于此前所选时间帧内研究每种形态。 在此阶段,出现了一个问题,应为形态和时间帧预先设置将哪些数据。 GetData() 方法为此负责,因为它定义了您在 EA 属性窗口中设置的设定,以及应用程序设置选项卡中的已用到指标(图例 5 位置 1)。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CProgram::GetData(int index,string symb,ENUM_TIMEFRAMES tf,datetime start,datetime end,double &arr[]) { //--- int Handle=INVALID_HANDLE,copied; //--- Close price if(index==0) { MqlRates rt[]; ZeroMemory(rt); copied=CopyRates(symb,tf,start,end,rt); ArrayResize(arr,copied); for(int i=0;i<copied;i++) { arr[i]=rt[i].close; if(m_applied_price==PRICE_OPEN) arr[i]=rt[i].open; else if(m_applied_price==PRICE_CLOSE) arr[i]=rt[i].close; else if(m_applied_price==PRICE_HIGH) arr[i]=rt[i].high; else if(m_applied_price==PRICE_LOW) arr[i]=rt[i].low; } return(copied); } //--- ATR if(index==1) Handle=iATR(symb,tf,m_atr_period,m_applied_price); //--- CCI if(index==2) Handle=iCCI(symb,tf,m_cci_period,m_applied_price); //--- DeMarker if(index==3) Handle=iDeMarker(symb,tf,m_dem_period); //--- Force Index if(index==4) Handle=iForce(symb,tf,m_force_period,m_force_ma_method,m_force_applied_volume); //--- WPR if(index==5) Handle=iWPR(symb,tf,m_wpr_period); //--- RSI if(index==6) Handle=iRSI(symb,tf,m_rsi_period,m_applied_price); //--- if(Handle==INVALID_HANDLE) { Print("Failed to get indicator handle"); return(-1); } copied=CopyBuffer(Handle,0,start,end,arr); return(copied); }
接收用于分析的数据之后,该算法将继续调用 GetPatternType() 方法,该方法用于在选定的时间帧内搜索所有先前设置的形态。
//+------------------------------------------------------------------+ //| Define the patterns | //+------------------------------------------------------------------+ PATTERN_TYPE CProgram::GetPatternType(double A,double B,double C,double D,double E) { //--- M1 if(B>A && A>D && D>C && C>E) return(M1); //--- M2 if(B>A && A>D && D>E && E>C) return(M2); //--- M3 if(B>D && D>A && A>C && C>E) return(M3); //--- M4 if(B>D && D>A && A>E && E>C) return(M4); //--- M5 if(D>B && B>A && A>C && C>E) return(M5); //--- M6 if(D>B && B>A && A>E && E>C) return(M6); //--- M7 if(B>D && D>C && C>A && A>E) return(M7); //--- M8 if(B>D && D>E && E>A && A>C) return(M8); //--- M9 if(D>B && B>C && C>A && A>E) return(M9); //--- M10 if(D>B && B>E && E>A && A>C) return(M10); //--- M11 if(D>E && E>B && B>A && A>C) return(M11); //--- M12 if(B>D && D>C && C>E && E>A) return(M12); //--- M13 if(B>D && D>E && E>C && C>A) return(M13); //--- M14 if(D>B && B>C && C>E && E>A) return(M14); //--- M15 if(D>B && B>E && E>C && C>A) return(M15); //--- M16 if(D>E && E>B && B>C && C>A) return(M16); //--- W1 if(A>C && C>B && B>E && E>D) return(W1); //--- W2 if(A>C && C>E && E>B && B>D) return(W2); //--- W3 if(A>E && E>C && C>B && B>D) return(W3); //--- W4 if(A>C && C>E && E>D && D>B) return(W4); //--- W5 if(A>E && E>C && C>D && D>B) return(W5); //--- W6 if(C>A && A>B && B>E && E>D) return(W6); //--- W7 if(C>A && A>E && E>B && B>D) return(W7); //--- W8 if(E>A && A>C && C>B && B>D) return(W8); //--- W9 if(C>A && A>E && E>D && D>B) return(W9); //--- W10 if(E>A && A>C && C>D && D>B) return(W10); //--- W11 if(C>E && E>A && A>B && B>D) return(W11); //--- W12 if(E>C && C>A && A>B && B>D) return(W12); //--- W13 if(C>E && E>A && A>D && D>B) return(W13); //--- W14 if(E>C && C>A && A>D && D>B) return(W14); //--- W15 if(C>E && E>D && D>A && A>B) return(W15); //--- W16 if(E>C && C>D && D>A && A>B) return(W16); return(-1); }
在检测到形态时,利用 GetCategory() 方法对其进行评估。 在此使用先前定义的 RATING_SET 类型结构数组。
//+------------------------------------------------------------------+ //| Define the profit categories | //+------------------------------------------------------------------+ bool CProgram::GetCategory(const string symbol,const int shift,RATING_SET &rate,ENUM_TIMEFRAMES timeframe,int threshold) { MqlRates rt[]; datetime start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00"); start+=PeriodSeconds(timeframe)*shift; int copied=CopyRates(symbol,timeframe,start,4,rt); //--- Get the data of previous candles if(copied<4) return(false); double high1,high2,high3,low1,low2,low3,close0,point; close0=rt[0].close; high1=rt[1].high; high2=rt[2].high; high3=rt[3].high; low1=rt[1].low; low2=rt[2].low; low3=rt[3].low; if(!SymbolInfoDouble(symbol,SYMBOL_POINT,point)) return(false); //--- Check for Uptrend if((int)((high1-close0)/point)>=threshold) { rate.a_uptrend++; } else if((int)((high2-close0)/point)>=threshold) { rate.b_uptrend++; } else if((int)((high3-close0)/point)>=threshold) { rate.c_uptrend++; } //--- Check for Downtrend if((int)((close0-low1)/point)>=threshold) { rate.a_dntrend++; } else if((int)((close0-low2)/point)>=threshold) { rate.b_dntrend++; } else if((int)((close0-low3)/point)>=threshold) { rate.c_dntrend++; } return(true); }
处理完毕的评估数据将传递给 AddRow() 方法,该方法将计算概率值和绩效,并将其添加到结果表格中。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::AddRow(CTable &table,string pattern_name,RATING_SET &rate,int found,ENUM_TIMEFRAMES timeframe) { int row=m_total_row; double p1,p2,k1,k2; int sum1=0,sum2=0; sum1=rate.a_uptrend+rate.b_uptrend+rate.c_uptrend; sum2=rate.a_dntrend+rate.b_dntrend+rate.c_dntrend; //--- p1=(found>0)?(double)sum1/found*100:0; p2=(found>0)?(double)sum2/found*100:0; k1=(found>0)?(m_k1*rate.a_uptrend+m_k2*rate.b_uptrend+m_k3*rate.c_uptrend)/found:0; k2=(found>0)?(m_k1*rate.a_dntrend+m_k2*rate.b_dntrend+m_k3*rate.c_dntrend)/found:0; //--- table.AddRow(row); table.SetValue(0,row,pattern_name); table.SetValue(1,row,(string)found); table.SetValue(2,row,TimeframeToString(timeframe)); table.SetValue(3,row,DoubleToString(p1,2),2); table.SetValue(4,row,DoubleToString(p2,2),2); table.SetValue(5,row,DoubleToString(k1,2),2); table.SetValue(6,row,DoubleToString(k2,2),2); ZeroMemory(rate); m_total_row++; }
为了消除调用该应用程序会导致的有关潜在问题,以下视频显示了具有不同设置的计算示例。
测试美林形态的建议:
下面随附的文档包含所有论述的文件,这些文件应安置在相应的文件夹中。 为了正确操作,请将 MQL5 文件夹放置在终端的根目录中。 若要打开 MQL5 文件夹所在的终端根目录,请在 MetaTrader 5 终端中按下 Ctrl+Shift+D 组合键,或使用关联菜单,如下面的图例 7 中所示。
图例 7、 在 MetaTrader 5 终端根目录中打开 MQL5 文件夹
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...