Español Português
preview
Моделирование рынка: Position View (VI)

Моделирование рынка: Position View (VI)

MetaTrader 5Тестер |
52 0
Daniel Jose
Daniel Jose

Введение

Приветствую всех в новой статье из серии о системе репликации/моделирования.

В предыдущей статье, Моделирование рынка: Position View (V), мы объяснили, как можно получить необходимые данные, чтобы понять то, что происходит в приложении. Это позволит реализовать систему с использованием функции OnTradeTransaction. Там было показано лишь то, как сделать это в советнике. Изучая те данные и советник, уважаемый читатель, вы, наверняка, сильно запутались, и у вас появились сомнения в том, насколько это практично. Небольшая доля скептицизма не повредит. Также будет полезно и даже желательно испытывать некоторые сомнения, когда мы сталкиваемся с чем-то совершенно новым.

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

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


Давайте разберемся, что мы будем делать.

Многие из вас, вероятно, привыкли постоянно видеть или выполнять одни и те же задачи. Не то, что бы этот подход неправильный, но если есть другой способ, почему бы не попробовать? Именно этот момент необходимо здесь понять. Основная цель советника не состоит в размещении объектов на графике или выполнении вычислений. Вы даже можете сделать это так. Это не ошибка. Однако главная задача советника — создать средство для правильной связи с торговым сервером. Поэтому на каждом графике мы можем разместить только одного советника. Возложение на советника таких задач, как создание объектов или работа с ними на графике, является не самым удачным решением.

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

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

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


Небольшое улучшение

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

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

Таким образом, когда трейдер снова размещает советник на графике, в ситуации могут произойти изменения. Или точнее: до сбоя советник был настроен на мониторинг мини-контрактов. Однако, когда трейдер возвращает его на график, советник может быть настроен на мониторинг полных контрактов. Но некоторые индикаторы позиции на графике показывали наличие мини-контрактной позиции. В данный момент у нас могут возникнуть проблемы. Поэтому, чтобы избежать этого, нам необходимо добавить строку кода в процедуру инициализации советника. Данная строка позволит очистить график от остаточных индикаторов.

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

15. //+------------------------------------------------------------------+
16. C_Orders       *Orders;
17. C_Terminal     *Terminal;
18. //+------------------------------------------------------------------+
19. int OnInit()
20. {
21.     Terminal = NULL;
22.     Orders = new C_Orders(0xC0DEDAFE78514269);
23.     
24.     return INIT_SUCCEEDED;
25. }
26. //+------------------------------------------------------------------+
27. void OnTick() {}
28. //+------------------------------------------------------------------+
29. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
30. {
31.     int handle;
32.     
33.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
34.     switch (id)
35.     {
36.         case CHARTEVENT_CHART_CHANGE:
37.             if (Terminal != NULL) break;
38.             else
39.             {
40.                 ulong ul;
41.                 Terminal = new C_Terminal(0, 0, user00);
42.                 for (int count = PositionsTotal() - 1; count >= 0; count--)
43.                 {
44.                     ul = PositionGetTicket(count);
45.                     if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol)
46.                     {
47.                         ChartIndicatorDelete(0, 0, IntegerToString(ul));
48.                         continue;
49.                     }
50.                     handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
51.                     ChartIndicatorAdd(0, 0, handle);
52.                     IndicatorRelease(handle);
53.                 }
54.             }
55.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
56.             EventChartCustom(0, evEA_At_ChartTrade, user00, 0, "");
57.             break;
58.     }
59. }
60. //+------------------------------------------------------------------+

Фрагмент кода советника

Обратите внимание на строку 47 предыдущего фрагмента. Именно она отвечает за предотвращение того, что мы только что описали. Другими словами, когда советник будет снова размещен на графике, он будет использовать строку 47 для удаления старых индикаторов, которые могут не соответствовать критериям инициализации. Или, иначе говоря, если трейдер меняет вид контракта, советник это обнаружит и удалит все индикаторы позиции, которые не соответствуют ожидаемым результатам. Если индикатор соответствует виду контракта, проблем не возникнет, ничего с ним не произойдёт. В худшем случае это просто вызовет ошибку у советника, но она допустима и её можно проигнорировать.

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


Реализация взаимодействия между советником и индикатором позиции

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.     #define macro_DEBUG_MODE(A) \
08.                     Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.     #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay          "RePlay"
14. #define def_MaxPosSlider          400
15. #define def_MaskTimeService       0xFED00000
16. #define def_IndicatorTimeFrame    (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame        4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.     double   dValue;
22.     long     _long;                                  // 1 Information
23.     datetime _datetime;                              // 1 Information
24.     uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.     ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.     uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents     {
30.             evTicTac,                        //Event of tic-tac
31.             evHideMouse,                     //Hide mouse price line
32.             evShowMouse,                     //Show mouse price line
33.             evHideBarTime,                   //Hide bar time
34.             evShowBarTime,                   //Show bar time
35.             evHideDailyVar,                  //Hide daily variation
36.             evShowDailyVar,                  //Show daily variation
37.             evHidePriceVar,                  //Hide instantaneous variation
38.             evShowPriceVar,                  //Show instantaneous variation
39.             evCtrlReplayInit,                //Initialize replay control
40.             evChartTradeBuy,                 //Market buy event
41.             evChartTradeSell,                //Market sales event 
42.             evChartTradeCloseAll,            //Event to close positions
43.             evChartTrade_At_EA,              //Event to communication
44.             evEA_At_ChartTrade,              //Event to communication
45.             evChatWriteSocket,               //Event to Mini Chat
46.             evChatReadSocket,                //Event To Mini Chat
47.             evUpdate_Position                //Event to communication
48.                         };
49. //+------------------------------------------------------------------+
50. enum EnumPriority {                          //Priority list on objects
51.             ePriorityNull = -1,
52.             ePriorityDefault = 0,
53.             ePriorityChartTrade = 5,
54.             ePriorityOrders = 10
55.                         };
56. //+------------------------------------------------------------------+

Defines.mqh

В этом файле мы добавляем новое перечисление, определяющее событие, которое отвечает за запуск вызова обновления индикатора позиции. Это событие определено в строке 47. Сделав это, мы можем перейти к коду советника. Полный код советника представлен ниже:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
004. #property description "Demo version between interaction"
005. #property description "of Chart Trade and Expert Advisor"
006. #property version   "1.118"
007. #property link "https://www.mql5.com/pt/articles/13203"
008. //+------------------------------------------------------------------+
009. #include <Market Replay\Order System\C_Orders.mqh>
010. #include <Market Replay\Auxiliar\C_Terminal.mqh>
011. //+------------------------------------------------------------------+
012. enum eTypeContract {MINI, FULL};
013. //+------------------------------------------------------------------+
014. input eTypeContract user00 = MINI;                 //Cross order in contract
015. //+------------------------------------------------------------------+
016. C_Orders           *Orders;
017. C_Terminal         *Terminal;
018. //+------------------------------------------------------------------+
019. int OnInit()
020. {
021.    Terminal = NULL;
022.    Orders = new C_Orders(0xC0DEDAFE78514269);
023.    
024.    return INIT_SUCCEEDED;
025. }
026. //+------------------------------------------------------------------+
027. void OnTick() {}
028. //+------------------------------------------------------------------+
029. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
030. {
031.    int handle;
032.    
033.    (*Orders).DispatchMessage(id, lparam, dparam, sparam);
034.    switch (id)
035.    {
036.            case CHARTEVENT_CHART_CHANGE:
037.                    if (Terminal != NULL) break;
038.                    else
039.                    {
040.                            ulong ul;
041.                            Terminal = new C_Terminal(0, 0, user00);
042.                            for (int count = PositionsTotal() - 1; count >= 0; count--)
043.                            {
044.                                    ul = PositionGetTicket(count);
045.                                    if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol)
046.                                    {
047.                                            ChartIndicatorDelete(0, 0, IntegerToString(ul));
048.                                            continue;
049.                                    }
050.                                    handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
051.                                    ChartIndicatorAdd(0, 0, handle);
052.                                    IndicatorRelease(handle);
053.                            }
054.                    }
055.            case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
056.                    EventChartCustom(0, evEA_At_ChartTrade, user00, 0, "");
057.                    break;
058.    }
059. }
060. //+------------------------------------------------------------------+
061. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
062. {
063.    if (Terminal == NULL) return;
064.    static ulong ticket = 0;
065.    switch (trans.type)
066.    {
067.            case TRADE_TRANSACTION_HISTORY_ADD:
068.                    ticket = (trans.order != trans.position ? trans.position : 0);
069.                    break;
070.            case TRADE_TRANSACTION_REQUEST:
071.                    if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
072.                    {
073.                            case TRADE_ACTION_DEAL:
074.                                    if (ticket) EventChartCustom(0, evUpdate_Position, ticket, 0, "");
075.                                    else
076.                                    {
077.                                            int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order);
078.                                            ChartIndicatorAdd(0, 0, handle);
079.                                            IndicatorRelease(handle);
080.                                    }
081.                                    ticket = 0;
082.                                    break;
083.                            case TRADE_ACTION_SLTP:
084.                                    EventChartCustom(0, evUpdate_Position, request.position, 0, "");
085.                                    break;
086.                    }
087.                    break;
088.    };
089. }
090. //+------------------------------------------------------------------+
091. void OnDeinit(const int reason)
092. {
093.    ulong ul;
094.    
095.    switch (reason)
096.    {
097.            case REASON_REMOVE:
098.            case REASON_INITFAILED:
099.                    EventChartCustom(0, evEA_At_ChartTrade, -1, 0, "");
100.                    break;
101.    }
102.    if (Terminal != NULL) for (int count = PositionsTotal() - 1; count >= 0; count--)
103.    {
104.            ul = PositionGetTicket(count);
105.            if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue;
106.            ChartIndicatorDelete(0, 0, IntegerToString(ul));
107.    }
108.    delete Orders;
109.    delete Terminal;
110. }
111. //+------------------------------------------------------------------+

Expert Advisor.mq5

Большая часть данного кода уже была объяснена выше. Поэтому нам следует сосредоточиться на том, что здесь нового, то есть, на функции OnTradeTransaction. В данный момент не стоит беспокоиться о некоторых моментах, которые вам могут быть ещё непонятны. Мы всё спокойно объясним. Но здесь я хочу особо подчеркнуть наличие функции EventChartCustom. Данная функция встречается в строках 74 и 84. Именно эти строки позволяют советнику сообщить индикатору позиции о том, что произошло какое-то событие и что индикатор позиции нуждается в обновлении.

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

Здесь мы рассмотрим три различные ситуации. Первая из них возникает, когда при использовании индикатора Chart Trade, мы просим советника совершить рыночную покупку или продажу. После этого у статической переменной ticket будет нулевое значение. Причина в том, что строка 68 будет содержать одинаковые значения для trans.order и trans.position. Хорошо. Таким образом, когда сервер отправляет уведомление TRADE_TRANSACTION_REQUEST, к выполненной операции будет применена соответствующая обработка. Напоминаем: в этот момент позиция ещё только открывается. Таким образом, когда сервер сгенерирует TRADE_ACTION_DEAL, проверка в строке 74 завершится неудачей. И это происходит, потому что переменная ticket равна нулю. В данном случае код будет выполнен между строками 76 и 80. Обратите внимание, что это тот же код, который использовался при инициализации советника, чтобы сообщить MetaTrader 5 о том, что мы хотим добавить индикатор позиции на график.

В итоге, поскольку нам неизвестна причина срабатывания TRADE_ACTION_DEAL, мы обнуляем переменную ticket. Вот и всё. Индикатор позиции появится на графике, отображая ту же информацию, которую мы бы увидели, если бы MetaTrader 5 поддерживал данный режим. Ещё раз напомню: В MetaTrader 5 система кросс-ордеров реализована недостаточно полно. По этой причине, когда мы используем этот торговый режим, нам необходимо создать способ отображения данных на графике. Для этого и существует индикатор позиции.

Хорошо, теперь наша позиция создана и отображается на графике. Спустя некоторое время трейдер решает увеличить позицию. Затем он снова воспользуется индикатором Chart Trade и попросит его увеличить объем позиции. Вот важный момент. На счетах типа HEDGING это новое открытие или запрос на увеличение позиции не приведет к реальному изменению объема. На самом деле произойдет следующее: откроется новая позиция. Поскольку мы не делаем различий между счетами HEDGING и NETTING, то при запросе на увеличение объема на счете HEDGING происходит именно то, что я только что объяснил для открытия позиции. Однако при выполнении того же типа запроса, то есть увеличения позиции на счете NETTING, ситуация будет немного иной. В этом случае, когда сервер генерирует событие TRADE_TRANSACTION_HISTORY_ADD, значения trans.order и trans.position будут отличаться. Когда такое случается, это означает, что произошло какое-то изменение в открытой позиции. Таким образом, значение переменной ticket больше не будет равно нулю. В этом случае тикет будет иметь значение соответствующей позиции. В определенный момент сервер сгенерирует запрос TRADE_TRANSACTION_REQUEST в ответ на полученный запрос.

И когда это произойдёт и будет обработано действие TRADE_ACTION_DEAL, проверка в строке 74 пройдёт успешно. В этот момент будет запущено пользовательское событие, которое указывает индикатору позиции на необходимость его обновления. Обратите внимание, что мы не привязывали это событие к какому-либо конкретному индикатору или приложению. Все приложения, представленные на графике, получат это же пользовательское событие. Однако, поскольку мы фильтруем данное событие внутри каждого приложения, сообщение будет понято только правильным индикатором или приложением. На этом этапе работа советника завершается. В действительности он понятия не имеет, поймет ли индикатор позиции это сообщение или нет. Он даже не знает, есть ли данный индикатор на графике. Для советника это имеет небольшое значение, он выполнил свою работу, и на этом всё. То же самое происходит при изменении значений тейк-профита и стоп-лосса для позиции. В этом случае мы увидим выполнение действия TRADE_ACTION_SLTP, которое находится в строке 83. Обратите внимание, что в строке 84 мы делаем то же самое, что и при увеличении позиции или частичного сокращения объёма позиции. Это относится к случаю, если вы используете счет типа NETTING. "Хорошо.

Но что происходит, когда позиция закрывается?" Полагаю, уважаемый читатель, вы ожидали бы увидеть здесь, в советнике, код, отвечающий за удаление индикатора позиции с графика. Однако это не совсем то, что можно увидеть в коде советника. Как же ему удаётся удалить данный индикатор с графика? Очень просто. Он делает то же самое, что и изменение объема в счетах NETTING. Другими словами, в строке 68 значения trans.order и trans.position будут отличаться. Это обеспечит успешную проверку строки 74 и отправит запрос на обновление индикатора. Однако, поскольку позиция была закрыта, индикатор будет удален с графика. Для полного понимания этого нам необходимо взглянуть на код индикатора позиции. Для разделения тем мы рассмотрим их в новом разделе.


Улучшенный индикатор позиции

Хорошо, вы должны знать, что код индикатора позиции на данный момент состоит из двух частей. Первый пример можно увидеть полностью ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Positions.ico"
04. #property description "Indicator for tracking an open position on the server."
05. #property description "This should preferably be used together with an Expert Advisor."
06. #property description "For more details see the same article."
07. #property version   "1.118"
08. #property link "https://www.mql5.com/pt/articles/13203"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. //+------------------------------------------------------------------+
12. #define def_ShortName "Position View"
13. //+------------------------------------------------------------------+
14. #include <Market Replay\Order System\C_IndicatorPosition.mqh>
15. #include <Market Replay\Defines.mqh>
16. //+------------------------------------------------------------------+
17. input ulong user00 = 0;                 //For Expert Advisor use
18. input color user01 = clrRoyalBlue;      //Color Line Price
19. input color user02 = clrForestGreen;    //Color Line Take Profit
20. input color user03 = clrFireBrick;      //Color Line Stop Loss
21. //+------------------------------------------------------------------+
22. C_IndicatorPosition *Positions = NULL;
23. //+------------------------------------------------------------------+
24. int OnInit()
25. {
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Positions = new C_IndicatorPosition(user01, user02, user03);
28.     if (!Positions.CheckCatch(user00))
29.     {
30.         ChartIndicatorDelete(0, 0, def_ShortName);
31.         return INIT_FAILED;
32.     }
33. 
34.     return INIT_SUCCEEDED;
35. }
36. //+------------------------------------------------------------------+
37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
38. {
39.     return rates_total;
40. }
41. //+------------------------------------------------------------------+
42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
43. {
44.     (*Positions).DispatchMessage(id, lparam, dparam, sparam);
45. };
46. //+------------------------------------------------------------------+
47. void OnDeinit(const int reason)
48. {
49.     delete Positions;
50. }
51. //+------------------------------------------------------------------+

Position View.mq5

Это основной код индикатора. Обратите внимание, что, по сути, единственное отличие здесь заключается в функции OnChartEvent. Поскольку нам необходима некоторая внутренняя информация из класса C_IndicatorPosition, в этом основном коде мы ничего не сделаем. Мы просто вызываем процедуру DispatchMessage. Следовательно, просто наблюдая за этим кодом, мы никак не можем понять, как работает взаимодействие с советником. Давайте теперь рассмотрим заголовочный файл C_IndicatorPosition.mqh, который представлен в полном объеме ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixTake    "Take"
05. #define def_SufixStop    "Stop"
06. //+------------------------------------------------------------------+
07. #include "..\Auxiliar\C_Terminal.mqh"
08. //+------------------------------------------------------------------+
09. class C_IndicatorPosition : private C_Terminal
10. {
11.     private    :
12.         struct st00
13.         {
14.             ulong     ticket;
15.             color     corPrice, corTake, corStop;
16.             string    szShortName;
17.         }m_Infos;
18. //+------------------------------------------------------------------+
19.         inline void MoveLineInfos(string szObjName, const double price)
20.         {
21.             szObjName = m_Infos.szShortName + szObjName;
22.             if (price <= 0) ObjectDelete(0, szObjName);
23.             else ObjectSetDouble(0, szObjName, OBJPROP_PRICE, price);
24.         }
25. //+------------------------------------------------------------------+
26.         void CreateLineInfos(string szObjName, const color cor, const string szDescription = "\n")
27.         {            
28.             szObjName = m_Infos.szShortName + szObjName;
29.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
30.             ObjectSetString(0, szObjName, OBJPROP_TEXT, szDescription);
31.             ObjectSetString(0, szObjName, OBJPROP_TOOLTIP, szDescription);
32.             ObjectSetInteger(0, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
33.         }
34. //+------------------------------------------------------------------+
35.     public    :
36. //+------------------------------------------------------------------+
37.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
38.             :C_Terminal()
39.         {
40.             ZeroMemory(m_Infos);
41.             m_Infos.corPrice = corPrice;
42.             m_Infos.corTake  = corTake;
43.             m_Infos.corStop  = corStop;
44.         }
45. //+------------------------------------------------------------------+
46.         ~C_IndicatorPosition()
47.         {
48.             if (m_Infos.ticket != 0)
49.                 ObjectsDeleteAll(0, IntegerToString(m_Infos.ticket));
50.         }
51. //+------------------------------------------------------------------+
52.         bool CheckCatch(ulong ticket)
53.         {
54.             m_Infos.szShortName = IntegerToString(m_Infos.ticket = ticket);            
55.             if (!PositionSelectByTicket(m_Infos.ticket)) return false;
56.             if (ObjectFind(0, m_Infos.szShortName) >= 0)
57.             {
58.                 m_Infos.ticket = 0;
59.                 return false;
60.             }
61.             IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
62.             CreateLineInfos(NULL, m_Infos.corPrice, "Position opening price.");
63.             CreateLineInfos(def_SufixTake, m_Infos.corTake, "Take Profit point.");
64.             CreateLineInfos(def_SufixStop, m_Infos.corStop, "Stop Loss point.");
65.             EventChartCustom(0, evUpdate_Position, ticket, 0, "");
66.             
67.             return true;
68.         }
69. //+------------------------------------------------------------------+
70.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
71.         {
72.             switch (id)
73.             {
74.                 case CHARTEVENT_CUSTOM + evUpdate_Position:
75.                     if (lparam != m_Infos.ticket) return;
76.                     if (!PositionSelectByTicket(m_Infos.ticket))
77.                     {
78.                         ChartIndicatorDelete(0, 0,  m_Infos.szShortName);
79.                         return;
80.                     };
81.                     MoveLineInfos(NULL, PositionGetDouble(POSITION_PRICE_OPEN));
82.                     MoveLineInfos(def_SufixTake, PositionGetDouble(POSITION_TP));
83.                     MoveLineInfos(def_SufixStop, PositionGetDouble(POSITION_SL));
84.                 break;
85.             }
86.         }
87. //+------------------------------------------------------------------+
88. };
89. //+------------------------------------------------------------------+
90. #undef def_SufixTake
91. #undef def_SufixStop
92. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

Хотя вы можете считать, что всё должно делаться одним способом, здесь мы реализуем это несколько иначе, чем многие привыкли. В принципе, на этом этапе мы не внесли много изменений в код, но можно заметить, что здесь присутствуют разные элементы. Для начала, в строке 19 у нас новая процедура. Ее цель — изменить точку, в которой расположена горизонтальная линия цены. Об этой процедуре особо нечего сказать, так как когда цена равна нулю или отрицательна, объект линии будет удален. Это делается в строке 22 приведенного выше кода.

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

Но давайте будем двигаться шаг за шагом. Для начала мы рассмотрим, как заставить работать индикатор позиции. "Хорошо. Если цена действительна, в строке 23 мы укажем объекту HLINE, где она должна располагаться. Всё просто. Сейчас мы рассмотрим интересный момент, касающийся функции строки 52. Обратите внимание, что пользовательское событие запускается в строке 65. Но зачем это делается так? Причина проста: один раз запрограммируй, тестируй всегда. Поскольку данный индикатор будет получать пользовательские сообщения или события, мы можем настроить его таким образом, чтобы при размещении на графике он вел себя так же, как и при получении запроса на обновление от советника.

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

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

Код в строке 70 довольно интересен. На данный момент нас интересует обработка только одного события: события evUpdate_Position, обработка которого начинается в строке 74. В строке 75 мы проверяем, соответствует ли тикет позиции, указанный в вызове события, тому, который ожидает индикатор. Если тикеты отличаются, обработка на этом этапе прекращается. Важно проверить, совпадает ли номер тикета, поскольку сообщение о событии отправляется всем приложениям, находящимся на графике. Поэтому важно правильно отфильтровать события перед использованием данных. После завершения фильтрации мы можем проверить в строке 76, существует ли эта позиция. Помните: советник не сообщает нам о том, обновляем ли мы, закрываем или открываем новую позицию. Там просто написано: индикатор позиции, у меня есть кое-что для тебя. Сделайте всё, что нужно. И это всё.

За остальное отвечает индикатор, который должен выполнить и проверить все необходимые действия. Если позиция отсутствует, мы завершаем работу индикатора в строке 78, и он удаляется с графика. Но если позиция создается или обновляется, ценовые линии будут смещаться. Это делается в строках с 81 по 83. Вот как работает данный механизм. Однако в коде, показанном выше, есть проблема. Необходимо понять это, прежде чем пытаться разобраться с кодом, который мы рассмотрим в следующей теме.


Неужели так должно быть?

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixTake    "Take"
05. #define def_SufixStop    "Stop"
06. //+------------------------------------------------------------------+
07. #include "..\Auxiliar\C_Terminal.mqh"
08. //+------------------------------------------------------------------+
09. class C_IndicatorPosition : private C_Terminal
10. {
11.     private    :
12.         struct st00
13.         {
14.             ulong     ticket;
15.             color     corPrice, corTake, corStop;
16.             string    szShortName;
17.         }m_Infos;
18. //+------------------------------------------------------------------+
19.         inline void MoveLineInfos(const string szDefine, const double price)
20.         {
21.             string szName = m_Infos.szShortName + szDefine;
22.             if (price <= 0) ObjectDelete(0, szName);
23.             else
24.             {
25.                 if (ObjectFind(0, szName) < 0)
26.                 {
27.                     if (szDefine == def_SufixTake) CreateLineInfos(def_SufixTake, m_Infos.corTake, "Take Profit point."); else
28.                     if (szDefine == def_SufixStop) CreateLineInfos(def_SufixStop, m_Infos.corStop, "Stop Loss point."); else
29.                     CreateLineInfos(NULL, m_Infos.corPrice, "Position opening price.");
30.                 }                    
31.                 ObjectSetDouble(0, szName, OBJPROP_PRICE, price);
32.             }
33.         }
34. //+------------------------------------------------------------------+
35.         void CreateLineInfos(string szObjName, const color cor, const string szDescription = "\n")
36.         {            
37.             szObjName = m_Infos.szShortName + szObjName;
38.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
39.             ObjectSetString(0, szObjName, OBJPROP_TEXT, szDescription);
40.             ObjectSetString(0, szObjName, OBJPROP_TOOLTIP, szDescription);
41.             ObjectSetInteger(0, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
42.         }
43. //+------------------------------------------------------------------+
44.     public    :
45. //+------------------------------------------------------------------+
46.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
47.             :C_Terminal()
48.         {
49.             ZeroMemory(m_Infos);
50.             m_Infos.corPrice = corPrice;
51.             m_Infos.corTake  = corTake;
52.             m_Infos.corStop  = corStop;
53.         }
54. //+------------------------------------------------------------------+
55.         ~C_IndicatorPosition()
56.         {
57.             if (m_Infos.ticket != 0)
58.                 ObjectsDeleteAll(0, IntegerToString(m_Infos.ticket));
59.         }
60. //+------------------------------------------------------------------+
61.         bool CheckCatch(ulong ticket)
62.         {
63.             m_Infos.szShortName = IntegerToString(m_Infos.ticket = ticket);            
64.             if (!PositionSelectByTicket(m_Infos.ticket)) return false;
65.             if (ObjectFind(0, m_Infos.szShortName) >= 0)
66.             {
67.                 m_Infos.ticket = 0;
68.                 return false;
69.             }
70.             IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
71.             EventChartCustom(0, evUpdate_Position, ticket, 0, "");
72.             
73.             return true;
74.         }
75. //+------------------------------------------------------------------+
76.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
77.         {
78.             switch (id)
79.             {
80.                 case CHARTEVENT_CUSTOM + evUpdate_Position:
81.                     if (lparam != m_Infos.ticket) return;
82.                     if (!PositionSelectByTicket(m_Infos.ticket))
83.                     {
84.                         ChartIndicatorDelete(0, 0,  m_Infos.szShortName);
85.                         return;
86.                     };
87.                     MoveLineInfos(NULL, PositionGetDouble(POSITION_PRICE_OPEN));
88.                     MoveLineInfos(def_SufixTake, PositionGetDouble(POSITION_TP));
89.                     MoveLineInfos(def_SufixStop, PositionGetDouble(POSITION_SL));
90.                 break;
91.             }
92.             ChartRedraw();
93.         }
94. //+------------------------------------------------------------------+
95. };
96. //+------------------------------------------------------------------+
97. #undef def_SufixTake
98. #undef def_SufixStop
99. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

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

По сути, в указанной в строке 61 функции мы больше не создаём объекты HLINE. Строки, которые приводили к такому результату, были удалены. На самом деле, они не были уничтожены, а их переместили. Обратите внимание, что весь код остаётся неизменным. Однако, если мы перейдём к строке 19, то увидим нечто очень странное, чего раньше не происходило.

Ранее, когда мы запрашивали перемещение ценовые линии, код только указывал объекту о том, что он должен переместиться. Но теперь появился дополнительный шаг. Он находится в строке 25. Наличие этого шага позволяет нам воссоздать линию при необходимости. Иными словами, когда трейдер удаляет линию, советник дает указание индикатору позиции обновить данные. Индикатор удалит ту же линию, которую удалил трейдер. Однако, когда трейдер снова задаст линию, указывая на то, что теперь будет цена стоп-лосса или тейк-профита, советник даст указание индикатору позиции снова обновить данные. На этом этапе, при выполнении строки 25, индикатор позиции заметит, что объект HLINE не существует, и создаст его соответствующим образом для нового состояния позиции. Только тогда индикатор позиции установит линию в правильном месте. Это делается с помощью строки 31.

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


Заключительные идеи

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

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

В любом случае, я не собираюсь менять способ представления кода. Если вы хотите попробовать этот код, но не знаете, как создать все файлы (хотя весь код представлен здесь в полном объеме), вы сможете найти уже скомпилированные приложения в прикрепленных файлах. Таким образом, будет достаточно использовать их в MetaTrader 5 и посмотреть, как работает обмен сообщениями. Эти приложения были протестированы на счетах типа HEDGING и NETTING, и они работают безупречно. Ничего менять не надо, просто распакуйте вложенный файл в папку MQL5. Вы получите тот же результат, что показан в видео.

Файл Описание
Experts\Expert Advisor.mq5
Показывает на графике взаимодействие между Chart Trade и советником. (Для взаимодействия необходим Mouse Study.)
Indicators\Chart Trade.mq5 Создает окно для настройки порядка отправки. (Для взаимодействия необходим Mouse Study.)
Indicators\Market Replay.mq5 Создает элементы управления для взаимодействия со сервисом репликации/моделирования. (Для взаимодействия необходим Mouse Study.)
Indicators\Mouse Study.mq5 Обеспечивает взаимодействие между графическими элементами управления и пользователем. (Необходимо как для работы системы репликации/моделирования, так и на реальном рынке.)
Indicators\Order Indicator.mq5 Отвечает за отображение рыночных ордеров, а также позволяет взаимодействовать с ними и контролировать их.
Indicators\Position View.mq5 Отвечает за отображение рыночных позиций, обеспечивая возможность взаимодействия с ними и контроля над ними.
Services\Market Replay.mq5 Создает и поддерживает сервис репликации/моделирования рынка. (Основной системный файл.)

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

Прикрепленные файлы |
Anexo.zip (779.24 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Разработка инструментария для анализа Price Action (Часть 70): Автоматическое исполнение сделок по сигналам паттерна "флаг" Разработка инструментария для анализа Price Action (Часть 70): Автоматическое исполнение сделок по сигналам паттерна "флаг"
В статье описаны буферная архитектура сигналов для пробоев паттерна "флаг" и советник, который ее использует. Стрелки пробоя и высота флагштока записываются в отдельные буферы только после подтверждения, что предотвращает перерисовку и неоднозначность. Советник считывает данные из буферов через CopyBuffer(), проверяет сигналы с помощью настраиваемых фильтров и исполняет сделки с фиксированными или динамическими уровнями SL/TP.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
MLP (многослойный перцептрон) внутри советника MQL5: Обучение на истории без Python и без файлов весов MLP (многослойный перцептрон) внутри советника MQL5: Обучение на истории без Python и без файлов весов
Статья показывает, как перенести MLP-фильтр советника GridSurvivor из офлайн-обучения в Python в полностью встроенное обучение в MQL5. Сеть тренируется на истории текущего символа и таймфрейма, периодически переобучается и используется как последний фильтр сигналов. Подход исключает внешние файлы и рассинхрон нормализации, делая советник самодостаточным и воспроизводимым.