![从头开始开发智能交易系统(第 14 部分):添加价格成交量(II)](https://c.mql5.com/2/49/Developing_a_trading_Expert_Advisor_from_scratch_005_600x314.jpg)
从头开始开发智能交易系统(第 14 部分):添加价格成交量(II)
概述
我们的 EA 已经具有了一些帮助交易的资源 — 我们在之前的文章中已陆续把这些资源加入。 然而,该 EA 在可视化和调整大小方面存在一些问题。 它们不会干扰交易,但在某些情况下,却会导致屏幕上出现混乱,直到您强制刷新屏幕。 此外,还缺少一些可以为我们提供有价值信息的东西。 这些都是具体的事情,但信息可能是必要的。
那么,我们开始实现这些新的改进。 这篇有趣的文章可以提供一些展示信息的新思路和方法。 与此同时,它能帮助修复项目中的小缺陷。
规划和实施新的价格成交量函数
1. 计划
关于交易这是一件挺好奇的事情。 我们经常看到市场在某些价格区域堆积,当买入或卖出方触发止损时,价格会快速波动。 这种走势可以通过时序与交易(Times & Trade)。 我们在之前的文章时序与交易(I)和 时序与交易(II)中曾研究过这些。 在这些文章中,我们研究了如何创建一个替代图形系统,来读取和分析已执行的订单流。 如果您仔细观察,您会注意到,在某些时刻,价格往往会回到堆积区域,在该处它并不打算即刻离开。 但当我们观察价格成交量指标时,很难判定这个特定区域的价格在最近会有多大变化。 该指标已在文章添加价格成交量(I)中实现。 使用它,我们可以通过简单地更改分析起点,来分析最近的相对走势,而通过调整下图中所示对象的值也可完成:
但这实际上这有点不切实际,因为我们要与主要时间帧挂钩,即,如果您有一个 60 分钟时间帧图表,您将无法掌握低于该时间帧的价格走势。 您必须切换到较低的时间帧才能调整分析点。 但在交易期货合约时,大多数交易员实际上都采用较短的时间帧,例如 5、10 或 30 分钟,因此调整分析起点没有问题。 但正如我之前解释的那样,有时价格会因为触发止损而退出堆积,这种回报通常在不到 5 分钟内发生。 在这种情况下,图表上会出现一个上、下阴影较长的烛条。 在这种情况下,价格动作会告诉我们所发生的事件是什么样的市场声音,从下面烛条上的箭头指示可以看到这种走势:
典型的买方测试动作,或做空单触发停止
典型的卖方测试动作,或做多单触发停止
这种走势类型频繁发生,分析每个价格区间的成交量非常重要,因为它能够了解市场是正在测试、亦或趋势是真的在逆转。 但是,若用前面提出的成交量指标,不可能正确或迅速地做到这一点。
然而,我们可以针对指标对象类进行一个小的修改,以便更清楚地了解发生了什么。 这将为给定时间段内发生的交易显示痕迹。
2. 实现
分析之前首先要做的是跟踪时间设置多久,您也许会设置 60、45、30、19、7 还是 1分钟。 尽管如此,我们建议使用足够倍数的值,以便跟踪系统真正有用。 出于实际原因,我们将采用 30 分钟跟踪来实现它,因此我们将在以下代码行中定义它:
#define def_MaxTrailMinutes 30
但为什么要恰恰是 30 分钟? 实际上,跟踪系统每分钟执行一次,但最长跟踪时间为 30 分钟。 即,您将始终有 30 分钟的跟踪,例如,当跟踪切换到第 31 分钟时,则首个交易分钟将不再显示。 它是如何实现的? 所用的捕获系统如下所示:
inline void SetMatrix(MqlTick &tick) { int pos; if ((tick.last == 0) || ((tick.flags & (TICK_FLAG_BUY | TICK_FLAG_SELL)) == (TICK_FLAG_BUY | TICK_FLAG_SELL))) return; pos = (int) ((tick.last - m_Infos.FirstPrice) / Terminal.GetPointPerTick()) * 2; pos = (pos >= 0 ? pos : (pos * -1) - 1); if ((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) m_InfoAllVaP[pos].nVolBuy += tick.volume; else if ((tick.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL) m_InfoAllVaP[pos].nVolSell += tick.volume; m_InfoAllVaP[pos].nVolDif = (long)(m_InfoAllVaP[pos].nVolBuy - m_InfoAllVaP[pos].nVolSell); m_InfoAllVaP[pos].nVolTotal = m_InfoAllVaP[pos].nVolBuy + m_InfoAllVaP[pos].nVolSell; m_Infos.MaxVolume = (m_Infos.MaxVolume > m_InfoAllVaP[pos].nVolTotal ? m_Infos.MaxVolume : m_InfoAllVaP[pos].nVolTotal); m_Infos.CountInfos = (m_Infos.CountInfos == 0 ? 1 : (m_Infos.CountInfos > pos ? m_Infos.CountInfos : pos)); m_Infos.Momentum = macroGetMin(tick.time); m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum); if (m_Infos.memMomentum != m_Infos.Momentum) { for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++) m_TrailG30[m_Infos.Momentum].nVolume[c0] = 0; m_Infos.memMomentum = m_Infos.Momentum; } m_TrailG30[m_Infos.Momentum].nVolume[pos] += tick.volume; }
添加到对象类源代码中的行以高亮显示 — 它们实现了成交量跟踪捕获。 下面的几行保证了跟踪将按预期进行。
m_Infos.Momentum = macroGetMin(tick.time);
m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);
跟踪捕捉系统已准备就绪。 现在我们需要做出新的决定。 请记住,跟踪会每 1 分钟捕获一次。 如此这般,我们就能在 1 分钟内看到每个价格范围的成交量。 若我们长久以这种方式绘制图表,您也许会考虑执行以下操作:
较浅的色调代表时间较近的成交量,这也许是一个好主意...
虽然这似乎是一个好主意,但当成交量较低或走势非常快时,即使所表达的成交量只是片刻,它实际上也可能不可见,因为在进行绘制时是依据迄今为止发现的最大成交量。 因此,您也许希望以稍微不同的方式来绘制,从而解决这个问题,如此它看起来是这样的:
每种颜色表示成交量跟踪中的特定周期。
这有助于分析成交量上非常狭窄的波带,并调整第一种情况下偶尔出现的问题。 但是,我们仍然会遇到调整问题,譬如当某时刻成交量相对于整体成交量难以表现时。 此外,必须仔细选择每个时段的颜色,从而在极其活跃的交易期间不会混淆分析。
因此,此处我们将采用一个更简单的模型,该模型可以再次调整,以便分析不同时期的走势。 然而,请记住上述问题。 这将由您决定。 然后轨迹将显示如下:
我们在这里看到了一条纯净的轨迹。 当它发生时,我们应该一并分析"时序与交易“和价格行为,以了解正在发生的事情。
无论如何,为了更改成交量显示,唯一需要修改的是以下函数:
void Redraw(void) { uint x, y, y1, p; double reason = (double) (m_Infos.MaxVolume > m_WidthMax ? (m_WidthMax / (m_Infos.MaxVolume * 1.0)) : 1.0); double desl = Terminal.GetPointPerTick() / 2.0; ulong uValue; Erase(); p = m_WidthMax - 8; for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++) { if (m_InfoAllVaP[c0].nVolTotal == 0) continue; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, m_Infos.FirstPrice + (Terminal.GetPointPerTick() * (((c0 & 1) == 1 ? -(c0 + 1) : c0) / 2)) + desl, x, y); y1 = y + Terminal.GetHeightBar(); FillRectangle(p + 2, y, p + 8, y1, macroColorRGBA(m_InfoAllVaP[c0].nVolDif > 0 ? m_Infos.ColorBuy : m_Infos.ColorSell, m_Infos.Transparency)); FillRectangle((int)(p - (m_InfoAllVaP[c0].nVolTotal * reason)), y, p, y1, macroColorRGBA(m_Infos.ColorBars, m_Infos.Transparency)); uValue = 0; for (int c1 = 0; c1 < def_MaxTrailMinutes; c1++) uValue += m_TrailG30[c1].nVolume[c0]; FillRectangle((int) (p - (uValue * reason)), y, p, y1, macroColorRGBA(clrRoyalBlue, m_Infos.Transparency)); } C_Canvas::Update(); };
为了更准确,只需要修改高亮显示出的代码。 您可以试演它,直到您得到想要的结果。 除了高亮显示的部分外,不需要修改类中的任何其它内容。 编译程序,并在图表上运行后,您将看到如下内容:
解决渲染问题
虽然代码没有任何特别问题,但在调整图表大小时存在一个小缺陷:当将最大化的图表调整到任意其它尺寸,然后返回到最大化时,一些对象会丢失,表现不符合预期,并定位在错误的位置。 没有多少事情需要解决。 问题出在下面的代码中 — 我们在以前的文章中曾用过它。
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { Chart.DispatchMessage(id, lparam, dparam, sparam); VolumeAtPrice.DispatchMessage(id, sparam); switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); WallPaper.Resize(); TimesAndTrade.Resize(); break; } ChartRedraw(); }
有一个非常简单的修改,但您也许会想“我什么都没看到 — 代码是正确的”。 乍一看,我也没有发现任何错误,只是代码仍然存在运行时错误。 但是当我加上一些额外的功能时,我注意到了问题,而这正是我上面描述的问题。 为了解决这个问题,需要对代码进行如下修改:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); WallPaper.Resize(); TimesAndTrade.Resize(); break; } Chart.DispatchMessage(id, lparam, dparam, sparam); VolumeAtPrice.DispatchMessage(id, sparam); ChartRedraw(); }
它听起来可能很愚蠢,但要理解为什么,只需查看整个函数代码和高亮显示的部分。 现在系统既然已经修复,我们就可以推进到下一步。
添加额外资源
我们现在要添加的函数非常简单,许多人可能觉得没有理由去实现它,但实现它会有助于处理订单,包括开仓、移动、或只是观察价格成交量指标。
首先要做的是更改价格行调整代码所归属的类。 这段代码来自 C_OrderView 类,并植入 C_Terminal 类,但对于这一点,它也经历了一些小的变化,因为它开始操控类本身的变量。 下面是新代码的样子。
double AdjustPrice(const double arg) { double v0, v1; if(m_Infos.TypeSymbol == OTHER) return arg; v0 = (m_Infos.TypeSymbol == WDO ? round(arg * 10.0) : round(arg)); v1 = fmod(round(v0), 5.0); v0 -= ((v1 != 0) || (v1 != 5) ? v1 : 0); return (m_Infos.TypeSymbol == WDO ? v0 / 10.0 : v0); };
如此这般,我们就能够创建一个新的 EA 类 — 它将是 C_Mouse 类。 这个类对象负责并作为鼠标事件的基础,我们来看看在这个开发阶段它是如何进行的。 但首先,我们要查看我们的智能交易系统当前的类结构,如下图所示:
为了实现下一个系统,有必要引入一种新的结构...
因此,给定的上述结构,我们降解 C_Mouse 对象类的代码,从变量声明开始,如下所示:
class C_Mouse { private : struct st00 { color cor01, cor02, cor03; string szNameObjH, szNameObjV, szNameObjT, szNameObjI, szNameObjB; }m_Infos; struct st01 { int X, Y; datetime dt; double price; uint ButtonsStatus; }Position;
鉴于目前的开发阶段需求较少,我们推进到值得我们关注的下一个点:
~C_Mouse() { // ... Internal code ... ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, false); ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, true); }
此代码恢复十字光标 CHART_CROSSHAIR_TOOL,并在图表上禁用鼠标事件,这意味着 MT5 不再需要将此类事件发送到图表,因为它们将由平台本身接手处理。
我们还有两个非常常见的函数,当我们需要控制鼠标时会用到:
inline void Show(void) { ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, m_Infos.cor01); } //+------------------------------------------------------------------+ inline void Hide(void) { ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, clrNONE); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, clrNONE); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, clrNONE); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, clrNONE); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, 0, 0); }
有趣的是,鼠标实际上并未真正消失,只有我们创建的对象从屏幕上消失了,当我们“打开”鼠标时,只有价格线才真正可见。 这也许看起来很奇怪,但它在 EA 的一些特定点上有其用途。 其中一点是下面 C_OrderView 类对象代码中高亮显示的部分:
inline void MoveTo(uint Key) { static double local = 0; int w = 0; datetime dt; bool bEClick, bKeyBuy, bKeySell; double take = 0, stop = 0, price; bEClick = (Key & 0x01) == 0x01; //Left click bKeyBuy = (Key & 0x04) == 0x04; //SHIFT pressed bKeySell = (Key & 0x08) == 0x08; //CTRL pressed Mouse.GetPositionDP(dt, price); if (bKeyBuy != bKeySell) Mouse.Hide(); else Mouse.Show(); ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0)); ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1))); ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1))); if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade); else local = 0; ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE)); };
注意高亮显示部分上方的那些行:
Mouse.GetPositionDP(dt, price);
此行将捕获鼠标位置的数值。 下面是报告这些数值的代码:
inline void GetPositionDP(datetime &dt, double &price) { dt = Position.dt; price = Position.price; }
但这还不是全部。 在某些情况下,我们需要图表在屏幕位置的笛卡尔坐标。 还有另一个函数能够获取相关值。 它如下所示:
inline void GetPositionXY(int &X, int &Y) { X = Position.X; Y = Position.Y; }
回到 C_OrderView 类,我们有一个有趣的要点也值得注意:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, pp, pt, ps; eHLineTrade hl; switch (id) { case CHARTEVENT_MOUSE_MOVE: MoveTo(Mouse.GetButtonStatus()); break; // ... The rest of the code ... }
在该函数的稍前一点可以看到 MoveTo 函数。 它也是 C_OrderView 类的一部分。 但更重要的是 Mouse.GetButtonsStatus 函数。 此函数返回与鼠标事件相关的按钮和按键的状态。
函数 Mouse.GetButtonStatus 如下所示:
inline uint GetButtonStatus(void) const { return Position.ButtonsStatus; }
这一行返回一个变量,其中包含自上次鼠标事件以来记录的值。 现在我们得到记录该值的代码。 但是首先,我们来看一下鼠标初始化代码,因为它应该告诉 EA 我们打算初始化鼠标,并自现在起 EA 将处理各种与鼠标相关的事情。 负责此操作的代码如下所示:
// ... Other things .... input group "Mouse" input color user50 = clrBlack; //Price line input color user51 = clrDarkGreen; //Positive move input color user52 = clrMaroon; //Negative move //+------------------------------------------------------------------+ // ... General information ... //+------------------------------------------------------------------+ int OnInit() { static string memSzUser01 = ""; Terminal.Init(); WallPaper.Init(user10, user12, user11); Mouse.Init(user50, user51, user52); // ... The rest of the code ...
因此,我们需要定义系统将要用到的三种颜色。 所选颜色,应确保数据在图表上能够清晰可见。 看一下 Mouse.Init 的代码,以便了解更多一点的信息。 可以在下面看到。
void Init(color c1, color c2, color c3) { m_Infos.cor01 = c1; m_Infos.cor02 = c2; m_Infos.cor03 = c3; if (m_Infos.szNameObjH != NULL) return; ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true); ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false); m_Infos.szNameObjH = "H" + (string)MathRand(); m_Infos.szNameObjV = "V" + (string)MathRand(); m_Infos.szNameObjT = "T" + (string)MathRand(); m_Infos.szNameObjB = "B" + (string)MathRand(); m_Infos.szNameObjI = "I" + (string)MathRand(); //--- ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjH, OBJ_HLINE, 0, 0, 0); ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjV, OBJ_VLINE, 0, 0, 0); ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjT, OBJ_TREND, 0, 0, 0); ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjB, OBJ_BITMAP, 0, 0, 0); ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjI, OBJ_TEXT, 0, 0, 0); //--- ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_TOOLTIP, "\n"); ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_TOOLTIP, "\n"); ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_TOOLTIP, "\n"); ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_TOOLTIP, "\n"); ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TOOLTIP, "\n"); //--- ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_WIDTH, 2); //--- ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_BMPFILE, "::" + def_Fillet); //--- ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONT, "Lucida Console"); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONTSIZE, 10); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_BACK, false); Hide(); Show(); }
这段代码没有什么特别之处 — 我们只是创建了一些对象,供类使用。 但是高亮显示的部分可能有些混乱,因为如果您是在类中查看它,您也许找不到声明它的对应位置。 这是因为它实际上与其它资源的声明一起,混同在 EA 文件的代码中声明当中。 稍后,我将把所有这些声明分门别类分组到一个文件之中,但现在它将保持这种方式。 因此,如果您查看 EA 代码,您会发现以下几行:
#define def_Resource "Resources\\SubSupport.ex5" #define def_Fillet "Resources\\Fillet.bmp" //+------------------------------------------------------------------+ #resource def_Resource #resource def_Fillet
该行显示了鼠标初始化代码中高亮显示的资源。
好的,我们已经在这个类中达到了顶峰,它会在以下片段里调用:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { Mouse.DispatchMessage(id, lparam, dparam, sparam); switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); WallPaper.Resize(); TimesAndTrade.Resize(); break; } Chart.DispatchMessage(id, lparam, dparam, sparam); VolumeAtPrice.DispatchMessage(id, sparam); ChartRedraw(); }
然后 EA 代码中高亮显示的行将调用以下代码:
void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) { int w = 0; uint key; static int b1 = 0; static double memPrice = 0; switch (id) { case CHARTEVENT_MOUSE_MOVE: Position.X = (int)lparam; Position.Y = (int)dparam; ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price)); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0); key = (uint) sparam; if ((key & 0x10) == 0x10) { ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01); b1 = 1; } if (((key & 0x01) == 0x01) && (b1 == 1)) { ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price); b1 = 2; } if (((key & 0x01) == 0x01) && (b1 == 2)) { ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02)); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER)); ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice)); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER)); } if (((key & 0x01) != 0x01) && (b1 == 2)) { b1 = 0; ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true); Hide(); Show(); } Position.ButtonsStatus = (b1 == 0 ? key : 0); break; } }
请注意,上述代码并非填充实现代码。 它只支持并解决 EA 迄今为止的开发阶段的主要任务。 若要理解这一点,请注意鼠标初始化代码中的一件事 — 它有以下行:
ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
这一行防止在单击鼠标中键时显示十字光标。 但是为什么我们要阻止产生十字准线呢? 为了理解这一点,我们来看看以下这张 gif 图片:
这是 WDO 图表,它从从 0.5又移回 0.5。 但是当我们试图进行分析时,我们发现精度不够,而在某些情况下,为了进行分析,精度很重要。 但 MetaTrader 5 中的十字线工具不足以应对特定情况。 在这种情况下,我们则要求助于新系统,因此我们强制 MetaTrader 5 在 EA 运行时停止创建十字准星线。 取而代之,我们创建自己的十字准星线来执行分析。 这令我们能够添加某些与我们更相关的数据和数值,并按我们认为最合适的方式呈现它们。 这可以在下图中看到,它展示了 EA 运行情况下运用数据建模得到的系统结果。
如您所见,指定的值对应于确定的走势值。 甚而,我们还有一个直观的指示:如果值为正,指示变为绿色;如果为负,则指示变为红色。 据此创建轨迹,且起点和终点也将可见。 但是,正如我曾提到的那样,这个系统还不完善。 如果您有打算,并且需要的话,您仍然可以对其进行改进。 在 C_Mouse 类的新版本发布之前,您可以改进旧版本,并得到所需的更多数据。 但为了做到这一点,您需要了解这一切是如何工作的,因此,我们来仔细看看 C_Mouse 类的消息代码。
理解 C_Mouse 类的 DispathMessage 代码
代码伊始首先捕获和调整鼠标位置变量的数值。 它是在以下代码中完成的:
Position.X = (int)lparam; Position.Y = (int)dparam; ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
位置值由 MetaTrader 5 平台报告,但这些值实际上来自操作系统,且对应屏幕坐标,即 X 和 Y。但我们需要将它们转换为图表坐标,为此,我们调用 MQL5 中提供的 ChartXYToTimePrice 函数,这大大简化了我们的生活。
一旦这步完成,我们就能移动价格和时间线。
ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price)); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);
但时间线最初对我们来说不可见,所以我们最初无法在图表上看到它。 然后,我们捕捉鼠标的状态
key = (uint) sparam;
到现在为止,一直都还不错。 现在我们来进行以下操作:我们将检查鼠标中键是否处于按下状态。 如果是,则时间线在图表上可见。 这在以下代码中实现:
if ((key & 0x10) == 0x10) { ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01); b1 = 1; }
为此,我们使用静态变量来存储该事件,如此从现在起,EA 不会接受和处理任何其它事件 / 它只处理我们希望在图表上进行的研究。 但事实上,只有当我们按下鼠标左键时,研究才开始,即,我使用 MetaTrader 5 平台所有用户已经知道的相同工作模式进行研究。 这是最合适的方式,因为如果用户必须学习一种新的研究方式,他们可能会放弃系统。 然后 EA 等待点击左键,这是由以下代码完成的
if (((key & 0x01) == 0x01) && (b1 == 1)) { ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price); b1 = 2; }
单击时,图表移动系统被锁定。 然后,我们以一条趋势线,来指示分析点。 然后系统切换到下一步,从 b1 中的新值可以看出。 现在,这部分实际上您可添加更多信息,或放进去您认为最相关的东西。 在此,我只是演示这个系统,但请随意放置您想要的任何内容。 这些应按如下所示进行:
if (((key & 0x01) == 0x01) && (b1 == 2)) { ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02)); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER)); ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice)); ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER)); }
请注意高亮显示的行,因为这是显示和计算图表屏幕上的数值的地方。 您可以在那里添加更多有用的信息。 该部分的操作导致在按下鼠标左键时计算并显示数据。 因此,这与 MetaTrader 5 中的默认行为相同,但这些值能够根据您的愿望和需求进行调整和建模。
现在我们需要再做一个测试,如下所示。
if (((key & 0x01) != 0x01) && (b1 == 2)) { b1 = 0; ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true); Hide(); Show(); }
释放鼠标左键后,图表被释放并可以拖动,用于创建分析的所有元素都被隐藏,只有价格线再次可见。 最后,我们有最后一个代码部分,如下所示:
Position.ButtonsStatus = (b1 == 0 ? key : 0);
如果无需任何研究,则会存储鼠标按钮状态,并可 EA 中的其它位置可供使用;但如果调用了研究,则会将 NULL 空值作为状态数据,因此无法创建订单或更改其仓位。
在下面的视频中,您可以看到这条轨迹实际上是如何工作的,它是如何调整屏幕上的成交量的。 这个指标很有帮助,如果您学会如何正确使用它,那将是一件很棒的事情。 故此,与"时序与交易"一起,他们形成了一个双噪声分析工具,这是市场上最先进的交易方法之一。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10419
![从头开始开发智能交易系统(第 13 部分):时序与交易(II)](https://c.mql5.com/2/46/development__4.png)
![学习如何基于抛物线 SAR 设计交易系统](https://c.mql5.com/2/46/why-and-how__5.png)
![学习如何基于 ADX 设计交易系统](https://c.mql5.com/2/46/why-and-how__3.png)