English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
从头开始开发智能交易系统(第 14 部分):添加价格成交量(II)

从头开始开发智能交易系统(第 14 部分):添加价格成交量(II)

MetaTrader 5交易系统 | 28 七月 2022, 08:46
1 329 0
Daniel Jose
Daniel Jose

概述

我们的 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

附加的文件 |
EA_-_Mouse.zip (5986.31 KB)
学习如何基于 ATR 设计交易系统 学习如何基于 ATR 设计交易系统
在本文中,我们将学习一款可在交易中运用的新技术工具,作为我们学习如何设计简单交易系统系列的延续。 这次我们将选取另一个流行的技术指标:平均真实范围(ATR)。
从头开始开发智能交易系统(第 13 部分):时序与交易(II) 从头开始开发智能交易系统(第 13 部分):时序与交易(II)
今天,我们将针对市场分析构建《时序与交易》系统的第二部分。 在前一篇文章《时序与交易(I)》当中,我们讨论了一种替代的图表组织系统,该系统能够针对市场上执行的成交进行最快速的解释。
学习如何基于抛物线 SAR 设计交易系统 学习如何基于抛物线 SAR 设计交易系统
在本文中,我们将继续讲述如何基于最流行的指标设计交易系统。 在本文中,我们将详细学习抛物线 SAR 指标,以及如何运用一些简单的策略来设计用于 MetaTrader 5 的交易系统。
学习如何基于 ADX 设计交易系统 学习如何基于 ADX 设计交易系统
在本文中,我们将继续有关基于最流行指标设计交易系统的系列文章,这次我们将讨论平均方向指数(ADX)指标。 我们将详细学习该指标,从而能够更好地理解它,并将学习如何在简单策略里运用它。 通过深入学习,我们可以获得更多的认知,可以更好地运用它。