更改 MQL4 程序的外部参数而无需重启
Alexey Koshevoy | 17 三月, 2016
简介
任何启动过 EA 或指标进行操作的用户都可能需要初步设置外部参数,这些参数往往是程序成功执行的基础。程序执行期间,可能需要更改参数。但如何在不进行初始化的情况下完成更改?某些情况下,这会涉及到管理之前打开的订单。本文中,我们试着尽可能灵活地解决这个问题。
问题陈述
我们将基于 Expert Advisor 而非指标来考虑这个问题。对于指标来说,这个问题并没有那么尖锐,解决方法是通用的,可以将它们应用于任何程序。
在经典的 EA 编写中,每一次价格变动都必须创建上下文。有两种上下文 - 状态上下文和交易上下文。状态上下文包含作为打开/关闭/修改订单的相关决策依据的指标和其他元素的当前状态。交易上下文包含有关开仓订单及其状态和数量的信息。分析交易上下文之后,交易者决定如何处理订单的操作。上下文可以有条件地按类型进行划分。引入此分类主要是为了更加便利。
如果每一次价格变动时上下文都可恢复,问题就不会发生了。你可以自由更改外部参数,并且你的 EA 将按照其逻辑继续操作。首先将进行初始化,然后 EA 将计算指标的值,“接管”开仓订单,并根据收集的信息执行一系列操作。一切都简单而透明。
现在我们要考虑到,某些情况下我们需要一种更精确而且不会导致程序全面重启的参数更改技术。
示例1.
在我们的开仓订单中,有些订单经过特殊处理。即我们需要在我们的 EA 打开的其他订单中找到它们。为此我们可以使用一些参数:Ticket(订单号)、Magic Number(幻数)、Currency(货币)、Comment(注释)。Magic Number(幻数)用于将我们的订单与由系统中其他 EA 打开或手动打开的订单区分开来。除了“Magic Number(幻数)”外,“Currency(货币)”允许我们将同一 EA 用于不同的货币对。只有剩下的两个参数对于每个订单是唯一的。“Comment(注释)”是适应范围最广的参数,它可以包含任何文本信息,但它有一个缺点 - 可以在服务器侧修改它,例如在部分收盘时。“Ticket(订单号)”对各个订单都是唯一的,但单独的订单号不足以用来按类划分订单,因此订单的编号必须与某些其他信息进行比较。我们可以看到,在这种情况下,最方便的办法是使用“Ticket(订单号)”参数并根据订单类别存储订单号。相应地,在重启时,包含订单号的数组将丢失,因此我们将无法恢复订单虚拟类别的相关信息。
示例2.
现在已有一种通用的机制,允许我们分析订单状态的变化。即订单状态有两个上下文。第一个列表基于当前价格变动,第二个列表基于前一个价格变动。分析信息后,我们可以得出结论,确定打开/修改/关闭了哪个订单以及此类操作的理由。此方法非常适用于 EA 调试和交易逻辑开发。但它的缺点是必须保存上一次价格变动时的订单状态。相应地,此数据将在 EA 重启时丢失。
示例 3.
一个 EA 正在接收外部数据。数据的接收方法和来源都无关紧要。重要的是,如果我们需要累积此数据以进行处理,并且无法请求其历史记录,则我们将必须在 EA 的整个工作期间创建上下文。更改外部参数将导致系统重启,并且我们将必须重新从外部源累积数据。
示例4.
MQL4 程序作为一个已编译文件派发。这样用户就不能更改其代码或查看你的算法。这是对你的知识产权的有力保护。但是,此程序可能有一些外部变量,除非一次性进行更改并用新值进行编译,否则每次都要更改它们不太方便。尽管此示例与上下文存储问题并无直接的关联,但对于此处要解决的问题来说,此示例是非常典型的。
创建 EA 时,你构建的逻辑应避免在操作期间存储上下文。在这种情况下,此类开发可能会复杂的多,但可在最大程度上提高系统的稳定性。然而,如果我们无法避免存储上下文,正在考虑的方法将帮助我们灵活地管理 EA 设置,而不会在更改其参数时丢失数据。
应用
为了在上述示例中解决这些问题,我们来开发一个综合解决方案以管理 MQL4 程序的外部参数。使用此类系统可以做什么?
根据定义,我们可以更改以下所有类型的外部参数:
• Integer
• Bool
• Double
• String
• Datetime
但是,我们不应满足已取得的成绩。为了挖掘此方法的所有潜力,我们应该基于一个非常简单的示例考虑系统的操作原理。
此综合解决方案的工作方式如下。在初始化阶段,MQL4 程序将用户可更改的所有值保存到对应的文件。外部程序打开此文件,读取数据,将数据放在屏幕上,然后用户开始工作。一旦用户更改外部程序中的其中一个参数,此文件就会被修改。即,将写入用户设置的新值,取代旧值。然后在进行函数“start”调用时,MQL4 程序将检查数据文件中的已更改值,一旦找到任何更改,就将调用到对应函数。用于处理外部值中更改的函数执行一些预设算法。在大部分情况下,只要将新值分配给变量就足够了。这就是外部参数更改的综合解决方案的一次循环所做的大致工作。
从上述示例中,我们可以看到,在 MQL4 程序与外部程序之间创建稳定的对话是综合解决方案能够成功实施的最重要因素。现在我们必须选择组织此对话的途径和方法。但是,即便是不使用 DLL 的最简单的方法,我们所能做的也不仅仅是更改外部参数。
让我们看看一些示例,它们揭示了此方法的一些其他功能。由于我们有一个双边对话,MQL4 程序可以回复外部程序的请求。
例如,某用户更改一个变量的值。外部程序将该变量的新值写入到文件,并将此变量的状态更改为“等待 MQL4 程序的操作”。处理此文件时,MQL4 程序仅考虑拥有此状态的变量,其他变量未发生变化。处理找到的变量时,它会将状态更改为“已接受更改”。因此,在外部程序中,我们可以看到更改直接在 MQL4 程序中执行的时间。这是一个很有用的监控功能。
让我们试着实现“操作”功能。我们需要让外部程序中的变量更改不会引发 MQL4 程序中对应变量的更改,而是只执行一次某个特定操作。实现方式如下。此变量在外部程序中进行了更改。此变量可取的值为 0 和 1。如果值为 1,MQL4 程序执行一个特定操作,并将值 0 分配给此变量。现在可从外部程序再次执行此操作。这并不仅仅是更改变量。通过这些操作,我们可以实现很多有用的功能。这些操作与 MQL4 脚本非常相似,但有一个重大的差异。脚本可以在 MetaTrader4 客户终端操作期间的任何时候执行,而操作仅可在调用“start”函数时执行,即在当前价格发生变化时。
最后,我们可以考虑一个示例,即如何使用此方法将外部源的数据转移至 MQL4 程序中。假设我们不需要更改任何外部变量,但必须从外部源管理 MQL4 程序,更准确的说,是管理 EA。外部源处理数据,进行计算,并基于获得的结果做出有关打开/关闭订单的决策。要打开一个订单,必须将主要参数和建仓命令传递给程序。订单参数的状态为“等待”,因为它们的更改不应强制程序执行操作。在本例中,操作变量才是读取建仓参数的事件。
我们考虑了使用综合解决方案时的三个方面。第一个方面是将一个方向上的带状态的参数从外部程序传递到 MQL4 程序。在这种情况下,对话是通过更改状态值实现的。第二是执行特定操作的请求。在对话中,变量值和状态值都会被更改。第三也是最简单的一个方面,即提取文件中存储的事件信息。不使用对话。
开发综合解决方案
要开发综合解决方案,我们要解决很多问题。首先,我们要开发可在程序之间传输信息的文档结构。然后我们要选择一个用于传输此文档的协议。最后,我们要用 MQL4 或任何适用于特定外部程序的其他编程语言编写供系统使用的库。
开发文档结构
开发文档结构是该综合解决方案最重要的任务之一。此文档必须反映上述所有方面。
对于信息存储,最普遍的方法是使用 XML 技术。但是,这会引发很多问题。首先,MQL4 没有任何使用 XML 的内置方法。第二,如果我们通过 DLL 使用 XML,则整个系统将比原来复杂数倍。所以,我们要使用标准文本文件以及我们自己的数据表示结构。
此文档的主要目标是变量或值。此值有一个精心定义的结构,包含以下元素:
• Title
• Type
• Status
• Value
让我们逐个进行说明:
• Title - 变量名称。程序用它来识别对象。
Type - 变量类型。处理逻辑根据变量类型而变化。这些类型是根据数值指定的。
类型 | 说明 | MQL 中的类似项 | 值 |
---|---|---|---|
0 | 布尔型或逻辑型 | bool | 0 - false,1 - true |
1 | 字符串型 | string | 字符集。 |
2 | 整数字符 | int | 与 MQL4 规则对应的边界值。 |
3 | 实数字符 | double | 与 MQL4 规则对应的边界值。分隔符为点。 |
4 | 日期 | datetime | 与 MQL4 规则对应的记录格式。 |
5 | 操作 | 无等同项 | 0 - 不需要执行,1 - 需要执行。 |
Status - 状态变量。它由数值指定。我们来看一张包含可能变化的表:
值 | 说明 |
---|---|
0 | 等待。无需处理。 |
1 | 请求更改。MQL4 程序必须回应此状态值。 |
2 | 无需处理。它是必须处理的变量的值(针对除直接更改变量以外的事件)。 |
值 - 变量值。值的记录规则取决于变量类型。
参数相互作用。
外部程序。更改此值。
如果 Status=0,则更改“Value”的值,Status=1。
如果 Status=1,则更改“Value”的值,“Status”的值不变。
如果 Status=2,则更改“Value”的值,“Status”的值不变。
外部程序。取消更改此值。
有时会出现值被误改或替换成错误值的情况,或用户在确认更改之后又反悔了。让我们考虑取消此程序的可能性。但是,必须要考虑的是,如果 MQL4 程序已处理了包含更改的文件,那就无法再取消了
如果 Status=1,则“Value”更改为上一个值,且 Status=0。
如果 Status=2,则“Value”更改为上一个值,但“Status”的值不变。
MQL4 程序。处理更改。
MQL4 程序仅在变量“Status”等于 1 时才会考虑更改。根据定义,变量类型的更改无关紧要,因为我们会针对每次更改调用处理函数。不同之处仅在“Value”是否有值,即变量的更改是操作执行更改还是操作执行请求。我们来详细地分析这一点。
如果 Status=1 且“Type”值的范围为 0 到 4,则使用“Value”并将“Status”更改为 0。
如果 Status=1 且 Type=5,则执行一项操作并将“Status”更改为 0,将“Value”更改为 0。
如果在执行操作期间,我们在 Status=2 时使用了其他值,则使用之后“Status”的值不变。
我们来看一些示例。
示例 1.更改变量。
假设变量 Var1 为 Integer 类型,值为 10。此变量处于“等待”状态。
Title=Var1
Type=2
Status=0
Value=10
在外部程序中,将 Var1 的值更改为 30。现在变成:
Title=Var1
Type=2
Status=1
Value=30
“Value”的值已更改,“Status”的值也已更改为“请求更改处理”。
MQL4 程序检测这些更改,调用必要的函数,并修改此文档:
Title=Var1
Type=2
Status=0
Value=30
“Value”的值已被接受,“Status”的值也已更改为“等待”。
示例 2.执行操作。
假设变量 Var2 为 Action 类型。此变量处于“等待”状态。
Title=Var2
Type=5
Status=0
Value=0
在外部程序中,将 Var2 的值更改为 1。把目光放远一点,我们可以这么说,为了避免在外部程序中将错误的值分配给操作变量,我们会使用按钮而非输入字段。现在变成:
Title=Var2
Type=5
Status=1
Value=1
“Value”的值已更改,“Status”的值也已更改为“请求更改处理”。
MQL4 程序检测这些更改,调用必要的函数,并修改此文档:
Title=Var2
Type=5
Status=0
Value=0
“Value”的值已更改为 0,而“Status”的值已更改为“等待”。
我们可以看到,执行操作时,“Status”和“Value”的值都会更改。如果此变量发生更改,只有“Status”的值也会更改,而此变量的值保持不变。
选择传输协议
MQL4 程序与外部程序之间的数据传输有很多方法,我们选择文件方法。使用文件不需要为 MT 4 开发其他的库。此方法相当普遍,占用资源少,实现起来也简单。它的很多缺点都很容易克服,也没有严重的限制。
MQL4 程序仅可针对特定事件对变量在文件中的更改进行响应,此事件指当前价格基于当前货币对发生变化。
我们可以随时从外部程序检查文件中的变量。我们应该选择检查的最佳时间间隔,以避免 PC 过载并防止长时间占用此文件,因为此文件同时还会被 MQL4 程序使用。外部程序中的更改主要是人为的,自然不会像计算机那么快,因此时间足够检查此文件(频率不会高于每半秒一次)以跟踪相关情况。当然,此参数必须是可调整的,而且你应该根据经验自己选择此时间间隔。
在尝试用 MQL4 程序打开文件时,可以用外部程序打开此文件进行编写。因此,我们应该预见到这种情况,并能够在一次处理中多次调用此文件,这样就不会浪费时间等待新的价格变化。外部程序也是如此:如果此文件正被 MQL4 程序使用,你应该按照特定时间间隔多次尝试调用此文件。
针对 MQL4 的调整
变量
int ExVH_VarCnt; string ExVH_FileName; string ExVH_Project; string ExVH_Title[]; string ExVH_Type[]; string ExVH_Status[]; string ExVH_Value[];
用户的函数
bool ExVH_Open(string FileName) { bool Result=true; ExVH_FileName=FileName; if (!ExVH_i_Load()) Result=false; if (Result) if (!ExVH_i_GetVarCnt()) Result=false; if (Result) ExVH_i_Disassemble(); return(Result); } int ExVH_Close() { ExVH_i_Assemble(); ExVH_i_Save(); } int ExVH_GetStatus(int Id) { if ((Id<1)||(Id>ExVH_VarCnt)) return(-1); else return(StrToInteger(ExVH_Status[Id-1])); } int ExVH_SetStatus(int Id, int Status) { int Result; if ((Id<1)||(Id>ExVH_VarCnt)) Result=-1; else { Result=1; ExVH_Status[Id-1]=Status; } return(Result); } string ExVH_GetValue(int Id) { if ((Id<1)||(Id>ExVH_VarCnt)) return("N/A"); else return(ExVH_Value[Id-1]); }
内部使用函数
bool ExVH_i_Load() { bool Result=true; ExVH_Project=""; int i=0; int FS=0; int handle; int Buf[]; handle=FileOpen(ExVH_FileName,FILE_BIN|FILE_READ); if(handle>0) { FS=FileSize(handle); ArrayResize(Buf,FS); while(!FileIsEnding(handle)) { Buf[i] = FileReadInteger(handle, CHAR_VALUE); i++; } FileClose(handle); string Str=""; for (i=0;i<FS;i++) Str=Str+CharToStr(Buf[i]); ExVH_Project=Str; } else Result=false; return(Result); } bool ExVH_i_Save() { bool Result=true; int handle=FileOpen(ExVH_FileName,FILE_BIN|FILE_WRITE); if(handle>0) { FileWriteString(handle,ExVH_Project,StringLen(ExVH_Project)); FileClose(handle); } else Result=false; return(Result); } bool ExVH_i_GetVarCnt() { bool Result=true; string Value=ExVH_i_GetVarValue("Var_Cnt"); if (Value=="N/A") Result=false; else ExVH_VarCnt=StrToInteger(Value); return(Result); } void ExVH_i_Disassemble() { int i; ArrayResize(ExVH_Title, ExVH_VarCnt); ArrayResize(ExVH_Type, ExVH_VarCnt); ArrayResize(ExVH_Status, ExVH_VarCnt); ArrayResize(ExVH_Value, ExVH_VarCnt); for (i=0;i<ExVH_VarCnt;i++) { ExVH_Title[i]=ExVH_i_GetVarValue("Var"+(i+1)+"_Title"); ExVH_Type[i]=ExVH_i_GetVarValue("Var"+(i+1)+"_Type"); ExVH_Status[i]=ExVH_i_GetVarValue("Var"+(i+1)+"_Status"); ExVH_Value[i]=ExVH_i_GetVarValue("Var"+(i+1)+"_Value"); } } void ExVH_i_Assemble() { ExVH_Project="[ExVH 1.0]\r\n\r\n"; ExVH_Project=ExVH_Project+"Var_Cnt="+ExVH_VarCnt+"\r\n\r\n"; int i; for (i=0;i<ExVH_VarCnt;i++) { ExVH_Project=ExVH_Project+"Var"+(i+1)+"_Title="+ExVH_Title[i]+"\r\n"; ExVH_Project=ExVH_Project+"Var"+(i+1)+"_Type="+ExVH_Type[i]+"\r\n"; ExVH_Project=ExVH_Project+"Var"+(i+1)+"_Status="+ExVH_Status[i]+"\r\n"; ExVH_Project=ExVH_Project+"Var"+(i+1)+"_Value="+ExVH_Value[i]+"\r\n\r\n"; } } string ExVH_i_GetVarValue(string VarName) { string Result="N/A"; int Start,Stop; VarName=VarName+"="; Start=StringFind(ExVH_Project,VarName,0); if (Start!=-1) { Start=Start+StringLen(VarName); Stop=StringFind(ExVH_Project,CharToStr('\n'),Start); if (Stop!=-1) { Stop=Stop-1; Result=StringSubstr(ExVH_Project,Start,Stop-Start); } } return(Result); }
针对 C++ 的调整
此项目是用 C++ Builder 2007 编写的。本文随附提供一个名为ExVH_CPP.zip的源代码存档。
测试
让我们实现一个可以表现大部分可能性的小范例。
因此,我们来创建一个测试文档:
[ExVH 1.0]
Var_Cnt=5
Var1_Title=Boolean test
Var1_Type=0
Var1_Status=2
Var1_Value=0
Var2_Title=String test
Var2_Type=1
Var2_Status=2
Var2_Value=Hello world!
Var3_Title=Integer test
Var3_Type=2
Var3_Status=0
Var3_Value=12345
Var4_Title=Double test
Var4_Type=3
Var4_Status=0
Var4_Value=123.45
Var5_Title=Action test
Var5_Type=5
Var5_Status=0
Var5_Value=0
将文档保存为ExVH.evh保存在[MetaTrader]/experts/files/ 中。
签名 [ExVH 1.0] 允许我们在打开此文件时识别文档,并了解所用文档结构的版本。如果文档结构发生更改,签名也必须随之更改。考虑到文档文件扩展名将保持不变,签名的更改可以避免混淆。
Var_Cnt=5.此记录告诉我们,此文档包含 5 个变量。
后跟同一类型的记录,其中包含各个变量的信息。记录是根据上述规格创建的。
VarX_Title=
VarX_Type=
VarX_Status=
VarX_Value=
因此,我们有两个必须在操作 (Var5) 显示时立即读取的变量(Var1 和 Var2)以及另外两个变量(Var3 和 Var4),这些变量的更改必须视为独立操作。
一旦在变量中进行了更改,MQL4 程序必须在屏幕上显示对应的消息。
测试 MQL4 代码
int init() { return(0); } int deinit() { return(0); } int start() { if (!ExVH_Open("ExVH.evh")) return(0); // Checking for action status if (ExVH_GetStatus(5)==1) { Alert("Actioned!"); string VarValue; if (ExVH_GetValue(1)=="1") VarValue="true"; else VarValue="false"; // Boolean variable value Alert("Boolean test variable="+VarValue); // String variable value Alert("String test variable="+ExVH_GetValue(2)); ExVH_SetStatus(5,0); } // Integer variable value if (ExVH_GetStatus(3)==1) { Alert("Integer test variable has been changed. New value="+ExVH_GetValue(3)); ExVH_SetStatus(3,0); } // Double variable value if (ExVH_GetStatus(4)==1) { Alert("Double test variable has been changed. New value="+ExVH_GetValue(4)); ExVH_SetStatus(4,0); } ExVH_Close(); return(0); }
启动和测试
最先启动综合解决方案的哪个部分是无关紧要的。本例中先启动 MetaTrader 4。首先将文件 ExVH_Demo.mq4 复制到文件夹 [MetaTrader]/experts/ 中,然后启动终端。文档 ExVH.evh 已写入到程序代码中。启动 EA。由于 EA 要等待文件中的更改,所以现在不会发生什么状况。
启动早前安装在 PC 上的 ExVH.exe。
程序的一般视图
打开“项目”,选择“打开”项...
打开文档
程序将加载变量,现在我们可以更改这些变量。
“Status”列中反映了两个值:idle(等待)和 Changed by ExVH(更改是在外部程序中执行的)。这是激活我们的“Action”之后屏幕显示的画面。
激活“Action”后程序的一般视图
不同变量类型的更改是通过不同的方式实现的。
对于布尔型变量,程序会给出以下两条消息之一:
对于“Action”激活,它会给出以下消息:
对于所有其他类型的变量,则使用以下统一的独立形式。
更改变量值的形式
进行各种更改后,终端中的“Alert”窗口可能会显示如下:
程序操作期间的消息框示例
缺点
任何好点子都有优缺点。
• 此方法无法主动用于回溯测试。
• 更改参数可能会与 EA 的逻辑产生冲突。处理变量中的更改时应非常谨慎。这更像是一个特征而不是缺点。
• 仅可在出现新的价格变动时执行命令。
还有什么可以做的
可增强系统各个方面的功能。操作的范围几乎是无限的。
• 根据不同的场景更改参数。例如,对于密码,可以隐藏输入的信息。
• 存储加密文件可以隐藏密码。
• 可以更改数据传输协议,使其更可靠更安全。
总结
我们讨论了在 MQL 程序中更改内部变量值的方法。现在我们能够在不重启程序的情况下管理外部变量和其他变量。
本文随附文件
文件
说明
ExVH_Demo.mq4
测试 EA
ExVH_Project.zip
准备的项目文件
ExVH_CPP.zip
外部程序的源代码
ExVH_Install.zip
外部程序安装程序
注意ExVH_Install 是使用“高级安装程序”的试用版本创建的。