外汇EA编写教程:使用MQL5和MQL4实现的选择和导航实用程序:添加"家庭作业"选项卡并保存图形对象

总结

在前一篇文章中,我们开发了一个实用的程序来挑选具有适当入口点的品种。我们还学习了如何根据各种参数对品种进行分类,以及如何使用专门设计的按钮浏览品种。然而,在品种选择上,情况并不乐观。目前,我们必须在一张纸上写下所选金融产品的代码,这对地球上的森林物种有着非常负面的影响。

在本文中,我们将保护树木免遭砍伐,并将学习如何自动保存在图表上创建的图形对象,命令您将不必继续创建它们。

应用条件编译函数

首先,我们简化了实用程序到MQL4的迁移。在前一篇文章中,我们用另一个代码模块替换了一个代码模块,以使程序在MQL4模式下工作。现在我们面临的任务更加艰巨。我们可以用一种语言(例如MQL5)进行开发,不断发现哪些代码块在MQL4中不起作用,然后用正确的代码替换它们,或者我们可以同时开发两套程序版本:MQL5和MQL4。

两种选择都不是最优的。我们应该不断地替换在MQL4(每个版本)中不工作的模块,或者记住我们在实用程序中修改的代码部分,以便它们可以用另一种语言实现。

因此,我们将采取不同的方法。MQL5和MQL4都支持条件编译指令,允许执行一个代码模块或有条件地执行另一个代码模块。在这些指令中,根据当前的MQL语言版本执行一个构造。其主要语法如下:

      #ifdef __MQL5__ 
         // 代码块仅在 MQL5 中执行
      #else 
         // 代码块仅在 MQL4 中执行
      #endif 

我们使用它来使checksymbwithpos函数在MQL5和MQL4上正常工作,而不需要不断地替换它:

bool checkSYMBwithPOS(string name){
   // 隐藏已有持仓和订单的品种
   bool isskip=false;
   if( noSYMBwithPOS ){
      #ifdef __MQL5__ 
         // 查看所有持仓清单
         int cntMyPos=PositionsTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            // 如果当前品种有持仓,则跳过
            if(PositionGetSymbol(ti) == name ){
               isskip=true;
               break;
            }
         }
         if(!isskip){
            int cntMyPosO=OrdersTotal();
            if(cntMyPosO>0){
               for(int ti=cntMyPosO-1; ti>=0; ti--){
                  ulong orderTicket=OrderGetTicket(ti);
                  if( OrderGetString(ORDER_SYMBOL) == name ){
                     isskip=true;
                     break;
                  }
               }
            }
         }
      #else 
         // 查看所有持仓清单
         int cntMyPos=OrdersTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if(OrderSymbol() == name ){
               isskip=true;
               break;
            }
         }
      #endif 
   }
   return isskip;
}

在本文中,我们将使用此结构立即迁移在MQL4中不工作的代码模块。

作业卡选择

为了拯救地球上的森林,我们将创建三个选项卡,只显示以前选择的物种。我们把这些皮卡命名为长皮卡、短皮卡和远程皮卡。当然,你不必单独在那里添加上游、下游或水平品种。您可以自行决定使用它们。

因此,图表上还有另一行启动我们的实用程序,包括四个按钮:所有按钮和上面描述的三个按钮。

默认情况下,所有按钮都已按下,这意味着与我们的过滤器匹配的所有品种的列表将显示如下:

添加选卡以便选择 homework

因此,目标已经确定。现在我们必须实现它。要做到这一点,我们必须重写实用程序的一小部分。

用于存储选项卡内容的数组。首先,我们添加变量来存储选项卡的内容。以前,我们只有一个选项卡的内容存储在arrpanel1变量中。我们将向其他卡选择添加类似变量:

// 相应选项卡中所显示品种的数组:
CArrayString arrPanel1;
CArrayString arrPanel2;
CArrayString arrPanel3;
CArrayString arrPanel4;

此外,我们还将创建另一个数组,以便能够访问循环中的选项卡。此数组存储指向所有四个以前创建的数组的指针:

// 用于合并所有选项卡的数组
CArrayString *arrPanels[4];

我们在oninit()函数中初始化数组:

   arrPanels[0]=&arrPanel1;
   arrPanels[1]=&arrPanel2;
   arrPanels[2]=&arrPanel3;
   arrPanels[3]=&arrPanel4;

卡片的标题。因为卡选择是在循环中执行的,所以最好将卡名存储在循环中并从循环中访问它们。因此,让我们创建一个数组来存储提取卡的名称:

// 选卡名称数组
string panelNames[4]={"All", "LONG", "SHORT", "Range"};

辅助变量。另一个更改涉及panel1val变量。我们已将其名称改为panelval。这是一个纯粹的装饰性修正案,但应该注意。

cur_panel参数还添加了包含当前激活选项卡的索引。变量类型为UChar。这意味着它可能需要一个介于0和255之间的值,这就足够了,因为我们只有四个选项卡。

默认情况下,第一个选项卡(数组中的索引0)被激活。因此,我们向OnIIT()函数添加一个字符串,将变量赋值为0。最后,OnInit()函数的最终形式是:

int OnInit()
  {
   // 当前激活选项卡的索引
   cur_panel=0;
   // 初始化选项卡数组
   arrPanels[0]=&arrPanel1;
   arrPanels[1]=&arrPanel2;
   arrPanels[2]=&arrPanel3;
   arrPanels[3]=&arrPanel4;
   
   start_symbols();

//--- 创建定时器
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }

其他修改。没有必要列出所有实现的变更,因为其中许多变更是无关的。现在让我们转到主要的变化。对于较小的更改,可以通过将新实用程序的源代码与上一篇文章附带的源代码进行比较来测试它们。

主要变化是修改新的卡片选择显示。我们决定在一个循环中操作选项卡。让我们看看如何做到这一点。

因为我们有一行选项卡,所以需要以某种方式显示它。为了实现它,我们创建了一个单独的函数。代码如下:

void show_panel_buttons(){
   int btn_left=0;
   // 定义显示选项卡的最大可能 x 轴坐标。
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   string tmpName="";
   
   for( int i=0; i<ArraySize(panelNames); i++ ){
      // 如果新按钮的起始坐标超过最大可能值,
      // 移到新行。
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      // 如果“homework”选项卡包含品种,添加其编号
      // 至选卡名称
      tmpName=panelNames[i];
      if(i>0 && arrPanels[i].Total()>0 ){
         tmpName+=" ("+(string) arrPanels[i].Total()+")";
      }
      
      // 显示选卡按钮
      ObjectCreate(0, exprefix+"panels"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetString(0,exprefix+"panels"+(string) i,OBJPROP_TEXT,tmpName);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_SELECTABLE,false);
      // 如果按钮选项卡当前处于激活状态,
      // 令其处于按下状态
      if( cur_panel == i ){
         ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_STATE, true);
      }
      
      btn_left+=BTN_WIDTH;
   }

}

在调用show_symbols函数显示variety按钮之前,我们在start_symbols函数中调用它。

显示符号本身的功能已经改变了,尽管只是轻微的改变。现在我们只显示当前激活I卡上的品种按钮:

   // 在图表中为当前激活选卡的数组中每个品种
   // 显示一个按钮
   // 我们将在按钮上书写一个品种名称
   for( int i=0; i<arrPanels[cur_panel].Total(); i++ ){
      
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      
      ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,arrPanels[cur_panel].At(i));    
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);

      if( !noSYMBwithPOS || cur_panel>0 ){
         if( checkSYMBwithPOS(arrPanels[cur_panel].At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
      }
      
      btn_left+=BTN_WIDTH;
   }

用于向卡片选择添加品种的按钮。现在,我们需要向所选选项卡添加品种。我们将在打开的图表页上使用新按钮“添加长”、“添加短”和“添加范围”来执行此操作。

如果您还记得,在上一篇文章中,我们实现了单击所需变体按钮的能力。单击某个品种后,将打开一个图表,在左下角有一个按钮区域,用于导航整个品种列表。我们在这个区域添加按钮。根据项目是否在适当的选项卡上,按钮会将其添加或从选项卡中删除。

使用创建btns功能显示按钮区域。添加一个向其添加新按钮的循环:

   for( int i=ArraySize(panelNames)-1; i>0; i-- ){
      isyes=false;
      if(arrPanels[i].Total()){
         for(int j=0; j<arrPanels[i].Total(); j++){
            if( arrPanels[i].At(j)==name ){
               isyes=true;
               break;
            }
         }
      }
      if( isyes ){
         ObjectCreate(CID, exprefix+"_p_btn_panelfrom"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_XDISTANCE,110); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_YDISTANCE,tmpHeight);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_CORNER,CORNER_LEFT_LOWER); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_SELECTABLE,false); 
         ObjectSetString(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_TEXT,"Del "+panelNames[i]);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_BGCOLOR,clrPink); 
      }else{
         ObjectCreate(CID, exprefix+"_p_btn_panelto"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_XDISTANCE,110); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_YDISTANCE,tmpHeight);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_CORNER,CORNER_LEFT_LOWER); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_SELECTABLE,false); 
         ObjectSetString(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_TEXT,"Add "+panelNames[i]);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_BGCOLOR,clrHoneydew); 
      }
      tmpHeight+=25;
   }

导航按钮区

要启用按下新按钮,请将按钮状态的检查代码添加到OnTimer()功能中。一切都和我们在上一篇文章中所做的一样:

            for( uchar i=1; i<ArraySize(panelNames); i++ ){
               // 如果从选卡中删除的按钮被按下,首先,删除品种,并再次打开品种图表
               if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_STATE)==true ){
                  delToPanel(i, ChartSymbol(curChartID[tmpCIDcnt-1]));
                  curchart();
                  return;
               }
               // 如果添加到选项卡的按钮被按下,则首先添加品种,然后打开下一个品种图表
               if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_panelto"+(string) i,OBJPROP_STATE)==true ){
                  addToPanel(i, ChartSymbol(curChartID[tmpCIDcnt-1]));
                  nextchart();
                  return;
               }
            }

Deltopanel函数首先从所选选项卡中删除品种,然后更新启动实用程序的图表上的所有品种按钮,或者只更新标题按钮:

void delToPanel(uchar num, string name){
   // 沿整个数组移动并删除第一个元素
   // 其名称与我们的品种相似
   for(int i=0; i<arrPanels[num].Total(); i++){
      if( arrPanels[num].At(i)==name ){
         arrPanels[num].Delete(i);
         break;
      }
   }
   // 如果选卡当前已打开,
   if(num==cur_panel){
      initial_btn_line();
      // 从图表中删除以前创建的品种按钮:
      ObjectsDeleteAll(0, exprefix);
      // 显示更新后的品种列表:
      show_panel_buttons();
      show_symbols();
   }else{
      // 如果打开任何其他选项卡,只需更新标题按钮
      upd_panel_title();
   }
   
   
}

addtopanel函数与我们刚刚研究的函数相反。它将为选择卡添加一个品种。此外,它还检查其他选项卡上是否存在该变体。如果品种已经存在,从中删除:

void addToPanel(uchar num, string name){
   // 在选卡中添加一个品种
   arrPanels[num].Add(name);
   // 从其他选卡中删除品种
   // 如果存在的话
   for( int j=1; j<ArraySize(arrPanels); j++ ){
      if(j==num) continue;
      for(int i=0; i<arrPanels[j].Total(); i++){
         if( arrPanels[j].At(i)==name ){
            if( panelval==i && i>0 ){
               panelval--;
            }
            arrPanels[j].Delete(i);
            break;
         }
      }
   }
   if(num==cur_panel){
      initial_btn_line();
      // 从图表中删除以前创建的品种按钮:
      ObjectsDeleteAll(0, exprefix);
      // 显示品种列表:
      show_panel_buttons();
      show_symbols();
   }else{
      upd_panel_title();
   }
}

在应用程序启动之间保存选项卡内容。如果我们意外关闭EA会发生什么?那我们的一切努力都将白费。我们要重新开始添加所有内容吗?让我们确保添加到“家庭作业”选项卡的项目列表可以保存到文件中,并在稍后重新打开时还原。

出于某种原因,我们使用carraystring类型的对象来存储所选品种的列表。这种对象的许多优点之一是标准方法,它可以轻松地将数组的所有内容发送到文件中,并从文件中恢复数组。在关闭实用程序之前,我们使用它们将数组的内容保存到文件中。换句话说,我们应该将对新的savepanels函数的调用添加到标准的ondeinit()函数中:

void savePanels(){
   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Save(fh);
         FileClose(fh);
      }
   }
}

数组的内容将在标准OnInit()函数中还原:

   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Load(fh);
         FileClose(fh); 
      }
   }

添加标题以标识当前参数

如果你在不同的市场进行交易,在从一个市场切换到另一个市场时,你必须不断调整公用事业。毕竟,如果你目前在美国股市交易,并在股市开盘后的前半小时等待突破,你就不需要在很久以前开盘的欧洲或俄罗斯市场上持有股票。同样,如果你在俄罗斯市场交易,你不需要美国股票。

为了不分散注意力,集中精力在必要的品种上,可以为不同国家的市场和外汇市场创建单独的参数集,并在必要时加载每个设置文件。这很简单。只需要几秒钟。一、二、一、二、二、二、三、二、三、二、三、三、四、四、四、四、四、六、四、四、六、四、四、四、

为了查看所应用的参数集,我们将添加CMT输入,在其中我们注意到对当前被操纵的市场的解释:

input string         cmt=""; // 用于 (eng) 的参数

我们在选项卡的按钮行上显示此注释:

显示当前设置的标题

为此,在显示所有按钮后,添加以下代码模块以显示“面板”按钮hanshu1:

   // 如果指定,则显示注释:
   if(StringLen(cmt)>0){
      string tmpCMT=cmt;
      ObjectCreate(0, exprefix+"title", OBJ_EDIT, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XDISTANCE,btn_left+11); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XSIZE,133); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_COLOR,clrGold); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BGCOLOR,clrNONE); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BORDER_COLOR,clrBlack);
      ObjectSetString(0,exprefix+"title",OBJPROP_TEXT,tmpCMT);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_SELECTABLE,false);
   }

除了识别当前参数集之外,CMT输入还可以帮助我们在“家庭作业”选项卡中分离品种列表。毕竟,如果我们在作业卡上加上各种各样的东西来操纵美国市场,我们操纵俄罗斯股票时就不需要它了。设置文件有不同的参数集,还应为作业卡选择提供单独的列表。

为了实现它们,我们必须稍微修改将数组保存到文件中的代码,并从中恢复它们。我们考虑将修改后的函数保存到文件中作为示例:

void savePanels(){
   string tmpCmt=cmt;
   StringReplace(tmpCmt, " ", "_");
   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+tmpCmt+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Save(fh);
         FileClose(fh);
      }
   }
}

保存图形对象

我们需要解决的另一个障碍是操纵图表,以自动保存和恢复我们在图表上创建的图形对象。如果我们在图表上设置了一个级别,我们希望在关闭图表窗口并重新打开它时再次看到它。每次我们打开多样性图表,我们肯定不愿意重新定位几十个级别。

到目前为止,我们编写的代码在mql5和mql4中同样有效。但是,对于保存和恢复图形对象的函数,情况并非如此。在mql4中,图形对象类型及其单个属性由数字类型的常量描述,而mql5则为此应用枚举。这就是为什么很难将它们保存到文件中并还原它们。至少在一般意义上我不能处理这个任务。保存mql4图形对象的功能对我们更有帮助。理论上,它可以保存任何图形对象(我没有对所有对象进行测试,所以可能会有例外)。MQL5只允许它操作水平线、标签和文本字段。如果需要保存其他图形对象,则必须自己实现它们。

因为MQL4更容易保存图形对象,所以让我们从该语言中的函数开始。将图形对象保存到文件的函数:

   void savechart(ulong id){
      // 保存图形对象,仅在 
      // "Save created graphical objects" 参数 = true
      if(saveGraphics){
         // 获取品种名称
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         // 清除图形对象数组
         saveG.Resize(0);
         
         // 将所有用户创建的图形对象及其属性添加到数组中
         int obj_total=ObjectsTotal((long) id); 
         string name;
         string tmpObjLine="";
         for(int i=0;i<obj_total;i++){
            name = ObjectName((long) id, i);
            if( StringFind(name, exprefix)<0 && StringFind(name, "fix")<0 && StringFind(name, "take")<0 && StringFind(name, "stop loss")<0 && StringFind(name, "sell")<0 && StringFind(name, "buy")<0 ){
               tmpObjLine=name;
               
               StringAdd(tmpObjLine, "|int~OBJPROP_TYPE~"+(string)(int) OBJPROP_TYPE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TYPE));
               StringAdd(tmpObjLine, "|int~OBJPROP_COLOR~"+(string)(int) OBJPROP_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_COLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_STYLE~"+(string)(int) OBJPROP_STYLE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STYLE));
               StringAdd(tmpObjLine, "|int~OBJPROP_WIDTH~"+(string)(int) OBJPROP_WIDTH+"~"+(string) ObjectGetInteger(id, name, OBJPROP_WIDTH));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIME~"+(string)(int) OBJPROP_TIME+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIME));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIMEFRAMES~"+(string)(int) OBJPROP_TIMEFRAMES+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
               StringAdd(tmpObjLine, "|int~OBJPROP_ANCHOR~"+(string)(int) OBJPROP_ANCHOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_XDISTANCE~"+(string)(int) OBJPROP_XDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YDISTANCE~"+(string)(int) OBJPROP_YDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_STATE~"+(string)(int) OBJPROP_STATE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STATE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XSIZE~"+(string)(int) OBJPROP_XSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YSIZE~"+(string)(int) OBJPROP_YSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XOFFSET~"+(string)(int) OBJPROP_XOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_YOFFSET~"+(string)(int) OBJPROP_YOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_BGCOLOR~"+(string)(int) OBJPROP_BGCOLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_BORDER_COLOR~"+(string)(int) OBJPROP_BORDER_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));
               StringAdd(tmpObjLine, "|double~OBJPROP_PRICE~"+(string)(int) OBJPROP_PRICE+"~"+(string) ObjectGetDouble(id, name, OBJPROP_PRICE));
               StringAdd(tmpObjLine, "|string~OBJPROP_TEXT~"+(string)(int) OBJPROP_TEXT+"~"+(string) ObjectGetString(id, name, OBJPROP_TEXT));
               
               saveG.Add(tmpObjLine);
            }
         }
         // 将数组内容保存到文件中
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Save(fh);
            FileClose(fh);
         }
      }
   }

如您所见,我们不保留对象的所有属性,只保留对象的所有属性,只保留对象的颜色、对象的样式、对象的宽度、对象的时间、对象的时间、对象的时间、对象的时间、对象的锚、对象的锚、对象的距离、对象的距离、对象的距离、对象的距离、对象的距离、对象的状态teteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteteetetetetetetetetetetet大小、对象的系统化、对象的系统化、对象的系统化、对象的系统化、对象的系统化、对象的测试、对象的测试测试、对象的测试、对象的系统化、对象的测试。jprop_jjprop_xfsrop_jjjobo公司BBOBOBOBOBOBOBOBOBOBOBROPPRR,ObjPrimBordRoad颜色,ObjPrimalPalm和ObjPrimType。如果在使用此实用工具时未能正确保存任何应用的图形对象,则意味着所有应用的属性都不被保存。在这种情况下,只有缺失的属性被添加到这个函数来支持这样的图形对象。

现在让我们看看从文件加载图形对象并在图表上显示它们的函数:

   void loadchart(ulong id){
      // 显示图形对象,仅在 
      // "Save created graphical objects" 输入 = true
      if(saveGraphics){
         // 获取品种名称
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         string tmpObjLine[];
         string tmpObjName="";
         string sep1="|";
         string sep2="~";
         
         // 清除图形对象数组
         saveG.Resize(0);
         // 将图形对象列表从文件加载到数组
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Load(fh);
            FileClose(fh); 
         }
         // 在图表上连续显示图形对象
         for( int i=0; i<saveG.Total(); i++ ){
            StringSplit(saveG.At(i), StringGetCharacter(sep1,0), tmpObjLine);
            for( int j=0; j<ArraySize(tmpObjLine); j++ ){
               if(j>0){
                  string tmpObjSubLine[];
                  StringSplit(tmpObjLine[j], StringGetCharacter(sep2,0), tmpObjSubLine);
                  if(ArraySize(tmpObjSubLine)==4){
                     if(tmpObjSubLine[0]=="int"){
                        // 对象类型总是在行中排在第一位
                        // 这样我们初始创建一个对象之后就能形成其属性
                        if(tmpObjSubLine[1]=="OBJPROP_TYPE"){
                           ObjectCreate(id, tmpObjName, (int) tmpObjSubLine[3], 0, 0, 0);
                        }else if( (int) tmpObjSubLine[3] >= 0 ){
                           ObjectSetInteger(id, tmpObjName, (int) tmpObjSubLine[2], (int) tmpObjSubLine[3]);
                        }
                     }else if(tmpObjSubLine[0]=="double"){
                        if( (double) tmpObjSubLine[3] >= 0 ){
                           ObjectSetDouble(id, tmpObjName, (int) tmpObjSubLine[2], (double) tmpObjSubLine[3]);
                        }
                     }else if(tmpObjSubLine[0]=="string"){
                        if( StringLen(tmpObjSubLine[3]) > 0 ){
                           ObjectSetString(id, tmpObjName, (int) tmpObjSubLine[2], tmpObjSubLine[3]);
                        }
                     }
                  }
               }else{
                  tmpObjName=tmpObjLine[j];
               }
            }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }
         
         
      }
   }

如前所述,MQL5中的这些功能是低效的:

   void savechart(ulong id){
      if(saveGraphics){
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         saveG.Resize(0);
         
         int obj_total=ObjectsTotal((long) id); 
         string name;
         string tmpObjLine="";
         for(int i=0;i<obj_total;i++){
            name = ObjectName((long) id, i);
            if( StringFind(name, exprefix)<0 && StringFind(name, "fix")<0 && StringFind(name, "take")<0 && StringFind(name, "stop loss")<0 && StringFind(name, "sell")<0 && StringFind(name, "buy")<0 ){
               tmpObjLine=name;
               // 我们只能使用 OBJ_HLINE,OBJ_TEXT 和 OBJ_LABEL 对象类型,
               // 所以,我们跳过其他类型的对象
               if( ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_HLINE && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_TEXT && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_LABEL ){
                  continue;
               }
               StringAdd(tmpObjLine, "|int~OBJPROP_TYPE~"+(string)(int) OBJPROP_TYPE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TYPE));
               StringAdd(tmpObjLine, "|int~OBJPROP_COLOR~"+(string)(int) OBJPROP_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_COLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_STYLE~"+(string)(int) OBJPROP_STYLE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STYLE));
               StringAdd(tmpObjLine, "|int~OBJPROP_WIDTH~"+(string)(int) OBJPROP_WIDTH+"~"+(string) ObjectGetInteger(id, name, OBJPROP_WIDTH));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIME~"+(string)(int) OBJPROP_TIME+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIME));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIMEFRAMES~"+(string)(int) OBJPROP_TIMEFRAMES+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
               StringAdd(tmpObjLine, "|int~OBJPROP_ANCHOR~"+(string)(int) OBJPROP_ANCHOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_XDISTANCE~"+(string)(int) OBJPROP_XDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YDISTANCE~"+(string)(int) OBJPROP_YDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_STATE~"+(string)(int) OBJPROP_STATE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STATE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XSIZE~"+(string)(int) OBJPROP_XSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YSIZE~"+(string)(int) OBJPROP_YSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XOFFSET~"+(string)(int) OBJPROP_XOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_YOFFSET~"+(string)(int) OBJPROP_YOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_BGCOLOR~"+(string)(int) OBJPROP_BGCOLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_BORDER_COLOR~"+(string)(int) OBJPROP_BORDER_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));
               StringAdd(tmpObjLine, "|double~OBJPROP_PRICE~"+(string)(int) OBJPROP_PRICE+"~"+(string) ObjectGetDouble(id, name, OBJPROP_PRICE));
               StringAdd(tmpObjLine, "|string~OBJPROP_TEXT~"+(string)(int) OBJPROP_TEXT+"~"+(string) ObjectGetString(id, name, OBJPROP_TEXT));
               
               saveG.Add(tmpObjLine);
            }
         }
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Save(fh);
            FileClose(fh);
         }
      }
   }
   void loadchart(ulong id){
      if(saveGraphics){
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         string tmpObjLine[];
         string tmpObjName="";
         string sep1="|";
         string sep2="~";
         
         saveG.Resize(0);
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Load(fh);
            FileClose(fh); 
         }
         for( int i=0; i<saveG.Total(); i++ ){
            StringSplit(saveG.At(i), StringGetCharacter(sep1,0), tmpObjLine);
            for( int j=0; j<ArraySize(tmpObjLine); j++ ){
               if(j>0){
                  string tmpObjSubLine[];
                  StringSplit(tmpObjLine[j], StringGetCharacter(sep2,0), tmpObjSubLine);
                  if(ArraySize(tmpObjSubLine)==4){
                     if(tmpObjSubLine[0]=="int"){
                        // 根据类型创建对象
                        if(tmpObjSubLine[1]=="OBJPROP_TYPE"){
                           switch((int) tmpObjSubLine[3]){
                              case 1:
                                 ObjectCreate(id, tmpObjName, OBJ_HLINE, 0, 0, 0);
                                 break;
                              case 101:
                                 ObjectCreate(id, tmpObjName, OBJ_TEXT, 0, 0, 0);
                                 break;
                              case 102:
                                 ObjectCreate(id, tmpObjName, OBJ_LABEL, 0, 0, 0);
                                 break;
                           }
                        }else if( (int) tmpObjSubLine[3] >= 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_COLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_COLOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_STYLE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_STYLE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_WIDTH"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_WIDTH, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_TIME"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_TIME, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_TIMEFRAMES"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_TIMEFRAMES, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_ANCHOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_ANCHOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XDISTANCE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XDISTANCE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YDISTANCE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YDISTANCE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_STATE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_STATE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XSIZE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XSIZE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YSIZE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YSIZE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XOFFSET"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XOFFSET, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YOFFSET"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YOFFSET, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_BGCOLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_BGCOLOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_BORDER_COLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_BORDER_COLOR, (int) tmpObjSubLine[3]);
                           }
                        }
                     }else if(tmpObjSubLine[0]=="double"){
                        if( (double) tmpObjSubLine[3] >= 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_PRICE"){
                              ObjectSetDouble(id, tmpObjName, OBJPROP_PRICE, (double) tmpObjSubLine[3]);
                           }
                        }
                     }else if(tmpObjSubLine[0]=="string"){
                        if( StringLen(tmpObjSubLine[3]) > 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_TEXT"){
                              ObjectSetString(id, tmpObjName, OBJPROP_TEXT, tmpObjSubLine[3]);
                           }
                        }
                     }
                  }
               }else{
                  tmpObjName=tmpObjLine[j];
               }
            }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }
         
         
      }
   }

如果仔细观察,我们将注意到必须使用单独的字符串为不同类型的对象创建对象,而在MQL4中,一个字符串就足以容纳所有对象。对象属性也是如此。在MQL4中,我们使用一个属性来创建一个字符串(字符串、实数或整数)。在MQL5中,每个属性都需要一个单独的创建字符串。

在合并语言时,我们使用条件编译使EA能够根据语言应用必要的功能版本:

#ifdef __MQL5__ 
   void savechart(ulong id){
      // MQL5 函数
   }
   void loadchart(ulong id){
      // ...
   }
#else 
   void savechart(ulong id){
      // MQL4 函数
   }
   void loadchart(ulong id){
      // ...
   }
#endif 

应用程序函数现在将调用函数添加到程序的相应部分。

loadchart函数的调用添加在showcharts函数中,该函数根据我们按下的按钮打开图表。

图表保存功能的调用将添加到图表导航按钮的相应下推响应代码模块:下一个图表、上一个图表和Los图表,以及作业卡片选择中添加/删除品种的按钮。

使用finviz.com网站初步选择股票

在前一篇文章中,我们提到,过滤后的品种列表不仅可以从代理提供的品种列表中获得,还可以从输入中获得。首先,这只允许以必要的顺序显示有限的品种集合。第二,自定义品种集合可用于在finviz.com或类似网站上执行初步筛选。

在前一篇文章中,我们形成了一组输入,允许用户按价格、ATR等对品种进行排序。但是,与finviz.com网站过滤器相比,这些功能显得苍白无力。最重要的是,MQL4不能根据实际交易量对品种进行排序,这是许多基于级别的交易策略中非常重要的指标。finviz.com允许您根据股票的平均交易量和当天的交易量对股票进行排名。

添加从输入参数获取品种列表的能力。要使用第三方品种列表,我们将向实用程序添加三个附加输入:

input string         ""; // 仅品种 (分隔符 - ; 或空格)
input string         ""; // 为品种加入前缀
input string         ""; // 为品种加入后缀

如果代理的名称与官方代码不同,我们只需要symbols前缀和symbols后缀参数。一些经纪人补充道。美国股票的美国后缀,欧洲股票的欧盟后缀,以及一些经纪人在所有代码中添加M后缀。经纪人也可以在股票代码的开头添加。

添加从文件导入品种的功能。一提到这一点,我将讨论从进口商品中进口品种时遇到的问题。这个问题涉及到最大字符串长度。使用输入时,我们最多只能输入15-20个代码。因此,输入只能用于有限数量的金融产品。

因此,除了输入外,还可以在符号中放入必要的变种。在文件文件夹中创建的txt文件。

它是用代码实现的。我们将所有选项卡中的品种列表的形成过程分为两个模块。

第一个模块检查文件或输入中是否有变体。如果是,请用它们填充结果数组。此数组添加到OnInit()函数:

         // 如果 "Symbols only (separator - ; or space)" 输入
         // 提供任何数据
         if( StringLen(onlySymbols)>0 ){
            // 将输入行切分为数组元素
            // 元素由 ";" 分隔
            StringSplit(onlySymbols,StringGetCharacter(";",0),result); 
            if( ArraySize(result)>1 ){
            }else{
               // 如果拆分结果在数组中只存在一个数值,
               // 则拆分失败,显然行中的分隔符是空格
               // 所以,现在我们将输入行切分为数组元素
               // 且用空格作为元素分隔符
               StringSplit(onlySymbols,StringGetCharacter(" ",0),result); 
            }
         // 否则,检查 Files 文件夹是否包含 symbols.txt 文件
         }else if( FileIsExist("symbols.txt") ){
            // 如果文件存在,则将其内容放入 'outfile' 临时变量中
            int filehandle=FileOpen("symbols.txt",FILE_READ|FILE_TXT); 
            if(filehandle>=0){
               string outfile=FileReadString(filehandle);
               // 如果 'outfile' 变量包含一个字符串,
               // 尝试将其拆分为数组元素
               // 首先,使用分隔符 ";" 接着是空格
               if(StringLen(outfile)>0){
                  StringSplit(outfile,StringGetCharacter(";",0),result); 
                  if( ArraySize(result)>1 ){
                  }else{
                     StringSplit(outfile,StringGetCharacter(" ",0),result); 
                  }
                  if( ArraySize(result)>1 ){
                     from_txt=true;
                  }
               }
               FileClose(filehandle);
            }
         }

在Prepare_symbols函数中,我们首先检查结果数组是否包含任何数据,如果包含,则使用它们。否则,我们将继续对经纪人提供的所有品种或添加到市场观察小组的品种进行排名:

   // 如果数组拥有两个以上的品种,则使用它们
   // 如有必要,初步添加所需的后缀或前缀
   if( ArraySize(result)>1 ){
      for(int j=0;j<ArraySize(result);j++){
         StringReplace(result[j], " ", "");
         if(StringLen(result[j])<1){
            continue;
         }
         tmpSymbols.Add(onlySymbolsPrefix+result[j]+onlySymbolsSuffix);
      }
   // 否则,使用经纪商提供的所有品种
   }else{
      for( int i=0; i<SymbolsTotal(noSYMBmarketWath); i++ ){
         tmpSymbols.Add(SymbolName(i, noSYMBmarketWath));
      }
   }

finviz.com过滤器用于形成品种列表。最后,让我们看看如何将finviz.com上选择的代码导入到我们的实用程序中。

一切都很简单。排序后,转到筛选器页上的标签页。您将看到一组选定的代码名称云。选择它们,复制并粘贴到符号中。TXT文件或输入。如果有多个具有排序结果的页面,请转到下一页并执行相同的操作。

结束语

我们已经做了很多工作使我们的公用事业更加实用。现在我们可以很容易地使用它,忘记纸笔记本了。我希望这个星球上的森林能欣赏到这一点。=)

本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/5417。

附加文件下载zip finder 4.mq4(125.66 kb)、finder 4.ex4(83.41 kb)、finder.mq5(125.65 kb)finde r.ex5(151.23 kb)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投