免费期货量化软件策略:从头开始开发智能交易系统(第 25 部分)

文章讲述了如何通过将EA(智能交易系统)中的背景、价格对应成交量和Times&Trade等非交易元素转化为指标,以提高EA的性能和可靠性。通过这样的重构,EA可以更专注于订单管理和分析,避免不必要的计算,减少与其它事件的冲突,从而提升系统效率。
摘要由CSDN通过智能技术生成

概述

在上一篇文章提供系统健壮性 (I)中,赫兹期货量化已经看到了如何更改 EA 的某些部分,从而令系统更加可靠和健壮。

这只是针对兹期货量化将要在本文中所做之事的介绍。 请忘记您所知道的、计划的、或希望的一切。 这里最困难的事情莫过于能将事物分离。 自本系列开始以来,EA 几乎一直在持续发展:我们在增加、修改、甚至删除一些东西。 而这一次,我们将把我们一直在做的事情推向极致。

与看似的景象对比,有一个大疑惑:设计优良的 EA 内部能否不含任何类型的指标。 它只观察并确保遵守指示的订单位置。 完美的 EA 本质上只是一个向导,针对价格的所作所为提供真实的洞察力。 它不查看指标,而只查看图表上的仓位或订单。

您也许会认为我在胡言乱语,不知道自己在说啥。 但您有没有想过为什么 MetaTrader 5 要为不同的事务提供不同的类? 为什么平台将指标、服务、脚本、和智能系统分门别类,而不是混杂在一块呢? 这个嘛...

这就是重点。 如果事情被分开来,那么正是因为它们最好分开来处理。

指标用处较广泛,无论它是什么。 如果指标的设计经过深思熟虑,那就太好了,如此可避免损害整体性能 — 我的意思是不要损害 MetaTrader 5 平台,但非其它指标。 因为它们在不同的线程上运行,所以它们可以非常有效地并行执行任务。

服务则以不同的方式提供帮助。 例如,在本系列的文章 访问 Web 上的数据(II)访问 Web 上的数据(III)中,兹期货量化利用服务以非常有趣的方式访问数据。 事实上,我们可以直接在 EA 中执行此操作,但正如我已经在其它文章中解释的那样,这并非最合适的途径。

脚本则以一种非常独特的方式帮助我们,因为它们只能存在一段时间,完成一些非常具体的事情,然后就从图表中消失。 或者它们也能一直驻留,直到我们更改某些图表设置,例如时间帧。

这稍微限制了它的可塑性,但这是我们必须接受的一部分。 智能交易系统,或 EA,则相反,是特定的操控交易系统。 虽然我们可以在 EA 中添加不属于交易系统的函数和代码,但这种做法在高性能或高可靠性系统中不是很合适。 原因是所有不属于交易系统的东西都不应该出现在 EA 当中:所有东西应该放在正确的位置,并正确处理。

因此,若要提高可靠性,首先要做的就是从代码中坚决地删除不属于交易系统的所有内容,并将这些东西替换为指标或类似的东西。 EA 代码中唯一保留的是负责管理、分析和处理订单或仓位的部分。 所有其它东西都将被删除。

所以,我们开始吧。

2.0. 实现

2.0.1. 删除 EA 背景

虽然这不会损害 EA,或导致任何问题,但有些人有时希望他们的屏幕是空白的,屏幕上只显示某些项目。 因此,我们将从 EA 中删除这部分,并将其转换为指标。 它非常容易实现。 兹期货量化不会触及任何类,但要创建以下代码:

 
 

#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh> //+------------------------------------------------------------------+ input string user10 = "Wallpaper_01"; //Used BitMap input char user11 = 60; //Transparency (from 0 to 100) input C_WallPaper::eTypeImage user12 = C_WallPaper::IMAGEM; //Background image type //+------------------------------------------------------------------+ C_Terminal Terminal; C_WallPaper WallPaper; //+------------------------------------------------------------------+ int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper"); Terminal.Init(); WallPaper.Init(user10, user12, user11); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); WallPaper.Resize(); break; } ChartRedraw(); } //+------------------------------------------------------------------+

如您所见,一切都非常自然,且易于理解。 兹期货量化只是简单地从 EA 中删除了代码,并将其转换为可以添加到图表中的指标。 而任何变化,无论是背景、透明度,甚至是从图表中删除它,都不会对 EA 操作产生影响。

现在我们将开始删除真正导致 EA 性能下降的内容。 这些就是不时,或每次价格变动都会运作的事情,因此有时会导致 EA 变慢,从而阻碍它完成真正的工作 — 观察图表上的订单或仓位发生了什么。

2.0.2. 把价格对应的成交量转换为一个指标

尽管看起来不似这样,但价格对应的交易量系统需要时间,这对 EA 来说通常至关重要。 我指的高波动时刻是,价格剧烈波动,但却没有太多的方向性情况下。 正是在这些时候,EA 需要每个可用的机器周期来完成其任务。 错过一个好时机会令人沮丧,因为一些指标决定接管该项工作。 因此,兹期货量化将其从 EA 中删除,并通过创建以下代码将其转换为真实的指标:

 
 

#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh> //+------------------------------------------------------------------+ input color user0 = clrBlack; //Bar color input char user1 = 20; //Transparency (from 0 to 100 ) input color user2 = clrForestGreen; //Buying input color user3 = clrFireBrick; //Selling //+------------------------------------------------------------------+ C_Terminal Terminal; C_VolumeAtPrice VolumeAtPrice; //+------------------------------------------------------------------+ int OnInit() { Terminal.Init(); VolumeAtPrice.Init(user2, user3, user0, user1); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+ void OnTimer() { VolumeAtPrice.Update(); } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { VolumeAtPrice.DispatchMessage(id, sparam); ChartRedraw(); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+

这是最简单的部分。 兹期货量化从 EA 中删除了代码,并将其放入指标之中。 如果您想将代码放回 EA 当中,您只需复制指标代码,并将其放回 EA 中即可。

所以,我们先从简单的事情开始。 但现在事情会变得更加复杂 — 我们将从 EA 中删除 Times & Trade。

2.0.3. 把 Times & Trade 转换到一个指标

如果我们的目标是创建可以在 EA 和指标中都能用的代码,这并不那么简单。 作为在子窗口中操作的指标,将其转换为指标似乎很容易。 而正因为它是在子窗口中操作,其实这并不容易。 主要问题是,如果我们像前面的情况一样完成所有事情,那么我们将在指标窗口中得到以下结果:

编辑

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

不建议将此类内容放在指标窗口中,因为如果用户想从屏幕中删除指标,这会让用户感到困惑。 因此,应该以不同的方式完成这件事情。 在这条路径的末端,也许看起来很困惑,但实际上是一组简单的指令和一些剪辑,兹期货量化将在指标窗口中得到以下结果。

编辑

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

这正是用户所期望的 — 而不是上图中看到的混乱。

以下是 Times & Trade 指标的完整代码:

 
 

#property copyright "Daniel Jose" #property version "1.00" #property indicator_separate_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh> //+------------------------------------------------------------------+ C_Terminal Terminal; C_TimesAndTrade TimesAndTrade; //+------------------------------------------------------------------+ input int user1 = 2; //Scale //+------------------------------------------------------------------+ bool isConnecting = false; int SubWin; //+------------------------------------------------------------------+ int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade"); SubWin = ChartWindowFind(); Terminal.Init(); TimesAndTrade.Init(user1); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { if (isConnecting) TimesAndTrade.Update(); return rates_total; } //+------------------------------------------------------------------+ void OnTimer() { if (TimesAndTrade.Connect()) { isConnecting = true; EventKillTimer(); } } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); TimesAndTrade.Resize(); break; } } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+

该代码似乎与 EA 中所用的代码相似,除了高亮显示的行在 EA 代码中不存在。 那有什么收获呢? 还是一无所获? 实际上,有一些收获:代码不完全相同,其中存在差异,它并不在指标或 EA 代码里,而是在类代码之中。 但在研究差别之前,我们先思考以下几点:我们如何告诉编译器要编译什么,以及不要编译什么? 也许,在编程时,您根本不担心这一点 — 也许,您只管简单地创建代码,如果您不喜欢任何东西,直接删掉就好了。

经验丰富的程序员有一条规则:只在肯定不起作用时才会删除某些内容,否则即使它们实际上没有被编译,也要保留片段。 但是,当我们希望编写的函数始终工作时,如何在线性代码中做到这一点呢? 此处的问题是:您知道如何告诉编译器要编译什么,和不要编译什么吗? 如果答案是“否”,那就无妨。 当我开始时,我个人也不知道该怎么做。 但它有很大帮助。 故此,我们来找出如何做到这一点。

某些语言具有编译指令,根据作者的不同,这些指令也可能称为预处理器。 但思路是一致的:告诉编译器要编译什么,以及如何进行编译。 有一种非常特殊类型的指令可用来有意隔离代码,以便我们可以测试特定的东西。 这些就是条件编译指令。 如果使用得当,它们允许我们在编译相同的代码时走不同的途径。 这恰恰是 Times & Trade 示例中所做到的。 我们可选择谁来负责生成条件编译:EA 或指标。 定义该参数后,创建 #define 指令,然后使用条件指令 #ifdef #else #endif 通知编译器如何去编译代码。

这可能难于理解,那么我们就看看它是如何操作的。

在 EA 代码中,定义并添加下面高亮显示的行:

 
 

#define def_INTEGRATION_WITH_EA //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh> #ifdef def_INTEGRATION_WITH_EA #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh> #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh> #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh> #endif //+------------------------------------------------------------------+

发生以下情况:如果您想编译 EA 时包含 MQH 文件中的类,则在智能系统中保留预定义的 #ifdefine def_INTEGRATION_WITH_EA 指令。 这样就会令 EA 包含我们需要的所有类,原本它们是插入在指标当中。 如果您要删除指标,无需删除代码,而只需简单地注释掉预定义语句即可。 这可简单地通过将声明指令的行转换成注释行来完成。 如此编译器就无视该指令,并认为不存在;且由于它不存在,每次碰到条件指令 #ifdef def_INTEGRATION_WITH_EA 时,都会完全忽略它,而它和上面示例中 #endif 部分之间的代码也不会被编译。

这是兹期货量化在 C_TimesAndTrade 类中所要实现的思路。 以下是新类的样子。 我只展示一点来引起您的注意力:

 
 

#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh> #ifdef def_INTEGRATION_WITH_EA #include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh> class C_TimesAndTrade : private C_FnSubWin #else class C_TimesAndTrade #endif { //+------------------------------------------------------------------+ #define def_SizeBuff 2048 #define macro_Limits(A) (A & 0xFF) #define def_MaxInfos 257 #define def_ObjectName "TimesAndTrade" //+------------------------------------------------------------------+ private : string m_szCustomSymbol; // ... The rest of the class code.... }

对于未用过编译指令的人来说,代码可能看起来很奇怪。 def_INTEGRATION_WITH_EA 指令是在 EA 中声明。 然后发生以下情况。 当编译器依此文件生成目标代码时,它将假定以下关系:如果正在编译的文件是 EA,并且含有声明的指令,则编译器将生成的目标代码会包含介于条件指令 #ifdef def_INTEGRATION_WITH_EA 和 #else 之间的部分。 通常在这种情况下,兹期货量化要用 #else 指令。 如果编译另一个文件的情况,例如,未定义指令 def_INTEGRATION_WITH_EA 的指标,则将编译指令 #else 和 #endif 之间的所有内容。 这就是它如何操作的。

当编译 EA 或指标时,查看 C_TimesAndTrade 类的整个代码,从而了解这些测试和常规操作中的每个部分。 因此,MQL5 编译器将完成所有设置,从而节省我们维护两个不同文件所需的时间和精力。

2.0.4. 令 EA 更敏捷

如前所述,EA 应当仅与订单系统协同操作。 迄今为止,它所具备的功能,现在已能演变为指标。 这样做的原因非常个人化,这与 EA 所做事情涉及的计算有关。 但是这个计算系统已经被修改,并转移至另一种方法。 有因于此,我注意到由 EA 接管订单处理,订单系统受到一些事情的损害。 问题最严重之处是 OnTick 事件:

 
 

void OnTick() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]); #ifdef def_INTEGRATION_WITH_EA TimesAndTrade.Update(); #endif }

该事件现可受控于条件指令,如此,那些在高波动期间不做交易的人,若需要的话,就能拥有一个包含所有原始指标的 EA。 但在您认为这是一个好主意之前,我要提醒您 Times & Trade 更新功能是如何工作的。

 
 

inline void Update(void) { MqlTick Tick[]; MqlRates Rates[def_SizeBuff]; int i0, p1, p2 = 0; int iflag; long lg1; static int nSwap = 0; static long lTime = 0; if (m_ConnectionStatus < 3) return; if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0) { // ... The rest of the code... } }

上面的代码是 C_TimesAndTrade 类中存在的更新函数的一部分。 问题出在高亮显示的部分。 每次它在执行时,都会向服务器发送一个请求,并返回自某个时间点以来完成的所有交易单号,顺便说一下,这并不是问题所在。 问题在于,该调用有时会与另两个事件重合。

第一个也是最明显的事件是发生大量交易占用,这会导致 OnTick 函数的调用次数大增。 除了必须运行 C_TimesAndTrade 类中存在的上述代码外,此函数还将应对另一个问题:调用 C_IndicatorTradeView 类中存在的 SecureChannelPosition 函数。 故此,这是另一个小问题,但这还不是全部。 我曾一次次说过,尽管波动性很低,但我们会遇到两个事件的重合,第一个就是该事件。

第二个是在 OnTime 事件中,该事件已被更新,如下所示:

 
 

#ifdef def_INTEGRATION_WITH_EA void OnTimer() { VolumeAtPrice.Update(); TimesAndTrade.Connect(); } #endif

如果您打算按照设计的方式使用 EA,还考虑让它接收更多的代码,那么由于重合事件,它有时可能会出现问题。 当这种情况发生时,EA 就会停顿(即使一秒钟)转而去做与订单系统无关的事情。

与 C_TimesAndTrade 中出现的函数不同,此函数存在于 C_VolumeAtPrice 类中,且在管理订单时确实会损害 EA 性能。 发生这种情况的原因如下:

 
 

inline virtual void Update(void) { MqlTick Tick[]; int i1, p1; if (macroCheckUsing == false) return; if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0) { if (m_Infos.CountInfos == 0) { macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time)); m_Infos.FirstPrice = Tick[0].last; } for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++); for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]); if (p1 == i1) return; m_Infos.memTimeTick = Tick[i1 - 1].time_msc; m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time); Redraw(); }; };

原因在于高亮显示的部分,但其中最糟糕的是重绘。 它极大地损害了 EA 性能,因为每次所接受跳价的交易量高于指定阈值时,整个价格对应的交易量都会从屏幕上删除、重新计算、并在原位重绘。 它会每 1 秒左右发生一次。 这样就很可能与其它事情重合,这就是为什么要把所有指标都从 EA 里删除的原因。 虽然我保留了它们,这是为了让您可以直接在 EA 中使用它们;但由于前面解释的原因,我仍然不建议这样做。

这些修改是必要的。 但还有另一个,更具象征意义,需要去完成。 这次的修改涉及 OnTradeTransaction 事件。 使用此事件是令系统尽可能灵活的一次尝试。 许多编写程序化订单执行 EA 的人都会用到 OnTrade 事件,他们检查哪些订单是否在服务器上,或者哪些持仓仍未平仓。 我并未指责他们做错了。 只在于它不是很有效,因为服务器会通知我们发生了什么。 但 OnTrade 事件的最大问题在于,事实上我们不得不持续检查无关紧要的事情。 如果我们要用 OnTradeTransaction 事件,兹期货量化将拥有一个至少在走势分析方面更有效的系统。 但这不是此处的宗旨。 每个人都会采用最适合自己准则的方法。

当开发此 EA 时,我决定不采用任何存储结构,因此可操控的订单或仓位数量不受限制。 但这一事实令状况变得如此复杂,以至于需要 OnTrade 事件的替代方案,这可在 OnTradeTransaction 事件中发现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值