Tag Archives:mql4

MQL4:利用结构改进方案设计

你好,外汇软件的同事们!

我们今天的课程将集中在数据结构上,以及如何利用它们来提高MQL4软件开发的效率。代码的有效性取决于许多因素。在减少计算复杂性(优化算法)的同时,还需要注意智能数据设计,这将允许快速访问信息和经济地分配计算机内存。

一个重要的方面是代码的可读性。结构的使用在某种程度上有助于解决这些问题,因为正确组织的数据很容易被程序员理解,提高开发效率。

什么是结构?

MQL4中的结构是称为结构元素或字段的数据集。与…不同地块仅包含一种类型元素的结构可以由不同类型的元素组成。因此,结构的第一个也是最重要的用途是能够将变量按某种方式分组。结构是用户数据类型,因此可以声明这种类型的变量。这种结构变量可以通过赋值运算符复制到另一种类型,作为参数传递给函数,从函数中返回值,声明这些变量的数组。每个结构元素都可以像普通变量一样直接访问和修改。然而,所有这些活动都有一些限制,我们将在后面讨论。

语法

结构数据类型一般定义为:

struct<结构名称>{ <type><element_1>;<type><element_2>;<type><element_3>;<type><element_n>;};

让我们看看一个具体的例子。定义描述某个对象的结构数据类型。让这个对象成为一个酒吧图表金融工具(或蜡烛). 使用MQL4,我们可以获得特定蜡烛的以下特性:

  • 开盘价格;
  • 收盘价;
  • 最大值;
  • 最小;
  • 体积;
  • 开放时间。

其他特征,如蜡烛类型(“熊市”或“牛市”),蜡烛体高度,上阴影大小,下阴影大小等,预计将实时计算。这并不总是方便的,特别是如果您需要经常为整个蜡烛数组处理此类设置。您可以在新蜡烛形成时进行所有计算,并将结果存储在变量中。那么,为了存储这些蜡烛特性,我们需要11个不同类型的变量:

double   open;                  // 开盘价double   close;                 // 收盘价double   high;                  // 最大值double   low;                   // 最小值long     volume;                // 柚木体积datetime time;                  // 开放时间uchar    type;                  // 蜡烛类型(0-“狗”,1-公牛,2-熊)int      height_full;           // 总烛光高度int      height_body;           // 蜡烛体尺寸int      height_top_shadow;     // 上阴影尺寸int      height_bottom_shadow;  // 下阴影尺寸

如果我们想操作一个集合,比如说100根蜡烛,我们必须宣布11个数组,每个数组有100个元素。假设我们需要将所有这些数据传递到一个函数中,这将是一个非常笨重的结构,使代码和程序员的感知更加复杂。可能导致错误但是,所有这些数据可以合并为一个结构:

struct Candle{double   open;                  // 开盘价double   close;                 // 收盘价double   high;                  // 最大值double   low;                   // 最小值long     volume;                // 柚木体积datetime time;                  // 开放时间uchar    type;                  // 蜡烛类型(0-“狗”,1-公牛,2-熊)int      height_full;           // 总烛光高度int      height_body;           // 蜡烛体尺寸int      height_top_shadow;     // 上阴影尺寸int      height_bottom_shadow;  // 下阴影尺寸};

在这个阶段,记忆不会被分配到结构下,因为我们只是定义了类型。要访问结构对象本身,只需声明此类型的变量:

Candle Bar;

访问结构元素

要访问结构元素,必须使用点从属操作()。例如:

Bar.volume=iVolume(_Symbol,_Period,1)

在这里,我们将bar结构变量volume元素分配给当前图表上索引为1的条形图的滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答滴答。

结构初始化

请注意,默认情况下,结构元素不会在声明时初始化,就像普通变量一样。因此,必须确保在调用结构时,其元素被初始化。
可以在播发过程中使用初始化列表对结构进行初始化。例如:

Candle Bar = {0,0,0,0,100,D'2018.03.10 15:08:02',0,0,0,0,12};

在这里,我们宣布了变量bar,并将结构的所有元素从第一个到第十一个值进行了近似。
那么

Candle Bar = {0.1012,0.6321,0.2194,0.1784,100};

我们只用非零值初始化前五个元素,其余元素自动为零。如果结构中的所有元素都必须为零值拟合,则只需编写

Candle Bar = {};

因为我们结构的所有元素都是数值的,所以我们也可以用一个值对它们进行指数化:

Candle Bar = {100};

严格地说,这种初始化是不正确的,因为元素time类型datetime100是1970年1月1日以来的100秒。因此,这种结构初始化必须考虑隐式类型转换的特殊性。

嵌套结构

结构元素不仅可以是简单的类型,也可以是复杂的:字符串、静态和动态数组和结构。嵌套结构单独定义。让我们用一个具体的例子来说明如何以这种方式组织数据。在我们的结构中Candle最后四个元素包含整个蜡烛及其各个部分的大小。在此基础上,可以将这些元素合并为一个单独的结构:

struct Height{int               full;           // 总烛光高度int               body;           // 蜡烛体尺寸int               top_shadow;     // 上阴影尺寸int               bottom_shadow;  // 下阴影尺寸};

在结构上Candle只需宣布一个类型的变量Height:

struct Candle{double            open;                  // 开盘价double            close;                 // 收盘价double            high;                  // 最大值double            low;                   // 最小值long              volume;                // 柚木体积datetime          time;                  // 开放时间uchar             type;                  // 蜡烛类型(0-“狗”,1-公牛,2-熊)Height            height;                // 蜡烛部分高度};

初始化列表还必须包含子列表:

Candle Bar={0.0,0.0,0.0,0.0,100,D'2018.03.10 15:08:02',1,{0,0,0,0}};

现在,要查看蜡烛体的大小,必须使用两次点:

Bar.height.body = MathAbs(iOpen(_Symbol,_Period,1) - iClose(_Symbol,_Period,1));

然而,不必要地使结构复杂化是不必要的。在我们的例子中,将高度分配到一个单独的结构是多余的,并且仅用于显示嵌套结构。但是,对于复杂的数据组织,例如层次结构,使用嵌套结构是有益的。

机构间数据交换

您可以使用赋值运算符(=)为同一类型的另一个变量赋值结构变量。但是,这只能在所有元素都具有简单类型的结构中完成。在我们的例子中,结构Height只有整数字段,因此以下代码完全正确:

Heigh h1, h2={1,4,1,8};h1=h2;

由于所有变量字段的值h2复制到变量字段h1.

变量-包含复杂类型字段的结构:字符串、数组、结构不能分配给其他变量-这种类型的结构。在这种情况下,您必须按元素复制数据:

Candle Bar1, Bar2= {0.0,0.0,0.0,0.0,100,D'2018.03.10 15:08:02',1,{0,0,0,0}};Bar1.open                 = Bar2.open;                  // 开盘价Bar1.close                = Bar2.close;                 // 收盘价Bar1.high                 = Bar2.high;                  // 最大值Bar1.low                  = Bar2.low;                   // 最小值Bar1.volume               = Bar2.volume;                // 柚木体积Bar1.time                 = Bar2.time;                  // 开放时间Bar1.type                 = Bar2.type;                  // 蜡烛类型(0-“狗”,1-公牛,2-熊)Bar1.height.full          = Bar2.height.full;           // 总烛光高度Bar1.height.body          = Bar2.height.body;           // 蜡烛体尺寸Bar1.height.top_shadow    = Bar2.height.top_shadow;     // 上阴影尺寸Bar1.height.bottom_shadow = Bar2.height.bottom_shadow;  // 下阴影尺寸

结构和职能

结构可以像普通变量一样作为参数传递给函数。在这种情况下,只有通过引用才能将结构转移到功能中。因此,函数中参数的所有更改都会导致传递参数的更改。

//包含平面上点坐标的结构struct Coordinates{int   x; int   y;};//设定坐标点(x=120,y=35)Coordinates P={120,35};//+------------------------------------------------------------------+将坐标移到:?//shift_x水平//shift_y垂直//+------------------------------------------------------------------+void MovePoint(Coordinates &point, int shift_x=0, int shift_y=0){point.x+=shift_x;point.y+=shift_y; }//+------------------------------------------------------------------+//| Expert initialization function                                   |//+------------------------------------------------------------------+int OnInit(){Print("x=",P.x,"; y=",P.y);MovePoint(P,2,15);Print("x=",P.x,"; y=",P.y);return(INIT_PARAMETERS_INCORRECT);}

结果:

P.x=122; P.y=50P.x=120; P.y=35

在上面的例子中,我们将初始化变量P结构和坐标的水平和垂直偏移传递到函数中。MovePoint()因此,函数更改了结构的两个元素,这可以通过在调用函数之前和之后打印这些值来查看。

但是,函数可以返回结构,因此可以避免更改传递给函数的参数。下一篇:改变我们的功能MovePoint()因此,它返回变量结构:

//+------------------------------------------------------------------+将坐标移到:?//shift_x水平//shift_y垂直//返回具有新坐标的结构。//+------------------------------------------------------------------+Coordinates MovePoint(Coordinates &point, int shift_x=0, int shift_y=0){Coordinates p;p.x=point.x+shift_x;p.y=point.y+shift_y; return(p);}//+------------------------------------------------------------------+//| Expert initialization function                                   |//+------------------------------------------------------------------+int OnInit(){Print("P.x=",P.x,"; P.y=",P.y);Coordinates MovedPoint=MovePoint(P,2,15);Print("MovedPoint.x=",MovedPoint.x,"; MovedPoint.y=",MovedPoint.y);Print("P.x=",P.x,"; P.y=",P.y);return(INIT_PARAMETERS_INCORRECT);}

结果:

P.x=120; P.y=35MovedPoint.x=122; MovedPoint.y=50P.x=120; P.y=35

我们看到函数返回变量MovedPoint计算值,但变量P,作为参数传递给函数的值没有变化。请注意,只能返回不包含数组或嵌套结构的简单结构。

方法

不仅可以根据特定的标准对特定的数据进行分组,还可以对这些数据的操作进行分组。这些函数称为方法。

在我们的结构中Candle定义获取蜡烛打开时间的方法,在时间序列数组中搜索它,并用找到的蜡烛填充结构字段。

struct Height{int               full;           // 总烛光高度int               body;           // 蜡烛体尺寸int               top_shadow;     // 上阴影尺寸int               bottom_shadow;  // 下阴影尺寸};struct Candle{double            open;                  // 开盘价double            close;                 // 收盘价double            high;                  // 最大值double            low;                   // 最小值long              volume;                // 柚木体积datetime          time;                  // 开放时间uchar             type;                  // 蜡烛类型(0-“狗”,1-公牛,2-熊)Height            height;                // 蜡烛部分高度void              GetCandleParam(datetime open_time){//获取蜡烛时间索引或最近蜡烛的索引int index=iBarShift(_Symbol,_Period,open_time);//计算结构域open                 = iOpen(_Symbol,_Period,index);                  // 开盘价close                = iClose(_Symbol,_Period,index);                 // 收盘价high                 = iHigh(_Symbol,_Period,index);                  // 最大值low                  = iLow(_Symbol,_Period,index);                   // 最小值volume               = iVolume(_Symbol,_Period,index);                // 柚木体积time                 = iTime(_Symbol,_Period,index);                  // 开放时间height.full          = int((high-low)/_Point);                        // 总烛光高度height.body          = int((close-open)/_Point);                      // 蜡烛体尺寸type                 = height.body>0?1:(height.body<0?2:0);           // 蜡烛类型(0-“狗”,1-公牛,2-熊)height.top_shadow    = int((high-((type<2)?close:open))/_Point);      // 上阴影尺寸height.bottom_shadow = int((((type<2)?open:close)-low)/_Point);       // 下阴影尺寸height.body          = MathAbs(height.body);}} Bar; // 让我们立即宣布一个变量-结构//+------------------------------------------------------------------+//| Expert initialization function                                   |//+------------------------------------------------------------------+int OnInit(){Bar.GetCandleParam(D'2020.10.02 10:00');打印(“打开蜡烛的价格:”,doubletostr(bar.open,_digits));打印(“蜡烛关闭价格:”,doubletostr(bar.close,_digits));打印(“蜡烛类型:”,bar.type==0?“dogie”:(bar.type==1?“公牛”:“熊”);打印(“蜡烛高度:”,integertostring(bar.height.full));打印(“时间:”,bar.time);return(INIT_PARAMETERS_INCORRECT);}

访问细节

要限制对字段和结构方法的访问,请使用访问特定符:private,protectedpublic默认情况下,所有字段和结构方法都有修饰符public也就是说,你可以从外面联系他们。如果不使用继承,则protected和private特异性是相同的。

之后声明的所有字段和方法private:在…之前public:,只有结构方法可用。

struct Candle{private:double            open;                  // 开盘价double            close;                 // 收盘价double            high;                  // 最大值double            low;                   // 最小值long              volume;                // 柚木体积datetime          time;                  // 开放时间public:uchar             type;                  // 蜡烛类型(0-“狗”,1-公牛,2-熊)Height            height;                // 蜡烛部分高度void              GetCandleParam(datetime open_time){//获取蜡烛时间索引或最近蜡烛的索引int index=iBarShift(_Symbol,_Period,open_time);//计算结构域open                 = iOpen(_Symbol,_Period,index);                  // 开盘价close                = iClose(_Symbol,_Period,index);                 // 收盘价high                 = iHigh(_Symbol,_Period,index);                  // 最大值low                  = iLow(_Symbol,_Period,index);                   // 最小值volume               = iVolume(_Symbol,_Period,index);                // 柚木体积time                 = iTime(_Symbol,_Period,index);                  // 开放时间height.full          = int((high-low)/_Point);                        // 总烛光高度height.body          = int((close-open)/_Point);                      // 蜡烛体尺寸type                 = height.body>0?1:(height.body<0?2:0);           // 蜡烛类型(0-“狗”,1-公牛,2-熊)height.top_shadow    = int((high-((type<2)?close:open))/_Point);      // 上阴影尺寸height.bottom_shadow = int((((type<2)?open:close)-low)/_Point);       // 下阴影尺寸height.body          = MathAbs(height.body);}}

在这个结构域的例子中open,close,high,volume,time仅从方法可用GetCandleParam().

用结构写顾问

考虑如何使用结构优化顾问代码的示例。让我们写两个版本的简单网格:没有结构和使用结构收集有关当前帐户状态的信息。

顾问算法

我们的算法顾问总的来说,它看起来像这样:

  • 当你打开一个新的蜡烛时,你会得到一个交易信号,如果上一个蜡烛关闭在附近的最低值以下,你会得到一个买入信号,如果上一个蜡烛关闭在附近的最低值以下,你会得到一个卖出信号,在其他情况下,没有信号;
  • 如果没有开放订单进入信号;
  • 如果有未打开的订单,并且收到相反的信号,则当前订单利润再加上一个最小的,我们就可以关闭它。利润;
  • 如果有未平仓订单及其当前亏损,则信号与网格的方向一致,并且当前价格距离网格的极端订单很远,一个最小的步骤-打开平均订单放大测深.

输入参数

根据算法,输入参数顾问应为:

input  double  Lots                = 0.01;     // 固定测深input  double  Multiplier          = 2.0;      // 乘数input  int     MaxLegs             = 10;       // 最大膝盖数input  int     MinProfit           = 1.0;      // 最低利润input  int     MinStep             = 100;      // 订单之间的最小距离(步骤)input  int     Magic               = 1100;     // Magic Number(订单ID)input  int     Slippage            = 30;       // 最大容许滑动

顾问的职能

首先,我们需要一个新酒吧信号功能:

//+------------------------------------------------------------------+新酒吧在当前时间//+------------------------------------------------------------------+bool IsNewBar(){static datetime last_bar=0;datetime null_bar=iTime(_Symbol,_Period,0);if(last_bar!=null_bar){last_bar=null_bar;return(true);}return(false);}

接下来,我们写一个生成交易信号的函数:

//+------------------------------------------------------------------+//–如果形成了购买信号,则返回OPu BUY。//opu sell如果有销售信号,并且//如果封闭酒吧没有信号,则为空。//+------------------------------------------------------------------+int GetSignal(){if(iClose(_Symbol,_Period,1)<iLow(_Symbol,_Period,2))return(OP_BUY);if(iHigh(_Symbol,_Period,2)<iClose(_Symbol,_Period,1))return(OP_SELL);return(EMPTY);}

下一个函数定义当前订单网格的方向。一旦第一个订单打开顾问,它的类型将被视为网格的方向。我们正在写一个演示版,所以我们不会处理可能的例外情况时,出现了问题,帐户打开了反订单。假设我们在每一个时刻帐单只有一个方向的订单打开或没有打开。

//+------------------------------------------------------------------+//^返回第一个找到的订单类型,该订单已打开。//当前符号的顾问//+------------------------------------------------------------------+int GetOpenedOrdersType(){for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES))if(OrderSymbol()==_Symbol && OrderMagicNumber()==Magic)return(OrderType());return(EMPTY);}

以下两个函数返回打开的总数顾问订单和利润根据这些命令:

//+------------------------------------------------------------------+//^返回打开的顾问总数..//当前字符上指定的订单类型。//+------------------------------------------------------------------+int GetOrdersCount(int type){int count=0;for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES))if(OrderSymbol()==_Symbol && OrderMagicNumber()==Magic && OrderType()==type)count++;return(count);}//+------------------------------------------------------------------+//^退还顾问开立的所有订单的利润。//+------------------------------------------------------------------+double GetProfit(){double profit=0;for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES))if(OrderSymbol()==_Symbol && OrderMagicNumber()==Magic)profit+=OrderProfit()+OrderCommission()+OrderSwap();return(profit);}

由于销售订单是平均的,如果价格高于开盘价格,我们需要找到从当前价格到最高销售订单的距离来验证,是否会在允许的距离内有新的平均订单:

//+------------------------------------------------------------------+//返回最高的开盘价。//在网格中公开出售订单›//+------------------------------------------------------------------+double GetTopSellPrice(){double price=0;for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES))if(OrderSymbol()==_Symbol && OrderMagicNumber()==Magic && OrderType()==OP_SELL)if(price<OrderOpenPrice())price=OrderOpenPrice();return(price);}

对于购买订单,我们将写一个类似的功能,只有在这种情况下,我们将检查从当前价格到最低购买订单的距离:

//+------------------------------------------------------------------+//返回最低开盘价格。//在网格中打开购买订单›//+------------------------------------------------------------------+double GetBottomBuyPrice(){double price=0;for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES))if(OrderSymbol()==_Symbol && OrderMagicNumber()==Magic && OrderType()==OP_BUY)if(OrderOpenPrice()<price || price==0)price=OrderOpenPrice();return(price);}

测深我们将使用以下函数计算下一个网格订单:

//+------------------------------------------------------------------+//^返回计算为n项的批量大小。几何级数//+------------------------------------------------------------------+double GetLot(int n){return(NormalizeDouble(Lots*MathPow(MathMax(1,Multiplier),n),2));}

也在参赞我们不能没有贸易功能。第一个订单将在给定方向上以当前市场价格打开指定数量的订单。

//+------------------------------------------------------------------+#22312;大批量交易。去COM的方向//+------------------------------------------------------------------+bool SetOrder(double lot, int com){MqlTick tick;if(SymbolInfoTick(_Symbol,tick)){for(int i=0; i<5; i++){if(OrderSend(_Symbol,com,lot,((com==OP_BUY)?tick.ask:tick.bid),Slippage,0,0,NULL,Magic,0,((com==OP_BUY)?clrBlue:clrRed))>EMPTY)return(true);Sleep(1000);}}return(false);}

第二,关闭所有开放的顾问订单:

//+------------------------------------------------------------------+//关闭顾问发布的所有市场订单。目前的符号//+------------------------------------------------------------------+void CloseOrders(){for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES))if(OrderSymbol()==_Symbol && OrderMagicNumber()==Magic){MqlTick tick;if(SymbolInfoTick(_Symbol,tick)){for(int j=0; j<5; j++){if(OrderClose(OrderTicket(),OrderLots(),((OrderType()==OP_BUY)?tick.bid:tick.ask),Slippage,((OrderType()==OP_BUY)?clrBlue:clrRed)))break;Sleep(1000);}}}}

现在,所有必要的功能都已创建,剩下的就是编码我们顾问的简单交易逻辑,并将整个代码放入处理程序中。OnTick():

//+------------------------------------------------------------------+//| Expert tick function                                             |//+------------------------------------------------------------------+void OnTick(){//打开新订单,只在新酒吧开张时记录利润if(IsNewBar()){收到新信号int Signal=GetSignal();//如果未在封闭的酒吧中形成信号,则立即退出功能。if(Signal<0)return;//是否有公开的命令顾问int OpenedOrderType=GetOpenedOrdersType();// (!!!)//有未打开的订单,它们不等于信号if(OpenedOrderType!=EMPTY && Signal!=OpenedOrderType){如果有利润,我们将关闭订单。if(GetProfit()>MinProfit)// (!!!){CloseOrders();OpenedOrderType=GetOpenedOrdersType();// (!!!)}}//没有公开订单if(OpenedOrderType==EMPTY){//打开第一个网格订单SetOrder(Lots,Signal);return;}//按信号打开平均订单if(Signal==OpenedOrderType){//计算下一批订单的未平仓订单数int Count=GetOrdersCount(OpenedOrderType);// (!!!)double Price;switch(OpenedOrderType){case OP_BUY:Price=GetBottomBuyPrice();// (!!!)//检查到最高买入订单的最小步骤if(int((Price-Ask)/_Point)>MinStep){SetOrder(GetLot(Count),OP_BUY);}break;//检查最低卖出订单的最小步骤case OP_SELL:Price=GetTopSellPrice();// (!!!)if(int((Bid-Price)/_Point)>MinStep){SetOrder(GetLot(Count),OP_SELL);}break;}}}}

正如你所看到的,顾问接受了贸易解决方案根据目前的情况,帐单:利润大小,任何类型的未平仓订单的可用性,从市场价格到网格最大订单的距离。所有这些参数都使用以下函数计算:

  • GetOpenedOrdersType();
  • GetOrdersCount();
  • GetProfit();
  • GetTopSellPrice();
  • GetBottomBuyPrice().

这些函数的调用用注释(!!)标记。所有这些用于计算的函数都会在一个循环中搜索未打开的订单,选择顾问打开的订单并满足特定条件的订单。因此,我们多次扫描市场情况,首先从调用每个功能开始过度订单。这不是一个人工的例子-这种顾问架构经常出现。我们花在服务器上的时间最多,所以问题是,如果我们在一次访问中获得所有必要的信息,会发生什么?这个问题的答案是使用一个结构,它包含一组元素,我们将在其中放置所有必要的数据,只需在循环中移动所有订单一次:

struct state{int               buy_count;         // 订单数量Buyint               sell_count;        // 订单数量selldouble            buy_bottom_price;  // 最低订单开仓价格double            sell_top_price;    // 最高卖出订单开仓价格double            profit;            // 所有订单的利润//收集账户信息的方法//更新结构元素void              Refresh();} State;

结构中有一种方法Refresh(),它计算并为结构元素分配值。在结构外定义方法的主体。为此,使用上下文解析操作(::)。上下文是结构的描述符(名称):

//+------------------------------------------------------------------+//方法收集有关当前帐户状态的信息。//并更新相应的结构字段。//+------------------------------------------------------------------+void state::Refresh(void){//将结构的数字字段归零ZeroMemory(this);for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES))if(OrderSymbol()==_Symbol && OrderMagicNumber()==Magic){double OpenPrice=OrderOpenPrice();profit+=OrderProfit()+OrderCommission()+OrderSwap();switch(OrderType()){case OP_BUY:buy_count++;if(OpenPrice<buy_bottom_price || buy_bottom_price==0)buy_bottom_price=OpenPrice;break;case OP_SELL:sell_count++;if(OpenPrice>sell_top_price || sell_top_price==0)sell_top_price=OpenPrice;break;}}}

请注意,在方法体中,我们不使用点来访问结构元素,因为我们使用上下文解析操作。在方法正文开头更新数字字段之前,函数将其归为零。ZeroMemory()关键字this,因此,结构将链接传递给自己。

处理程序中的主顾问代码OnTick()现在看起来像这样:

//+------------------------------------------------------------------+//| Expert tick function                                             |//+------------------------------------------------------------------+void OnTick(){for(int i=0; i<2000; i++){double b=MathSqrt(i);}//打开新订单,只在新酒吧开张时记录利润if(IsNewBar()){收到新信号int Signal=GetSignal();//如果未在封闭的酒吧中形成信号,则立即退出功能。if(Signal<0)return;//更新结构State.Refresh(); // (!!!)//如果当前利润超过最低值,并且收到回调信号if(State.profit>MinProfit)if((Signal==OP_BUY && State.sell_count>0) ||(Signal==OP_SELL && State.buy_count>0)){关闭所有订单CloseOrders();//更新结构State.Refresh(); // (!!!)}//购买订单if(State.buy_count>0){//如果到最低购买的距离大于最小步数,则按信号平均if(Signal==OP_BUY && int((State.buy_bottom_price-Ask)/_Point)>MinStep)SetOrder(GetLot(State.buy_count),Signal);}else//订单出售if(State.sell_count>0){//如果最高卖出距离大于最低卖出距离,则按信号平均if(Signal==OP_SELL && int((Bid-State.sell_top_price)/_Point)>MinStep)SetOrder(GetLot(State.sell_count),Signal);}else//没有订单,我们打开一个新的信号。SetOrder(Lots,Signal);}}

方法调用State.Refresh()取代了五个功能:GetOpenedOrdersType(),GetOrdersCount()GetProfit(),GetTopSellPrice(),GetBottomBuyPrice()因此,对服务器的访问量也减少了,这可能会影响速度。此外,代码变得更紧凑。

结论

在本课程中,我们熟悉了结构数据类型及其MQL4实现。本教程的主要目的是展示如何使用结构来帮助程序员提高代码的效率。我希望这个目标已经实现了。然而,结构是将数据及其操作结合在一起的对象,是过程编程和面向对象编程之间的桥梁。因此习惯在你的代码中使用结构化数据类型对那些计划学习的人来说是一个很好的训练。巴解组织.

论坛主题

尊敬的尤里·洛塞夫AKA LSV107
Tlap.com

MQL4教程 ,,,,

MQL4:金字塔战略顾问

你好,亲爱的外汇程序员们!

今天的MQL4课程将致力于创建一个贸易专家,在其中实现金字塔的建设待处理订单以及随后的护送。首先,让我们看看什么是金字塔战略,它有多有效,以及它隐藏着什么陷阱。然后我们继续写一个以金字塔为基础的顾问。

金字塔:基本概念

必须立即指出金字塔不是独立的。贸易体系更恰当的说法是增加法。利润贸易趋势.金字塔化的主要思想是所谓的“填充物”,当存在明显的趋势新开放项目滚动或者在下一次合并时同时,总增长体积因此,并且利润这种行为的模式类似于阶梯金字塔,因此得名——“金字塔”。

在上图中,我们可以看到金字塔的工作原理。8个未平仓的商品趋势最终,一切都关闭了。Stop Loss:最后一笔交易亏损,倒数第二位-B收支平衡剩下的六个都是利润。可能会出现这样的问题:第一个位置是否可以立即打开。测深关闭拖车停止? 事实上,我们不知道价格是否会朝着我们想要的方向发展,在这种情况下,我们会冒着全部风险。金字塔允许逐渐增加总成交量,因为下一个头寸只有在上一个头寸盈利后才会打开。

有人认为,金字塔是识字和相对安全的唯一方法超额存款在几天的成功趋势中,将存款翻一番——翻三番——是可行的,但风险仅为可用资金的2%。

入口点

如前所述,金字塔化是一种增加趋势交易利润的方法。显然这种方法不适合波段(浮动)交易,可能导致完全损失存款所以在建造金字塔之前交易者确定趋势的开始很重要。我们将把这项任务排除在今天的教训之外。值得注意的是,寻找这样的入口点是最好的。时间范围H1及以上

在第一次发生之后交易当我们确信价格朝着正确的方向发展时,另一个问题出现了:在什么地方应该重新进入?最明显的方法是在主信号下输入,但在较小的信号上输入。时间范围触摸时可以倒水移动平均进或出振荡器进出超买或超卖区域,当出现蜡烛模型继续趋势时,在更大的范围内体积巩固的突破,穿孔测试强者水平但你可以做得更容易,进入新的交易固定步长项目这一步骤可以根据所选择的战术扩大或缩小。我们将在我们的贸易专家中使用这种纯粹的数学方法来构建金字塔。

扩大阵地范围

增加总容量的最保守和最常见的方法是以固定的方式打开每个新头寸。测深.

如果有一个强大的稳定趋势,可以使用更具侵略性的方法,当每个新头寸的数量大于上一个头寸的数量时。例如,1;2; 3; 4或1;2; 4; 8; 16.在这种情况下,重要的是要及时固定利润,否则如果最后一个头寸被平仓。Stop Loss由此产生的损失可能会抵消以前积累的全部利润。

也有一种方法与前一种方法相反,即每个新位置的体积小于前一个位置。例如,4;3; 2; 1.这种方法是合理的,因为每一个趋势都有衰减的趋势,随着金字塔的每一个新台阶,逆转或大幅回落的可能性会增加。最后一批亏损较低测深将被第一批利润所抵消。这种方法的缺点是可能的利润损失,尽管风险当然,他们会变得更低。在我们未来的顾问中,我们将采用这两种方法。

利润固定

在经典金字塔的情况下,使用通用的利润固定Stop Loss换言之,在新的立场上Stop Loss所有现有头寸均转入最后一个未结余额。我们将在创建顾问时使用这种机制,稍后将详细讨论。

另一种常见的方法是在达到一定利润水平时关闭金字塔。在这种情况下交易者它决定了足够的利润来关闭金字塔。

最后,关闭金字塔的决定可以根据技术分析在当前趋势逆转或减弱的早期迹象。

一般顾问算法

让我们开始建立一个贸易专家。正如“百闻不如一见”这句格言所言,让我们看看真实的世界是怎样的。图表这可能是一个金字塔的待审订单,我们的顾问将建立。

在这里,我们看到一个金字塔出售出售停止订单。每个后续的体积订单它以0.01步的算术级数增长。

当价格向下移动时,前四个卖出止损订单被激活时,金字塔看起来就是这样。现在我们有四个市场订单和六个延期订单。我们只需要平仓时的一般止损。但后来。

可以使用以下流程图大致记录顾问算法:

我们将确定未来顾问的全部功能,并描述其输入参数。

Money Management

此部分包含与资本管理:

Lots–固定/启动批量;

LotsMultMode–每个新金字塔位置的增减模式,可以接受三个值之一:禁用(所有位置的体积将相同,等于lots参数);算术(体积在算术级数中增加);几何(体积将以几何级数增加);

LotsMultiplicatorAr–算术级数差,每个新批次都增加该值;如果参数为0(0),则级数的差分是系列中第一个订单的批量。使用此参数的负值,可以减少批量;

LotsMultiplicatorGm–几何级数的分母,每个新批量都会增加指定的次数。如果值小于1,则批量将减少。例如,在1.5时,每个新的批量将增加1.5倍。为了使批量减少一半,需要1/1.5=0.67。

信号

如前所述,为了成功的贸易,我们需要尽可能靠近趋势运动的可能开始建造金字塔。我们使用以下方案:从待定订单中构建金字塔随机进入超买区(金字塔出售)或超卖区(金字塔购买)。如果使用经典的信号不是输入而是输出线路随机从超买/超卖区域来看,由于指示灯延迟,虚假输入将大大增加。我们正在努力向前迈进。如果价格没有转向我们,我们只需要删除金字塔(所有待售订单)并等待新的信号。

StochasticPeriod随机期;

StochasticSlowing-减速;

StochasticLevelDn–超卖/超买自动(例如,对于StochaSticleVeldn=20,上限为100–StochaSticleVeldn=80)。

一般交易参数

DistanceToGrid–收到随机信号后,网格与价格的距离;

AfterSignaBarsCount–等待网格激活的酒吧数量。由于我们在随机进入超买或超卖区域时构建网格,因此我们只能假设可能不会发生的逆转。因此,如果金字塔没有激活,它必须在一段时间后移除。使用此选项,可以在当前时间范围的栏中指定此检查期。很明显,当反向信号到达时,金字塔也会被移除。如果参数AfterSignaBarsCount如果为零,则只有在反向信号形成后才能删除金字塔;

Magic–魔法号码,顾问打开的订单ID;

Slippage最大允许滑动.

网格设置

在我们的顾问中,金字塔将被构建为一个待售订单网格,该网格是在一定距离内设置的买入止损或卖出止损订单。这种延期订单在价格走势方向上激活,这意味着它们适合趋势交易。此部分设置订单和整个网格的设置。

OrdersAmount–网格中待处理订单的数量;

GridStep–网格间距,即挂起的网格订单之间的距离。这个距离可以是固定的,也可以是递减的,这取决于参数。StepMultMode在动态步骤的情况下,指定其初始值;

StepMultMode–当订单远离入口点时,网格中订单之间的扩展/减少距离模式。与位置体积相似的步长可以在算术或几何级数中变化;

StepMultiplicatorAr–算术级数的差,每个订单将与上一个订单的距离相同,后者将增加该值;如果参数为零,则进度差为参数值GridStep参数的负值将步数减少到该值;

StepMultiplicatorGm–几何级数的分母,步长增加指定的次数。如果值小于1,则步骤将减小。例如,在1.5时,步骤将增加1.5倍。若要将步长减半,则需要1/1.5=0.67;

StopLossRatio停止失去一步。经典金字塔的构造方式是,下一个订单的止损等于上一个订单的开仓价格。但是,您可以使用此参数将止损大小与步数关联。

TakeProfit–一般利润为了固定利润,我们的顾问使用一般的止损,但可能会激活所有金字塔订单,价格将继续趋势。为了避免在这种罕见的情况下损失利润,我们使用了Take Profit保险,否则我们将不得不等待价格的回归和总的止损。不过,最好还是在金字塔里使用更多的订单。

顾问的职能

让我们从上面的流程图开始算法的实现。这个方案(块)的每个元素都可以作为函数执行。“新柚木”块对应于处理程序OnTick()在其中,我们将调用其他功能。

功能RefreshState()

让我们从一个函数开始,这个函数在我们的方案中没有单独的块表示,因为它属于服务类别,但值得特别注意。功能RefreshState()收集有关顾问打开的订单的信息并将其放入提交的状态结构:

//顾问发出的订单的存储结构struct state{double            BuyTopPrice;         // 最高订单开仓价格double            SellBottomPrice;     // 开仓价格最低double            BuyTopSL;            // 最高订单100%买入double            BuyBottomSL;         // 买入最低订单double            SellTopSL;           // 最高卖出订单double            SellBottomSL;        // 卖出最低订单datetime          BuyStopStart;        // 金字塔安装时间(打开网格中第一个买入停止订单)datetime          SellStopStart;       // 安装金字塔的时间(打开网格中第一个卖出停止订单)int               BuyCount;            // 订单数量Buyint               SellCount;           // 订单数量sellint               BuyStopCount;        // 订单数量Buy Stopint               SellStopCount;       // 订单数量Sell Stop};

基于收集的功能RefreshState()统计数据,顾问跟踪当前形势贸易账户并确定算法的确切步骤。

例如,下面的代码确定所有金字塔市场订单都以止损方式关闭,现在需要删除不起作用的延迟订单:

//订单网格在总止损处关闭,删除剩余余额if((State.BuyStopCount>0 && State.BuyStopCount<OrdersAmount && State.BuyCount==0) ||(State.SellStopCount>0 && State.SellStopCount<OrdersAmount && State.SellCount==0)){//删除待处理订单DeletePendingOrders();}

在这里,我们首先检查是否有任何延期订单;如果有,我们将检查它们的数量是否小于原始值,也就是说,从网格构建的那一刻起,它们中的一些就起作用了。现在,如果没有公开的市场订单,我们可以得出结论,它们是以止损收盘的。因此,剩余的待处理订单是无效的,必须删除。

在代码的以下部分中,只有在没有市场订单或延迟订单的情况下,我们才决定构建新网格:

//如果形成了新的条形图,请使用getSignal()查询指示器//并将结果写入tradesignal变量int TradeSignal=GetSignal();//新酒吧开张时没有延期订单或市场订单if(State.BuyCount==0 && State.SellCount==0 && State.BuyStopCount==0 && State.SellStopCount==0){//如果形成了交易信号,我们将构建一个给定类型的金字塔if(TradeSignal!=EMPTY)BuildPyramid(TradeSignal);//退出,因为进一步处理没有意义,因为要么根本没有搜查令,要么我们刚刚建造了一座金字塔。return;}

使用有关打开订单的信息,您还可以设计更复杂的条件。下一个代码将删除网格,如果自创建以来形成了更多的网格AfterSignaBarsCount酒吧或形成相反的信号:

//如果自网格创建以来形成了更多的Aftersignabarscount条,//或形成相反的信号,删除网格if((State.BuyStopCount>0 && State.BuyCount==0 &&((AfterSignaBarsCount>0 && State.BuyStopStart>0 && iTime(_Symbol,_Period,0)-State.BuyStopStart>=BarsInSeconds) || TradeSignal==OP_SELL))||(State.SellStopStart>0 && State.SellCount==0 &&((AfterSignaBarsCount>0 && State.SellStopStart>0 && iTime(_Symbol,_Period,0)-State.SellStopStart>=BarsInSeconds) || TradeSignal==OP_BUY))){//删除待处理订单DeletePendingOrders();return;}

功能GetSignal()

此函数轮询指示器并返回:OP_BUY(0–买入信号),如果随机进入超卖区域(从上到下穿过较低的信号水平);OP_SELL(1)当随机进入超买区(从下到上穿过顶部信号电平)时,销售信号。如果信号未形成,函数将返回EMPTY(-1):

//+------------------------------------------------------------------+函数返回:?//opu buy(买入信号),如果随机进入区域→//超卖(跨越较低信号水平)。(从上到下)/–OPu SELL(待售信号),如果随机进入区域›超买(跨越信号层)(自下而上)没有空信号。//+------------------------------------------------------------------+int GetSignal(){//在索引为1的酒吧中获得随机值double StochasticOnFirstBar=iStochastic(_Symbol,_Period,StochasticPeriod,1,StochasticSlowing,MODE_SMA,0,MODE_MAIN,1);//随机从上到下穿过较低的信号电平if(StochasticOnFirstBar<StochasticLevelDn){//为了节省资源,我们只在索引为2的酒吧获得随机值如何确定第一部分条件得到满足if(iStochastic(_Symbol,_Period,StochasticPeriod,1,StochasticSlowing,MODE_SMA,0,MODE_MAIN,2)>StochasticLevelDn)return(OP_BUY);}//随机从下到上穿过顶部信号电平if(StochasticOnFirstBar>100-StochasticLevelDn){//为了节省资源,我们只在索引为2的酒吧获得随机值如何确定第一部分条件得到满足if(iStochastic(_Symbol,_Period,StochasticPeriod,1,StochasticSlowing,MODE_SMA,0,MODE_MAIN,2)<100-StochasticLevelDn)return(OP_SELL);}在任何情况下,返回emptyreturn(EMPTY);}

功能BuildPyramid()

如果发送到该函数的信号参数等于“买入停止”参数,则此函数将构建一个挂起订单金字塔。OP_BUY,如果信号值为0,则传销停止传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销传销OP_SELL.

网格的构建距离等于买入止损订单的DISTANCETOGRID输入参数与卖出止损网格的要约值。如果此值小于最小允许值,则在给定字符的卖出停止距离上构建网格。

如果假设每个新订单的批数或从上一个订单发出的步数递增变化,则会调用服务功能。GetProgressionMember()此函数返回算术或几何级数的第n项。

//+------------------------------------------------------------------+//–从待办订单中构建金字塔›//如果signal参数等于opu buy,则buy stop。//如果signal参数等于opu sell,则sell stop//距离当前价格的距离›//+------------------------------------------------------------------+void BuildPyramid(int signal){设定正确的Stop Level值int StopLevel=GetTrueStopLevel();//如果建造金字塔的距离小于停止水平,//将其设置为stop levelint Distance=(DistanceToGrid<StopLevel)?StopLevel:DistanceToGrid;int Com;//待处理订单类型//在安装网格之前检查批量是否正确if((Lots<SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN)) ||(Lots>SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX))){打印(“起始交易量值不正确,金字塔将无法建造”)。return;}if(LotsMultMode>mlNone){double LastOrderLots=NormalizeDouble(GetProgressionMember(LotsMultMode,OrdersAmount-1,((LotsMultMode==mlArithmetical)?LotsMultiplicatorAr:LotsMultiplicatorGm),Lots),2);if((LastOrderLots<SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN)) ||(LastOrderLots>SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX))){打印(“检测到不正确的交易量值,不会建造金字塔”)。return;}}//在发布最后一个金字塔订单之前检查步骤是否正确if((GridStep<StopLevel)  ||(StepMultMode>mlNone &&(int)GetProgressionMember(StepMultMode,OrdersAmount-1,((StepMultMode==mlArithmetical)?StepMultiplicatorAr:StepMultiplicatorGm),GridStep)<StopLevel)){打印(“发现不正确的步骤值,金字塔将无法建造”)。return;}让我们看看停止损失的正确性。if((int(StopLossRatio*GridStep)<StopLevel) ||(StepMultMode>mlNone &&int(StopLossRatio*GetProgressionMember(StepMultMode,OrdersAmount-1,((StepMultMode==mlArithmetical)?StepMultiplicatorAr:StepMultiplicatorGm),GridStep))<StopLevel)){打印(“停止丢失值不正确,金字塔将无法建造”)。return;}检查是否正确Take Profitif(TakeProfit>0 && TakeProfit<StopLevel){print(“一般获利”,takeprofit,“小于最小允许值”,stoplevel,“金字塔不会建造”);return;}double Price, Sign, Step=GridStep;MqlTick tick;if(!SymbolInfoTick(_Symbol,tick))return;//确定起始价格和待售订单类型if(signal==OP_BUY){Price=tick.ask;Sign=1.0;Com=OP_BUYSTOP;}elseif(signal==OP_SELL){Price=tick.bid;Sign=-1.0;Com=OP_SELLSTOP;}elsereturn;买入止损订单高于价格,卖出止损订单低于价格Price+=Sign*Distance*_Point;for(int i=0; i<OrdersAmount; i++){//计算下一个网格订单的批量double TradeLots;//根据进度类型增加或减少当前批量if(LotsMultMode>mlNone)TradeLots=GetProgressionMember(LotsMultMode,i,((LotsMultMode==mlArithmetical)?LotsMultiplicatorAr:LotsMultiplicatorGm),Lots);elseTradeLots=Lots;double StopLoss,StopLossInPoints;//按步骤的比例计算stoplossStopLossInPoints=StopLossRatio*Step;//买入止损低于开盘价,卖出止损高于开盘价。StopLoss=Price+EMPTY*Sign*StopLossInPoints*_Point;五次尝试发布另一个金字塔订单for(int j=0; j<5; j++){if(OrderSend(_Symbol,Com,NormalizeDouble(TradeLots,2),NormalizeDouble(Price,_Digits),Slippage,NormalizeDouble(StopLoss,_Digits),0,NULL,Magic,0,OrderColor(Com))!=EMPTY)break;elseSleep(1000);}//根据进度类型增加或减少当前步骤if(StepMultMode>mlNone)Step=GetProgressionMember(StepMultMode,i,((StepMultMode==mlArithmetical)?StepMultiplicatorAr:StepMultiplicatorGm),GridStep);买入止损订单高于价格,卖出止损订单低于价格Price+=Sign*Step*_Point;}//我们为刚刚发布的延期订单安装通用胶粘if(TakeProfit>0){double tp=NormalizeDouble(Price+Sign*(TakeProfit<StopLevel?StopLevel:TakeProfit)*_Point,_Digits);for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) && OrderMagicNumber()==Magic && OrderSymbol()==_Symbol && OrderType()>OP_SELL){if(NormalizeDouble(OrderTakeProfit(),_Digits)!=tp){for(int j=0; j<5; j++)if(OrderModify(OrderTicket(),OrderOpenPrice(),OrderStopLoss(),tp,0))break;elseSleep(1000);}}}}

功能CommonStopLoss()

跟踪挂起的订单激活和修改市场订单止损的关键功能。每一个新激活的市场订单都是金字塔起始价格最远的。因此,当出现止损时,所有以前的市场订单都被设置为新订单的止损水平:

//+------------------------------------------------------------------+//更新金字塔市场订单的总成交量,即..//将所有市场订单的100%设置在一个水平上。//+------------------------------------------------------------------+void CommonStopLoss(state &Orders){int type;bool refresh;double sl;//市场订单中只有买入,这意味着最初有一个金字塔的待售买入停止订单if(Orders.BuyCount>0 && Orders.SellCount==0){type=OP_BUY;//最高买入订单的100%与最低买入订单的100%不匹配,因此必须//将所有订单设置为最高止损,换句话说,将“拉”到收支平衡中refresh=(NormalizeDouble(Orders.BuyTopSL,_Digits)!=NormalizeDouble(Orders.BuyBottomSL,_Digits));sl=NormalizeDouble(Orders.BuyTopSL,_Digits);}else//市场订单中只有卖出订单,这意味着最初有一个金字塔的待售止损订单if(Orders.BuyCount==0 && Orders.SellCount>0){type=OP_SELL;//最高卖出订单的止损不等于最低卖出订单的止损,这意味着必须//将所有订单设置为最低止损,换句话说,将“拉”到收支平衡中refresh=(NormalizeDouble(Orders.SellTopSL,_Digits)!=NormalizeDouble(Orders.SellBottomSL,_Digits));sl=NormalizeDouble(Orders.SellBottomSL,_Digits);}elsereturn;if(refresh)for(int i=OrdersTotal()-1; i>=0; i--)if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) && OrderMagicNumber()==Magic && OrderSymbol()==_Symbol && OrderType()==type){if(NormalizeDouble(OrderStopLoss(),_Digits)!=sl){for(int j=0; j<5; j++)if(OrderModify(OrderTicket(),OrderOpenPrice(),sl,OrderTakeProfit(),0))break;elseSleep(1000);}}}

您可以在以下动画中更直观地看到函数的工作原理:

处理程序OnTick()

完成交易专家的创建后,根据流程图对所述块的调用算法进行编码。将生成的代码放入处理程序中OnTick():

//+------------------------------------------------------------------+//| Expert tick function                                             |//+------------------------------------------------------------------+void OnTick(){state State;//获取有关顾问打开的订单的信息并将其放入状态结构RefreshState(State);查看新酒吧是否已建成if(IsNewBar()){//如果形成了新的条形图,请使用getSignal()查询指示器//并将结果写入tradesignal变量int TradeSignal=GetSignal();//新酒吧开张时没有延期订单或市场订单if(State.BuyCount==0 && State.SellCount==0 && State.BuyStopCount==0 && State.SellStopCount==0){//如果形成了交易信号,我们将构建一个给定类型的金字塔if(TradeSignal!=EMPTY)BuildPyramid(TradeSignal);//退出,因为进一步处理没有意义,因为要么根本没有搜查令,要么我们刚刚建造了一座金字塔。return;}//如果自网格创建以来形成了更多的Aftersignabarscount条,//或形成相反的信号,删除网格if((State.BuyStopCount>0 && State.BuyCount==0 &&((AfterSignaBarsCount>0 && State.BuyStopStart>0 && iTime(_Symbol,_Period,0)-State.BuyStopStart>=BarsInSeconds) || TradeSignal==OP_SELL))||(State.SellStopStart>0 && State.SellCount==0 &&((AfterSignaBarsCount>0 && State.SellStopStart>0 && iTime(_Symbol,_Period,0)-State.SellStopStart>=BarsInSeconds) || TradeSignal==OP_BUY))){//删除待处理订单DeletePendingOrders();return;}}//有未开仓的买入或卖出订单,意味着金字塔已经建成,//有些延期订单起作用了if(State.BuyCount>0 || State.SellCount>0){//如果需要,更新金字塔市场订单的总止损CommonStopLoss(State);return;}//订单网格在总止损处关闭,删除剩余余额if((State.BuyStopCount>0 && State.BuyStopCount<OrdersAmount && State.BuyCount==0) ||(State.SellStopCount>0 && State.SellStopCount<OrdersAmount && State.SellCount==0)){//删除待处理订单DeletePendingOrders();}}

历史测试

为了测试我们的顾问的工作能力,并评估他的交易算法,我们将做一些测试历史数据我们将在欧元兑美元上测试。质量99.9%.测试期为2017年1月1日至2020年1月1日。H1时间(1小时)初始存款10000件。

默认设置:固定批量0.01,网格距离100 p,间距固定300 p,在15个订单的网格中,随机设置为标准,超卖级别为20,止损比为1.0,我们将等待网格运行,直到反向信号出现。

默认情况下,顾问不会对结果视而不见,也不会合并结果。让我们在指标设置中稍微“推高”超卖和超买水平,将StochaSticleveldn设置为10而不是20。入口将减少,但可能更准确。我们不会触摸其他设置。

这一次,结果明显更好,因为我们只改变了一个参数。

让我们看看顾问在算术级数中如何增加批量。为此,在设置中选择“算术”模式,参数LotsMultiplicatorAr(算术级数差)将值设置为0.01。我们可以保留零,在这种情况下,参数自动等于初始批量,我们的值为0.01。因此,金字塔的第一个订单是0.01,最后一个订单是0.15。我们得到以下图片:

现在,我们将算术级数从0.15减少到0.01。对于此lotsmultiplicatorar参数,我们将其设置为负值-0.01,并将起始批量设置为0.15:

根据趋势交易的特点,减少批量的选项似乎更可取,因为随着趋势的减弱,我们也减少了“溢出”的数量。当然,在这种情况下,利润预期会更高,但下降也会相应增加。

在第一近似中进行的测试显示了整个方法的有效性,特别是顾问的有效性。然而,最终的结论只能从贸易的结果中得出。实际计算无论如何,识字优化参数不会影响顾问。

结论

我们创建的顾问正在实施金字塔战略的数学版本。然而,这种方法的更复杂的变体很难形式化。这是一个问题的代价:所花费的努力是否考虑到金字塔结构的许多细微差别,例如,在交易水平或软件方面技术分析图是否有足够的网格以优化的步骤来帮助顾问?

金字塔化的优点也有严重的缺点。通过金字塔订单可以很好地赚钱的强大趋势并不经常发生。在长笛中,这种策略导致了金钱的损失。因此,识别趋势运动的任务仍然是金字塔形交易中的主要任务,无论是手动交易还是自动交易。

在我们的顾问中,按照趋势构建金字塔、跟踪市场订单和固定利润的机制得到了充分的阐述。在这个工具的基础上,您可以通过处理顾问的信号部分或计算网格参数来创建自己的金字塔主题变体,例如,考虑到当前波动性.

辅导员的源代码提供了详细的、几乎逐行的注释,因此可以被视为独立的学习材料。

从课堂上下载顾问源

论坛主题

尊敬的尤里·洛塞夫AKA LSV107
Tlap.com



MQL4教程 ,,,,,

MQL4:数组和循环

你好,亲爱的外汇程序员们!

在今天的课程中,根据您的要求,我们将详细介绍如何使用MQL4数组和循环。

循环用于重复代码段,数组用于存储无限数量的数据结构,然后在循环中进行处理。

让我们开始循环。由于数组元素通常使用循环来访问,所以我们首先学习如何使用它们,然后再访问数组。

MQL4中的while循环语法如下:

while(<条件>){“循环体”;}

在这种情况下,循环体将在括号中的真实条件下运行。如果这个条件在循环完成时被证明是错误的,则循环体不会执行一次。举个例子,在终端日志从1到5的整数,每个从新行开始。代码将是这样的:

int i = 1;while (i <= 5){Print( i );i++;}

根据问题的条件,我们首先定义了整数变量i,并将其设置为1。循环算子while检查变量是否小于或等于5i如果是,则执行循环体:将值打印到日志中并增加1。第一次迭代后,数字“1”出现在日志中,变量的值出现在日志中i 等于2。经过几次迭代,变量i等于6,条件i<=5错了因此,循环体将不再运行。

循环for

这种循环的工作原理与已经考虑过的相同。while在这里,只要一个条件是真的,循环体就存在。但是,在运算符中,可以指定在循环开始前执行的操作(例如,初始化计数器变量),在每个迭代完成后(增加或减少计数器)。这种循环主要用于我们事先知道迭代次数的情况。MQL4中for循环的语法如下:

for(表达式1;表达式2;表达式3){“循环体”;}
  • 表达–周期初始化通常是迭代计数器的声明和初始化;
  • 表达-循环持续的条件,只要它是真的,循环体就完成了;
  • 表达–每次迭代后计算的表达式通常会更改迭代计数器。

看看循环for在下面的例子中,我们可以找到1到1000之间的整数和。

int sum=0; // 数和变量for(int i=1; i<=1000; i++) sum+=i; // 再加上当前金额打印(“1到100的和:”,i);

我们宣布了一个变量sum为了将当前数和写入其中,并将其与零值进行指数化。

在for变量语句中i它是一个迭代计数器。我们将其宣布为初始值“1”。我们需要循环执行1000次,也就是说,只要一个变量i小于或等于1000。循环延续条件为i<=1000每次迭代后,我们必须将计数器放大1。用分号写i++.

循环体只有一个算子sum+=i,相当于记录sum=sum+i因此,这里不需要运算符括号“{}”。在循环体中,sum变量的值增加到变量的值。i,在当前迭代中包含从1到1000的另一个数字。经过一千次循环后,变量sum它将包含从1到1000的所有整数之和。

运算符中的任何三个或所有表达式for(表达式1;表达式2;表达式3)可能缺席。不能只省略分号分隔表达式,这意味着必须始终包含两个字符“;». 例如,记录for(;;)这是一个无限的循环。表达表达可以由逗号运算符“,”组合的多个表达式组成。

让我们解决这个问题。让我们在一个循环中同时打印1到5和5到1的数字。循环体只能由一个函数调用组成。Print().

for(int i=1, j=5; i<=5; i++, j--)Print(i," ",j);

如你所见,循环体执行5次,其中变量 i 从1到5,变量j从5到1。执行结果如下:

5 14 23 32 41 5

请注意,随着增量,即增加变量i在这里,我们使用减法,也就是说,把变量减少一个j.

让我们在日志中记录从1到5的五个整数循环的输出如下:

int n=1;  for(;n<=5;){Print(n);n++;}

在这里,我们宣布了一个变量-计数器到循环运算符,并将其增量放在循环体中。

循环计数器for可以增加或减少任何值,而不仅仅是1。这样就可以一步一步地实现循环。例如,在日志中打印从1到10的所有奇数。为此,我们将每次迭代后的计数器增加2,从1开始:

for(int i=1; i<=10; i+=2)Print(i);

在这种情况下,计数器变量依次为1、3、5、7、9、11,并在日志中显示1、3、5、7、9。

循环do while

如前所述,如果循环条件为c(while)最初是假的,然后循环体永远不会完成。然而,有些任务至少需要一次循环。为此,有一个循环,无论条件如何,身体至少会运行一次。循环语法do while或者,正如人们所说,MQL4中的后条件循环有以下形式:

do{“循环体”;}while(<条件]);

使用循环从1到5输出整数来完成相同的任务do while:

int i=1;do{Print(i);i++;}while(i<=5);

首先,我们宣布了一个变量-计数器i并根据任务条件将其值1初始化。然后,循环体立即完成。在日志中显示变量的值i,当时等于1,然后是变量的值i增加了一个,然后检查了条件。i<=5因为2小于5,所以循环一直持续到变量的值i不等于六。请注意,我们在将计数器的值输入日志后生成循环体中计数器的增量。如果我们在日志中显示之前这样做,则必须降低计数器的初始值并更改条件。在这种情况下,循环将是这样的:

int i=0;do{i++;Print(i);}while(i<5);

在MQL4语言中,增量操作可以直接在函数调用中执行,并且必须记住后缀增量(i++)在表达式中使用变量后立即将变量值增加1,前缀增量(++i)在表达式中使用变量之前,将变量值增加一个。现在我们的例子可以写得更紧凑:

int i=1;doPrint(i++);while(i<=5);

这里的功能Print()在日志中显示变量的值i接下来是放大。i每单位(增量):

int i=0;doPrint(++i);while(i<5);

调用函数时Print()首先,变量增加i一个,然后一个值i出现在杂志上。

让我们把这个例子更难。参赞,将其附加到图表后,它不会立即开始处理输入的抽签,而是在我们在几秒钟内指定的一段时间后开始处理。同时参赞我必须用图表上的注释告诉你还有多少秒才能运行。显然,这种延迟必须放在处理程序内部。OnInit(),因为此功能在初始化顾问时执行。

//+------------------------------------------------------------------+//| Expert initialization function                                   |//+------------------------------------------------------------------+int OnInit(){uint begin=gettickcount(),//记住gettickcount()函数的值passed_seconds,//秒过去了pause=10;            // 秒间歇Comment("");              // 清除图形上的注释,输出一个空字符串do{passed_seconds=(GetTickCount()-begin)/1000;// 发射几秒钟后comment(“顾问将通过:”,passed_seconds开始工作);}while(passed_seconds<pause);// 循环体在数量上运行//过去的秒数少于秒数Comment("");              // 清除图形上的注释,输出一个空字符串return(INIT_SUCCEEDED);}

在这个代码中,我们实现了延迟输出的剩余时间,以秒为单位的循环开始do while功能GetTickCount()返回系统启动后的毫秒数。我们用它来计算过去和现在的秒数。

操作员breakcontinue

操作员breakcontinue可用于任何类型的循环体。

在操作员的帮助下break你可以立即停止循环体的运行,而不需要任何额外的条件。它将控制传递给循环后的第一个操作员。考虑我们熟悉的在终端日志中输出1到5整数的问题的实现:

int i=1;while(true){Print(i);i++;if(i>5) break;}

在这里,我们有一个无限的循环。while,因为括号中的条件总是真的。循环体将当前变量值输出到日志中i和它的增殖。一旦意义i6,算子break停止循环。这里有一个重要的看法。如果一个循环涉及大量迭代,或者一个错误导致循环无限,程序可能不会响应“挂起”。为了避免这种情况,在循环的条件下添加函数调用IsStopped()真理的回归(true)如果终端发出命令以完成MQL4程序(例如,在运行MQL4时单击“停止”按钮)测试仪). 考虑到这一点,我们的例子可以这样写:

while(!IsStopped){Print(i);i++;if(i>5) break;}

这仍然是相同的无限循环,但它一直运行,直到被运算符打断。break或者直到终端的命令来完成程序。

需要注意的是,MQL4允许函数内部的循环被运算符打断。return例如:

void PrintNumber(){int i=1;while(!IsStopped){Print(i);i++;if(i>5) return;}}

在这种情况下,操作员return中断循环并将控制转移到调用函数的程序位置PrintNumber().

算子continue中断当前循环迭代并开始新循环。循环体中跟随算子的所有算子continue,不会执行。为了说明操作员的工作,我们稍微修改了我们的问题,将1到5个整数输入日志。所有的数字,除了2。

for(int i=1; i<=5; i++){if(i==2) continue;Print(i);}
12345

数字“2”的输出将被忽略。

算子continue如果循环体足够大,则使用方便。您可以跳过所有后续运算符并进行新的迭代。

地块

数组几乎存在于所有编程语言中,MQL4也不例外。从本质上讲,数组是一组具有相同名称的变量,但每个变量都有自己的唯一编号。例如,我们需要三个整数变量。我们可以这样宣布:

int Var1, Var2, Var3;

在这种情况下,每个变量都可以用其唯一的名称访问。例如,给变量分配值:

Var1=35;Var2=8;Var3=21;

另一方面,变量可以指定为一个由三个元素组成的数组:

int Var[3];

这些变量的名称和数组中的编号应该是:

Var[0]=35;Var[1]=8;Var[2]=21;

数组中变量的编号称为索引(来自拉丁语)index食指)。在声明数组时,方括号中表示数组的大小(元素数),在访问变量时,表示元素索引。

请注意,数组中的元素编号从零开始,而不是从1开始。这种情况通常会导致错误和问题,特别是在初学者中。数组与普通变量一样,是一个以地址开头的内存区域。在数组的情况下,对索引中每个元素的访问指定为从开始的偏移。然后,第一个元素的地址与整个数组的地址相同,即偏移量为零。第二个元素的地址是第一个元素的地址加上第一个元素大小的偏移量。第二个地址是两个位移,等等。D.其他事项

静态和动态数组

静态数组的大小在编译阶段设置,并且在程序执行过程中不会更改较大的数组。静态数组声明示例:

int Array[10];

在这里,我们宣布了一个由10个整数元素组成的数组,编译器将为其分配内存。

在执行过程中可以调整大小的数组称为动态数组。随着数组大小的变化,内存要么被分配,要么被动态释放。动态数组声明示例:

int Array[];

因此,如果在方括号中没有指定数组的大小,则它将声明为动态数组。在这种情况下,我们宣布了一个动态的整数元素数组。要使用数组,必须使用函数指定数组的大小。ArrayResize():

ArrayResize(Array,10);

现在数组Array它由10个元素组成,索引从0到9。

此函数有第三个可选参数reserveu size。您可以使用它指定“带备用”数组的大小。例如,函数的第一个调用形式为ArrayResize(Array, 10, 1000)将数组大小增加到10个元素,但物理内存将分配给数组,就像它包含1010个元素一样。也就是说,1000个元素下的内存被存储在储备中。现在,如果数组大小在1010范围内增减,则不会有物理内存分配。如果将大小增加到1011,则会分配额外的1000个备用项,以此类推。由于物理内存分配是一个相当缓慢的过程,如果程序中的阵列大小经常变化,则函数的第三个参数ArrayResize()这将是非常有用的。

在MQL4中,数组的所有元素在声明为零时初始化:数字数组为零,字符串为空。对于逻辑值数组,这是false但是,在某些情况下,数组必须用特定的值进行同化。对于静态数组,可以在声明数组时按以下方式进行:

int Numbers[5] = {3, 21, 72, 1, 8};

在这里,索引为0到4的五个元素中的每个元素在大括号中按顺序分配值。您可以以类似的方式将数组的所有元素同化为一个值:

int Numbers[5] = {82};

所有五个元素的值都为82。

不能以这种方式初始化动态数组。即使您声明数组:

int Numbers[] = {3, 21, 72, 1, 8};

编译器将其定义为静态,并根据大括号中的值数自动设置大小。

可以使用函数初始化动态数组ArrayInitialize()或者ArrayFill()第一个值填充整个数组,第二个值只能填充数组的一部分。但这样就可以初始化或只填充简单类型的数组,例如字符串需要在循环中填充。在索引中搜索所有元素并为每个元素分配所需的值。如何做到这一点,我们将在下一节讨论。

在数组中使用循环

由于数组元素是按顺序编号的,因此使用循环处理数组非常方便:对数组的所有元素或其部分执行操作,搜索具有所需特征的元素,根据某些标准对数组中的元素进行排序。最常见的应用于数组的操作是其值循环中的过度,称为数组迭代。

让我们解决这个问题。

有一个单词列表:记忆, 汽车,比萨饼,水,剪刀,猫,电池,鱼竿,雷声,桌子,旗帜,电话,雨伞,文件搜索所有超过五个字符的单词,并在日志中显示所有这些单词以及这些单词的总数。

要处理单词,请创建一个字符串数组并将单词写入其中:

字符串[]={“机器”、“比萨饼”、“水”、“剪刀”、“猫”、“电池”、“鱼竿”、“雷霆”、“桌子”、“旗帜”、“电话”、“雨伞”、“文档”};int size=ArraySize(Words);int n=0;for(int i=0; i<size; i++){if(StringLen(Words[i])>5){Print(Words[i]);n++;}}if(n>0)print(字数大于5:“,n);

在这个例子中,我们需要一个额外的变量size,我们将用函数找到的ArraySize()数组大小。在每次迭代中调用函数将其值与计数器进行比较是不合理的。在这种情况下,绕过数组元素的方向并不重要。因此,您可以通过按相反的顺序遍历数组来避免额外的变量:

int n=0;for(int i=ArraySize(Words)-1; i>=0; i--)if(StringLen(Words[i])>5){Print(Words[i]);n++;}if(n>0)print(字数大于5:“,n);

下一步任务:从动态整数数组中删除具有指定索引的元素。

double Array[]; // 准备数组来演示算法的工作原理int size=21; // 让我们把它设置为21个元素。int removed_element=17; // 删除项目索引打印(“新大小”,arrayresize(array,size));for(int i=0;i<size;i++)//用100到120的数字填充数组Array[i]=i+100.0;if(removedu element<size)//要删除的元素的索引不超出数组范围{print(“将删除索引元素”,removedu element,“value”,array[removedu element]);for(int i=removed_element+1;i<size;i++)//将删除的所有元素向左移动一个Array[i-1]=Array[i];size=ArrayResize(Array,size-1); // 将数组大小减少1,从而消除不必要的最后一个元素打印(“新数组大小:”,大小);for(int i=size-1;i>=0;i——)//将数组的所有元素记录在日志中{Print("Array[",i,"]=",Array[i]);}}elseprint(“要删除的元素的索引位于数组之外”);

数组传递到函数

数组只能通过引用传递到函数,这意味着只将现有数组的地址传递给函数,而不是其副本。函数不能返回数组本身。它只对整个数组或其单个元素执行操作。因此,如果函数的参数是数组,则必须在其名称之前写一个字符。«&»(ampersand)表示参数是通过引用传递的,名称后面有空的方括号“[]”,表示参数是数组。

让我们使用一个函数来解决上一个问题,该函数将从引用的数组中删除具有指定索引的元素。

double Array[]; // 让我们准备一个数组来演示我们未来的功能。int size=21;ArrayResize(Array,size);for(int i=0;i<size;i++)//用100到120的数字填充数组Array[i]=i+100.0;if( RemoveFromArray(Array, 17) )for(int i=ArraySize(Array)-1; i>=0; i--)//将数组的所有元素记录在日志中Print("Array[",i,"]=",Array[i]);让我们写一个函数来删除指定的元素。//+------------------------------------------------------------------+//^删除具有指定索引的动态数组元素›//如果删除成功,则返回“true”,否则。//返回false。//+------------------------------------------------------------------+bool RemoveFromArray(double &array[], int index){int size=ArraySize(array);if(index<size)//要删除的项目的索引不超出数组范围{for(int i=index+1;i<size;i++)//将删除的所有元素向左移动一个array[i-1]=array[i];size=ArrayResize(array,size-1); // 将数组大小减少1,从而消除不必要的最后一个元素return(true); // 返回真理,因为删除成功}else{print(“要删除的元素的索引位于数组之外”);return(false);// 返回错误,因为删除失败}}

如果出于某种原因,您不想更改源数组,并且要使用数组的副本执行所有操作,则可以使用arraycopy()函数。它将一个数组的内容复制到另一个数组。接收器数组自然必须声明,并且与源数组具有相同的类型。由于复制操作需要一段时间,请在任务中真正需要时尝试使用此功能。

多维数组

每个元素都有一个索引的数组称为一维数组。在此之前,我们只考虑一维数组。如果数组的每个元素都有一个以上的索引,那么我们正在处理一个多维数组。

视图记录:

int Numbers[5][4];

声明一个二维数组,换句话说,一个由四个元素组成的五个数组的数组。在视觉上,二维数组可以表示为表,在这种情况下,由五行和四列组成。二维数组的元素可以表示为表的单元格。单元格相交的字符串和列是数组元素的索引。例如,表达式Numbers[2][1]=48可以这样解释:数字“48”被写入第二列第三行表的单元格中(请记住,每个数组维度的索引都从零开始)。

因为每个数组元素Numbers两个索引必须通过两个循环来执行,一个循环嵌套在另一个循环中。例如,以下代码将数组中的所有元素设置为“10”,但元素除外。Numbers[2][1]将其设置为“48”:

for(int i=0; i<5; i++)for(int j=0; j<4; j++){if(i==2 && j==1)Numbers[i][j]=48;elseNumbers[i][j]=10;}

MQL4最多支持四个数组维度:

double prices[8][2][5]//一个三维数组的例子double levels[4][3][1][6]//四维数组

多维数组也可以是静态的或动态的。MQL4中多维动态数组的一个重要特征是,它们只能在第一个维度中动态,以下所有维度都是静态的。它们的大小必须在声明数组时显式指定。

double Prices[][2][5]; // 正确声明三维动态数组double Prices[][][5]; // 错误公告

因此,通过函数ArrayResize()只能更改数组第一维度的大小:

double Prices[][2][5];int new_size = ArrayResize(Prices,10);

需要注意的是,在这种情况下,函数ArrayResize()返回数组元素的总大小,即new_size等于100(10*2*5)。

实际上,您必须面对的大多数任务不太可能需要超过2的数组。例如,二维动态数组非常适合处理表数据,因此比其他多维数组更常见。然而,有时需要能够在两个维度中更改二维数组的大小。例如,在执行程序时,我们可能需要数组Numbers[Rows][Columns]哪里RowsColumns-这些是变量,但我们只能用变量指定第一维度的大小。您可以使用一个简单的技巧来绕过此限制:将数组的两个维度折叠为一个。也就是说,宣布一维动态数组并将其大小设置为乘积Rows*Columns,第二个维度用一个简单的公式来定义row*Columns+column哪里rowcolumn–虚拟表中单元格的坐标,并且Columns这是第二维度的大小(列的数量)。

为了更方便地使用一维动态数组作为二维数组,让我们写一个“包装”作为一个结构,我们将存储数组本身及其属性。以及使用两个索引访问数组元素的方法。

struct int2DArray{private:int               rows;                                                                // 行数int               columns;                                                             // 列数int               Values[];                                                            // 一维动态数组int               GetIndex(int row, int col) {return(row*columns+col);};               // 返回数组中单元格的索引public:int               Create(int row, int col);                                            // 设置存储单元所需的数组大小int               Get(int row, int col) {return(Values[GetIndex(row,col)]);};          // 返回单元格内容(行,col)void              Set(int row, int col, int value) {Values[GetIndex(row,col)]=value;}; // 将值写入单元格(row,col)int               AddRows(int amount);                                                 // 将字符串添加到二维数组int               Rows() {return(rows);};                                              // 返回数组中的行数int               Columns() {return(columns);};                                        // 返回数组中的行数};//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+int int2DArray::Create(int row, int col){int size=row*col;if(size<=0)return(EMPTY);rows=row;columns=col;ArrayFree(Values);ArrayResize(Values,size);ArrayInitialize(Values,0);return(size);}//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+int int2DArray::AddRows(int amount){int new_size=ArrayResize(Values,ArraySize(Values)+amount*columns);rows+=amount;return(new_size);}//+------------------------------------------------------------------+//| Expert initialization function                                   |//+------------------------------------------------------------------+int OnInit(){//宣布结构变量:int2DArray Array;//将数组大小设置为10行和5列:int size=Array.Create(10,5);//所有单元格均为零,其总数为50:print(“数组大小=”,size);//用非零值填充单元格(0;3),(5;0),(8;4)Array.Set(0,3,9);Array.Set(5,0,18);Array.Set(8,4,3);//确保我们在set方法中指定索引的单元格已填充:for(int i=0; i<Array.Rows(); i++)for(int j=0; j<Array.Columns(); j++){PrintFormat("Array[%d,%d]=%d",i,j,Array.Get(i,j));}//在数组中添加2行。因为我们有五列的总数,所以在物理上,2*5=10个元素将添加到一维数组的末尾。Array.AddRows(2);//用一些非零值填充单元格(10;3),(11;4)Array.Set(10,3,4);Array.Set(11,4,31);//确保我们在set方法中指定索引的单元格已填充:for(int i=0; i<Array.Rows(); i++)for(int j=0; j<Array.Columns(); j++){PrintFormat("Array[%d,%d]=%d",i,j,Array.Get(i,j));}return(INIT_SUCCEEDED);}

这种结构也可以用于其他类型的数组。不幸的是,无法创建适合任何类型数组的通用结构。

“一个错误”

循环中最常见的错误之一for或者,如果他们使用计数器,则称为“未计算单位错误”或“每单位错误”。这是由于不正确地使用比较运算符造成的。例如,循环

for(int i=1; i<=5; i++)Print(i);

5次,循环

for(int i=1; i<5; i++)Print(i);

只做了4次。在第二种情况下,值为5的变量i条件i<5它将是虚假的,这意味着循环的身体将不满足这个值。重要的是要记住,一个循环只有当它的持续条件是真的时才会发生。“未计算单位错误”在数组上下文中是最大的危险。由于数组元素索引从零开始,因此最后一个元素的索引比数组本身小1。如果由于“未计数单位”的原因,数组迭代循环比所需的迭代循环多执行一次,则程序将因严重错误而停止工作。这就是所谓的“超越数组”:

int Numbers[];int size=ArrayResize(Numbers,100);for(int i=0;i<=size;i++)//必须使用严格的i<size条件Print(Numbers[i]);

这里有变量size相当于数组的大小,即100。在循环的最后一次迭代中,计数器i它将设置为100,但数组中没有索引元素,后者的索引为99。因此,循环的条件必须是严格的——i<size.

使用链接数组快速排序复合数据

考虑使用数组以数据库风格构建索引的一个有趣的例子。MQL4有一个功能ArraySort()在第一维度上对数组进行排序。例如,使用函数对结构或对象的数组进行排序ArraySort()你不能。似乎唯一的解决方案是编写一个排序函数,与结构数组元素的物理交换位置。但是你可以创建一个二维动态数组,将所需的值复制到该数组中即可进行排序,并创建一个元素索引,这些数据在源数组中所属。通过按第一维度对这样的数组进行排序,我们可以通过第二维度中的索引访问源数组中所需的元素。不会对源数组进行物理排序。让我们以一个具体的例子来考虑这种排序。只有我们才不会对结构数组进行排序,而是对打开的订单信息进行排序,甚至不将其复制到数组中。

我们将排序标准定义为列表:

enum field{forderopenttime,//开放时间fordertype,//类型forderlots,//体积forderopenprice,//开盘价格fOrderStopLoss,   // S/LfOrderTakeProfit, // T/Pfordercommission,//委员会forderswap,//交换利润};

让我们写一个函数,它根据传递的排序标准构建索引:

//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+void BuildIndex(field criterion, int &out[]){double Index[][2];int size=OrdersTotal(); // 订单总数ArrayResize(Index,size);for(int i=size-1;i>=0;i——)//搜索所有订单if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)){Index[i][1]=OrderTicket();switch(criterion){case fOrderOpenTime:Index[i][0]=double(OrderOpenTime());break;case fOrderType:Index[i][0]=OrderType();break;case fOrderLots:Index[i][0]=OrderLots();break;case fOrderOpenPrice:Index[i][0]=OrderOpenPrice();break;case fOrderStopLoss:Index[i][0]=OrderStopLoss();break;case fOrderTakeProfit:Index[i][0]=OrderTakeProfit();break;case fOrderCommission:Index[i][0]=OrderCommission();break;case fOrderSwap:Index[i][0]=OrderSwap();break;case fOrderProfit:Index[i][0]=OrderProfit();break;}}ArraySort(Index);// 按第一维度排序ArrayResize(out,size);//按排序顺序将票证复制到传输的数组for(int i=0; i<size; i++)out[i]=int(Index[i][1]);}//+------------------------------------------------------------------+//| Expert initialization function                                   |//+------------------------------------------------------------------+int OnInit(){ /*在调用函数之前,必须声明一个数组,该数组将按排序顺序放置对命令的引用。很明显,这些链接将使用票证-唯一的订单ID。int Tickets[];/*调用函数。作为标准,我们指定订单利润,也就是说,我们将根据订单从最小到最大的当前利润进行排序:*/BuildIndex(fOrderProfit, Tickets);//我们将按利润排序的未平仓订单信息输入日志。//搜索票证数组for(int i = ArraySize(Tickets) -1; i>=0; i--){//选择带有指定票证的订单if(OrderSelect(Tickets[i],SELECT_BY_TICKET)){int digits=int(SymbolInfoInteger(OrderSymbol(),SYMBOL_DIGITS));//创建一个包含所选订单信息的字符串并在日志中显示Print(OrderTicket()," | ",TimeToString(OrderOpenTime())," | ",(OrderType()==OP_BUY)?"buy":"sell"," | ",DoubleToStr(OrderLots(),2)," | ",OrderSymbol()," | ",DoubleToStr(OrderOpenPrice(),digits)," | ",DoubleToStr(OrderStopLoss(),digits)," | ",DoubleToStr(OrderTakeProfit(),digits)," | ",DoubleToStr(OrderCommission(),2)," | ",DoubleToStr(OrderSwap(),2)," | ",DoubleToStr(OrderProfit(),2));}}return(INIT_SUCCEEDED);}

带结果的日志片段:

分隔符后的最右边数字是当前利润。订单如您所见,字符串是按此值排序的。

结论

在本教程中,我们讨论了循环在数组中的使用。同时,他们还记得并巩固了数组和循环本身的基本信息,研究了MQL4语言中实现数组和循环的特性,分析了数组和循环编程的典型例子和方法。

论坛主题

尊敬的尤里·洛塞夫AKA LSV107
Tlap.com

MQL4教程 ,,,

MQL4:使用酒吧,搜索RSI发散

你好,MQL4程序员的同事们!

在今天的教程中,我们将了解如何使用MQL4实现最强大和最可靠的模式之一-发散。为此,我们将编写一个脚本,相对强度指数(RSI)将从技术指标中帮助我们。我们还将研究分析酒吧的工作,并编写脚本来标记历史上的分形。

传统上,交易平台上的价格流在某些时间段分组。在这段时间内收到的所有价格形成一个酒吧,这段时间被称为时间范围。有几个预定义的时间范围从一分钟到一个月。

从广义上讲,酒吧不仅是一个价格周期的图形图像,而且是一个分析元素。所以,指标按比例计算。有相当流行的交易方法,仅基于分析酒吧:这和烛光的分析,和Price Action,其中重复的图形和条形组合作为独立的交易信号。蜡烛组也用于商业建筑水平技术分析图.

算法交易使用条形图很容易正式化任务技术分析例如,建造趋势线或确定发散价格和指示器.

最常见的图形表现形式是所谓的“日本蜡烛”。这是我们用来说明的,我们称之为酒吧。

因此,每家酒吧都有一个开盘价——一个时期开始时的价格;收盘价是该期间收到的最后一个价格;期间的最高和最低价格。

酒吧阵列图表在历史数据的深度称为时间序列。此数组中的编号从右向左,即数组中的索引0(0)表示当前条的数据,该条对应于给定时间段的未完成时间段。时间范围可以通过调用ibars函数或使用预定义的bars变量来获取数组时间序列的大小。

通过以下功能访问特定酒吧的数据:

IOPEN–酒吧开张价格;

ICLOSE–酒吧关闭价格;

Ihigh–酒吧的最高价格;

ILOW:酒吧最低价格;

ITIME–酒吧开放时间;

柚木体积或者在酒吧形成期间,价格变化了多少次。

使用计时器的例子

首先,让我们看看一个简单的时间序列示例。在所有可用的历史中找到一支蜡烛体积同时计算蜡烛体的平均大小:

//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+void ShowBarsParameters(){Comment("");long VolumeMax=0;  // 最大容积int VolumeMaxIndex=0;// 最大容量酒吧指数double BodySum=0;  // 烛光体的总和for(int i=Bars-1; i>=0; i--){if(VolumeMax<iVolume(NULL,0,i)){VolumeMax=iVolume(NULL,0,i);VolumeMaxIndex=i;}BodySum+=MathAbs(iOpen(NULL,0,i)-iClose(NULL,0,i));}Comment(“图表上的所有:”,bars,“bar,”",“最大体积值”,VolumeMax,“收到”ITIME(Null,0,VolumeMaxindex),“在酒吧”,VolumeMaxindex,",“蜡烛体的平均值,以点为单位:”,int(bodysum/_point)/bars;}

修改代码,以便可以访问任意字符的时间序列。为此,我们创建一个函数,将所需的字符和时间范围传递给该函数:

//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+void ShowBarsParametersExt(string symbol, int timeframe){Comment("");long VolumeMax=0;  // 最大容积int VolumeMaxIndex=0;// 最大容量酒吧指数double BodySum=0;  // 烛光体的总和for(int i=iBars(symbol,timeframe)-1; i>=0; i--){if(VolumeMax<iVolume(symbol,timeframe,i)){VolumeMax=iVolume(symbol,timeframe,i);VolumeMaxIndex=i;}BodySum+=MathAbs(iOpen(symbol,timeframe,i)-iClose(symbol,timeframe,i));}comment(“在图表上”,symbol,“(“,timeframe,“)总计:”,ibars(symbol,timeframe),“bar,”",“最大体积值”,VolumeMax,“收到”ITIME(symbol,timeframe,volumemaxindex),“在酒吧”,volumemaxindex,",“蜡烛体的平均值,以点为单位:”,int(bodysum/_point)/bars;}

例如,获取字符的数据EURJPY从一小时的时间范围开始,函数调用如下:

ShowBarsParameters("EURJPY",PERIOD_H1);

比尔·威廉姆斯分形标记

让我们学习如何识别最简单的蜡烛组。因为我们要寻找发散RSI振荡器的价格,我们需要解决在图表上定义局部极值的问题。这些基准点可以趋势线它将显示价格走势的方向。确定这些点的最简单方法是标记分形比尔·威廉姆斯分形是由5蜡烛如果高中心蜡烛位于左侧两个蜡烛和右侧两个蜡烛的最高价格之上,则会出现“向上分形”:

如果中央酒吧的低水平低于左侧两个酒吧和右侧两个酒吧的最低水平,则前面是“分形向下”:

终端有一个内置的指示器。分形:

我们会写脚本,它将在历史数据上标记分形,就像内置的分形指示器一样。

使用MetaEditor主面板中的按钮创建脚本“分形.mq4”:

脚本更易于演示,因为它最初由一个主函数onstart()组成,一旦脚本被添加到图形中,它就会运行货币对.onstart()执行一次后,脚本将从内存中卸载。我们将所有工作代码放在功能中,然后可以轻松地转移到您的顾问和指标中。
//+------------------------------------------------------------------+//-分形.mq4.//|                        Copyright 2019, MetaQuotes Software Corp. |//|                                             https://www.mql5.com |//+------------------------------------------------------------------+#property copyright "Copyright 2019, MetaQuotes Software Corp."#property link      "https://www.mql5.com"#property version   "1.00"#property strict//+------------------------------------------------------------------+//| Script program start function                                    |//+------------------------------------------------------------------+void OnStart(){//---}

为了显示分形,我们将使用图形对象“箭头”(obju arrow)。我们需要一个功能,可以在价格图上创建这样的对象。使用MQL目录中的现成ArrowCreate函数,该函数与终端一起运行:

//+------------------------------------------------------------------+ 他创造了射手。//+------------------------------------------------------------------+ bool arrowcreate(const long chart_id=0,//图形ID)const string name=“arrow”,//箭头名称const int subu window=0,//子窗口号datetime time=0,//绑定点时间double price=0,//参考点价格const uchar arrow_code=252,//箭头码const enum_arrow_anchor_anchor=anchor_bottom,//绑定点的位置const color clr=clrred,//箭头颜色const enumu lineu style=styleu solid,//边线样式const int width=3,//箭头大小const bool back=false,//后台const bool selection=false,//选择移动const bool hidden=true,//隐藏在对象列表中const long zu order=0)//按鼠标优先

创建一个函数,获取条形索引并确定是否在其上形成分形:

//+------------------------------------------------------------------+//–检查酒吧是否有索引›分形。//|                                                                  |//isu up-如果已形成,则此变量为true。//分形向上。//isu down-如果已形成,则此变量为true。分形向下。//如果分形不形成,则两个变量/.//值为false。//+------------------------------------------------------------------+void GetFractalType(int index, bool &is_up, bool &is_down){//重置变量值is_up=is_down=false;//检查前两个和后两个主蜡烛的最大值是否高于主蜡烛is_up=(iHigh(_Symbol,_Period,index+1)<iHigh(_Symbol,_Period,index) &&iHigh(_Symbol,_Period,index+2)<iHigh(_Symbol,_Period,index) &&iHigh(_Symbol,_Period,index-1)<iHigh(_Symbol,_Period,index) &&iHigh(_Symbol,_Period,index-2)<iHigh(_Symbol,_Period,index));//检查前两个和后两个的低(最低)中心蜡烛是否低于is_down=(iLow(_Symbol,_Period,index+1)>iLow(_Symbol,_Period,index) &&iLow(_Symbol,_Period,index+2)>iLow(_Symbol,_Period,index) &&iLow(_Symbol,_Period,index-1)>iLow(_Symbol,_Period,index) &&iLow(_Symbol,_Period,index-2)>iLow(_Symbol,_Period,index));}

在这个函数中,除了Bar索引外,还通过链接传递两个逻辑变量,然后可以在调用函数的代码中分析这些变量。如果在给定的索引上形成了向上的分形,则一个变量获得true,如果分形向下,则另一个变量获得true。

现在,我们需要一个函数来搜索数组的所有元素——时间序列(bar),并使用getfractaltype函数检查它们是否形成分形。根据在条形图上形成的分形类型,我们将创建所需类型的图形对象,并将其放置在蜡烛的最大或最小值上:

//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+void ShowFractals(){//我们在计时赛结束和开始时通过两个酒吧for(int i=Bars-2; i>1; i--){string Name;// 对象名称bool IsUp, IsDown;//如果在给定条上形成分形类型,则得到分形类型GetFractalType(i,IsUp,IsDown);//形成向上的分形if(IsUp){Name="FRCTL_UP_"+_Symbol+"_"+IntegerToString(iTime(_Symbol,_Period,i));在高蜡烛上画一个蓝色的点ArrowCreate(0,Name,0,iTime(_Symbol,_Period,i),iHigh(_Symbol,_Period,i),159,ANCHOR_BOTTOM,clrBlue,STYLE_SOLID,2,true,false);}//形成向下分形if(IsDown){Name="FRCTL_DOWN_"+_Symbol+"_"+IntegerToString(iTime(_Symbol,_Period,i));//在低蜡烛下画一个红色的点ArrowCreate(0,Name,0,iTime(_Symbol,_Period,i),iLow(_Symbol,_Period,i),159,ANCHOR_BOTTOM,clrRed,STYLE_SOLID,2,true,false);}}}

我们还需要一个功能,它将删除图形对象,这些对象可能仍然存在于我们以前运行的脚本中。此函数将搜索以特定字符集(称为前缀)开头的图形对象,并仅删除它们。我们以前确保创建的对象具有前缀“frctl”。在调用showfractals函数之前,我们将删除对象。

//+------------------------------------------------------------------+ //–删除带有PRFX前缀的图形对象›//+------------------------------------------------------------------+void DeleteObjects(string prfx){for(int i=ObjectsTotal()-1; i>=0; i--){string nm=ObjectName(0,i);if(StringSubstr(nm,0,StringLen(prfx))==prfx)ObjectDelete(0,nm);}}

Onstart()函数如下:

//+------------------------------------------------------------------+//| Script program start function                                    |//+------------------------------------------------------------------+void OnStart(){DeleteObjects("FRCTL");ShowFractals();}

脚本的结果:

基于RSI振荡器的自动发散搜索

一旦我们学会了使用分形来定义本地价格极值,我们就可以开始我们课程的主要任务,即自动搜索。发散指示器RSI价格。首先,我们必须确定术语。广义上发散–这是指标线与价格图表的差异。

看跌和看跌的区别。

看跌趋异是指价格在上涨时形成越来越高的高点的情况。趋势,而指标的每个新顶点RSI低于前一个:

看跌发散是一个信号,表明趋势很有可能逆转为下跌(看跌)。

看涨趋异是指价格在下跌时形成越来越低的低点。趋势,RSI指标的每个新凹陷都高于上一个:

牛市发散是一个变化概率高的信号。趋势上升(牛市)

很少使用收敛信号或通常称为“隐藏”发散信号。收敛信号显示现有的继续趋势.

隐藏的熊市发散:

隐藏的多头发散:

让我们编写一个脚本,在历史数据中搜索两种发散:通常的发散用实线标记,隐藏的发散用虚线标记。

使用MetaEditor主窗格中的“新建”按钮,我们将创建Diversity.mq4脚本。

在这个脚本中,我们需要输入参数,因此我们将添加scriptu showu inputs指令:

#property script_show_inputs

现在,脚本将不会在添加到图表后立即运行,而是像顾问或指示器一样打开参数窗口。

我们的脚本将使用以下选项:

input int  RSIPeriod             = 9;     // RSI周期input bool ShowFracrals          = false; // 显示分形input bool ShowHiddenDivergence  = false; // 显示隐藏的发散

定义图形对象名称的字符串常量前缀。我们在寻找要删除它们的对象时需要它:

#define PREFIX    "OBJ_DIV_"

描述存储分形数据的结构,并宣布向上分形和向下分形的两组结构:

struct fractal{datetime          time;      // 分形酒吧的开放时间double            peak;      // 根据分形类型,最大或最小条形double            indicator; // 条形图RSI指示器值} FractalsUp[],FractalsDown[];

我们将以前编写的deleteobjects函数添加到代码中,并在onstart()处理程序中调用它。这样,我们将从图形中删除以前脚本运行后可能留下的对象。将分形数组设置为零大小,并定义数组中索引的方向,就像时间序列(从右到左)一样。

//+------------------------------------------------------------------+//| Script program start function                                    |//+------------------------------------------------------------------+void OnStart(){//删除可能是以前脚本运行遗留的对象DeleteObjects(PREFIX);//将分形数组设置为零ArrayResize(FractalsUp,0);ArrayResize(FractalsDown,0);//将分形数组中索引的方向定义为时间序列(从右向左)ArraySetAsSeries(FractalsUp,true);ArraySetAsSeries(FractalsDown,true);}//+------------------------------------------------------------------+//–删除带有PRFX前缀的图形对象›//+------------------------------------------------------------------+void DeleteObjects(string prfx){for(int i=ObjectsTotal()-1; i>=0; i--){string nm=ObjectName(0,i);if(StringSubstr(nm,0,StringLen(prfx))==prfx)ObjectDelete(0,nm);}}

让我们写一个函数,用历史上发现的分形填充fractalsup[]和fractalsdown[]数组。在这种情况下,如果参数showfracrals=true,则找到的分形将以彩色点显示在图表上。图形对象的渲染功能来自MQL手册,定义分形类型的getfractaltype函数来自我们以前编写的脚本“分形.mq4”。

//+------------------------------------------------------------------+//找到历史上所有的分形并填充相应的..//分形数组。//根据参数showfracrals显示找到的›//图形上的分形/.//+------------------------------------------------------------------+  void ShowFractalsAndFillArrays(){//我们在计时赛结束和开始时通过两个酒吧int to=Bars-2;for(int i=2; i<to; i++){string Name;// 对象名称bool IsUp, IsDown;double rsi;//如果在给定条上形成分形类型,则得到分形类型GetFractalType(i,IsUp,IsDown);//形成向上的分形if(IsUp){rsi=iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i);//也有RSI的最大值if(iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i+1)<rsi &&iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i-1)<rsi){//将分形数据添加到数组中int ind=AddFractalToArray(FractalsUp,iTime(_Symbol,_Period,i),iHigh(_Symbol,_Period,i),rsi);在高蜡烛上画一个蓝色的点if(ShowFracrals){Name=PREFIX+_Symbol+"_FRCTL_UP_"+IntegerToString(FractalsUp[ind].time);ArrowCreate(0,Name,0,FractalsUp[ind].time,FractalsUp[ind].peak,159,ANCHOR_BOTTOM,clrBlue,STYLE_SOLID,2,true,false);}   }}//形成向下分形if(IsDown){rsi=iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i);//至少有RSIif(iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i+1)>rsi && iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i-1)>rsi){//将分形数据添加到数组中int ind=AddFractalToArray(FractalsDown,iTime(_Symbol,_Period,i),iLow(_Symbol,_Period,i),rsi);//在低蜡烛下画一个红色的点if(ShowFracrals){Name=PREFIX+_Symbol+"_FRCTL_DOWN_"+IntegerToString(FractalsDown[ind].time);ArrowCreate(0,Name,0,FractalsDown[ind].time,FractalsDown[ind].peak,159,ANCHOR_TOP,clrRed,STYLE_SOLID,2,true,false);}   }}}}

一旦两个分形数组都被填充,我们需要一个函数来找到价格和RSI振荡器的可能发散并用线标记:

//+------------------------------------------------------------------+//–搜索并标记趋势发散线›//价格图表和振荡器RSI›//+------------------------------------------------------------------+void ShowDivergence(){int size;string Name=PREFIX+_Symbol+"TLINE_";/--------在局部最大值上绘制熊市发散线size=ArraySize(FractalsUp);for(int i=0; i<size-1; i++){int Style=EMPTY;//看跌趋异-价格上涨,RSI下跌if(FractalsUp[i].peak>FractalsUp[i+1].peak &&FractalsUp[i].indicator<FractalsUp[i+1].indicator)Style=STYLE_SOLID;else//隐藏的熊市发散(趋同)-价格下跌,RSI上涨if(ShowHiddenDivergence && FractalsUp[i].peak<FractalsUp[i+1].peak &&FractalsUp[i].indicator>FractalsUp[i+1].indicator)Style=STYLE_DOT;if(Style>EMPTY){TrendCreate(0,Name+"BEAR_BAR_"+IntegerToString(FractalsUp[i].time),0,FractalsUp[i+1].time,FractalsUp[i+1].peak,FractalsUp[i].time,FractalsUp[i].peak,clrMaroon,ENUM_LINE_STYLE(Style));TrendCreate(0,Name+"BEAR_RSI_"+IntegerToString(FractalsUp[i].time),1,FractalsUp[i+1].time,FractalsUp[i+1].indicator,FractalsUp[i].time,FractalsUp[i].indicator,clrMaroon,ENUM_LINE_STYLE(Style));}}//--------在局部极小值上绘制牛市发散线size=ArraySize(FractalsDown);for(int i=0; i<size-1; i++){int Style=EMPTY;//看涨趋异-价格下跌,RSI上涨if(FractalsDown[i].peak<FractalsDown[i+1].peak &&    FractalsDown[i].indicator>FractalsDown[i+1].indicator)Style=STYLE_SOLID;else//隐含多头趋异(趋同)-价格上涨,RSI下跌if(ShowHiddenDivergence && FractalsDown[i].peak>FractalsDown[i+1].peak && FractalsDown[i].indicator<FractalsDown[i+1].indicator)Style=STYLE_DOT;if(Style>EMPTY){TrendCreate(0,Name+"BULL_BAR_"+IntegerToString(FractalsDown[i].time),0,FractalsDown[i+1].time,FractalsDown[i+1].peak,FractalsDown[i].time,FractalsDown[i].peak, clrGreen,ENUM_LINE_STYLE(Style));TrendCreate(0,Name+"BULL_RSI_"+IntegerToString(FractalsDown[i].time),1,FractalsDown[i+1].time,FractalsDown[i+1].indicator,FractalsDown[i].time,FractalsDown[i].indicator,clrGreen,ENUM_LINE_STYLE(Style));}}}

如您所见,发散搜索算法相当简单:价格运动的方向由两个相邻的分形决定;如果RSI在这些条形图上形成极值,则比较振荡器的方向和价格。在发散的情况下,绘制连接蜡烛极值和RSI极值的线段。

将函数调用添加到onstart()处理程序:

//+------------------------------------------------------------------+//| Script program start function                                    |//+------------------------------------------------------------------+void OnStart(){//删除可能是以前脚本运行遗留的对象DeleteObjects(PREFIX);//将分形数组设置为零ArrayResize(FractalsUp,0);ArrayResize(FractalsDown,0);//将分形数组中索引的方向定义为时间序列(从右向左)ArraySetAsSeries(FractalsUp,true);ArraySetAsSeries(FractalsDown,true);ShowFractalsAndFillArrays();ShowDivergence();}

我们的脚本准备好了。让我们在欧元美元小时图表上运行它,先将RSI指示器附加到图表上,其周期与脚本中的周期相同:

脚本可以在没有RSI指示器的情况下运行。在这种情况下,发散线将仅基于价格图表构建。

启用分形显示(彩色点)和隐藏发散显示(虚线):

结论

在本教程中,我们了解了MQL4如何使用酒吧进行价格流分析。我们学会了如何找到简单的五条形地层——所谓的分形比尔·威廉并用它们来确定局部极值。因此,我们编写了一个脚本,用于自动搜索价格差异和RSI振荡器。这些解决方案可以用于创建自定义指标和交易。顾问。

论坛主题

尊敬的尤里·洛塞夫AKA LSV107
Tlap.com

MQL4教程 ,,,,

在MQL 4/5上开发一个方便的编程环境

欢迎您,MQL程序员!

许多人可能不喜欢使用标准的MetaEditor来编程你的软件。顾问,脚本指标今天我们将讨论如何提高编程的性能和可用性。

为了工作,我们将安装phpstorm和一个特殊的MQL插件,以及Git控制台,并在Bitbucket上注册。这些工具将大大提高开发速度和可用性。如何快速有效地做到这一点-我们将从今天的课程中学习!

PHPStorm它是一个强大的IDE(开发环境),支持多种语言。它有许多不同的功能和功能,专门用于方便和加速开发。

此外,还可以安装额外的插件来扩展功能。插件本身有成千上万个,所以你可以很容易地找到你的解决方案。

要安装phpstorm,请转到开发者网站下载操作系统的安装文件。安装本身很简单,需要5-10分钟,所以在这里详细描述这个过程没有意义。

安装phpstorm mql idea插件

打开phpstorm程序单击新项目并打开终端的MQL4文件夹。

然后单击设置->插件:

搜索MQL IDEA插件,安装。现在,PHPSTORM将理解MQL文件的语法。

注册Bitbucket

登记bitbucket这也不难。只需输入邮件,创建密码,然后通过电子邮件中的链接确认邮件。

登录到帐户,点击存储库。我们需要为我们的顾问创建一个新的存储库。

然后想出存储库的名称。如果您愿意,可以添加描述:

Git Bash控制台

下载Git控制台这里它可用于任何操作系统。我还建议你把它放在远程服务器上。安装简单,没有问题。安装后,您将看到以下图标:

在终端文件夹中,调用控制台并将其写入git init。然后在phpstorm中,您将需要提交第一次提交,并且在创建存储库时,您将看到以下命令以创建远程存储库:

结论

之后,您可以完全使用Git版本控制系统并编写MQL。方案最方便的编程IDE之一。上述整个过程在本教程的视频中有详细描述。

在开发中使用phpstorm可能会让你慢一点,因为你正在学习新功能。但最终你会加快三倍的速度,不再需要在MetaEditor中编程。

Git系统可能看起来也不舒服,但几周后你会欣赏它的存在。

论坛主题

尊敬的Dmitry Aka Silentspec
Tlap.com

MQL4教程,MQL5教程 ,,,,,

MQL4:学习WebRequest方法

大家好!

不久前开放位置的教训我们从网上的网站上提取数据。我们当时在课堂上所做的事情可以用另一种方法来完成,而无需使用DLL,因此不依赖于操作系统。

您只需要MetaTrader 4终端和MQL代码编辑器。以及标准知识mql4WebRequest方法,我们今天将与您分享。

阅读更多
MQL4教程 ,,,,

MQL4和MQL5的区别是什么?

欢迎您,尊敬的软件交易员同事!

今天是我们平台的入门课。Metatrader 5我们来看看两者之间的区别MT4MQL4编程语言(MQL4编程语言)和MT5编程语言(MQL5编程语言)一般和直接用于这些语言的编程。这段视频首先会让那些长期想尝试MQL5但没有决定的人感兴趣。

观看视频

MQL4教程,MQL5教程 ,,,

MQL4:给给定模式的贸易顾问写信

大家好!

你们中的许多人经常会注意到,图表一个货币对重复他们的动作。事实上,在大多数情况下,这只是一个暂时的现象,无法赚足够的钱。但是,如果有这样可靠的价格走势和模式,我们不知道呢?今天我们将写一个简单的参赞这将有助于我们的市场研究。

观看视频
MQL4教程 ,,,,,

MQL4:我们写信给未平仓交易者指标顾问

不久前,网站上出现了一个指标。未平仓交易者我喜欢它,因为它可以同时从多个来源获取数据。此外,在写作过程中此文章我有点测试从视觉上看,这是一个指标,结果似乎很有希望。今天,我们将与该指标合作,在我们的顾问中使用其数据。

观看视频
MQL4教程 ,,,,

MQL4:从顾问管理计算机

有时你需要按第三方应用程序中的软件按钮,从某个组件读取文本,运行另一个终端,任何其他程序,打开文本文件,在特定时间关闭计算机,执行其他操作。在这里,我们将使用shell32.dll和winuser32.mqh库中的WinAPI shellexecute函数。如何自动化整个过程-我们将从新的外汇机器人编程课程中学习。这个主题是不寻常的,它将极大地扩展你作为程序员的技能。

了解更多
MQL4教程 ,,,,