指标间的数据交换:易如反掌!

简介

赫兹量化之所以欣赏初学者,是因为他们固执地不愿意使用搜索,而诸如“常见问题解答”、“初学者指南”或“妇孺皆知的问题解答”的话题随处可见。他们真正的任务是提出问题,比如“如何...”、“有没有可能...”,但他们常常获得“没门”、“不可能”等诸如此类的答案。 恒古流传的警句“永不说不”一直激励着科学家、工程师和程序员思考和推陈出新。

1. 问题定义 例如,下文引用自 MQL4 社区论坛的一个主题(译自俄语): ...有两个指标(我们称之为 A 和 B)。像往常一样,指标 A 使用直接来自价格图表的数据,B 使用指标 A 的数据。问题如下:如何使用指标 A 的数据(已经附加)而不是 iCustom(“指标 A”,...)动态执行 B 的计算,即,如果我更改了指标 A 的某些设置,更改应该在指标 B 的参数更改上体现出来。

或换言之: ...假设我们已将“移动平均线”附加至图表。现在要如何才能直接访问其数据缓冲区? 又如:

... 如果我使用 iCustom 调用某项指标,即使该指标在调用前已经加载也仍将重新加载。是否有什么方法防止指标重复加载?

诸如此类的问题还有很多,并且被反复问及 - 提问者不仅仅是初学者。概括而言,问题在于赫兹量化没有办法访问自定义指标数据而不使用 iCustom (MQL4) 或 iCustom + CopyBuffer 绑定 (MQL5)。但是用 MQL 代码编写一些函数以使用来自指定图表的数据获取数据或进行计算,对于开发下一部大作而言是十分诱人的。 使用上文中提到的标准函数并不方便:例如,在 MQL4 中,当调用 iCustom 时,将为每个调用方创建指标的副本;而对于 MQL5,通过使用句柄使问题得到了部分解决,现在仅为原本执行一次计算。但句柄也不是万灵药:如果涉及的指标占用大量的计算资源,则终端互斥等待几乎是通过每次重新初始化的十几秒钟保证。 我记得有几个提供的方法可访问原本,如下所示:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

  • 在物理磁盘或内存上映射文件;

  • 经由 DLL 共享内存使用以用于数据交换;

  • 使用客户端的全局变量以用于数据交换及其存储。

此外还有属于上述方法的变体的其他一些方法,以及一些不常用的方法,如套接字、邮件槽等(有一种基本方法在“EA 交易”中经常使用,即直接将指标计算转移至“EA 交易”代码,但这远远超出了本文论述的范围)。 在笔者看来,所有这些方法都有可取之处,但它们有一个共同的劣势:数据首先是复制到某个位置,然后才分发给其他方。首先,这需要占用部分 CPU 资源;其次,产生了传输数据相关性的新问题(在此我们不做论述)。

所以,让我们尝试定义问题: 我们希望创建这样一个环境,即能够提供对附加于图表的指标的数据访问,并具有以下属性:

  • 没有数据复制(也没有数据相关性的问题);

  • 只需稍加修改我们需要使用的可用方法的代码;

  • MQL 代码优先(当然,我们必须使用 DLL,但我们将只使用一些 C++ 代码字符串)。

笔者将 C++ Builder 用于 DLL 创建和赫兹量化客户端。下面的源代码以 MQL5 编写,MQL4 代码已附于本文;赫兹量化将在下文中讨论两种代码的主要区别。

2. 数组 首先,赫兹量化需要一些理论,因为我们将要使用指标缓冲区,并且我们必须知道缓冲区的数据在内存中是如何分布的。这一信息没有适当地文档化。 MQL 中的动态数组是具有可变大小的结构,如果数组大小增加而在数组后没有空闲内存,则 MQL 如何解决数据重新分配的问题将变得十分有趣。我们有两种方法解决问题:

  1. 在可用内存的其他部分重新分配新数据(并储存该数组所有部分的地址,例如使用引用列表),或

  2. 将整个数组作为整体移动至内存新的部分,该部分有足够的空间用于分配数组。

第一种方法带来了其他一些问题,因为在这种情况下我们必须调查 MQL 编译程序创建的数据结构。以下考虑仍然证明了第二个变体(仍然较慢):当动态数组传递至外部函数时(传递至 DLL),后者获得数据第一个元素的指针,其他数组元素按照顺序排列在第一个数组元素后。 当通过引用传递时,数组数据可以更改,因此这意味着对于第一种方法,复制整个数组至单独的内存区域以传递给外部函数,然后将结果添加至源数组存在一个问题,即,执行第二种方法隐含的相同的操作。

虽然这一结论在逻辑上不是 100% 精确,赫兹量化仍可将它视为是相当可靠的(这也被我们基于此理念的产品的正确运行所证明)。

因此,我们可以假设下面的说法是正确的:

  • 在每一时刻,动态数组的数据在内存中一个接一个地连续排列;并且数组可重新分配至计算机内存的其他区域,但只能作为一个整体操作。

  • 当动态数组作为参数通过引用传递至外部函数时,第一个数组元素的地址被传递至 DLL 库。

然而我们还需要其他一些假设:所谓时刻,赫兹量化指的是调用对应指标的 OnCalculate()(MQL4 则为 start () 函数)函数的时刻;此外,为了简便,我们假设我们的数据缓冲区具有相同的维度,且类型均为 double []。 3. 必要条件 MQL 不支持指针(除了所谓的对象指针,对象指针并不是普遍意义上的指针),因为这已被 MetaQuotes Software 公司的代表反复声明和确认。因此,我们来看看事实是否如此。

什么是指针?指针并不只是带星号的的标识符,它是计算机内存单元的地址。那么什么是单元地址?单元地址是起始于某个起点的序列号。最后,什么是序列号?序列号是一个整数,对应于计算机内存的某个单元。为什么我们不能像使用整数那样使用指针?不,我们可以,因为 MQL 程序可完美处理整数! 那么如何将指针转换为整数?动态链接库可帮助我们达成目的,我们将利用 C++ 类型转换的机会。由于 C++ 指针为四字节数据类型,对我们而言,可以很方便地将 int 用作此类四字节数据类型。 符号位并不重要,我们对其不作考虑(如果它等于 1,意味着整数为负),重要的是我们可以保持所有指针位不变。当然,赫兹量化可以使用无符号整数,但 MQL5 和 MQL4 的代码相似则更为理想,因为 MQL4 未提供无符号整数。

因此, extern "C" __declspec(dllexport) int __stdcall GetPtr(double *a) { return((int)a); } 这样,赫兹量化就有了值为数组起始地址的长型变量!现在,我们需要了解如何读取 i-th 数组元素的值:

extern "C" __declspec(dllexport) double __stdcall GetValue(int pointer,int i) { return(((double*) pointer)[i]); }

... 并写入值(在我们的示例中它仍然不是必须的...)

extern "C" __declspec(dllexport) void __stdcall SetValue(int pointer,int i,double value) { ((double*) pointer)[i]=value; } 以上便是全部内容。现在我们可以在 MQL 中使用指针。

4. 包装

我们已创建系统的内核,现在必须为其在 MQL 程序中使用方便做准备。然而,美学粉也不要感到不安,我们会向大家展现一些不同之处。

有很多种方法可用,我们将选择以下方式。让我们回想一下,客户端具有一个特别功能用于独立 MQL 程序间的数据交换 - 全局变量。最自然的方式是用它们存储指向我们将要访问的指标缓冲区的指针。我们会将这些变量作为描述符表考虑。每个描述符将具有如下所示的以字符串表示的名称:

string_identifier#buffer_number#symbol#period#buffer_length#indexing_direction#random_number, 并且其值将等于计算机内存中相应缓冲区指针的整数显示。 有关描述符字段的一些细节。

  • string_identifier – 任意字符串(例如,指标的名称 - 可使用 short_name 变量等);它将被用于搜索必要的指针,我们指的是一些指标将使用相同的标识符注册描述符,并在相互间使用字段进行区分:

  • buffer_number – 将用于区分缓冲区;

  • buffer_length – 我们需要用它来控制界限,否则可能导致客户端和 Windows 崩溃Blue Screen of Death :);

  • symbol, period – 交易品种和周期用于指定图表窗口;

  • ordering_direction – 它指定了数组元素的排序方向:0 – 正常排序,1 – 逆向排序(AS_SERIES 标志为 true);

  • random_number – 如果指标有多个副本通过不同的窗口或不同的参数集附加至客户端时使用(它们的第一个和第二个字段可设置相同的值,这也是我们需要通过某些方式区分它们的原因)- 也许这不是最佳的解决方案,但它确实管用。

首先,赫兹量化需要用到一些函数来注册和删除描述符。看一看函数的第一个字符串 - 调用 UnregisterBuffer() 函数对于从全局变量列表中删除旧的描述符来说是必要的。 对于每个新柱,缓冲区大小将增加 1,因此我们必须调用 RegisterBuffer()。如果缓冲区大小改变,新的描述符将在表中创建(其大小信息包含在其名称内),旧的描述符将留在表中。这就是我们使用它的原因。 void RegisterBuffer(double &Buffer[], string name, int mode) export { UnregisterBuffer(Buffer); //首先删除变量以防万一 int direction=0; if(ArrayGetAsSeries(Buffer)) direction=1; //设置正确的ordering_direction name=name+"#"+mode+"#"+Symbol()+"#"+Period()+"#"+ArraySize(Buffer)+"#"+direction; int ptr=GetPtr(Buffer); // 获取缓存指针 if(ptr==0) return; MathSrand(ptr); //使用指针值代替当前时间,来生成随机数比较方便 while(true) { int rnd=MathRand(); if(!GlobalVariableCheck(name+"#"+rnd)) //检查独特的名称 - 我们假设 { //没有人会使用更多的RAND_MAX缓存 :) name=name+"#"+rnd; GlobalVariableSet(name,ptr); //写入全局变量 break; } } }} void UnregisterBuffer(double &Buffer[]) export { int ptr=GetPtr(Buffer); //我们将在缓存的真实地址中注册 if(ptr==0) return; int gt=GlobalVariablesTotal(); int i; for(i=gt-1;i>=0;i--) //遍历所有全局变量 { //并且从所有地方删除缓存 string name=GlobalVariableName(i); if(GlobalVariableGet(name)==ptr) GlobalVariableDel(name); } } 详细的注释在此处是不必要的,我们只是指出第一个函数在全局变量列表中创建上述格式的新的描述符的事实;第二个函数搜索所有全局变量并删除变量和其值等于指针的描述符。

现在考虑第二个任务 - 从指标获取数据。在我们实施对数据的直接访问前,我们需要找出相应的描述符。可以使用以下函数实现这一点。它的算法如下所述:我们遍历所有全局变量,并检查是否存在描述符中指定的字段值。 如果找到,赫兹量化将其名称添加至数组,作为最后一个参数传递。因此,结果是函数返回所有匹配搜索条件的内存地址。返回值是找到的描述符的数量。 int FindBuffers(string name, int mode, string symbol, int period, string &buffers[]) export { int count=0; int i; bool found; string name_i; string descriptor[]; int gt=GlobalVariablesTotal(); StringTrimLeft(name); //整理字符串的不必要空间 StringTrimRight(name); ArrayResize(buffers,count); //将大小重置为0 for(i=gt-1;i>=0;i--) { found=true; name_i=GlobalVariableName(i); StringExplode(name_i,"#",descriptor); //分割字符串 if(StringFind(descriptor[0],name)<0&&name!=NULL) found=false; //根据匹配条件检查每一个字段 if(descriptor[1]!=mode&&mode>=0) found=false; if(descriptor[2]!=symbol&&symbol!=NULL) found=false; if(descriptor[3]!=period&&period>0) found=false; if(found) { count++; //条件满足,将其添加到列表中 ArrayResize(buffers,count); buffers[count-1]=name_i; } } return(count); } 正如我们在函数代码中所看到的,某些搜索条件可以省略。例如,如果我们传递 NULL 作为名称,系统将省略对 string_identifier 的检查。其他字段也是一样:mode<0,symbol:=NULL 或 period<=0。它允许在描述符表中扩展搜索选项。 例如,您可以找出所有图表窗口中的“移动平均线”指标,或使用周期 M15 将范围限制在 EURUSD 图表中。补充说明:对 string_identifier 的检查通过函数 StringFind() 执行而不是严格的相等检查。这样做是为了能够有机会通过描述符的一部分执行搜索(也就是说,当有多个指标设置为 "MA(xxx)" 类型的字符串时,搜索可通过子字符串 "MA" 进行 - 结果是我们将找出所有注册的“移动平均线”)。

赫兹量化还使用了函数 StringExplode(string s, string separator, string &result[])。该函数使用分隔符将指定的字符串分割为子字符串,并将结果写入结果数组。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值