外汇EA编写教程:MQL5中的电子表格

简介

通常,电子表格指的是表单处理程序(存储和处理数据的应用程序),如Excel。尽管本文中显示的代码没有那么强大,但它可以用作表处理程序的全功能实现的基类。我希望实现一个类来操作二维数组中不同类型的数据,而不是使用MQL5创建MS Excel。

尽管我实现的类在性能上无法与单一类型数据的二维数组(可以直接访问)相比,但它们的出现是为了便于使用。此外,该类可以被视为C++中实现的一个变体类,可以简化为特殊情况的列表。

对于那些热切的读者和那些想跳过实现算法分析的读者,我将从介绍可用方法的CTAB类开始。

1。分类方法说明

首先,我们考虑类的可用方法,以便更详细地了解它们的用途和使用原则。

1.1。首次调整大小

表布局,列类型说明,类型[]-枚举数据类型数组,用于确定行的大小和单元格类型。

void  FirstResize(const ENUM_DATATYPE &TYPE[]);

实际上,这个类是另一个具有一个参数的构造函数。这有两个原因:第一,它解决了在构造函数中传递参数的问题;第二,它提供了将对象作为参数传递的可能性,然后执行必要的数组分区。此函数允许类作为C++中的变量类使用。

实现的特殊性包括,尽管函数设置了第一个维度和列的数据类型,但不需要将第一个维度的大小指定为参数。此参数取自传递的数组类型的大小。

次要尺寸

将行数更改为“j”。

void  SecondResize(int j);

该函数为第二维度的所有数组设置指定的大小。因此,可以说它增加了表中的行数。

1.3。第一尺寸

此方法返回第一个维度的大小(行的长度)。

int   FirstSize();

第二尺寸

此方法返回第二个维度的大小(列长度)。

int   SecondSize();

1.5。修剪台

它为第一个维度设置新的大小;此更改在初始大小范围内是可能的。

void   PruningTable(int count);

实际上,函数不会更改行的长度;它只是重写存储行长度值的变量的值。此类包含另一个变量,该变量存储最初分区表时分配的内存集的实际大小。在变量值的范围内,可以实际更改第一个维度的大小。当一个表被复制到另一个表时,函数将删除不必要的部分。

1.6。可复制的

将表的第二个维度的整个长度复制到另一个表的方法:

void   CopyTable(CTable *sor);

此函数将一个表复制到另一个表。它启动接收表的初始化,并可以用作另一个构造函数。不会复制排序变量的内部结构,但会从初始表复制大小、列类型和数据。函数接收对可作为getpointer函数传递的参数的可复制对象类型的引用。

当您将一个表复制到另一个表时,将根据“SOR”示例创建一个新表。

void   CopyTable(CTable *sor,int sec_beg,int sec_end);

用其他参数覆盖上述函数:sec_beg-复制初始表的起点,sec_end-复制的终点(不要与复制的数据量混淆)。这两个参数都是针对第二维度的。数据将添加到接收表单的开头。接收台的大小设置为sec-end-sec-beg+1。

1.7。可打印的

返回列“i”(枚举数据类型)的类型“u”表值。

ENUM_DATATYPE  TypeTable(int i)

1.8。变化

change()方法执行列交换。

bool   Change(int &sor0,int &sor1);

如上所述,这个类交换列(对第一个维度的操作)。由于信息实际上不会移动,因此功能的运行速度不会受到第二维度大小的影响。

1.9。插入

此插入方法在指定位置插入列,

bool   Insert(int rec,int sor);

此函数与上面描述的函数相同,只是它根据应移动指定列的位置执行列移动。参数“rec”指定移动列的位置,“sor”指定移动列的位置。

1.10。变体/变体副本

接下来是varian系列的三个函数。表处理变量的识别是在类中实现的。

这个变种就像一个记事本。例如,如果按第三列排序,并且不希望在下次处理数据时重置数据,则应切换变量。要访问进程的最后一个变量,请调用“variant”函数。如果下一个处理应该基于上一个处理的结果,那么您应该复制变体。默认情况下,将变量设置为0。

设置变量(如果没有这样的变量,将创建它,以及所有缺失的变量,直到“ind”)并获取活动变量。变量复制方法将“sor”变量复制到“rec”变量。

void   variant(int ind);
int    variant(); 
void   variantcopy(int rec,int sor);

variant(int ind)方法切换所选变量。执行自动内存分配。如果指定的参数数目小于上次指定的参数数目,则不会重新分配内存。

variantcopy方法允许将“sor”变量复制到“rec”变量。创建此函数是为了排列变量。如果“rec”变量不存在,它会自动增加变量的数量并切换到新复制的变量。

1.11。SortTwodim数组

SortTwodiMaray方法按所选行“i”对表进行排序。

void   SortTwoDimArray(int i,int beg,int end,bool mode=false);

按指定列对表排序的函数。参数:i-列,beg-排序起始点,结束-排序结束点(含),模式-布尔变量,确定排序方向。如果模式=真,则表示值随着索引的增加而增加(“假”是默认值,因为索引从表的顶部到底部逐渐增加)。

快速搜索

该方法根据与“元素”模式相同的值快速搜索元素在数组中的位置。

int   QuickSearch(int i,long element,int beg,int end,bool mode=false);

1.13。搜索第一个

在排序后的数组中搜索与模式相同的第一个元素。返回第一个值的索引,该值等于“element”模式。必须指定在此作用域中之前执行的排序类型(如果没有此类元素,则返回-1)。

int  SearchFirst(int i,long element,int beg,int end,bool mode=false);

1.14。搜索上次

搜索与排序数组中的模式相同的最后一个元素。

int   SearchLast(int i,long element,int beg,int end,bool mode=false);

1.15。搜索大

搜索比排序数组中的模式大的最近的元素。

int   SearchGreat(int i,long element,int beg,int end,bool mode=false);

1.16。无搜索

搜索比排序数组中的模式小的最近的元素。

int  SearchLess(int i,long element,int beg,int end,bool mode=false);

设置/获取

set和get函数有空类型;它们由表中使用的四种数据类型覆盖。此函数标识数据类型,如果“value”参数与列类型不匹配,将显示一条警告消息,并且不会分配。唯一的例外是字符串类型。如果输入参数是字符串类型,它将转换为列类型。当无法设置接收单元值的变量时,此异常被定制以便于信息的传输。

设置值的方法(i-第一个维度的索引,第二个维度的索引)。

void   Set(int i,int j,long     value); // setting value of the i-th row and j-th column
void   Set(int i,int j,double   value); // setting value of the i-th row and j-th columns
void   Set(int i,int j,datetime value);// setting value of the i-th row and j-tj column
void   Set(int i,int j,string   value); // setting value of the i-th row and j-th column 

获取值的方法(i—第一维度的索引,第二维度的索引)。

   //--- getting value
void   Get(int i,int j,long     &recipient); // getting value of the i-th row and j-th column
void   Get(int i,int j,double   &recipient); // getting value of the i-th row and j-th column
void   Get(int i,int j,datetime &recipient); // getting value of the i-th row and j-th column
void   Get(int i,int j,string   &recipient); // getting value of the i-th row and j-th column

1.19。SGET

从列“j”的行“i”中获取字符串类型的值。

string sGet(int i,int j); // return value of the i-th row and j-th column 

get序列的唯一函数通过“返回”运算符而不是参数变量返回值。返回字符串类型的值(不考虑列类型)。

字符串数字

当类型转换为字符串时,可以通过函数使用精度设置:

void  StringDigits(int i,int digits);

设置“双精度”精度,以及

int   StringDigits(int i);

在“日期和时间”中设置,以显示秒的准确度;任何不等于-1次的值。指定的值将作为一列记录,因此您不必每次显示信息时都指出该值。可以多次设置精度,因为信息存储在原始类型中,并且仅在输出期间转换为指定的精度。在复制期间不存储精度值,因此在将表复制到新表时,新表的列精度将与默认精度匹配。

1.21。使用实例:

#include <Table.mqh>  
ENUM_DATATYPE TYPE[7]=
  {TYPE_LONG,TYPE_LONG,TYPE_STRING,TYPE_DATETIME,TYPE_STRING,TYPE_STRING,TYPE_DOUBLE};
//     0          1          2            3              4            5            6   //7 
void OnStart()
  {
   CTable table,table1;
   table.FirstResize(TYPE);             // dividing table, determining column types   
   table.SecondResize(5);               // change the number of rows    

   table.Set(6,0,"321.012324568");        // assigning data to the 6-th column, 0 row
   table.Insert(2,6);                   // insert 6-th column in the 2-nd position
   table.PruningTable(3);               // cut the table to 3 columns
   table.StringDigits(2,5);             // set precision of 5 digits after the decimal point
   Print("table ",table.sGet(2,0));       // print the cell located in the 2-nd column, 0 row  

   table1.CopyTable(GetPointer(table));  // copy the entire table 'table' to the 'table1' table
   table1.StringDigits(2,8);            // set 8-digit precision
   Print("table1 ",table1.sGet(2,0));     // print the cell located in the 2-nd column, 0 row of the 'table1' table.  
  }

结果是打印单元格(2;0)的内容。读者可能已经注意到,复制数据的准确性不会超过初始表的准确性。

2011.02.09 14:18:37     Table Script (EURUSD,H1)        table1 321.01232000
2011.02.09 14:18:37     Table Script (EURUSD,H1)        table 321.01232

现在让我们从算法本身的描述开始。

2。选择模型

有两种方式来组织信息:联接列方案(本文中实现的结构)及其以联接行形式出现的替代方案,如下所示。

由于信息是通过中介引用的(如第2页所述),所以在上部作用域的实现中没有太大的差异。但是我选择了列模型,因为它允许在存储数据的对象的下限上实现数据方法。替代方案可能需要覆盖用于处理此类CTables中信息的方法。如果需要的话,这会使类增强变得复杂。

因此,两种方案都可以使用。建议的方案允许快速的数据移动,而替代方案允许更快的数据添加(因为信息通常逐行添加到表中)和行获取。

还有另一种排列表格的方法——结构化数组。虽然这是最容易实现的,但它有一个主要的缺点。结构必须由程序表描述。因此,我们错过了使用自定义参数设置表属性的机会(而不更改源代码)。

3。动态阵列中的统一数据

为了实现在单个动态数组中统一不同类型数据的可能性,我们需要解决为数组单元分配不同类型数据的问题。标准库的连接列表解决了这个问题。我的第一个开发是一个基于类的标准库。但是在项目的开发过程中,我发现我需要对对象基类进行很多更改。

这就是为什么我决定发展我自己的课程。对于那些不熟悉标准库的人,我将解释如何解决刚才提到的问题。要解决这个问题,需要使用继承机制。

class CBase
  {
public:
                     CBase(){Print(__FUNCTION__);};                    
                    ~CBase(){Print(__FUNCTION__);};
   virtual void       set(int sor){};
   virtual void       set(double sor){};
   virtual int        get(int k){return(0);};
   virtual double     get(double k){return(0);};
  };
//+------------------------------------------------------------------+
class CA: public CBase
  {
private:
   int              temp;
public:
                     CA(){Print(__FUNCTION__);};  
                    ~CA(){Print(__FUNCTION__);};
   void              set(int sor){temp=sor;};
   int               get(int k){return(temp);};
  };
//+------------------------------------------------------------------+
class CB: public CBase
  {
private:
   double            temp;
public:
                    CB(){Print(__FUNCTION__);};  
                   ~CB(){Print(__FUNCTION__);};
   void             set(double sor){temp=sor;};   
   double           get(double k){return(temp);};
  };  
//+------------------------------------------------------------------+
void OnStart()
  {
   CBase *a;   CBase *b;  
   a=new CA(); b=new CB(); 
   a.set(15);  b.set(13.3);
   Print("a=",a.get(0)," b=",b.get(0.));
   delete a;
   delete b;
  }

继承机制的示意图看起来像一个梳子:

继承

如果声明了类的动态对象的创建,这意味着将调用基类的构造函数。正是这个属性使得可以分两步创建对象。由于基类的虚函数被覆盖,我们可以通过不同类型的参数从派生类中调用函数。

为什么简单覆盖不够?问题是执行的函数很大,因此如果我们在基类中指定它们的主体(不继承),则将为二进制代码中的每个对象创建未使用的函数和主体的完整代码。当使用继承机制时,将创建占用比填充代码的函数更少内存的空函数。

4。操作阵列

我拒绝使用标准类的主要和次要原因是对数据的引用。我通过中间的索引数组间接地引用数组单元格,而不是通过单元格的索引。它指定的工作速度比通过变量直接引用要慢。事实上,表示索引的变量比数组单元工作得更快,数组单元首先需要在内存中找到。

让我们分析一下一维数组和多维数组排序之间的本质区别。在排序之前,一维数组的元素具有随机位置,排序之后,元素按顺序排列。在对二维数组进行排序时,我们不需要对整个数组进行排序,只需要对其中的一列进行排序。所有行必须更改其位置以保持其结构。

在这里,行本身就是包含不同类型数据的绑定结构。为了解决这个问题,我们需要对所选数组中的数据进行排序,并保存初始索引的结构。这样,如果知道哪一行包含单元格,就可以显示整行。因此,在对二维数组进行排序时,我们需要在不改变数据结构的情况下获得已排序数组的索引数组。

例如:

before sorting by the 2-nd column
4 2 3
1 5 3
3 3 6
after sorting
1 5 3
3 3 6
4 2 3
Initial array looks as following:
a[0][0]= 4; a[0][1]= 2; a[0][2]= 3;
a[1][0]= 1; a[1][1]= 5; a[1][2]= 3;
a[2][0]= 3; a[2][1]= 3; a[2][2]= 6;
And the array of indexes of sorting by the 2-nd column looks as:
r[0]=1;
r[1]=2;
r[2]=0;
Sorted values are returned according to the following scheme:
a[r[0]][0]-> 1; a[r[0]][1]-> 5; a[r[0]][2]-> 3;
a[r[1]][0]-> 3; a[r[1]][1]-> 3; a[r[1]][2]-> 6;
a[r[2]][0]-> 4; a[r[2]][1]-> 2; a[r[2]][2]-> 3;

因此,我们获得了根据交易类型、建仓日期、利润等对信息进行分类的可能性。

大量的排序算法被开发出来。这些算法的最佳变种是稳定排序算法。

标准类中使用的快速排序算法是指不稳定的排序算法。这就是为什么它不适合我们的经典实现。但即使以稳定的形式引入快速排序(需要额外的数据复制和索引数组排序),它看起来也比气泡排序(最快的稳定排序算法之一)快。算法很快,但它使用递归。

这就是我在处理字符串数组类型(需要更多的堆栈内存)时使用混合排序(双向气泡排序)的原因。

5。二维阵列布置

我要讨论的最后一个问题是动态二维数组的排列。对于这种安排,将一维数组包装成类,然后通过指针数组调用对象数组就足够了。换句话说,我们需要创建和排列数组。

class CarrayInt
  {
public:
                    ~CarrayInt(){};
   int               array[];
  };
//+------------------------------------------------------------------+
class CTwoarrayInt
  {
public:
                    ~CTwoarrayInt(){};
   CarrayInt         array[];
  };
//+------------------------------------------------------------------+
void OnStart()
  {
   CTwoarrayInt two;
   two.array[0].array[0];
  }

6。程序结构

CCTLE类的代码是使用本文中介绍的模板编写的(使用伪模板而不是C++模板)。正是因为模板的使用,我才能够如此快速地编写如此庞大的代码。这就是为什么我不打算详细说明整个代码;而且,算法的大多数代码都是对标准类的修改。

我将向您介绍类的基本结构和一些有趣的函数特性,以便澄清一些要点。

CTable 结构图

结构图的右侧部分主要是衍生类Clongarray、CDoubleArray、CDateTimeArray和StringArray中的覆盖方法。它们中的每一个(私有段)都包含一个对应类型的数组。这些数组用于访问信息的所有技巧。上面列出的类方法的名称与公共方法的名称相同。

基类cbasearray是通过重写虚拟方法填充的,并且只有当cbasearray对象的动态数组在ctable类的private部分中声明时才是必需的。cbasearray指针数组声明为动态对象的动态数组。在firstResize()函数中完成了构建对象和选择必要实例的最后工作。这也可以在copytable()函数中完成,该函数在其主体中调用FirstResize()。

CTable类还执行数据处理方法(在CTable类的实例中)和控制CINT2D类索引的对象之间的协调。整个协调包装采用的是通用的覆盖方式。

CTabable类中经常重复的覆盖率被定义替换,以避免生成过长的代码:

#define _CHECK0_ Print(__FUNCTION__+"("+(string)i+","+(string)j+")");return;
#define _CHECK_ Print(__FUNCTION__+"("+(string)i+")");return(-1);
#define _FIRST_ first_data[aic[i]]
#define _PARAM0_ array_index.Ind(j),value
#define _PARAM1_ array_index.Ind(j),recipient
#define _PARAM2_ element,beg,end,array_index,mode

因此,更简洁的形式部分:

int QuickSearch(int i,long element,int beg,int end,bool mode=false){if(!check_type(i,TYPE_LONG)){_CHECK_}return(_FIRST_.QuickSearch(_PARAM2_));};

将以下代码行替换为预处理器:

int QuickSearch(int i,long element,int beg,int end,bool mode=false){if(!check_type(i,TYPE_LONG)){Print(__FUNCTION__+"("+(string)i+")");return(-1);} return(first_data[aic[i]].QuickSearch(element,beg,end,array_index,mode));};

在上面的例子中,数据处理方法很容易调用(“返回”部分)。

正如我前面提到的,CTable类在处理过程中不执行数据的实际移动;它只是更改索引对象中的值。为了提供数据处理方法与索引对象交互的可能性,它们作为数组索引参数传递给所有处理函数。

数组索引对象存储第二维度中元素的位置关系。第一个维度的索引是由在CTable类的私有部分中声明的aic[]动态数组完成的。它提供了更改列位置的可能性(当然不是实际移动,而是通过索引)。

例如,当执行change()操作时,只有两个包含列索引的内存单元会更改它们的位置。虽然它看起来像移动两列。文档中详细描述了CTable类的函数(一些甚至是逐行描述)。

现在,让我们转到从cbasearray继承类的函数。实际上,这些类的算法是标准类的算法。我用标准名称来标记它们。修改包括使用索引数组的间接返回值,这与直接返回值的标准算法不同。

首先,我们修改快速排序。由于算法来自不稳定的排序类别,所以我们需要在开始排序之前备份传递给算法的数据。我还根据数据更改模式添加了索引对象的同步修改。

void CLONGArray::QuickSort(long &m_data[],Cint2D &index,int beg,int end,bool mode=0)

排序算法的一些代码如下:

...
            if(i<=j)
              {
               t=m_data[i];            it=index.Ind(i);
               m_data[i++]=m_data[j];  index.Ind(i-1,index.Ind(j));
               m_data[j]=t;            index.Ind(j,it);
               if(j==0) break;
               else     j--;
              }
...

在原始算法中,没有CINT2D类的实例。我们对其他标准算法做了类似的修改。我不会解释所有的代码模板。如果读者想要改进代码,他们可以使用模板而不是实际类型从实际代码中获取模板。

至于编写模板,我使用处理长类型的类代码。在这种经济算法中,如果有可能使用int,开发人员会尽量避免不必要的整数使用。这就是为什么长类型变量最有可能成为覆盖参数的原因。使用模板时,它们将被“模板”替换。

总结

我希望这篇文章能够帮助新程序员学习面向对象的方法,并使处理信息更加容易。CTable类可能成为许多复杂应用程序的基类。本文介绍的方法可以作为开发各种解决方案类的基础,因为它们实现了数据处理的一般方法。

此外,本文还证明了滥用MQL5是没有根据的。是否要使用变量类型?在这里,它是使用MQL5实现的。因此,完全没有必要改变标准,削弱语言安全。祝你好运!

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

附加文件下载zip table.mqh(61.33 kb)

 

 


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

 

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

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

風險提示

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

邁投公眾號

聯繫我們

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

MyFxtops 邁投