mql4-lib, 面向专业开发人员的MQL4基金会库

分享于 

49分钟阅读

GitHub

  繁體 雙語
MQL4 Foundation Library For Professional Developers
  • 源代码名称:mql4-lib
  • 源代码网址:http://www.github.com/dingmaotu/mql4-lib
  • mql4-lib源代码文档
  • mql4-lib源代码下载
  • Git URL:
    git://www.github.com/dingmaotu/mql4-lib.git
    Git Clone代码到本地:
    git clone http://www.github.com/dingmaotu/mql4-lib
    Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/dingmaotu/mql4-lib
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
    mql4-lib

    面向专业开发人员的MQL基金会库

    简介

    由MetaQuotes提供的mql4/5 编程语言是非常有限的C++ 版本,它的标准库是( ugly ) MFC的一个克隆,这两种语言我都非常不习惯。 大多数MQL4程序还没有适应 MQL5 ( 面向对象) 风格,更不用说reuable和优雅的基于组件的设计和编程。

    mql4-lib是一个简单的库,它尝试使用面向对象的方法和一种类似Java的编码风格使MQL编程愉快,并鼓励编写可以重用。 这个库有雄心成为MQL的实际基础库。

    尽管库是针对MQL4的,但是它的大部分组件都与MQL5兼容。 除了交易相关类之外,你可以在MetaTrader5上使用库。 它旨在消除这个限制,并使库成为MT4和 MT5 ( x86/x64 )的真正跨版本库。 未来图书馆可能会将 NAME 转变为mql库。

    安装

    只需将库复制到文件夹目录的MetaTrader数据 Include,并使用你选择的root 目录 NAME,例如:

    • 对于 MT4: <MetaTrader Data>MQL4IncludeMql<mql4-lib content>
    • 对于 MT5: <MetaTrader Data>MQL5IncludeMql<mql4-lib content>

    请注意,推荐的root 目录 NAME 现在是 Mql ( Pascal案例)。 以前它是 MQL4,它比MT4更具体。 实际上,大多数库也可以在MT5上使用,因这里我开始了这个过程,使这个库兼容。 所有示例也将使用这里 root 名称。

    是 recommened,你使用最新版本的metatrader4/5,因为在旧版本中不能使用多个特性。

    用法

    图书馆处于早期阶段。 但是,大多数组件相当稳定,可以在生产中使用。 以下是主要组件:

    • Lang 目录包含增强MQL语言的模块
    • Collection 目录包含有用的Collection 类型
    • Format 目录包含序列化格式实现
    • Charts 目录包含多种图表类型和常用图表工具
    • Trade 目录包含用于交易的有用抽象
    • History 目录包含用于历史数据的有用抽象
    • Utils 目录包含各种实用程序
    • UI 图表对象和用户界面控件( 进行中)
    • OpenCL 为MT4带来了OpenCL支持( 进行中)

    基本程序

    Lang 中,我将三个程序类型( 脚本,指示器和专家顾问) 抽象为三个基类,你可以继承。

    基本上,你在可以重用类中编写程序,当你想将它们作为独立可执行文件使用时,你使用 MACROS 来声明它们。

    带输入参数或者没有输入参数的程序之间的宏区别。 下面是一个没有任何输入参数的简单脚本:

    #include<Mql/Lang/Script.mqh>classMyScript: publicScript{public:// OnStart is now mainvoidmain() {Print("Hello");}
    };// declare it: notice second parameter indicates the script has no inputDECLARE_SCRIPT(MyScript,false)

    下面是另一个例子,这次是一个带有输入参数的专家顾问:

    #include<Mql/Lang/ExpertAdvisor.mqh>classMyEaParam: publicAppParam{
     ObjectAttr(string,eaName,EaName);
     ObjectAttr(double,baseLot,BaseLot);public:// optionally override `check` method to validate paramters// this method will be called before initialization of EA// if this method returns false, then INIT_INCORRECT_PARAMETERS will// be returned// bool check(void) {return true;}};classMyEa: publicExpertAdvisor{private: MyEaParam *m_param;public:MyEa(MyEaParam *param)
     :m_param(param)
     {
     // Initialize EA in the constructor instead of OnInit;// If failed, you call fail(message, returnCode)// both paramters of `fail` is optional, with default return code INIT_FAIL// if you don't call `fail`, the default return code is INIT_SUCCESS; }
     ~MyEa()
     {
     // Deinitialize EA in the destructor instead of OnDeinit// getDeinitReason() to get deinitialization reason }
     // OnTick is now mainvoidmain() {Print("Hello from " + m_param.getEaName());}
    };// The code before this line can be put in a separate mqh header// We use macros to declare inputs// Notice the trailing semicolon at the end of each INPUT, it is needed// support custom display name because of some unknown rules from MetaQuotesBEGIN_INPUT(MyEaParam)
     INPUT(string,EaName,"My EA"); // EA Name (Custom display name is supported)INPUT(double,BaseLot,0.1); // Base LotEND_INPUTDECLARE_EA(MyEa,true) // true to indicate it has parameters

    ObjectAttr 宏声明类的标准获取/设置方法。 遵循 Java Beans(TM) 约定。

    我使用一些宏技巧来解决MQL的极限。 我有时间的时候会详细地记录图书馆。

    使用这种方法,你可以编写可重用的EAs。脚本或者指标。 你不需要担心 rtc,OnDeinit,a,OnTick,OnCalculate,等等,直接在你的EA中使用输入参数。 你可以写一个基本的EA,并且很。

    运行时控制指标和指示器驱动程序

    当你使用这个lib创建指示器时,它既可以作为独立的( 由终端运行时控制) 使用,也可以被你的程序驱动。 这是一个强大的概念,你可以将指示器写入一次,并在EA和脚本中使用它,或者。 可以在正常时间序列图表中使用指示器,或者让它由 HistoryData 派生类( TimeSeriesData,Renko或者时间段) 驱动。

    让我向你展示一个示例指示器 DeMarker。 首先,我们在头文件 DeMarker.mqh 中定义了通用的可以重用指示器 MODULE

    //+------------------------------------------------------------------+//| DeMarker.mqh |//| Copyright 2017, Li Ding |//| dingmaotu@hotmail.com |//+------------------------------------------------------------------+#propertystrict#include<Mql/Lang/Mql.mqh>#include<Mql/Lang/Indicator.mqh>#include<MovingAverages.mqh>//+------------------------------------------------------------------+//| Indicator Input |//+------------------------------------------------------------------+classDeMarkerParam: publicAppParam {
     ObjectAttr(int,AvgPeriod,AvgPeriod); //SMA Period };//+------------------------------------------------------------------+//| DeMarker |//+------------------------------------------------------------------+classDeMarker: publicIndicator {private:
     intm_period;protected:
     doubleExtMainBuffer[];
     doubleExtMaxBuffer[];
     doubleExtMinBuffer[];public:
     //--- this provides time series like access to the indicator bufferdoubleoperator[](constintindex) {returnExtMainBuffer[ArraySize(ExtMainBuffer)-index-1];}
     DeMarker(DeMarkerParam *param)
     :m_period(param.getAvgPeriod())
     {
     //--- runtime controlled means that it is used as a standalone Indicator controlled by the Terminal//--- isRuntimeControlled() is a method of common parent class `App`if(isRuntimeControlled())
     {
     //--- for standalone indicators we set some options for visual appearancestringshort_name;
     //--- indicator linesSetIndexStyle(0,DRAW_LINE);
     SetIndexBuffer(0,ExtMainBuffer);
     //--- name for DataWindow and indicator subwindow labelshort_name="DeM("+IntegerToString(m_period)+")";
     IndicatorShortName(short_name);
     SetIndexLabel(0,short_name);
     //---SetIndexDrawBegin(0,m_period);
     }
     }
     intmain(constinttotal,
     constintprev,
     constdatetime &time[],
     constdouble &open[],
     constdouble &high[],
     constdouble &low[],
     constdouble &close[],
     constlong &tickVolume[],
     constlong &volume[],
     constint &spread[])
     {
     //--- check for bars countif(total<m_period)
     return(0);
     if(isRuntimeControlled())
     {
     //--- runtime controlled buffer is auto extended and is by default time series likeArraySetAsSeries(ExtMainBuffer,false);
     }
     else {
     //--- driven by yourself and thus the need to resize the main bufferif(prev!=total)
     {
     ArrayResize(ExtMainBuffer,total,100);
     }
     }
     if(prev!=total)
     {
     ArrayResize(ExtMaxBuffer,total,100);
     ArrayResize(ExtMinBuffer,total,100);
     }
     ArraySetAsSeries(low,false);
     ArraySetAsSeries(high,false);
     intbegin=(prev==total)?prev-1:prev;
     for(inti=begin; i<total; i++)
     {
     if(i==0) {ExtMaxBuffer[i]=0.0;ExtMinBuffer[i]=0.0;continue;}
     if(high[i]>high[i-1]) ExtMaxBuffer[i]=high[i]-high[i-1];
     elseExtMaxBuffer[i]=0.0;
     if(low[i]<low[i-1]) ExtMinBuffer[i]=low[i-1]-low[i];
     elseExtMinBuffer[i]=0.0;
     }
     for(inti=begin; i<total; i++)
     {
     if(i<m_period) {ExtMainBuffer[i]=0.0;continue;}
     doublesmaMax=SimpleMA(i,m_period,ExtMaxBuffer);
     doublesmaMin=SimpleMA(i,m_period,ExtMinBuffer);
     ExtMainBuffer[i]=smaMax/(smaMax+smaMin);
     }
     //--- OnCalculate done. Return new prev.return(total);
     }
     };

    如果要将它的用作独立指示器,请创建 DeMarker.mq4:

    //+------------------------------------------------------------------+//| DeMarker.mq4 |//| Copyright 2017, Li Ding |//| dingmaotu@hotmail.com |//+------------------------------------------------------------------+#propertycopyright"Copyright 2017, Li Ding"#propertylink"dingmaotu@hotmail.com"#propertyversion"1.00"#propertystrict#propertyindicator_separate_window#propertyindicator_minimum0#propertyindicator_maximum1.0#propertyindicator_buffers1#propertyindicator_color1LightSeaGreen#propertyindicator_level10.3#propertyindicator_level20.7#propertyindicator_levelcolorclrSilver#propertyindicator_levelstyleSTYLE_DOT#include<Indicators/DeMarker.mqh>//--- input parametersBEGIN_INPUT(DeMarkerParam)
     INPUT(int,AvgPeriod,14); //Averaging PeriodEND_INPUTDECLARE_INDICATOR(DeMarker,true);

    或者,如果你想在你的EA中使用它,则由Renko图表驱动:

    //--- code snippets for using an indicator with Renko//--- OnInit//--- create the indicator manually, its runtime controlled flag is false by defaultDeMarkerParam *param=newDeMarkerParam;
     param.setAvgPeriod(14);
     deMarker=newDeMarker(param);//--- HistoryData::OnUpdate is an event that can be subscribed by Indicatorsrenko = newRenko(_Symbol,300);//--- add indicator to the driver: you can add multiple indicators//--- the OnUpdate event will delete its subscribers when destructedrenko.OnUpdate+=deMarker;//--- create the real driver that provide history data//--- OnTickrenko.update(Close[0]);//--- after update, all indicators attached to the renko.OnUpdate event will be updated//--- access DeMarkerdoublevalue = deMarker[0];//--- OnDeinit//--- need to release resourcesdeleterenko;

    事件处理

    专家顾问和指标可以从运行的图表中接收事件,它们从 EventApp 类派生,这提供了处理事件的便利性。

    默认情况下,EventApp 通过执行任何操作来处理所有事件。 如果你愿意,甚至可以创建空的EA或者指示器:

    #include<Mql/Lang/ExpertAdvisor.mqh>classMyEA: publicExpertAdvisor {};DECLARE_EA(MyEA,false)

    如果你创建的是纯UI应用程序,或者只是对外部事件进行操作,那么这一点很有用。 你不需要一个主要的方法。 你只需要处理你感兴趣的。

    #include<Mql/Lang/ExpertAdvisor.mqh>classTestEvent: publicExpertAdvisor {public:
     voidonAppEvent(constushortevent,constuintparam)
     {
     PrintFormat(">>> External event from DLL: %u, %u",event,param);
     }
     voidonClick(intx, inty)
     {
     PrintFormat(">>> User clicked on chart at position (%d,%d)", x, y);
     }
     voidonCustom(intid, longlparam, doubledparam, stringsparam)
     {
     //--- `id` is the SAME as the second parameter of ChartEventCustom,//--- no need to minus CHARTEVENT_CUSTOM, the library does it for youPrintFormat(">>> Someone has sent a custom event with id %d", id);
     }
     };DECLARE_EA(TestEvent,false)

    外部事件

    Lang/Event MODULE 提供了一种方法来从,终端运行时发送自定义事件,比如从 DLL。

    你需要调用 PostMessage/PostThreadMessage 函数,并通过 EncodeKeydownMessage 将参数传递给相同的算法。 然后从EventApp派生的任何程序都可以从它的onAppEvent 事件处理程序处理这里消息。

    下面是C 中的示例实现:

    #include<Windows.h>#include<stdint.h>#include<limits.h>staticconstint WORD_BIT = sizeof(int16_t)*CHAR_BIT;voidEncodeKeydownMessage(const WORD event,const DWORD param,WPARAM &wparam,LPARAM &lparam)
    {
     DWORD t=(DWORD)event;
     t<<= WORD_BIT;
     t |= 0x80000000;
     DWORD highPart= param & 0xFFFF0000;
     DWORD lowPart = param & 0x0000FFFF;
     wparam = (WPARAM)(t|(highPart>>WORD_BIT));
     lparam = (LPARAM)lowPart;
    }
    BOOL MqlSendAppMessage(HWND hwnd, WORD event, DWORD param)
    {
     WPARAM wparam;
     LPARAM lparam;
     EncodeKeydownMessage(event, param, wparam, lparam);
     returnPostMessageW(hwnd,WM_KEYDOWN,wparam, lparam);
    }

    机制使用自定义WM_KEYDOWN消息触发 OnChartEvent。 如果是 OnChartEvent 处理程序,EventApp 会检查KeyDown事件是否实际上是来自另一个源( 不是真正的钥匙)的自定义应用程序事件。 如果是,那么 EventApp 调用它的onAppEvent 方法。

    这里机制具有一定的局限性: 参数仅仅是一个整数( 32bit ),因为WM_KEYDOWN是如何在MetaTrader终端中处理的。 这个解决方案在 64bit MetaTrader5中可能无法工作。

    尽管有限制,这将使你从MetaTrader监狱中解脱出来: 你可以随时发送事件,并且不要在OnTimer中进行轮询,也可以在OnTick中创建管道/套接字。

    使用OnTimer不是一个好主意。 首先它无法从MQL侧接收任何参数。 你至少需要一个事件的标识符。 第二,WM_TIMER 事件在主线程中非常拥挤。 即使在周末没有数据进入的地方,WM_TIMER 也不断发送到主线程。 这使得执行更多的指令来决定它是否是程序的有效事件。

    警告: 这是临时解决方案。 处理异步事件的最佳方法是找出如何实现ChartEventCustom并在 C/C++ 实现它,因为 WIN32 消息没有实现,因为很强的抗调试措施,因此无法深入研究。

    在MetaTrader终端中,你最好使用ChartEventCustom发送定制事件。

    集合

    在高级MQL程序中,你必须使用更复杂的Collection 类型来进行订单管理。

    计划将公共 Collection 类型添加到库中,包括列表。哈希映射。树和其他类型。

    目前有两种列表类型:

    • 集合/linkedlist是链接列表实现
    • 集合/矢量是基于 array的实现

    有两个 Set 实现:

    • 基于 array的Collection/Set
    • 基于哈希的Collection/HashSet

    使用类模板和继承,我实现了层次结构:

    
    Iterable -> Collection -> LinkedList
    
    
     -> Vector
    
    
     -> Set
    
    
     -> HashSet
    
    
    
    

    但是在将来,层次结构可能是:

    
    Iterable -> Collection -> List -> ArrayList
    
    
     -> LinkedList
    
    
     -> Set -> ArraySet
    
    
     -> HashSet
    
    
    
    

    List 添加了一些重要的方法,比如基于索引的元素访问,以及 stackqueue 之类的方法( 比如。 pushpopshiftunshift,等等 )。 但目前 Set 基本上与 Collection 相同。 也许某些集合操作( 联合,交叉,等等 ) 是 necesary。

    对于简单和短的Collection,即使执行频繁插入和删除,array 实现也可能更快且开销较小。

    
    I'd like to point out some extremely useful undocumented MQL4/5 features:
    
    
    
     1. class templates(!)
    
    
     2. typedef function pointers(!)
    
    
     3. template function overloading
    
    
     4. union type
    
    
    
    Though inheriting multiple interfaces is not possible now, I think this will be
    
    
    possible in the future.
    
    
    
    Among these features, `class template` is the most important because we can
    
    
    greatly simplify Collection code. These features are used by MetaQuotes to port
    
    
    .Net Regular Expression Library to MQL.
    
    
    
    

    一般用法如下:

    LinkedList<Order*> orderList; // linked list based implementation, faster insert/removeLinkedList<int> intList; // yes it supports primary types as wellVector<Order*>; orderVector // array based implementation, faster random accessVector<int> intVector;

    要迭代 Collection,使用它的迭代器,因为迭代器知道什么是迭代迭代最有效的方法。

    Threre是用于迭代的alao两个 MACROS: foreachforeachv。你可以在循环中使用 breakreturn,而不必担心资源泄漏,因为我们使用 Iter 类包装。

    下面是一个简单的示例:

    
    //+------------------------------------------------------------------+
    
    
    //| TestOrderPool.mq4 |
    
    
    //+------------------------------------------------------------------+
    
    
    #property copyright"Copyright 2016-2017, Li Ding"
    
    
    #property link"dingmaotu@hotmail.com"
    
    
    #property version"1.00"
    
    
    #property strict
    
    
    
    #include <Mql/Trade/Order.mqh>
    
    
    #include <Mql/Trade/OrderPool.mqh>
    
    
    #include <Mql/Collection/LinkedList.mqh>
    
    
    
    //for simplicity, I will not use the Lang/Script class
    
    
    void OnStart()
    
    
     {
    
    
     OrderList<Order*> list;
    
    
     int total= TradingPool::total();
    
    
     for(int i=0; i<total; i++)
    
    
     {
    
    
     if(TradingPool::select(i))
    
    
     {
    
    
     OrderPrint();//to compare with Order.toString
    
    
     list.push(new Order());
    
    
     }
    
    
     }
    
    
    
     PrintFormat("There are %d orders.",list.size());
    
    
    
    //--- Iter RAII class
    
    
     for(Iter<Order*> it(list);!it.end(); it.next())
    
    
     {
    
    
     Order*o=it.current();
    
    
     Print(o.toString());
    
    
     }
    
    
    
    //--- foreach macro: use it as the iterator variable
    
    
     foreach(Order*,list)
    
    
     {
    
    
     Order*o=it.current();
    
    
     Print(o.toString());
    
    
     }
    
    
    
    //--- foreachv macro: declare element varaible o in the second parameter
    
    
     foreachv(Order*,o,list)
    
    
     Print(o.toString());
    
    
    
     }
    
    
    //+------------------------------------------------------------------+
    
    
    
    

    映射

    映射( 或者字典) 对于任何非平凡程序来说都非常重要。 Mql4-lib impelements使用Murmur3字符串哈希的高效哈希映射。 实现遵循CPython3哈希,并保留插入顺序。

    你可以使用任何内置类型作为键,包括任何类指针。 这些类型在 Lang/Hash 模块中实现了它们的哈希函数。

    HashMap接口非常简单。 下面是一个简单的例子来计算著名 Opera Hamlet的单词:

    #include<Mql/Lang/Script.mqh>#include<Mql/Collection/HashMap.mqh>#include<Mql/Utils/File.mqh>classCountHamletWords: publicScript {public:
     voidmain()
     {
     TextFiletxt("hamlet.txt", FILE_READ);
     if(txt.valid())
     {
     HashMap<string,int>wordCount;
     while(!txt.end() &&!IsStopped())
     {
     stringline= txt.readLine();
     stringwords[];
     StringSplit(line,'',words);
     intlen=ArraySize(words);
     if(len>0)
     {
     for(inti=0; i<len; i++)
     {
     intnewCount=0;
     if(!wordCount.contains(words[i]))
     newCount=1;
     elsenewCount=wordCount[words[i]]+1;
     wordCount.set(words[i],newCount);
     }
     }
     }
     Print("Total words: ",wordCount.size());
     //--- you can use the foreachm macro to iterate a mapforeachm(string,word,int,count,wordCount)
     {
     PrintFormat("%s: %d",word,count);
     }
     }
     }
     };DECLARE_SCRIPT(CountHamletWords,false)

    最近更新( 2017-11-28 ) 之后,映射迭代器不再是常量,并且支持两个附加操作: 删除和替换( setValue )。如果要从地图中删除某些元素,则必须将密钥存储在单独的位置。 既不优雅也不高效。 下面的示例显示了差异:

    HashMap<int,int> m;//--- before the updateVector<int> v;foreachm(int,key,int,value,m)
    {
     if(value%2==0) v.push(key);
    }foreachv(int,key,v)
    {
     m.remove(key);
    }//--- after the updateforeachm(int,key,int,value,m)
    {
     if(value%2==0) it.remove();
    }

    文件系统和 IO

    MQL文件功能通过设计直接对三种类型的文件进行操作: 二进制,文本和CSV对于我来说,这些类型的文件应该形成一个分层的关系: CSV是专门的文本,文本专用二进制(。文本的编码/解码)。 但是这些函数不是以这种方式设计的,而是通过允许各种函数在不同类型的文件上操作而混乱混乱。 例如,FileReadString 行为对于它是opearting的文件是完全不同的: 对于二进制文件,unicode字节是以指定长度读取的,对于整个行读取文本,而CSV只读取字符串字段。 我不喜欢这种设计,我既没有精力去重新实现文本编码/解码,也不需要再进行序列化/反序列化。

    所以我编写了 Utils/File MODULE,用更简洁的接口包装所有文件函数,但不改变整个设计。 这里有五个类:File 是基类,但是你不能实例化它;BinaryFileTextFileCsvFile 是子类,你可以在代码中使用这些子类,并使用相同的技术来循环访问目录文件。

    以下是文本文件和CsvFile的示例:

    #include<Mql/Utils/File.mqh>voidOnStart()
     {
     File::createFolder("TestFileApi");
     TextFiletxt("TestFileApiMyText.txt",FILE_WRITE);
     txt.writeLine("你好,世界。");
     txt.writeLine("Hello world.");//--- reopen closes the current file handle firsttxt.reopen("TestFileApiMyText.txt",FILE_READ);
     while(!txt.end())
     {
     Print(txt.readLine());
     }
     CsvFilecsv("TestFileApiMyCsv.csv",FILE_WRITE);//--- write whole line as a text filecsv.writeLine("This,is one,CSV,file");//--- write fields one by onecsv.writeString("这是");
     csv.writeDelimiter();
     csv.writeInteger(1);
     csv.writeDelimiter();
     csv.writeBool(true);
     csv.writeDelimiter();
     csv.writeString("CSV");
     csv.writeNewline();
     csv.reopen("TestFileApiMyCsv.csv",FILE_READ);
     for(inti=1;!csv.end(); i++)
     {
     Print("Line ",i);
     //--- notice that you SHALL NOT directly use while(!csv.isLineEnding()) here//--- or you will run into a infinite loopdo {
     Print("Field: ",csv.readString());
     }
     while(!csv.isLineEnding());
     }

    下面是 FileIterator的一个示例:

    #include<Mql/Utils/File.mqh>intOnStart()
    {
     for(FileIteratorit("*");!it.end(); it.next())
     {
     stringname=it.current();
     if(File::isDirectory(name))
     {
     Print("Directory: ",name);
     }
     else {
     Print("File: ",name);
     }
     }
    }

    或者,你可以使用功能强大的foreachfile 宏:

    #include<Mql/Utils/File.mqh>intOnStart()
    {//--- first parameter is the local variable *name* for current file name//--- second parameter is the filter pattern stringforeachfile(name,"*")
     {
     if(File::isDirectory(name))
     Print("Directory: ",name);
     elsePrint("File: ",name);
     }
    }

    还有一种特殊的文件叫做历史文件。 它们是支持MetaTrader图表显示的文件。 sepcial函数用于打开历史 file: HistoryFileOpen,历史文件具有固定的结构。 我在 Utils/HistoryFile 类中对历史文件进行了。 这里组件在实现自定义图表类型时非常有用,如脱机图表。 你可以在 Chart/PriceBreakChart 或者 Chart/RenkoChart 中看到示例用法。

    序列化格式

    有一些快速和可以靠的序列化格式来做持久性,messsaging,等等 有很多选项是非常有用的: JSON,ProtoBuf,等等,然而它们在MQL中有些难以实现。 比如 JSON,你必须使用映射或者字典来实现对象,比如数据结构,甚至解析数字并不是一个容易的任务。 而且我不喜欢每次收到简单JSON时都会创建字典。 因为你必须实现一个ProtoBuf编译器来生成MQL代码,所以ProtoBuf要困难得多。

    我决定重写mql4-redis绑定时,我计划制作一个纯 MQL Redis客户端。 所以我开始理解并实现 REdis Serialization Protocol ( 响应)。 它非常简单但非常有用。 它具有字符串。整数。array。bulk字符串( 字节) 和nil之类的数据类型。 所以我在MQL中实现了这些类型,并制作了完整的编码器/解码器。

    我把它放在这个通用库而不是mql客户端,因为它是一个可重用的组件。 考虑你可以将值序列化为缓冲区,并使用ZeroMQ将它们作为消息发送。 我将在这里部分解释响应协议组件的用法。

    要使用响应协议,你需要包括 Mql/Format/Resp.mqh。 值类型是直接向前的:

    #include<Mql/Lang/Resp.mqh>//--- RespValue is the parent of all values//--- it has some common methods that is implemented by all types//--- they are: encode, toString, getTypestringGetEncodedString(constRespValue &value)
     {
     charres[];
     value.encode(res,0);
     RespBytesbytes(res);
     //RespBytes' toString method prints the byte content as human readable string with proper escapesreturnbytes.toString();
     }voidOnStart()
     {
     //--- create an array preallocated with 7 elementsRespArraya(7);
     a.set(0, newRespBytes("set"));
     a.set(1,newRespBytes("x"));
     a.set(2, newRespBytes("10")); //Bulk strings (can contains spaces, newlines, etc. in the string)a.set(3, newRespError("ERROR test!")); //Same as RespString but indicate an errora.set(4,newRespString("abd akdfa"")); //RespString is what in hiredis REPLY_STATUS typea.set(5,newRespInteger(623));
     a.set(6,RespNil::getInstance()); //RespNil is a singleton typePrint(a.toString()); //Print a human readable representation of arrayPrint(GetEncodedString(a));
     charres[];
     a.encode(res,0);
     intencodedSize=ArraySize(res);
     RespMsgParsermsgParser; //RespMsgParser parses a buffer with complete inputRespValue *value=RespParser::parse(res,0);
     Print(parser.getPosition()==encodedSize);
     Print(value.getType()==TypeArray);
     Ptr<RespArray>b(dynamic_cast<RespArray*>(value));
     Print(b.r.size()==7);
     }

    如上面所示,你可以编写值arbituarily并将它们编码为缓冲区。 你可以无限地嵌套数组。 值类型为各种操作提供非常有用的方法。 你可以在相应的源文件中找到更多。

    我为这个协议编写了两个解析器。 第一个是面向消息的上下文,你可以在一个整体中接收缓冲区并开始解析。 类为 RespMsgParser,另一个是流上下文的RespStreamParser,可以接收一个缓冲区并开始解析。 它将告诉你如果需要更多输入,你可以在输入更多输入时继续进行分析。 后面的解析器受到 hiredis ReplyReader 实现的启发。

    它们都有一个 getError 方法,告诉你如果 parse 方法返回空值,( 检查 Mql/Format/RespParseError.mqh 以获取所有错误代码) 会发生什么错误。 如果没有 actaully,RespMsgParser 有一个 check 方法可以检查给定的缓冲区是否包含有效的RespValuecheck 方法还设置错误标志,如 parse 方法。

    两个解析器都没有嵌套限制,如 hiredis ReplyReader。 只有你对computer计算机( 或者堆栈)的记忆才是极限。 但是总体来说,ReplyReader 比我的解析器要快。 它是C,它保持一个与 static array的堆栈。 它并不打算成为通用编码/解码库。

    订单访问

    在大多数MQL4程序中,你可以通过 OrdersHistoryTotalOrdersTotalOrderSelect 访问订单。 代码通常是混乱的,因为你必须过滤某些命令。 基本逻辑如下所示:

    inttotal = OrdersTotal();for(inti=0; i<total; i++)
    {
     if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
     if(!MatchesWhateverPredicatesYouSet()) continue;
     DoSomethingYouWantWithTheOrder();
    }

    重点是,订单过滤代码通常是可以重用的,并且你不希望每次需要过滤订单时重写它。

    因此在 Trade/OrderTrade/OrderPool 模块中,我提供了一个OrderPool类,可以由你派生,并提供一个方便的迭代器来封装上述。 下面是一个演示基本用法的示例:

    #include<Mql/Trade/OrderPool.mqh>//+------------------------------------------------------------------+//| Only matches the profit orders in the history pool |//+------------------------------------------------------------------+classProfitHistoryPool: publicHistoryPool {public:
     boolmatches() const {returnOrder::Profit()>0;}
     };voidOnStart()
     {
     ProfitHistoryPoolprofitPool;
     //the foreachorder macro can iterate order pools for you//and take care of order filtering//both pointers and references are acceptedforeachorder(profitPool)
     {
     //--- here you can directly use Order* function like this://--- OrderPrint();//--- or create an Order classOrdero;
     Print(o.toString());
     }
     }

    Symetric顺序语义

    symetric顺序语义是什么意思? 要设置一个尾随 stoploss,请考虑以下代码,直到订单处于盈亏平衡状态:

    //Partial code for illustration purposes; not runnable if you don't have other codes//M_POINT is a constant (the Point value of current symbol)voidTrailToBreakeven(constOrder &order,OrderAttributes &attr)
     {//--- Check if profitted more than InpBreakevenPointsboolisSetStopLoss=false;
     doublestopLossLevel=0.0;
     if(order.getStopLoss()>0)
     {
     if(order.getType()==OP_BUY)
     {
     if(order.getStopLoss()<order.getOpenPrice())
     {
     doubleprofitPrice=m_symbol.getBid()-order.getOpenPrice()-InpBreakevenPoints*M_POINT;
     if(profitPrice>0)
     {
     if(InpBreakevenStep>0)
     {
     intfactor=int(profitPrice/(InpBreakevenStep*M_POINT));
     if(factor>0)
     {
     doublesl=attr.getOriginalStoploss()+InpBreakevenPoints*M_POINT*factor;
     if(sl>order.getStopLoss())
     {
     isSetStopLoss=true;
     if(sl>order.getOpenPrice())
     {
     stopLossLevel=NormalizeDouble(order.getOpenPrice()+M_POINT,M_DIGITS);
     }
     else {
     stopLossLevel=NormalizeDouble(sl,M_DIGITS);
     }
     }
     }
     }
     else {
     isSetStopLoss=true;
     stopLossLevel=NormalizeDouble(order.getOpenPrice()+M_POINT,M_DIGITS);
     }
     }
     }
     }
     else {
     if(order.getStopLoss()>order.getOpenPrice())
     {
     doubleprofitPrice=order.getOpenPrice()-InpBreakevenPoints*M_POINT-m_symbol.getAsk();
     if(profitPrice>0)
     {
     if(InpBreakevenStep>0)
     {
     intfactor=int(profitPrice/(InpBreakevenStep*M_POINT));
     if(factor>0)
     {
     doublesl=attr.getOriginalStoploss()-InpBreakevenPoints*M_POINT*factor;
     if(sl<order.getStopLoss())
     {
     isSetStopLoss=true;
     if(sl<order.getOpenPrice())
     {
     stopLossLevel=NormalizeDouble(order.getOpenPrice()-M_POINT,M_DIGITS);
     }
     else {
     stopLossLevel=NormalizeDouble(sl,M_DIGITS);
     }
     }
     }
     }
     else {
     isSetStopLoss=true;
     stopLossLevel=NormalizeDouble(order.getOpenPrice()-M_POINT,M_DIGITS);
     }
     }
     }
     }
     }
     if(isSetStopLoss)
     {
     if(OrderModify(order.getTicket(),0,stopLossLevel,0,0,clrNONE))
     {
     PrintFormat(">>> Setting order #%d stoploss to %f",order.getTicket(),stopLossLevel);
     if((order.getType()==OP_BUY && stopLossLevel>=order.getOpenPrice())
     || (order.getType()==OP_SELL && stopLossLevel<=order.getOpenPrice()))
     attr.setState(Breakeven);
     }
     else {
     Alert(StringFormat(">>> Error setting order #%d stoploss %f",order.getTicket(),stopLossLevel));
     }
     }
     }

    这在日常的EA开发中是非常典型的。 你可以看到购买和卖出订单的逻辑是相同的。 但是,代码必须单独编写,因为它们有足够的差异。 也很难判断你使用什么逻辑,因为它们被隐藏在计算细节中。 如果你在复制几乎同样的代码时忘记改变一些减号或者加号,这是错误的。 如果你能清楚地写出价格,我们会把你的逻辑只写一次,然后自动处理这两个?

    解决方案是 symetric顺序语义: 它是 Order 类中的一组方法(。使用实例和 static 版本)。 方法名是非常简单的( 我故意把它们 short ),而且每种方法都表示一个顺序的一般语义,而不是。 我将列出下面的基本操作并展示如何在以后使用它们。

    • 格式化和转换

    有 3个操作支持基于订单符号的价格值格式化和转换。

    
    * f(p) *format* price `p` to string with respect to the symbol digits
    
    
    * n(p) *normalize* price `p` to a double with respect to the symbol digits
    
    
    * ap(p) get the *absolute price difference* (double type) of point value `p` (int type)
    
    
    
    
    • 当前价格级别

    根据订单类型得到相应的询问/投标值是很常见的。 对于购买订单,打开与询价,与投标关闭;反之亦然销售订单。 我提供了 2个基本操作来获取。

    
    * s() get the correct price to *start* an order
    
    
    * e() get the correct price to *end* an order
    
    
    
    
    • 价格计算

    价格计算有 2个 opertions。

    
    * p(s,e) get the *profit* as the absolute price difference from start price
    
    
     `s` to the end price `e`. To get the loss, just use -p(s,e) or p(e,s); the
    
    
     former is preferred as it is clearer expressing the opposite of *profit*
    
    
    * pp(p,pr) the target *price* if we start from `p` and we want to *profit*
    
    
     `pr`. Use pp(p,-pr) if we want to lose `pr`
    
    
    
    

    pp 中有一个重载方法,其中 pr 参数是点值。 它只是为了方便而不是语义。

    使用这些基本的opperations,我们可以表达关于订单symetrically的大多数语义。 例如我们如何表达的盈亏平衡? 是 order.p(order.getOpenPrice(),order.getStoploss())>=0 这将计算为 order.getOpenPrice() <= order.getStopLoss() 购买订单,以及 order.getOpenPrice()> = order.getStoploss() 我们可以通过"如果我们从公开价格转移到止损价格,我们仍然盈利"来理解这个。 这样,你只需要写一组规则并明确表达。

    我将在本节的开头用 symetric顺序语义重写这个示例。 仔细阅读并与第一个示例进行比较。 了解代码长度如何大大减少,以及如何使目标变得非常清晰。

    //Partial code for illustration purposes; not runnable if you don't have other codesvoidTrailToBreakeven(constOrder &o,OrderAttributes &attr)
     {//--- Check if profitted more than InpBreakevenPointsboolisSetStopLoss=false;
     doublestopLossLevel=0.0;
     if(o.getStopLoss()>0)
     {
     //--- if not breakevenif(o.p(o.getOpenPrice(),o.getStopLoss())<0)
     {
     //--- how much do we already profit?doublepp=o.p(o.getOpenPrice(),o.e());
     //--- if we profit more than those points set in input parameterif(pp>o.ap(InpBreakevenPoints))
     {
     //--- if we need to trail step by stepif(InpBreakevenStep>0)
     {
     intfactor=int(pp/o.ap(InpBreakevenStep));
     if(factor>0)
     {
     //--- calculate the target price by move stoploss in FAVOR to us//--- NOTE that pp can accept points directly in its second parameterdoublesl=o.pp(attr.getOriginalStoploss(),InpBreakevenPoints*factor);
     //--- if target stoploss is better than current one//--- (we will profit if we move from current stop loss to `sl`)if(o.p(o.getStopLoss(),sl)>0)
     {
     isSetStopLoss=true;
     //--- if the target make this order breakevenif(o.p(o.getOpenPrice(),sl)>=0)
     //--- we set stop loss to be one point better than open pricestopLossLevel=o.pp(o.getOpenPrice(),1);
     elsestopLossLevel=sl;
     }
     }
     }
     else {
     isSetStopLoss=true;
     stopLossLevel=o.pp(o.getOpenPrice(),1);
     }
     }
     }
     }
     if(isSetStopLoss)
     {
     //--- modify stop loss to *NORMALIZED* priceif(OrderModify(o.getTicket(),0,o.n(stopLossLevel),0,0,clrNONE))
     {
     //--- notice that we use %s for the price because we need to display the price//--- to certain digits based on current order symbolPrintFormat(">>> Setting order #%d stoploss to %s",o.getTicket(),o.f(stopLossLevel));
     //--- we modify order state if this modification make the order breakevenif(o.p(o.getOpenPrice(),stopLossLevel)>=0) attr.setState(Breakeven);
     }
     else {
     Alert(StringFormat(">>> Error setting order #%d stoploss %s",o.getTicket(),o.f(stopLossLevel)));
     }
     }
     }

    捐赠指南

    如果你想为这个图书馆作出贡献,我会很高兴,但是你必须遵循一些规则。 我很重视代码质量( 还有格式)。 这绝对不能让你 discouraging。 相反,我欢迎来自所有开发人员的贡献。

    的默认样式将代码设置为 MetaEditor ( Ctrl-, )。 尽管MetaEditor不是最好的编辑器或者 IDE,但它是MQL的IDE,至少在现在。 默认风格并不适合所有( 我是指 3个空格),但它是大多数MQL开发人员所使用的标准。 即使我们不喜欢,我们也需要遵循社区标准来分享我们的知识和努力。 除非我们能创造一个更好的IDE让社区接受它。

    文件编码:UTF-8.如果使用中日文或者它的他非ascci字符,请将文件保存为 UTF-8. 在MetaEditor中没有选项,但是你可以使用favorate编辑器来完成它。 在MetaEditor中不保存到 Unicode ! 它将把文件保存在UTF16-LE中,Git会认为文件是二进制文件。

    空格和文件结束结束后不要离开空格。 在文件结尾处保留换行符。

    代码样式类成员如下所示: m_myMember ;方法 NAME 如下所示: doThis,常量定义和宏定义如下: SOME_MACRO ;类 NAME 如下所示: MyClass ;像这样的全局函数: DoThat ;使用 ObjAttr 或者相关 MACROS 定义getter和 setter。 可能还有其他事项需要注意。 尽量保持尽可能多的一致性。

    版权这个库是Apache2许可的。 你的捐赠也会被Apache2许可。 但你得到了你的信任。 如果修改现有文件的某些行,可以在版权行中添加 NAME 并指定更改的内容。

    我会跟你核对你的代码。 为我准备一些讨论或者问题。 我们需要确保代码是正确的。 我将尽可能多的帮助你。

    更改

    • 2017-12-13: Deprecate和删除 RenkoIndicatorDriver ;使用 HistoryData::OnUpdate 事件

    • 2017-11-28: 基于哈希表的容器主要重构。 HashMap和HashSet现在共享哈希和条目管理相同的代码库。 改进的压缩算法。 映射迭代器支持循环中的2 addional操作: 删除和替换( setValue )。

    • 2017-11-24: 使用 Symetric顺序语义改进和 stablize OrderManager

    • 2017-11-23: 设计和实现 Symetric顺序语义

    • 2017-09-28: 优化 OrderPool 代码实现订单跟踪 MODULE Trade/OrderTracker

    • 2017-08-15: 实施价格中断图表。 将ChartFile重命名为 HistoryFile,并生成了文件系统API的HistoryFile部分。 为MT4和 MT5 (。顶级包括目录 NAME 现在是 Mql )的统一库启动了进程。

    • 2017-07-14: 添加了 2个响应协议解析器: 一个用于消息缓冲区,一个用于流缓冲区;它们提供比hiredis解析器更具体的错误报告。

    • 2017-07-10: EventApp 中的事件处理程序;添加了 HashSet ;重新实现 HashMap

    • 在2017-07-10之前,我不会列出他们。 我决定为未来用户添加一个变更日志,以查看最初的情况。


    Foundation  MQL  MQL4  
    相关文章