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

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

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

Введение

Здравствуйте, уважаемые читатели! Добро пожаловать в новую статью о создании системы репликации/моделирования.

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

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


Размышление над решением проблемы

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

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

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

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

Но, несмотря на кажущуюся запутанность объяснения, в нем есть ещё одна небольшая проблема. Чтобы объяснение стало менее запутанным, попробуйте на мгновение представить следующую ситуацию: вы, как трейдер, работаете с любым символом и решаете купить его по цене, например, 100 долларов. Итак, ордер был отправлен по рынку и с использованием индикатора Chart Trade. Затем советник отправит запрос с уже определенными линиями тейк-профита и стоп-лосса. Спустя некоторое время вы решаете удалить эти линии. Для этого вы используете кнопки, которые были реализованы в предыдущих статьях, совместно с индикатором позиции. Хорошо, советник понял ваш запрос и отправил на сервер команду на удаление уровней тейк-профита и стоп-лосса. Теперь у нас есть желаемый сценарий. Иными словами, открытая позиция без линий тейк-профита и стоп-лосса. На этом этапе я хочу, чтобы вы начали размышлять и попытались логически следовать за моей мыслью.

Предположим, цена выросла и достигла 150 долларов. вы решаете, что пора установить стоп-лосс. Эта линия будет частью позиции; это не будет новый ордер, размещённый в стакане и ожидающий возврата цены. Это важно, так как это разные ситуации. Итак, что вы, как трейдер, должны сделать, чтобы добавить линию стоп-лосса к позиции? Самое естественное для трейдера — щёлкнуть по линии позиции и перетащить её. Отлично. Но перетащить её куда и до какого уровня? Вот в чём проблема. Но давайте немного упростим ситуацию. Ваша идея как трейдера состоит в том, чтобы установить стоп-лосс на уровне 140 долларов. Хорошо, это то, что вы, как трейдер, хотите сделать. Но как вы собираетесь это сделать? Или, точнее: каким образом вам, как программисту, следует это реализовать? И именно здесь начинаются сложности. Потому что если вы как программист не спланируете должным образом подобные ситуации, то даже вы, используя систему, которую сами же и реализовали, можете в итоге совершить ошибки при работе с программой, которую сами же и написали.

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

По всей видимости, это не станет большой проблемой, поскольку данная строка с ценой абсолютно ничего не значит. Здесь нужно сделать небольшое отступление, чтобы кое-что пояснить. То, что многие трейдеры считают правдой, но на самом деле это является не более чем большим заблуждением. Линии тейка-профита и стоп-лосса — НЕТ, и я повторю это снова, ОНИ НЕ ЯВЛЯЮТСЯ ТЕМ МЕСТОМ, ГДЕ ВСЁ ПРОИСХОДИТ. Позиция закроется тогда и только тогда, когда точно по этой цене будет совершена какая-либо сделка. И объем совершенных там сделок должен быть достаточным, чтобы поглотить все лоты до того, как цена изменится. Если цена изменится до того, как весь присутствующий там объем лотов будет поглощен, ордер будет пропущен. Иными словами, позиция останется открытой. Это довольно серьезная проблема, затрагивающая многих менее опытных трейдеров, поскольку многие используют роботов и полагают, что ордера тейк-профита и стоп-лосса никогда не будут пропущены ценой.

В одной из предыдущих серий статей мы объясняли подобные вещи. Если вы ещё не прочитали это, я настоятельно рекомендую прочитать данную серию. Это 15 довольно простых статей, в которых я объясняю, как можно разработать советник, работающий на 100% автоматически. Там, в этой серии публикаций, я показываю, как решить именно эту проблему с пропуском ордеров, и на данном этапе разработки системы репликации/моделирования это может помочь вам задуматься о других решениях. Например, использовать противоположный ордер с тем же объемом, который исполнился бы в любом случае, даже если цена перешагнет ордер, поскольку он вошел бы по рынку. Первую статью из этой серии можно найти по ссылке:

Как построить советник, работающий автоматически (Часть 01): Концепции и структуры

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

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

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


Подготовка к перемещению ценовых линий

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

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.             evMsgClosePositionEA,            //Event to communication
49.             evMsgCloseTakeProfit,            //Event to communication
50.             evMsgCloseStopLoss,              //Event to communication
51.             evMsgNewTakeProfit,              //Event to communication
52.             evMsgNewStopLoss                 //Event to communication
53.                         };
54. //+------------------------------------------------------------------+
55. enum EnumPriority {                          //Priority list on objects
56.             ePriorityNull = -1,
57.             ePriorityDefault = 0,
58.             ePriorityChartTrade = 5,
59.             ePriorityOrders = 10
60.                         };
61. //+------------------------------------------------------------------+

Defines.mqh

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Defines.mqh"
005. //+------------------------------------------------------------------+
006. class C_Orders
007. {
008.     protected:
009. //+------------------------------------------------------------------+
010. inline const ulong GetMagicNumber(void) const { return m_Base.MagicNumber; }
011. //+------------------------------------------------------------------+
012.         bool ClosePosition(const ulong ticket)
013.             {
014.                 bool   IsBuy;
015.                 string szContract;
016.                 
017.                 if  (!PositionSelectByTicket(ticket)) return false;
018.                 IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
019.                 szContract = PositionGetString(POSITION_SYMBOL);
020.                 ZeroMemory(m_Base.TradeRequest);
021.                 m_Base.TradeRequest.action    = TRADE_ACTION_DEAL;
022.                 m_Base.TradeRequest.type      = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
023.                 m_Base.TradeRequest.price     = NormalizeDouble(SymbolInfoDouble(szContract, (IsBuy ? SYMBOL_BID : SYMBOL_ASK)), (int)SymbolInfoInteger(szContract, SYMBOL_DIGITS));
024.                 m_Base.TradeRequest.position  = ticket;
025.                 m_Base.TradeRequest.symbol    = szContract;
026.                 m_Base.TradeRequest.volume    = PositionGetDouble(POSITION_VOLUME);
027.                 m_Base.TradeRequest.deviation = 1000;
028.                 
029.                 return SendToPhysicalServer() != 0;
030.             };
031. //+------------------------------------------------------------------+    
032.     private    :
033. //+------------------------------------------------------------------+
034.         struct stBase
035.         {
036.             MqlTradeRequest TradeRequest;
037.             ulong           MagicNumber;
038.             bool            bTrash;
039.         }m_Base;
040. //+------------------------------------------------------------------+
041.         struct stChartTrade
042.         {
043.             struct stEvent
044.             {
045.                 EnumEvents  ev;
046.                 string      szSymbol,
047.                             szContract;
048.                 bool        IsDayTrade;
049.                 ushort      Leverange;
050.                 double      PointsTake,
051.                             PointsStop;
052.             }Data;
053. //---
054.             bool Decode(const EnumEvents ev, const string sparam)
055.                 {
056.                     string Res[];
057.         
058.                     if (StringSplit(sparam, '?', Res) != 7) return false;
059.                     stEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])};
060.                     if ((ev == loc.ev) && (loc.szSymbol == _Symbol)) Data = loc;
061.                     else return false;
062.                     
063.                     return true;
064.                 }
065. //---
066.         }m_ChartTrade;
067. //+------------------------------------------------------------------+
068.         ulong SendToPhysicalServer(void)
069.             {
070.                 MqlTradeCheckResult  TradeCheck;
071.                 MqlTradeResult       TradeResult;
072.                 
073.                 ZeroMemory(TradeCheck);
074.                 ZeroMemory(TradeResult);
075.                 if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
076.                 {
077.                     PrintFormat("Order System - Check Error: %d", GetLastError());
078.                     return 0;
079.                 }
080.                 m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
081.                 if (TradeResult.retcode != TRADE_RETCODE_DONE)
082.                 {
083.                     PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
084.                     return 0;
085.                 };
086.                 
087.                 return TradeResult.order;
088.             }
089. //+------------------------------------------------------------------+    
090.         ulong ToMarket(const ENUM_ORDER_TYPE type)
091.             {
092.                 double price  = SymbolInfoDouble(m_ChartTrade.Data.szContract, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID));
093.                 double vol    = SymbolInfoDouble(m_ChartTrade.Data.szContract, SYMBOL_VOLUME_STEP);
094.                 uchar  nDigit = (uchar)SymbolInfoInteger(m_ChartTrade.Data.szContract, SYMBOL_DIGITS);
095.                 
096.                 ZeroMemory(m_Base.TradeRequest);
097.                 m_Base.TradeRequest.magic         = m_Base.MagicNumber;
098.                 m_Base.TradeRequest.symbol        = m_ChartTrade.Data.szContract;
099.                 m_Base.TradeRequest.price         = NormalizeDouble(price, nDigit);
100.                 m_Base.TradeRequest.action        = TRADE_ACTION_DEAL;
101.                 m_Base.TradeRequest.sl            = NormalizeDouble(m_ChartTrade.Data.PointsStop == 0 ? 0 : price + (m_ChartTrade.Data.PointsStop * (type == ORDER_TYPE_BUY ? -1 : 1)), nDigit);
102.                 m_Base.TradeRequest.tp            = NormalizeDouble(m_ChartTrade.Data.PointsTake == 0 ? 0 : price + (m_ChartTrade.Data.PointsTake * (type == ORDER_TYPE_BUY ? 1 : -1)), nDigit);
103.                 m_Base.TradeRequest.volume        = NormalizeDouble(vol + (vol * (m_ChartTrade.Data.Leverange - 1)), nDigit);
104.                 m_Base.TradeRequest.type          = type;
105.                 m_Base.TradeRequest.type_time     = (m_ChartTrade.Data.IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
106.                 m_Base.TradeRequest.stoplimit     = 0;
107.                 m_Base.TradeRequest.expiration    = 0;
108.                 m_Base.TradeRequest.type_filling  = ORDER_FILLING_RETURN;
109.                 m_Base.TradeRequest.deviation     = 1000;
110.                 m_Base.TradeRequest.comment       = "Order Generated by Experts Advisor.";
111. 
112.                 MqlTradeRequest TradeRequest[1];
113. 
114.                 TradeRequest[0] = m_Base.TradeRequest;
115.                 ArrayPrint(TradeRequest);
116. 
117.                 return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? SendToPhysicalServer() : 0);
118.             };
119. //+------------------------------------------------------------------+
120.         void CloseAllsPosition(void)
121.             {
122.                 for (int count = PositionsTotal() - 1; count >= 0; count--)
123.                 {
124.                     if (PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue;
125.                     if (PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue;
126.                     ClosePosition(PositionGetInteger(POSITION_TICKET));
127.                 }
128.             };
129. //+------------------------------------------------------------------+    
130.         void ModifyValueSLTP(const ulong ticket, const string symbol, const double sl, const double tp)
131.             {
132.                 ZeroMemory(m_Base.TradeRequest);            
133.                 MqlTradeRequest TradeRequest[1];
134. 
135.                 if ((sl < 0) || (tp < 0)) if (!PositionSelectByTicket(ticket)) return;
136.                 m_Base.TradeRequest.magic     = m_Base.MagicNumber;
137.                 m_Base.TradeRequest.action    = TRADE_ACTION_SLTP;
138.                 m_Base.TradeRequest.symbol    = symbol;
139.                 m_Base.TradeRequest.position  = ticket;
140.                 m_Base.TradeRequest.sl        = (sl < 0 ? PositionGetDouble(POSITION_SL) : sl);
141.                 m_Base.TradeRequest.tp        = (tp < 0 ? PositionGetDouble(POSITION_TP) : tp);
142.                 
143.                 TradeRequest[0] = m_Base.TradeRequest;
144.                 ArrayPrint(TradeRequest);
145.                 
146.                 SendToPhysicalServer();
147.             }
148. //+------------------------------------------------------------------+    
149.     public    :
150. //+------------------------------------------------------------------+
151.         C_Orders(const ulong magic)
152.             {
153.                 m_Base.MagicNumber = magic;
154.             }
155. //+------------------------------------------------------------------+    
156.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
157.             {
158.                 switch (id)
159.                 {
160.                     case CHARTEVENT_CUSTOM + evChartTradeBuy     :
161.                     case CHARTEVENT_CUSTOM + evChartTradeSell    :
162.                     case CHARTEVENT_CUSTOM + evChartTradeCloseAll:
163.                         if (m_ChartTrade.Decode((EnumEvents)(id - CHARTEVENT_CUSTOM), sparam)) switch (m_ChartTrade.Data.ev)
164.                         {
165.                             case evChartTradeBuy:
166.                                 ToMarket(ORDER_TYPE_BUY);
167.                                 break;
168.                             case evChartTradeSell:
169.                                 ToMarket(ORDER_TYPE_SELL);
170.                                 break;
171.                             case evChartTradeCloseAll:
172.                                 CloseAllsPosition();
173.                                 break;
174.                         }
175.                         break;
176.                     case CHARTEVENT_CUSTOM + evMsgClosePositionEA:
177.                         ClosePosition((ulong)(lparam));
178.                         break;
179.                     case CHARTEVENT_CUSTOM + evMsgCloseTakeProfit:
180.                         ModifyValueSLTP((ulong)(lparam), sparam, dparam, 0);
181.                         break;
182.                     case CHARTEVENT_CUSTOM + evMsgCloseStopLoss:
183.                         ModifyValueSLTP((ulong)(lparam), sparam, 0, dparam);
184.                         break;
185.                     case CHARTEVENT_CUSTOM + evMsgNewTakeProfit:
186.                         ModifyValueSLTP((ulong)(lparam), sparam, -1, dparam);
187.                         break;
188.                     case CHARTEVENT_CUSTOM + evMsgNewStopLoss:
189.                         ModifyValueSLTP((ulong)(lparam), sparam, dparam, -1);
190.                         break;
191.                 }
192.             }
193. //+------------------------------------------------------------------+    
194. };
195. //+------------------------------------------------------------------+

C_Orders.mqh

Возможно, кажется бессмысленным приводить весь код класса целиком, поскольку изменения затронули лишь его часть. Но ничего страшного, давайте посмотрим на произошедшие изменения. Обратите внимание, что в строках 185 и 188 мы реализовали новые события именно таким образом, чтобы советник понимал, как их обрабатывать. Теперь обратите внимание: мы использовали ту же самую процедуру, которая применялась ранее, то есть, ModifyValueSLTP. Но, в отличие от того, что мы делали раньше, теперь мы передаем отрицательное значение в одном из полей. Новое значение поля сообщения, как и прежде, передается в параметре dparam. А что насчет этого отрицательного значения? В чём причина этого? Чтобы это понять, необходимо взглянуть на код в строке 130, именно там мы реализуем процедуру ModifyValueSLTP.

Обратите внимание, что теперь внутри этой процедуры у нас есть небольшие отличия. Во-первых, в строке 135 мы проводим проверку, чтобы определить, является ли значение sl или tp отрицательным. В этом случае мы вызовем функцию PositionSelectByTicket. Этот вызов нужен, чтобы загрузить актуальные значения позиции. Но почему? Причину можно увидеть в строках 140 и 141. Если значение какого-либо из аргументов отрицательное, будет использоваться самое актуальное значение, присутствующее в позиции, чтобы сервер не удалял существующее значение. Однако, если значение равно нулю, как в случае удаления линии, или любому другому неотрицательному значению, именно это значение будет использовано сервером для размещения линии стоп-лосса или тейк-профита на месте, желаемом трейдером. На этом код советника обновлен и больше не требует никаких других изменений в своей структуре.

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_NameHLine        m_Info.szPrefixName + "#HLINE"
005. #define def_NameBtnClose     m_Info.szPrefixName + "#CLOSE"
006. //+------------------------------------------------------------------+
007. #define def_PathBtns "Images\\Market Replay\\Orders\\"
008. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
009. #resource "\\" + def_Btn_Close;
010. //+------------------------------------------------------------------+
011. #include "..\Auxiliar\C_Mouse.mqh"
012. //+------------------------------------------------------------------+
013. class C_ElementsTrade : private C_Mouse
014. {
015.     private    :
016. //+------------------------------------------------------------------+
017.         enum eObjects {LINE_PRICE, BTN_CLOSE, ELEM_NULL};
018. //+------------------------------------------------------------------+
019.         struct st00
020.         {
021.             ulong        ticket;
022.             string       szPrefixName;
023.             EnumEvents   ev;
024.             double       price;
025.             struct st01
026.             {
027.                 short xi, xf, yi, yf;
028.             }Positions[ELEM_NULL];
029.         }m_Info;
030. //+------------------------------------------------------------------+
031.         void UpdateViewPort(void)
032.         {
033.             int x, y;
034.             
035.             ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y);
036.             ObjectSetDouble(0, def_NameHLine, OBJPROP_PRICE, m_Info.price);
037.             x = 130;
038.             m_Info.Positions[BTN_CLOSE].xi = (short)(x - 8);
039.             m_Info.Positions[BTN_CLOSE].xf = (short)(x + 8);
040.             m_Info.Positions[BTN_CLOSE].yi = (short)(y - 8);
041.             m_Info.Positions[BTN_CLOSE].yf = (short)(y + 8);
042.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
043.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
044.         }
045. //+------------------------------------------------------------------+
046.         eObjects CheckMousePosition(double &price)
047.             {
048.                 short x, y;
049.                 st_Mouse loc;
050.                 
051.                 loc = GetPositionsMouse();
052.                 price = loc.Position.Price;
053.                 x = loc.Position.X_Graphics;
054.                 y = loc.Position.Y_Graphics;
055.                 for (eObjects c0 = LINE_PRICE; c0 < ELEM_NULL; c0++)
056.                     if ((x > m_Info.Positions[c0].xi) && (x < m_Info.Positions[c0].xf) &&
057.                         (y > m_Info.Positions[c0].yi) && (y < m_Info.Positions[c0].yf)) return c0;
058.                 return ELEM_NULL;
059.             }
060. //+------------------------------------------------------------------+
061.     public    :
062. //+------------------------------------------------------------------+
063.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n")
064.             :C_Mouse(0, "")
065.         {
066.             string szObj;
067.             
068.             ZeroMemory(m_Info);
069.             m_Info.szPrefixName = StringFormat("%I64u@%d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
070.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_HLINE, _color, ePrio);
071.             ObjectSetInteger(0, szObj, OBJPROP_WIDTH, 2);
072.             ObjectSetString(0, szObj, OBJPROP_TEXT, szDescr);
073.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr);
074.             ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull);
075.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityOrders + 1));
076.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
077.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
078.         }
079. //+------------------------------------------------------------------+
080.         ~C_ElementsTrade()
081.         {
082.             if (m_Info.szPrefixName != "")
083.                 ObjectsDeleteAll(0, m_Info.szPrefixName);
084.         }
085. //+------------------------------------------------------------------+
086.         inline void UpdatePrice(const double price)
087.         {
088.             m_Info.price = price;
089.             UpdateViewPort();
090.         }
091. //+------------------------------------------------------------------+
092.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
093.         {
094.             double price;
095.             
096.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
097.             switch (id)
098.             {
099.                 case CHARTEVENT_MOUSE_MOVE:
100.                     if (CheckClick(C_Mouse::eClickLeft)) switch(CheckMousePosition(price))
101.                     {
102.                         case LINE_PRICE:
103.                             break;
104.                         case BTN_CLOSE:
105.                             if (PositionSelectByTicket(m_Info.ticket)) switch (m_Info.ev)
106.                             {
107.                                 case evMsgClosePositionEA:
108.                                     EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
109.                                     break;
110.                                 case evMsgCloseTakeProfit:
111.                                     EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
112.                                     break;
113.                                 case evMsgCloseStopLoss:
114.                                     EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
115.                                     break;
116.                             }
117.                             break;
118.                     }
119.                     break;
120.                 case CHARTEVENT_CHART_CHANGE:
121.                     UpdateViewPort();
122.                     break;
123.             }
124.         }
125. //+------------------------------------------------------------------+
126. };
127. //+------------------------------------------------------------------+
128. #undef def_Btn_Close
129. #undef def_PathBtns
130. //+------------------------------------------------------------------+
131. #undef def_NameBtnClose
132. #undef def_NameHLine
133. //+------------------------------------------------------------------+

C_ElementsTrade.mqh

Глядя на этот код, вы, должно быть, думаете: "Но ведь в коде ничего не изменилось". Однако, это не совсем так, друг мой. Код полностью изменил свое поведение. Обратите внимание, что мы больше не используем класс C_Terminal, а используем класс C_Mouse. Это делается в строке 11. Точно так же класс теперь больше не наследуется от C_Terminal. Класс наследуется от C_Mouse и, как следствие, также включает в себя класс C_Terminal. Благодаря этому теперь мы начинаем использовать индикатор мыши для взаимодействия с индикатором позиций. Но простого внесения вышеупомянутых изменений недостаточно для достижения этой цели. Необходимы дальнейшие изменения в коде. Таким образом, в строке 17 появляется перечисление. Цель данного обновления — заменить работу, ранее выполнявшуюся в MetaTrader 5. Другими словами, теперь у нас будет немного больше контроля над кодом.

В результате этой замены мы объявили новую структуру в строке 25. Она содержит четыре переменные, которые объявляются в строке 27. Однако на самом деле данная структура представляет собой массив позиций, что можно увидеть в строке 28, где указано, что массив будет иметь размерность X элементов. В данном случае — 3 элемента. Хорошо, теперь нам нужно добавить элементы в этот массив. Это выполняется в процедуре, описанной в строке 31. Обратите внимание, что между строками 38 и 41 мы добавили позицию, или хитбокс (область попадания), где кнопка закрытия будет ожидать клика мыши. Значения «восемь», которые можно увидеть в этих строках, представляют собой половину размера битмапа. Напоминаю: размер битмапа составляет 16 на 16 пикселей. И поскольку он позиционируется по центру, нам необходимо правильно настроить этот хитбокс. Отлично, теперь мы знаем, где находится кнопка закрытия.

Но прежде чем перейти к строке 46, давайте перейдем к конструктору класса. Это можно увидеть в строке 63. Обратите внимание, что в строке 64 мы теперь инициализируем класс C_Mouse. Ранее это был класс C_Terminal. Такой способ инициализации класса C_Mouse сообщает MetaTrader 5, что мы не хотим создавать объект Mouse. Нам просто нужна возможность доступа к индикатору мыши, если он присутствует на графике.

Теперь мы можем вернуться к строке 46 и посмотреть, что там происходит. Обратите внимание, что данная функция кажется гораздо сложнее, чем есть на самом деле. Здесь мы просто фиксируем положение индикатора мыши на строке 51. Сразу после этого, в строке 52, мы отмечаем значение цены, чтобы вернуть его вызывающему коду, которую мы вскоре рассмотрим. Но нас интересуют именно следующие строки. Они проверяют хитбокс, чтобы вернуть объект, по которому был сделан клик. Если ни один объект не был идентифицирован, в строке 58 мы вернём вызывающей стороне недопустимое значение.

Теперь, наконец, мы можем увидеть объект, вызывающий функцию CheckMousePosition. Речь идёт именно о процедуре DispatchMessage, которая находится в строке 92. Теперь начинается самое интересное, ведь первым делом мы вызываем обработчик событий класса C_Mouse. Это происходит в строке 96. Причина заключается в том, что нам необходимо гарантировать корректность данных, поскольку мы не используем систему буферизации для получения данных от индикатора мыши. После обработки событий в классе C_Mouse, при возникновении события мыши, код выполнит строку 99.

А теперь внимание: События мыши здесь — это не то же самое, чего вы ожидаете, если планируете использовать тот же способ кодирования мыши, который обычно применяется в MQL5. Здесь события мыши фильтруются и обрабатываются иначе. Чтобы понять это, вернитесь к этой же серии статей о системе репликации/моделирования и посмотрите, как был реализован индикатор мыши. Там вы заметите, что при использовании индикатора мыши некоторые события щелчка могут игнорироваться. Это связано с тем, что этот индикатор использовался для проведения исследования. В этой же серии статей я показал, как можно модифицировать индикатор, что позволяет создать собственный режим анализа для прямого взаимодействия с графиком без необходимости настройки каких-либо объектов анализа. Указатель мыши сам сделает это за вас.

Поэтому тест по строке 100 будет считаться успешным только в том случае, если во время исследования не было совершено ни одного щелчка мыши. Если во время анализа вы случайно кликните по объекту, который отслеживается этим кодом класса C_ElementsTrade, данный клик будет проигнорирован. Таким образом, если предположить, что вы не выполняете графическое построение и кликаете в какой-то точке графика, проверка будет пройдена и вызовется функция CheckMousePosition. Однако есть одна важная деталь, которую следует упомянуть. Данная проверка полностью проигнорирует порядок отображения на графике или значение свойства ZOrder. Другими словами, будьте осторожны. Даже если вы щелкнете по объекту с более низким ZOrder, он может иметь приоритет, поскольку порядок здесь будет определяться перечислением в строке 17. И два объекта, даже если они относятся к разным индикаторам, но имеют одинаковое положение хитбокса, получат это событие. При этом не будет иметь значения, у какого объекта значение ZOrder больше и какой из них находится на переднем плане. Будьте осторожны с этим до тех пор, пока не будет предоставлено исправление.

Позже мы разберемся, как это сделать. Но пока будьте осторожны. Возвращаясь к коду, когда функция CheckMousePosition завершит выполнение, она укажет, какой именно объект получил клик. В этот момент мы разделяем эти случаи. Обратите внимание, что в строке 102 мы уже начали указывать на то, что при получении щелчка по объекту LINE_PRICE произойдет какое-то событие. В строке 104 мы разместили тот же самый код, который существовал раньше, когда MetaTrader 5 выполнял эту работу за нас. Таким образом, теперь мы полностью заменили то, чем раньше управляла платформа MetaTrader 5, чтобы этим управлял наш собственный код.

Весь остальной код индикатора останется неизменным, поэтому показывать его здесь нет необходимости.


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

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

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.121"
007. #property link "https://www.mql5.com/pt/articles/13265"
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.     static ulong ticket = 0;
064.     
065.     if (Terminal == NULL) return;
066.     switch (trans.type)
067.     {
068.         case TRADE_TRANSACTION_HISTORY_ADD:
069.             EventChartCustom(0, evUpdate_Position, trans.position, 0, "");
070.             ticket = (trans.order != trans.position ? trans.position : 0);
071.             break;
072.         case TRADE_TRANSACTION_REQUEST:
073.             if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
074.             {
075.                 case TRADE_ACTION_DEAL:
076.                     if (ticket > 0) EventChartCustom(0, evUpdate_Position, ticket, 0, "");
077.                     else
078.                     {
079.                         int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order);
080.                         ChartIndicatorAdd(0, 0, handle);
081.                         IndicatorRelease(handle);
082.                     }
083.                     ticket = 0;
084.                     break;
085.                 case TRADE_ACTION_SLTP:
086.                     EventChartCustom(0, evUpdate_Position, request.position, 0, "");
087.                     break;
088.             }
089.             break;
090.     };
091. }
092. //+------------------------------------------------------------------+
093. void OnDeinit(const int reason)
094. {
095.     ulong ul;
096.     
097.     switch (reason)
098.     {
099.         case REASON_REMOVE:
100.         case REASON_INITFAILED:
101.             EventChartCustom(0, evEA_At_ChartTrade, -1, 0, "");
102.             break;
103.     }
104.     if (Terminal != NULL) for (int count = PositionsTotal() - 1; count >= 0; count--)
105.     {
106.         ul = PositionGetTicket(count);
107.         if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue;
108.         ChartIndicatorDelete(0, 0, IntegerToString(ul));
109.     }
110.     delete Orders;
111.     delete Terminal;
112. }
113. //+------------------------------------------------------------------+

Expert Advisor.mq5

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

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

Если вы не знаете, где находится эта настройка, просто посмотрите на изображение ниже:

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

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

Файл Описание
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/13265

Прикрепленные файлы |
Anexo.zip (779.24 KB)
Модульная архитектура индикаторов в MQL5 (Часть 1): Хватит копировать и вставлять — начните писать масштабируемый, переиспользуемый код Модульная архитектура индикаторов в MQL5 (Часть 1): Хватит копировать и вставлять — начните писать масштабируемый, переиспользуемый код
В статье разработан объектно-ориентированный каркас для индикаторов MQL5 путем преобразования примитивного примера в пригодные для постоянного использования модули. В ней формализуется частичный пересчет буфера в OnCalculate, логика переносится в классы, вынесенные в заголовочные файлы (CAppliedPrice, CSma), а также вводятся CSubIndiBase, CIndicatorBase и реестр для централизации требований. В результате получаются переносимые компоненты, изолированные входные параметры и аккуратно организованные буферы при минимуме шаблонного кода, что ускоряет сборку новых индикаторов и упрощает их обслуживание.
Анализ CSV-данных (Часть 5): Потоковая передача CSV-данных в реальном времени из активных сессий MetaTrader 5 Анализ CSV-данных (Часть 5): Потоковая передача CSV-данных в реальном времени из активных сессий MetaTrader 5
В статье описывается фреймворк экспорта данных в реальном времени для MetaTrader 5, построенный на основе слабо связанной трёхуровневой архитектуры. Компонент MQL5 пакетирует записи по барам и тикам с помощью буфера записи и ежедневно выполняет ротацию CSV-файлов; демон Python читает поток дописываемых данных в режиме tail, отображает панель мониторинга в реальном времени и отмечает превышения порогов аномалий. Демонстрационный индикатор показывает точки интеграции, обеспечивая мониторинг в реальном времени, прозрачность и прослеживаемость во время торговых сессий.
Возможности Мастера MQL5, которые вам нужно знать (Часть 95): Использование DSU и DBN в пользовательском классе сигнала Возможности Мастера MQL5, которые вам нужно знать (Часть 95): Использование DSU и DBN в пользовательском классе сигнала
В этой статье мы переключимся на пользовательский класс Мастер MQL5 (MQL5 Wizard), который анализирует сигналы входа. На этот раз наш пользовательский класс называется ‘CSignalDSUDBN’ и построен на сочетании алгоритма Disjoint Set Union (DSU) и сети Deep Belief Network (DBN). Как и в предыдущих статьях этой серии, нашу модель можно протестировать с использованием советников, собранных с помощью Мастера MQL5, которые можно настраивать с помощью различных трейлинг-стопов и классов управления капиталом.
Автоматизация классических рыночных методов в MQL5 (Часть 2): Закон причины и следствия Вайкоффа — ценовые цели по графику "крестики-нолики" Автоматизация классических рыночных методов в MQL5 (Часть 2): Закон причины и следствия Вайкоффа — ценовые цели по графику "крестики-нолики"
В статье создаётся автономный советник на MQL5, реализующий полный цикл Вайкоффа: он распознаёт накопление и распределение с помощью конечного автомата, входит в рынок в точке LPS/LPSY — последней точке поддержки или последней точке предложения — и рассчитывает цели выхода по графику «крестики-нолики» в соответствии с законом причины и следствия Вайкоффа. Подробно рассматриваются размер клетки на основе ATR диапазона, разворот на одну клетку, проверка корректности целевой цены и резервная цель 2R. Читатель получает рабочий код, который можно запускать без внешних зависимостей.