English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Разработка торгового советника с нуля (Часть 14): Добавляем Volume at Price (II)

Разработка торгового советника с нуля (Часть 14): Добавляем Volume at Price (II)

MetaTrader 5Торговые системы | 6 июля 2022, 09:00
861 0
Daniel Jose
Daniel Jose

Введение

Наш советник уже имеет несколько ресурсов, которые помогают во время торговли — они были добавлены в ходе предыдущих статей. Тем не менее, у этого советника есть небольшие проблемы с отображением и изменением размера. Они не мешают торговле, но в некоторые моменты это приводит к захламлению экрана и приходится принудительно его перезапускать. Кроме того, недостает еще некоторых вещей, которые представляли бы для нас ценную информацию. Они могут быть специфичными, то тем не менее необходимыми.

Так что давайте приступим к работе и начнем реализовывать эти новые улучшения. Эта статья будет очень интересной, она может не только натолкнуть вас на новые идеи и методы представления информации, но и помочь вам исправить небольшие ошибки в ваших проектах.


Планирование и внедрение новой функции в Volume At Price

1. Планирование

Ну что ж, в трейдинге можно найти забавные вещи. Много раз мы видели, что рынок накапливается в определенных ценовых областях, и когда срабатывают стопы (на стороне покупки, или на стороне продажи), происходит быстрое движение цены, что можно наблюдать в Times & Trade. Мы представили это в статьях Times & Trade (I) и  Times & Trade (II). В них мы рассмотрели, как создать альтернативную графическую систему для чтения и анализа потока исполненных ордеров. Так вот, если понаблюдать, то мы заметим, что в некоторые из этих моментов цена стремится вернуться в область накопления, откуда она не хочет выходить в данный момент. Но глядя на индикатор Volume at Price, трудно определить, насколько давно цена оставалась в этой конкретной области. Данный индикатор был реализован в статье "Добавляем Volume At Price (I)", с помощью этого индикатора мы имеем возможность анализировать относительно недавние движения, просто изменяя начальную точку анализа, что делается с помощью корректировки значения объекта, показанного на рисунке ниже:

Но на самом деле это непрактично, поскольку мы привязаны к таймфрейму основного графика, т.е. если мы работаем с графиком на 60-минутном таймфрейме, мы не сможем анализировать движения ниже этого времени, нам придется переключиться на более низкий таймфрейм, чтобы иметь возможность настроить точку анализа. Но в случае торговли с фьючерсными контрактами, как правило, большинство трейдеров действительно торгуют в более коротких таймфреймах, таких как 5-и, 10-и или даже 30-и минутные, поэтому корректировка точки анализа не представляется проблемой. Однако, как мы объясняли ранее, иногда цена выходит из области накопления не просто так, а потому что сработали стопы позиций, и такой возврат обычно происходит менее чем за 5 минут, а на графике появится свеча с длинной тенью, либо выше, либо ниже. Именно в таких случаях Price Action нам подсказывает о том, что произошедшее было рыночным тестированием, этот тип движения можно увидеть ниже на свечах, обозначенных стрелками:

   

Типичное движение тестирования покупателями или срабатывание стопа продавцами

 

Типичное тестирование продавцами или срабатывание стопа покупателями

Такой вид движения происходит часто, и анализ объема, возникшего в каждом из ценовых диапазонов, очень важен нам для того, чтобы понять, проверяет ли рынок или действительно разворачивает тренд в одном направлении. Это невозможно сделать правильно, а точнее, быстро с помощью предложенного выше индикатора объема.

Однако, мы можем внести небольшое изменение в класс объекта индикатора, и у нас будет более четкое представление о том, что происходит. Это будет показано в виде мониторинга торговли, произошедшей за определенный период времени.


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 минуты, пока мы строим график таким образом, вы можете подумать о том, чтобы сделать что-то вроде того, что показано ниже:

 

Более светлые оттенки представляют более свежие объемы, что могло бы быть хорошей идеей...

Хотя это кажется хорошей идеей, когда объем низкий или движение очень быстрое, даже при выразительном объеме для данного момента, он может быть фактически не виден, так как будет построен на максимальном объеме, который был выполнен до этого момента, поэтому вы можете решить сделать немного другой график, чтобы решить эту проблему. Тогда он будет выглядеть так, как показано ниже:

Каждый цвет представляет определенный период в следе объема....

Это может помочь вам проанализировать очень узкие полосы в объеме, временами исправляя проблему, наблюдаемую в первом случае. Но мы все равно будем иметь проблему корректировки, которая возникает, когда объем может быть не таким выразительным по отношению к общему объему в другой точке. Кроме того, цвета для каждого периода должны быть выбраны тщательно, чтобы анализ не запутался во время напряженных переговоров.

Поэтому здесь мы будем использовать более простую модель, которую опять же можно настроить для анализа движений разных периодов, как мы показали выше, и это не представляет особых проблем. Но обратите внимание на проблемы, которые, как мы уже уточнили, возникнут. Оставляем их на ваше усмотрение, поэтому изображение следа будет таким, как показано ниже:

Здесь у нас есть чистый след, на нем мы должны смотреть как на Times & Trade, так и на Price Action, чтобы действительно понять, что происходит...

Так или иначе, единственная функция, которую необходимо модифицировать для изменения отображения объема - это функция, показанная ниже:

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();
}

Звучит глупо, но чтобы понять это, необходимо посмотреть на код выделенной функции. Теперь, когда система исправлена, мы можем перейти к следующему шагу.


Добавление дополнительных ресурсов

Функция, которую мы сейчас добавим, является очень простой, и многие могут не увидеть особой причины для ее внедрения, но ее реализация очень поможет при работе с ордерами, будь то при позиционировании, при перемещении или просто при наблюдении за индикатором Volume at Price.

Первое, что нужно сделать - это изменить класс, частью которого будет код корректировки ценовой линии, этот код выходит из класса 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);
};

Выполнив это, мы можем создать новый класс советника, это будет класс 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()
{
// ... Código interno ...
        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);
}

Интересно то, что на самом деле мышь не исчезает с экрана, только объекты, которые мы создаем, исчезают с экрана, и когда мы "включаем" мышь, только линия цены будет действительно видна, это может показаться любопытным, но это имеет свое применение в некоторых конкретных точках советника. Одной из таких точек является объект класса 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;    //Clique esquerdo
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT Pressionada
        bKeySell = (Key & 0x08) == 0x08;    //CTRL Pressionada
        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;

// ... Restante do código ....

}

функцию MoveTo можно увидеть чуть выше в данной статье, она также является частью класса C_OrderView, но главное - это функция Mouse.GetButtonsStatus. Эта функция возвращает состояние кнопок и клавиш, связанных с событиями мыши.

Эту функцию Mouse.GetButtonStatus можно увидеть ниже:

inline uint GetButtonStatus(void) const
{
        return Position.ButtonsStatus;
}

Это одна строка, которая возвращает переменную, содержащую значения, записанные после последнего события мыши. Далее мы перейдем к коду, который запишет это значение. Но сначала давайте рассмотрим код инициализации мыши, потому что именно он должен сообщить советнику, что мы хотим инициализировать мышь и что с этого момента советник будет обрабатывать различные вещи, связанные с мышью. Код, отвечающий за это, показан в следующем фрагменте:

// ... Other things ....

input group "Mouse"
input color     user50 = clrBlack;      //Price line
input color     user51 = clrDarkGreen;  //Positive move
input color     user52 = clrMaroon;     //Negative move
//+------------------------------------------------------------------+

// ... General info ....

//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        Mouse.Init(user50, user51, user52);

// ... The rest of the code ....

Итак, мы определяем 3 цвета, которые система будет использовать. Эти цвета должны быть выбраны так, чтобы данные были четкими и заметными на графике. Взгляните на код 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();
}

В этом коде нет ничего аномального, мы просто создаем некоторые объекты для использования классом, но выделенный фрагмент может вызвать сомнения, потому что если вы поищете его в классе, то не найдете ни одного места, где он был бы объявлен. Это потому, что он фактически объявлен в коде файла советника вместе с объявлениями другого ресурса, в будущем мы сгруппируем все это в файл, но пока это будет выглядеть так. Итак, если вы посмотрите на код советника, вы найдете следующие строки:

#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();
}

Затем выделенная строка в коде советника вызовет следующий код:

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;
        }
}

Подчеркнем, что приведенный выше код не является полным кодом реализации. Он поддерживает и решает только основные вопросы для советника до этой стадии разработки. Чтобы понять это, давайте обратим внимание на одну вещь в коде инициализации мыши, там есть следующая строка:

ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);

Эта строка предотвращает отображение анализа перекрестия, когда мы нажимаем среднюю кнопку мыши, но зачем предотвращать создание перекрестия? Чтобы понять это, давайте рассмотрим следующую анимацию:

Обратите внимание, что мы находимся на WDO, он движется от 0,5 до 0.5, но когда мы пытаемся сделать анализ, мы видим, что у нас нет большой точности, а в некоторых случаях мы должны иметь некоторую точность для реализации анализа, и это перекрестие, который MT5 предоставляет нам, недостаточно адекватен для конкретных случаев. Мы должны прибегнуть к новой системе, поэтому мы заставляем MT5 перестать создавать перекрестие для нас, когда советник запущен. То, что мы делаем - это создаем свое собственное перекрестие для анализа, с помощью которого мы можем добавить данные и значения, которые важны для нас, и представляем их таким образом, который мы считаем наиболее адекватным. Это можно увидеть ниже, где показывается результат использования системы моделирования данных при работающем советнике.


Мы видим, что указанные значения соответствуют точным значениям движения, и у нас есть визуальная индикация, если значение было положительным, в этом случае индикация становится зеленой, а если отрицательным, то индикация становится красной. След создается, и также начальные и конечные точки становятся заметными, но, как мы уже сказали, система пока не является полной. Вы еще можете внести в нее улучшения, если хотите и это вам нужно. Пока не выйдет новая версия класса C_Mouse, вы можете улучшить эту версию и иметь больше данных, которые вы считаете нужными, но для этого необходимо понимать, как все происходит, поэтому давайте рассмотрим подробнее код сообщений класса C_Mouse. 


4.0.1 - Понимание кода DispathMessage класса C_Mouse

Код начинается с перехвата и установки значений переменных положения мыши, что показано в приведенном ниже фрагменте:

Position.X = (int)lparam;
Position.Y = (int)dparam;
ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);

Значения позиции сообщает платформа MT5, но сами значения поступают от операционной системы и находятся в экранных координатах, т.е. X и Y. Но нам нужно преобразовать их в координаты графика, и для этого мы используем функцию ChartXYToTimePrice, доступную в MQL5, что значительно облегчает нашу жизнь.

Как только это сделано, мы перемещаем ценовую и временную строки.

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;
}

Для этого мы используем переменную static для хранения этого события, так что с этого момента никакие другие события не будут приниматься и обрабатываться советником, который позаботится об исследовании, которое мы хотим провести на графике. Но на самом деле исследование начинается только тогда, когда мы нажимаем левую кнопку мыши, т.е. я использую тот же способ работы, который уже известен всем пользователям платформы MT5 для проведения исследований, и это наиболее целесообразно, поскольку необходимость изучать новый способ проведения исследований линий может заставить вас отказаться от системы. Затем советник ожидает этого щелчка левой кнопкой мыши, что выполняется следующим фрагментом

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));
}

Обратите внимание на выделенную линию, потому что именно в ней отображается и рассчитывается значение, которое вы видите на экране графика, вы можете добавить туда различные вещи, не стесняйтесь. Работа этой части заключается в том, что данные рассчитываются и представляются при нажатой левой кнопке, поэтому поведение платформы MT5 будет таким же, как и у платформы по умолчанию, но со значениями, скорректированными и смоделированными в соответствии с вашими желаниями и потребностями.

Далее необходимо провести еще один тест, который показан ниже.

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}

После отпускания левой кнопки мыши график освобождается и его можно перетаскивать, а также скрываются все те элементы, которые использовались для создания исследования, только ценовая линия снова становится видимой. И, наконец, у нас есть последний фрагмент, который можно увидеть в строке ниже:

Position.ButtonsStatus = (b1 == 0 ? key : 0);

Если исследование не вызывается, состояние кнопок мыши сохраняется и может быть использовано в других местах советника, но если исследование было вызвано, в качестве данных о состоянии используется значение NULL, поэтому невозможно будет ни создать команды, ни изменить их положение.

На видео ниже вы можете заметить, как на самом деле работает этот след, как он настраивается и регулирует объем на экране. Индикатор сильно помогает, и будет здорово, если вы научитесь использовать его корректно. Вместе с лентой сделок они образуют двойной инструмент для анализа шума, что является одним из самых продвинутых способов торговли на рынке.




Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/10419

Прикрепленные файлы |
EA_-_Mouse.zip (5986.31 KB)
Разработка торгового советника с нуля (Часть 15): Доступ к данным в Интернете (I) Разработка торгового советника с нуля (Часть 15): Доступ к данным в Интернете (I)
Как получить доступ к данным в Интернете в MetaTrader 5. В Интернете у нас есть различные сайты и места, с огромным количеством информации, доступной для тех, кто знает, где искать и как лучше всего использовать эту информацию.
Индикатор CCI. Модернизация и новые возможности Индикатор CCI. Модернизация и новые возможности
В этой статье мы рассмотрим возможность модернизации индикатора CCI. Кроме того, будет представлен пример модификации этого индикатора.
Машинное обучение и Data Science (Часть 02): Логистическая регрессия Машинное обучение и Data Science (Часть 02): Логистическая регрессия
Классификация данных — важнейшая вещь для алготрейдера и программиста. В этой статье мы рассмотрим в подробностях один из классификационных логистических алгоритмов, который может помочь нам определить «да» или «нет», рост или падение, покупки или продажи.
Нейросети — это просто (Часть 19): Ассоциативные правила средствами MQL5 Нейросети — это просто (Часть 19): Ассоциативные правила средствами MQL5
Продолжаем тему поиска ассоциативных правил. В предыдущей статье мы рассмотрели теоретические аспекты данного типа задач. В этой статье я продемонстрирую реализацию метода FP-Growth средствами MQL5. А также мы протестируем нашу реализацию на реальных данных.