从头开始开发智能交易系统(第 12 部分):时序与交易(I)
概述
看盘是一些交易员在不同交易阶段用过的一种交易方法。 这种方法非常有效,当运用正确时,与运用其它众所周知的价格行为(纯粹的烛条观测)相比,它能提供更安全、更一致的方式来保持利润的稳定增长。 然而,现在再用看盘的形式,就会觉得过程非常复杂和乏味,且需要持续集中注意力。 随着时间的推移,我们不可避免地开始在观测中走神出错。
看盘的问题与我们所需分析的信息量有关。 我们来看一个典型的看盘用例:
问题真正在于,在分析过程中,我们必须查看价格以及价格发生了什么变化,但在迷你合约中检查这些数值并不实际。 因此,我们通常不看迷你合约中的数据流内容,而是更愿意遵守完整合约,因为它们才是推动市场的根源。 这就是实际发生的情况,因此系统看起来像下面的系统。 它更容易解释和跟随。
但即使在这种情况下,该系统的应用也是一个非常繁琐的过程,需要高度重视。 当停止位置被激活时,情况变得更加严峻,在这种情况下,我们可能会错过一些走势,因为屏幕上的信息滚动应该非常快。
计划
然而,MetaTrader 5 平台甚至为迷你合约提供了一个替代系统,这令监控更加高效和简单。 我们来看看操控迷你合约时的情况:
如您所见,解读要简单得多。 然而,出于我们前面讨论的原因,使用完整合约更恰当,如下所示:
请注意,交易数据受到买卖走势噪音的阻碍。 这些成交在此用圆圈表示。 红色显示卖出成交,蓝色显示买入成交,而绿色显示直接订单。 除了那些对于观测本身我们不需要的信息之外,我们还有另一个问题:系统与我们实际交易的图表分离,由此我们必须监控两个屏幕。 一方面,这是一个优势,但在某些情况下,它会令事情变得非常复杂。 因此,我建议创建一个易于阅读的系统,允许我们直接在交易图表上同步看到这个指标。
实现
我们要做的第一件事是修改 C_Terminal 类,以便我们可以访问完整的合约资产,这可以通过添加以下代码来完成:
void CurrentSymbol(void) { MqlDateTime mdt1; string sz0, sz1, sz2; datetime dt = TimeLocal(); sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); m_Infos.szFullSymbol = _Symbol; m_Infos.TypeSymbol = ((sz0 == "WDO") || (sz0 == "DOL") ? WDO : ((sz0 == "WIN") || (sz0 == "IND") ? WIN : OTHER)); if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return; sz2 = (sz0 == "WDO" ? "DOL" : (sz0 == "WIN" ? "IND" : sz0)); sz1 = (sz2 == "DOL" ? "FGHJKMNQUVXZ" : "GJMQVZ"); TimeToStruct(TimeLocal(), mdt1); for (int i0 = 0, i1 = mdt1.year - 2000;;) { m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1); m_Infos.szFullSymbol = StringFormat("%s%s%d", sz2, StringSubstr(sz1, i0, 1), i1); if (i0 < StringLen(sz1)) i0++; else { i0 = 0; i1++; } if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break; } } // ... 类代码 ... inline string GetFullSymbol(void) const { return m_Infos.szFullSymbol; }
添加高亮显示的行,我们就可以访问所需的资产,并将在时时序与交易计划中使用这些资产。 接下来,我们继续创建支持时序与交易的对象类。 该类将包含一些非常有趣的函数。 首先,有必要创建一个子窗口,其中将包含我们的指标。 这很容易做到,然而,出于实际原因,我们不会用以前用过的子窗口系统。 也许这一概念在未来会发生变化,但目前我们将在指标体系之外的一个单独窗口中处理时序与交易问题,这涉及到多项准备工作。
我们从创建一个新的支持文件开始,从而令指标具有不同的名称。 取代在文件顶层创建文件,我们做一些更优雅的举动。 我们正在修改支持文件,以便获得更多可能性。 新的支持文件如下所示:
#property copyright "Daniel Jose 07-02-2022 (A)" #property version "1.00" #property description "This file only serves as supporting indicator for SubWin" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ input string user01 = "SubSupport"; //短名 //+------------------------------------------------------------------+ int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, user01); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+
我在源文件里高亮显示出相应的修改。 现在我们需要对 EA 代码进行修改。 我们现在创建一个新类:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Terminal.mqh" //+------------------------------------------------------------------+ class C_FnSubWin { private : string m_szIndicator; int m_SubWin; //+------------------------------------------------------------------+ void Create(const string szIndicator) { int i0; m_szIndicator = szIndicator; if ((i0 = ChartWindowFind(Terminal.Get_ID(), szIndicator)) == -1) ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource, szIndicator)); m_SubWin = i0; } //+------------------------------------------------------------------+ public : //+------------------------------------------------------------------+ C_FnSubWin() { m_szIndicator = NULL; m_SubWin = -1; } //+------------------------------------------------------------------+ ~C_FnSubWin() { Close(); } //+------------------------------------------------------------------+ void Close(void) { if (m_SubWin >= 0) ChartIndicatorDelete(Terminal.Get_ID(), m_SubWin, m_szIndicator); m_SubWin = -1; } //+------------------------------------------------------------------+ inline int GetIdSubWinEA(const string szIndicator = NULL) { if ((szIndicator != NULL) && (m_SubWin < 0)) Create(szIndicator); return m_SubWin; } //+------------------------------------------------------------------+ inline bool ExistSubWin(void) const { return m_SubWin >= 0; } //+------------------------------------------------------------------+ }; //+------------------------------------------------------------------+
这个类取代了 C_SubWindow,现在由它支持在图表上创建子窗口。 若要了解该类的工作原理,请快速查阅下面的新 C_SubWindow 类:
#include "C_ChartFloating.mqh" #include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh> //+------------------------------------------------------------------+ class C_SubWindow : public C_ChartFloating { //+------------------------------------------------------------------+ private : C_FnSubWin m_fnSubWin; //+------------------------------------------------------------------+ public : //+------------------------------------------------------------------+ ~C_SubWindow() { Close(); } //+------------------------------------------------------------------+ void Close(void) { m_fnSubWin.Close(); CloseAlls(); } //+------------------------------------------------------------------+ inline int GetIdSubWinEA(void) { return m_fnSubWin.GetIdSubWinEA("SubWinSupport"); } //+------------------------------------------------------------------+ inline bool ExistSubWin(void) const { return m_fnSubWin.ExistSubWin(); } //+------------------------------------------------------------------+ }; //+------------------------------------------------------------------+
请注意,该类包含的指标定义能够支持模板。 它在上面的代码中高亮显示。 现在转入棘手的部分。 如果我们用另一个名称来替代SubWinSupport,则 C_FnSubWin 类会去搜索另一个指标。 我们利用此技巧来避免创建指标文件。 我们只需告诉 C_FnSubWin 类所需指标的短名应该是什么。 因此,我们不会受限于无必要子窗口的数量,亦或指标文件只用来创建智能交易系统子窗口。
之后,我们可以继续创建 C_TimeAndTrade 类。
C_TimesAndTrade 类
C_TimesAndTrade 对象类由几个小模块组成,每个小模块负责特定的内容。 下面显示的代码是 EA 为了调用该类所做的第一件事:
void Init(const int iScale = 2) { if (!ExistSubWin()) { CreateCustomSymbol(); CreateChart(); } ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale))); }
代码将检查支持的子窗口是否存在。 如果它还不存在,代码则会创建一个。 现在,查看如下该类初始支持的代码:
inline void CreateCustomSymbol(void) { m_szCustomSymbol = "_" + Terminal.GetFullSymbol(); SymbolSelect(Terminal.GetFullSymbol(), true); SymbolSelect(m_szCustomSymbol, false); CustomSymbolDelete(m_szCustomSymbol); CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol()); CustomRatesDelete(m_szCustomSymbol, 0, LONG_MAX); CustomTicksDelete(m_szCustomSymbol, 0, LONG_MAX); SymbolSelect(m_szCustomSymbol, true); };
此代码将创建自定义品种,并重置该品种的所有数据。 为了能够在我们欲创建的窗口中显示品种内容,首先应该将此品种添加到 Market Watch 当中。 这是由以下行来完成的:
SymbolSelect(m_szCustomSymbol, true);
自定义品种将在以下位置创建:Custom\Robot <品名>.。 其初始数据将由原始品种提供。 它由以下代码实现:
CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol());
基本上,就这些。 将类添加到 EA,并按如下方式运行:
// ... 智能交易系统代码 #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh> // ... Expert Advisor code input group "Times & Trade" input int user041 = 2; //Escala //+------------------------------------------------------------------+ C_TemplateChart Chart; C_WallPaper WallPaper; C_VolumeAtPrice VolumeAtPrice; C_TimesAndTrade TimesAndTrade; //+------------------------------------------------------------------+ int OnInit() { // ... Expert Advisor code TimesAndTrade.Init(user041); OnTrade(); EventSetTimer(1); return INIT_SUCCEEDED; }
结果如下:
这正是众所期待的。 现在,我们将已执行成交的值添加到 _DOLH22 图表当中。 该图表将反映所有已执行的成交,以便提供时序与交易的图形表示。 演示将采用日本烛条形态的形式,因为它们更易于使用。 在此之前,我们需要做一些事情,特别是连接和同步品种。 这是由以下函数完成的:
inline void Connect(void) { switch (m_ConnectionStatus) { case 0: if (!TerminalInfoInteger(TERMINAL_CONNECTED)) return; else m_ConnectionStatus = 1; case 1: if (!SymbolIsSynchronized(Terminal.GetFullSymbol())) return; else m_ConnectionStatus = 2; case 2: m_LastTime = TimeLocal(); m_MemTickTime = macroMinusMinutes(60, m_LastTime) * 1000; m_ConnectionStatus = 3; default: break; } }
该函数检查终端是否已连接,然后同步品种。 在这之后,我们就可以开始捕获数值,并将其显示在屏幕上。 但对于这一点,有必要针对初始化代码稍微进行一些修改。 修改的内容在以下代码中高亮显示:
void Init(const int iScale = 2) { if (!ExistSubWin()) { CreateCustomSymbol(); CreateChart(); m_ConnectionStatus = 0; } ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale))); }
之后我们就可看到捕获函数。
inline void Update(void) { MqlTick Tick[]; MqlRates Rates[def_SizeBuff]; int i0, p1, p2 = 0; int iflag; if (m_ConnectionStatus < 3) return; if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0) { for (p1 = 0, p2 = 0; (p1 < i0) && (Tick[p1].time_msc == m_MemTickTime); p1++); for (int c0 = p1, c1 = 0; c0 < i0; c0++) { if (Tick[c0].volume == 0) continue; iflag = 0; iflag += ((Tick[c0].flags & TICK_FLAG_BUY) == TICK_FLAG_BUY ? 1 : 0); iflag -= ((Tick[c0].flags & TICK_FLAG_SELL) == TICK_FLAG_SELL ? 1 : 0); if (iflag == 0) continue; Rates[c1].high = Tick[c0].ask; Rates[c1].low = Tick[c0].bid; Rates[c1].open = Tick[c0].last; Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag); Rates[c1].time = m_LastTime; p2++; c1++; m_LastTime += 60; } CustomRatesUpdate(m_szCustomSymbol, Rates, p2); m_MemTickTime = Tick[i0 - 1].time_msc; } }
上面的函数能够绝对捕获所有交易的记号,以便检查该记号处理应做多、亦或做空。 如果这些记号与出价或要价变化有关,即未伴随交易量,则不会保存信息。 同样的情况也与记号有关,记号是不影响价格变动的直接指令,尽管它们通常与价格变动有关,因为有些市场参与者只是为了填写直接指令而将价格强制到某个价位,然后不久就让价格自由变动。 这些与买卖修改相关的记号将在另一个版本中用到,我们将在下一篇文章中看到,因为它们在整个系统中处于次要地位。 在检查事务类型之后,我们有一连串非常重要的行,您应该理解它们。 下面代码中的这些行将分析系统,并在每个标记处构建一根烛条。
Rates[c1].high = Tick[c0].ask;
Rates[c1].low = Tick[c0].bid;
Rates[c1].open = Tick[c0].last;
Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag);
Rates[c1].time = m_LastTime;
烛条的高低位表示交易时的点差,即买卖之间存在的差值将作为所创建烛条的阴影,烛条的开盘价是交易实际完成时的价格。 现在来仔细看看高亮显示的代码行。 对于一个交易标记点,伴随有成交量,这条线将对针对成交量进行一个小的调整,以便比例不会溢出。 您也许会根据自己的分析,自行调整该数值,当然是根据具体的资产情况。
现在是最后一个细节 — 时间。 每根烛条对应一分钟,因为绘制的图形不可能低于该值了。 然后它们当中的每一个都会保持在一分钟内的相应位置。 这不是真实时间,这是虚拟时间。 不要将交易时间与图形时间混淆:操作可以按毫秒为单位,但图形信息将按照比例每分钟绘制一次。 我们可以用任意其它值,但这种可能性很小,故可大大简化编程。 该系统的结果如下所示:
我们看到,现在的的看盘方式很可取,而且解释起来很简单。 尽管在捕获时订单看盘速度很慢,但我认为这足以令人产生遐想。
有关该系统的最终信息见下图:
请注意,系统上能看到四种不同的配置。 它们都需要什么? 我们将在下一篇文章中获知这一点,这将有助于理解为什么“时序 & 交易”存在四种配置。 无论如何,我们已经有了一个可操作系统,它大概足以应对密集使用。 但如果您了解后续会发生什么,以及是什么导致了四种烛条形态的生成?您就能从该系统中得到更多,谁知道呢,也许它会成为您的主力指标...
结束语
我们已经创建了“时序 & 交易”系统,在我们的 EA 里可进行看盘分析。 它应该提供与 MetaTrader 5 中的替代系统相同的分析速度。 我们通过创建一个图表系统来实现这一点,替代了读取和尝试理解庞大数字和数值。 在下一篇文章中,我们将在系统中实现一些缺失的信息。 我们需要在智能交易系统代码中添加一些新的元素。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10410