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

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

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

Введение

Всем привет и добро пожаловать в новую статью из серии о том, как создать систему репликации/моделирования.

В предыдущей статье «Симуляция рынка: Position View (XV)» мы наконец дошли до момента, когда удалось достаточно ясно объяснить, как и, главное, почему система репликации/моделирования реализуется именно таким образом. Думаю, многие из вас и не предполагали, что с помощью MQL5 можно сделать нечто подобное, поскольку я не нашел более ранних материалов, где применялся бы именно тот подход, который объяснен в предыдущей статье.

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

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


Ещё несколько шагов

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_NameHLine       m_Info.szPrefixName + "#HLINE"
005. #define def_NameBtnClose    m_Info.szPrefixName + "#CLOSE"
006. #define def_NameBtnMove     m_Info.szPrefixName + "#MOVE"
007. #define def_NameInfoDirect  m_Info.szPrefixName + "#DIRECT"
008. #define def_NameObjLabel    m_Info.szPrefixName + "#PROFIT"
009. #define def_NameBackGround  m_Info.szPrefixName + "#BACKGROUND"
010. //+------------------------------------------------------------------+

                                 .
                                 .
                                 .

049. //+------------------------------------------------------------------+
050.         void UpdateViewPort(const double price)
051.         {
052.             int x, y;
053.             
054.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
055.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290));
056.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
057.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
058.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
059.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
060.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
061.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
062.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
063.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
064.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
065.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
066.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
067.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
068.         }
069. //+------------------------------------------------------------------+
070. inline void CreateLinePrice(void)
071.         {
072.             string szObj;
073.             
074.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
075.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
076.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
077.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
078.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
079.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
080.             macro_LineInFocus(false);
081.             CreateObjectGraphics(szObj = def_NameBackGround, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
082.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
083.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
084.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
085.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width + (m_Info.ev == evMsgClosePositionEA ? 40 : 32));
086.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height + 5);
087.         }
088. //+------------------------------------------------------------------+
089. inline void CreateBoxInfo(const bool bMove)
090.         {
091.             string szObj;
092.             const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0};
093.             
094.             CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
095.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
096.             ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c));
097.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? (m_Info.ev == evMsgCloseTakeProfit ? clrDarkGreen : clrMaroon) : (m_Info.bIsBuy ? clrDarkGreen : clrMaroon)));
098.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15));
099.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
100.         }
101. //+------------------------------------------------------------------+

Фрагмент C_ElementsTrade

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

Прошу заметить, что в строках 66 и 67 мы задаем положение объекта, который будет служить фоном. Здесь есть одна интересная деталь. Вычисление в строке 67 аналогично вычислению в строке 86, где мы задаем высоту создаваемого объекта. Однако в строке 67 мы делим значение на два, чтобы правильно расположить объект на экране.

Значение 10, которое вычитается из координаты X в строке 66, связано с тем, что изображение, используемое в кнопке закрытия, имеет размер 16 × 16 пикселей. Поскольку изображение выровнено по центру, нам нужно сдвинуть его на восемь пикселей влево. Однако, если бы мы ограничились только этим, фоновый объект начинался бы ровно от левого края изображения кнопки. Чтобы оставить небольшой зазор, мы добавляем ещё два пикселя, в результате чего получаем значение 10, которое используется в строке 66 для позиционирования объекта по оси X. Это очень просто и понятно.

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

На этом у нас почти всё готово. Я говорю «почти», потому что при открытии позиции индикатор позиции будет создаваться и размещаться правильно, а его элементы будут отображаться должным образом. Однако у нас всё ещё остаются две проблемы. На самом деле, если вы используете современный компьютер с хорошо настроенной операционной системой и без слишком большого количества запущенных в MetaTrader 5 приложений MQL5, у вас не возникнет никаких проблем, кроме небольшого неудобства. Оно возникает, когда вы нажимаете кнопку удаления тейк-профита или стоп-лосса, а затем решаете снова создать тейк-профит или стоп-лосс. В этом случае фоновый объект закрывает часть объекта перемещения. Решить эту проблему очень просто: достаточно изменить процедуру UpdatePrice, как показано ниже:

149. //+------------------------------------------------------------------+
150. inline void UpdatePrice(const double open, const double price)
151.         {
152.             ObjectsDeleteAll(0, m_Info.szPrefixName);
153.             if (price > 0)
154.             {
155.                 CreateLinePrice();
156.                 CreateButtonClose();
157.             }
158.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
159.             if (price > 0)
160.                 CreateObjectInfoText();
161.             m_Info.open = open;
162.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
163.             if (m_Info.ev != evMsgClosePositionEA)
164.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
165.         }
166. //+------------------------------------------------------------------+

Фрагмент C_ElementsTrade

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

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

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

049. //+------------------------------------------------------------------+
050.         void UpdateViewPort(const double price)
051.         {
052.             int x, y;
053.             
054.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
055.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 260 : 370));
056.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
057.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));

Фрагмент кода C_ElementsTrade

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

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

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


Отображение финансового результата

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property icon "/Images/Market Replay/Icons/Positions.ico"
004. #property description "Indicator for tracking an open position on the server."
005. #property description "This should preferably be used together with an Expert Advisor."
006. #property description "For more details see the same article."
007. #property version   "1.128"
008. #property link "https://www.mql5.com/pt/articles/13391"
009. #property indicator_chart_window
010. #property indicator_plots 0
011. //+------------------------------------------------------------------+
012. #define def_ShortName "Position View"
013. //+------------------------------------------------------------------+
014. #include <Market Replay\Order System\C_ElementsTrade.mqh>
015. #include <Market Replay\Defines.mqh>
016. //+------------------------------------------------------------------+
017. input ulong user00 = 0;        //Для использования в советнике
018. //+------------------------------------------------------------------+
019. struct st00
020. {
021.     ulong   ticket;
022.     string  szShortName,
023.             szSymbol;
024.     double  priceOpen,
025.             var;
026.     char    digits;
027.     bool    bIsBuy;
028. }m_Infos;
029. //+------------------------------------------------------------------+
030. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL;
031. //+------------------------------------------------------------------+
032. bool CheckCatch(ulong ticket)
033. {
034.     double vv, vs;
035.     
036.     ZeroMemory(m_Infos);
037.     m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket);
038.     if (!PositionSelectByTicket(m_Infos.ticket)) return false;
039.     if (ObjectFind(0, m_Infos.szShortName) >= 0)
040.     {
041.         m_Infos.ticket = 0;
042.         return false;
043.     }
044.     m_Infos.szSymbol = PositionGetString(POSITION_SYMBOL);
045.     m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
046.     vs = SymbolInfoDouble(PositionGetString(POSITION_SYMBOL), SYMBOL_TRADE_TICK_SIZE);
047.     vv = SymbolInfoDouble(PositionGetString(POSITION_SYMBOL), SYMBOL_TRADE_TICK_VALUE);
048.     m_Infos.var = vs / vv;
049.     IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
050.     EventChartCustom(0, evUpdate_Position, ticket, 0, "");
051.             
052.     return true;
053. }
054. //+------------------------------------------------------------------+
055. inline void ProfitNow(void)
056. {
057.     double ask, bid;
058.     
059.     ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK);
060.     bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID);    
061.     if (Open != NULL)
062.         (*Open).ViewValue((m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask));
063. }
064. //+------------------------------------------------------------------+
065. int OnInit()
066. {
067.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
068.     if (!CheckCatch(user00))
069.     {
070.         ChartIndicatorDelete(0, 0, def_ShortName);
071.         return INIT_FAILED;
072.     }
073. 
074.     return INIT_SUCCEEDED;
075. }
076. //+------------------------------------------------------------------+
077. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
078. {
079.     ProfitNow();
080.     
081.     return rates_total;
082. }
083. //+------------------------------------------------------------------+
084. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
085. {
086.     double volume;
087.     
088.     if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam);
089.     if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam);
090.     if (Stop != NULL)    (*Stop).DispatchMessage(id, lparam, dparam, sparam);
091.     switch (id)
092.     {
093.         case CHARTEVENT_CUSTOM + evUpdate_Position:
094.             if (lparam != m_Infos.ticket) break;
095.             if (!PositionSelectByTicket(m_Infos.ticket))
096.             {
097.                 ChartIndicatorDelete(0, 0, m_Infos.szShortName);
098.                 return;
099.             };
100.             if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, m_Infos.digits, StringFormat("%I64u : Position opening price.", m_Infos.ticket), m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY));
101.             if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, m_Infos.digits, StringFormat("%I64u : Take Profit price.", m_Infos.ticket), m_Infos.bIsBuy);
102.             if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, m_Infos.digits, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), m_Infos.bIsBuy);
103.             volume = PositionGetDouble(POSITION_VOLUME);
104.             (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var);
105.             (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP), volume, m_Infos.var);
106.             (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL), volume, m_Infos.var);
107.             ProfitNow();
108.             break;
109.     }
110.     ChartRedraw();
111. };
112. //+------------------------------------------------------------------+
113. void OnDeinit(const int reason)
114. {
115.     delete Open;
116.     delete Take;
117.     delete Stop;
118. }
119. //+------------------------------------------------------------------+

Основной код

Я кратко объясню, что изменилось. Обратите внимание, что в строке 25 у нас появилась новая переменная. Она инициализируется в строке 48 данными, полученными в строках 46 и 47. Мы вычисляем это значение здесь, чтобы не пересчитывать его постоянно. Поскольку оно не изменится, пока индикатор позиции остаётся на графике, нам не придётся пересчитывать его. Это значение используется вместе с другим значением, определённым в строке 103. Там мы получаем объём открытой позиции. Это связано с тем, что объём позиции изменится только тогда, когда советник отправит событие для обновления индикатора позиции. В некоторых из этих случаев потребуется обновить объём открытой позиции.

Теперь обратите внимание на строки 104–106, где используются эти два новых значения. Поэтому нам следует сосредоточить внимание на процедуре UpdatePrice, которую тоже придётся изменить. После завершения изменений в основном коде мы можем вернуться к классу C_ElementsTrade.

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

26. class C_ElementsTrade : private C_Mouse
27. {
28.     private    :
29. //+------------------------------------------------------------------+
30.         struct st00
31.         {
32.             struct st_01
33.             {
34.                 uchar   Width,
35.                         Height,
36.                         digits;
37.             }Text;
38.             ulong       ticket;
39.             string      szPrefixName,
40.                         szDescr;
41.             EnumEvents  ev;
42.             double      price,
43.                         open,
44.                         volume,
45.                         tickValue;
46.             bool        bClick,
47.                         bIsBuy;
48.             char        weight;
49.             color       _color;
50.         }m_Info;
51. //+------------------------------------------------------------------+
52.         void UpdateViewPort(const double price)

Фрагмент кода C_ElementsTrade

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

151. //+------------------------------------------------------------------+
152. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0)
153.         {
154.             ObjectsDeleteAll(0, m_Info.szPrefixName);
155.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
156.             m_Info.tickValue = (var > 0 ? var : m_Info.tickValue);
157.             if (price > 0)
158.             {
159.                 CreateLinePrice();
160.                 CreateButtonClose();
161.                 CreateObjectInfoText();
162.             }
163.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
164.             m_Info.open = open;
165.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
166.             if (m_Info.ev != evMsgClosePositionEA)
167.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
168.         }
169. //+------------------------------------------------------------------+

Фрагмент кода C_ElementsTrade

А теперь обратите внимание. Значения двух последних аргументов по умолчанию равны нулю. Почему? Потому что эта процедура вызывается из двух мест. Первый — это основной код, из которого мы будем получать ненулевые значения. Второй источник вызова — процедура DispatchMessage класса C_ElementsTrade, из которой эти значения приходят как нулевые. Таким образом, мы избегаем риска присвоить неверное значение. Хотя может показаться заманчивым передать в качестве аргументов значения, которые мы только что объявили в классе, я предпочитаю сразу задать в объявлении процедуры значение по умолчанию — НОЛЬ — для этих аргументов.

После этого обратите внимание на строки 155 и 156, где мы проверяем, равны ли значения аргументов нулю или являются положительными. Если они больше нуля, мы, в зависимости от ситуации, инициализируем или обновляем переменные, объявленные в строках 44 и 45.

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

169. //+------------------------------------------------------------------+
170.         void ViewValue(const double profit)
171.         {
172.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, StringFormat("%." + (string)m_Info.Text.digits + "f", (profit < 0 ? -(profit) : profit)));
173.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
174.             ChartRedraw();
175.         }
176. //+------------------------------------------------------------------+

Фрагмент кода C_ElementsTrade

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

169. //+------------------------------------------------------------------+
170.         void ViewValue(const double profit)
171.         {
172.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume));
173.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
174.             ChartRedraw();
175.         }
176. //+------------------------------------------------------------------+

Фрагмент кода C_ElementsTrade

В принципе, это решает задачу отображения финансовых результатов. Почему я говорю «в принципе»? Потому что теперь у нас возникает проблема с местом. Отображаемые значения могут превышать доступную ширину объекта OBJ_EDIT. Когда это произойдёт, информация будет обрезана, и отображаемые данные могут оказаться совершенно непонятными для трейдера. Тем не менее, для тестирования мы уже можем видеть финансовые результаты в индикаторе позиции; для этого нужно лишь скомпилировать код.

Отлично. Теперь у нас появилась новая проблема, требующая более тщательного анализа. Чтобы ясно объяснить, что нам нужно учитывать, давайте рассмотрим полный код класса C_ElementsTrade, приведённый ниже, со всеми изменениями, внесёнными к настоящему моменту:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_NameHLine       m_Info.szPrefixName + "#HLINE"
005. #define def_NameBtnClose    m_Info.szPrefixName + "#CLOSE"
006. #define def_NameBtnMove     m_Info.szPrefixName + "#MOVE"
007. #define def_NameInfoDirect  m_Info.szPrefixName + "#DIRECT"
008. #define def_NameObjLabel    m_Info.szPrefixName + "#PROFIT"
009. #define def_NameBackGround  m_Info.szPrefixName + "#BACKGROUND"
010. //+------------------------------------------------------------------+
011. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
012. //+------------------------------------------------------------------+
013. #define def_PathBtns "Images\\Market Replay\\Orders\\"
014. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
015. #resource "\\" + def_Btn_Close;
016. //+------------------------------------------------------------------+
017. #include "..\Auxiliar\C_Mouse.mqh"
018. //+------------------------------------------------------------------+
019. #ifdef def_FontName
020.     "Why are you trying to do this?"
021. #else 
022.     #define def_FontName "Lucida Console"
023.     #define def_FontSize 10
024. #endif 
025. //+------------------------------------------------------------------+
026. class C_ElementsTrade : private C_Mouse
027. {
028.     private    :
029. //+------------------------------------------------------------------+
030.         struct st00
031.         {
032.             struct st_01
033.             {
034.                 uchar   Width,
035.                         Height,
036.                         digits;
037.             }Text;
038.             ulong       ticket;
039.             string      szPrefixName,
040.                         szDescr;
041.             EnumEvents  ev;
042.             double      price,
043.                         open,
044.                         volume,
045.                         var;
046.             bool        bClick,
047.                         bIsBuy;
048.             char        weight;
049.             color       _color;
050.         }m_Info;
051. //+------------------------------------------------------------------+
052.         void UpdateViewPort(const double price)
053.         {
054.             int x, y;
055.             
056.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
057.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 260 : 370));
058.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
059.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
060.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
061.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
062.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
063.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
064.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
065.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
066.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
067.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
068.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
069.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
070.         }
071. //+------------------------------------------------------------------+
072. inline void CreateLinePrice(void)
073.         {
074.             string szObj;
075.             
076.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
077.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
078.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
079.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
080.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
081.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
082.             macro_LineInFocus(false);
083.             CreateObjectGraphics(szObj = def_NameBackGround, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
084.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
085.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
086.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
087.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width + (m_Info.ev == evMsgClosePositionEA ? 40 : 32));
088.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height + 5);
089.         }
090. //+------------------------------------------------------------------+
091. inline void CreateBoxInfo(const bool bMove)
092.         {
093.             string szObj;
094.             const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0};
095.             
096.             CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
097.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
098.             ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c));
099.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? (m_Info.ev == evMsgCloseTakeProfit ? clrDarkGreen : clrMaroon) : (m_Info.bIsBuy ? clrDarkGreen : clrMaroon)));
100.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15));
101.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
102.         }
103. //+------------------------------------------------------------------+
104. inline void CreateObjectInfoText(void)
105.         {
106.             string szObj;
107.             
108.             CreateObjectGraphics(szObj = def_NameObjLabel, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault));
109.             ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName);
110.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize);
111.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack);
112.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, m_Info._color);
113.             ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER);
114.             ObjectSetInteger(0, szObj, OBJPROP_READONLY, true);
115.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height);
116.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width);
117.         }
118. //+------------------------------------------------------------------+
119. inline void CreateButtonClose(void)
120.         {
121.             string szObj;
122.             
123.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
124.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
125.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
126.         }
127. //+------------------------------------------------------------------+
128.     public    :
129. //+------------------------------------------------------------------+
130.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, string szDescr = "\n", const bool IsBuy = true)
131.             :C_Mouse(0, "")
132.         {
133.             uint w, h;
134.                 
135.             ZeroMemory(m_Info);
136.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
137.             m_Info._color = _color;
138.             m_Info.szDescr = szDescr;
139.             m_Info.bIsBuy = IsBuy;
140.             m_Info.Text.digits = digits;    
141.             TextSetFont(def_FontName, def_FontSize * -10);
142.             TextGetSize(StringFormat("%." + (string)digits + "f", 8888.88888), w, h);
143.             m_Info.Text.Width = (uchar) w + 4;
144.             m_Info.Text.Height = (uchar) h + 4;
145.         }
146. //+------------------------------------------------------------------+
147.         ~C_ElementsTrade()
148.         {
149.             ObjectsDeleteAll(0, m_Info.szPrefixName);
150.         }
151. //+------------------------------------------------------------------+
152. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0)
153.         {
154.             ObjectsDeleteAll(0, m_Info.szPrefixName);
155.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
156.             m_Info.var = (var > 0 ? var : m_Info.var);
157.             if (price > 0)
158.             {
159.                 CreateLinePrice();
160.                 CreateButtonClose();
161.                 CreateObjectInfoText();
162.             }
163.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
164.             m_Info.open = open;
165.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
166.             if (m_Info.ev != evMsgClosePositionEA)
167.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
168.         }
169. //+------------------------------------------------------------------+
170.         void ViewValue(const double profit)
171.         {
172.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume));
173.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
174.             ChartRedraw();
175.         }
176. //+------------------------------------------------------------------+
177.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
178.         {
179.             string sz0;
180.             long _lparam = lparam;
181.             double _dparam = dparam;
182.             
183.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
184.             switch (id)
185.             {
186.                 case (CHARTEVENT_KEYDOWN):
187.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
188.                     _lparam = (long) m_Info.ticket;
189.                     _dparam = 0;
190.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, "");
191.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
192.                     if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev))
193.                         UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price);
194.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
195.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
196.                     m_Info.bClick = false;
197.                 case CHARTEVENT_CHART_CHANGE:
198.                     UpdateViewPort(m_Info.price);
199.                     if (m_Info.ev != evMsgClosePositionEA)
200.                         ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
201.                     break;
202.                 case CHARTEVENT_OBJECT_CLICK:
203.                     sz0 = GetPositionsMouse().szObjNameClick;
204.                     if (m_Info.bClick)
205.                     {
206.                         if (sz0 == def_NameBtnMove)
207.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
208.                         if (sz0 == def_NameBtnClose)
209.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL));
210.                     }
211.                     m_Info.bClick = false;
212.                     break;
213.                 case CHARTEVENT_MOUSE_MOVE:
214.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
215.                     if (m_Info.weight > 1)
216.                     {
217.                         UpdateViewPort(_dparam = GetPositionsMouse().Position.Price);
218.                         if (m_Info.ev != evMsgClosePositionEA)
219.                             ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam);
220.                         if (m_Info.bClick)
221.                         {
222.                             if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss))
223.                                 EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
224.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
225.                         }
226.                     }
227.                     break;
228.             }
229.         }
230. //+------------------------------------------------------------------+
231. };
232. //+------------------------------------------------------------------+
233. #undef macro_LineInFocus
234. //+------------------------------------------------------------------+
235. #undef def_Btn_Close
236. #undef def_PathBtns
237. #undef def_FontName
238. #undef def_FontSize
239. //+------------------------------------------------------------------+
240. #undef def_NameBackGround
241. #undef def_NameObjLabel
242. #undef def_NameInfoDirect
243. #undef def_NameBtnMove
244. #undef def_NameBtnClose
245. #undef def_NameHLine
246. //+------------------------------------------------------------------+

C_ElementTrade

Проблема связана именно с шириной объекта OBJ_EDIT. Она задаётся в строке 116, хотя значение, присваиваемое свойству OBJPROP_XSIZE, получается в строке 142, внутри конструктора класса. Эти моменты не являются проблемой. На самом деле, они являются частью решения. Однако в строке 57 действительно есть небольшая проблема. Даже если мы настроим ширину объекта OBJ_EDIT, объекты индикатора могут перекрываться. Позже мы увидим, как этого избежать, поскольку для этого потребуется внести несколько изменений. Пока с этим можно временно смириться. Тем не менее, чтобы наложение не затрудняло использование индикатора, нам также придётся скорректировать значения в строке 57.

Кроме того, мы добавим возможность, чтобы трейдер мог переключаться между доступными режимами отображения. Для этого нам нужно будет внести несколько изменений в приведённый выше код. Чтобы лучше отделить это объяснение, перейдём к новой теме.


Небольшие изменения для получения лучших результатов

Самое любопытное во всём, что мы будем делать, заключается в том, что достаточно было бы изменить строку 142, чтобы увеличить доступное пространство объекта OBJ_EDIT и тем самым решить все проблемы с гораздо меньшими усилиями. Однако это было бы не особенно интересно. Я хочу показать вам, как сделать так, чтобы ширина OBJ_EDIT динамически подстраивалась под размер, лучше подходящий для отображения значений на графике. Это не самая сложная часть. Настоящая сложность заключается в том, чтобы не допустить, чтобы объекты на графике сталкивались или перекрывались.

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

051. //+------------------------------------------------------------------+
052.         void UpdateViewPort(const double price)
053.         {
054.             int x, y;
055.             
056.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
057.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 260 : 370));
058.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
059.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
060.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
061.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
062.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
063.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
064.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
065.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
066.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
067.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
068.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
069.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
070.         }
071. //+------------------------------------------------------------------+
072. inline void CreateLinePrice(void)
073.         {
074.             string szObj;
075.             
076.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
077.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
078.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
079.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
080.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
081.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
082.             macro_LineInFocus(false);
083.             CreateObjectGraphics(szObj = def_NameBackGround, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
084.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
085.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
086.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
087.         }
088. //+------------------------------------------------------------------+

                   .
                   .
                   .

101. //+------------------------------------------------------------------+
102. inline void CreateObjectInfoText(void)
103.         {
104.             string szObj;
105.             
106.             CreateObjectGraphics(szObj = def_NameObjLabel, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault));
107.             ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName);
108.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize);
109.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack);
110.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, m_Info._color);
111.             ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER);
112.             ObjectSetInteger(0, szObj, OBJPROP_READONLY, true);
113.         }
114. //+------------------------------------------------------------------+

                   .
                   .
                   .
                                   
123. //+------------------------------------------------------------------+
124. inline void AdjustDinamic(const string szTxt)
125.         {
126.             uint w, h;
127. 
128.             TextSetFont(def_FontName, def_FontSize * -10);
129.             TextGetSize(szTxt, w, h);
130.             m_Info.Text.Height = (uchar) h + 4;
131.             m_Info.Text.Width = (uchar) w + 4;
132.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XSIZE, m_Info.Text.Width);
133.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YSIZE, m_Info.Text.Height);            
134.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + (m_Info.ev == evMsgClosePositionEA ? 40 : 32));
135.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5);
136.         }
137. //+------------------------------------------------------------------+
138.     public    :
139. //+------------------------------------------------------------------+
140.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, string szDescr = "\n", const bool IsBuy = true)
141.             :C_Mouse(0, "")
142.         {
143.             ZeroMemory(m_Info);
144.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
145.             m_Info._color = _color;
146.             m_Info.szDescr = szDescr;
147.             m_Info.bIsBuy = IsBuy;
148.             m_Info.Text.digits = digits;    
149.         }
150. //+------------------------------------------------------------------+
 
                   .
                   .
                   .
                                   
173. //+------------------------------------------------------------------+
174.         void ViewValue(const double profit)
175.         {
176.             string szTxt;
177.             
178.             szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume);
179.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
180.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
181.             AdjustDinamic(szTxt);
182.             ChartRedraw();
183.         }
184. //+------------------------------------------------------------------+

Фрагмент кода C_ElementsTrade

Вы можете сравнить этот фрагмент с полным кодом из предыдущего раздела и легко найти изменения. Следует отметить, что в строке 124 появилась новая процедура, цель которой — сделать компоновку полностью динамической, чтобы и OBJ_EDIT, и OBJ_BITMAP_LABEL могли подстраиваться под ширину отображаемого текста.

Обратите также внимание, что мы немного изменили процедуру ViewValue, чтобы динамически подстраивать ширину OBJ_EDIT. Тем не менее, этот подход можно улучшить, поскольку в строке 181 мы обновляем эту ширину при каждом вызове, даже когда ширина текста не меняется. Чтобы строка 181 не вызывала процедуру AdjustDinamic при каждом вызове, достаточно сохранить ширину отображаемого текста. Если ширина не изменилась, нет смысла снова вызывать AdjustDinamic.

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

173. //+------------------------------------------------------------------+
174.         void ViewValue(const double profit)
175.         {
176.             static int sizeTxt = 0;
177.             string szTxt;
178.             
179.             szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume);
180.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
181.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
182.             if (StringLen(szTxt) != sizeTxt)
183.             {
184.                 AdjustDinamic(szTxt);
185.                 sizeTxt = StringLen(szTxt);
186.             }
187.             ChartRedraw();
188.         }
189. //+------------------------------------------------------------------+

Фрагмент кода C_ElementsTrade

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

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

025. //+------------------------------------------------------------------+
026. class C_ElementsTrade : private C_Mouse
027. {
028.     private    :
029. //+------------------------------------------------------------------+
030.         struct st00
031.         {
032.             struct st_01
033.             {
034.                 uchar   Width,
035.                         Height,
036.                         digits;
037.             }Text;
038.             ulong       ticket;
039.             string      szPrefixName,
040.                         szDescr;
041.             EnumEvents  ev;
042.             double      price,
043.                         open,
044.                         volume,
045.                         var;
046.             bool        bClick,
047.                         bIsBuy;
048.             char        weight;
049.             color       _color;
050.             int         sizeText;
051.         }m_Info;
052. //+------------------------------------------------------------------+

                   .
                   .
                   .
                                   
156. //+------------------------------------------------------------------+
157. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0)
158.         {
159.             m_Info.sizeText = 0;
160.             ObjectsDeleteAll(0, m_Info.szPrefixName);
161.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
162.             m_Info.var = (var > 0 ? var : m_Info.var);
163.             if (price > 0)
164.             {
165.                 CreateLinePrice();
166.                 CreateButtonClose();
167.                 CreateObjectInfoText();
168.             }
169.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
170.             m_Info.open = open;
171.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
172.             if (m_Info.ev != evMsgClosePositionEA)
173.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
174.         }
175. //+------------------------------------------------------------------+
176.         void ViewValue(const double profit)
177.         {
178.             string szTxt;
179.             
180.             szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume);
181.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
182.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
183.             if (StringLen(szTxt) != m_Info.sizeText)
184.             {
185.                 AdjustDinamic(szTxt);
186.                 m_Info.sizeText = StringLen(szTxt);
187.             }
188.             ChartRedraw();
189.         }
190. //+------------------------------------------------------------------+

Фрагмент кода C_ElementsTrade

Обратите внимание, что в строке 50 мы добавили новую переменную. Она будет выполнять ту же функцию, что и статическая переменная, но у каждого из трёх сегментов будет своя область памяти для этой переменной. Таким образом, каждый сегмент сможет сохранять своё собственное значение. В этот фрагмент я также включил процедуру UpdatePrice, чтобы показать, где сбрасывается переменная, объявленная в строке 50. Это происходит в строке 159. Почему? Потому что при перемещении линий тейк-профита или стоп-лосса ширина объекта OBJ_EDIT может резко измениться. Чтобы гарантировать, что подходящая ширина будет вычисляться всегда, мы здесь сбрасываем значение переменной.

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

Необходимый код приведён в следующем фрагменте:

052. //+------------------------------------------------------------------+
053.         short UpdateViewPort(const double price, short size = 0, uint ui = 0)
054.         {
055.             static short _SizeControls;
056.             static short _Width;
057.             uint x, y;
058.             
059.             if (size > 0)
060.             {
061.                 size += (short)(ui + 8);
062.                 _SizeControls = (_SizeControls > size ? _SizeControls : size);
063.                 size = (short)(_SizeControls - ui - 12);
064.                 _Width = (_Width > size ? _Width : size);
065.             }else
066.             {
067.                 ChartTimePriceToXY(0, 0, 0, price, x, y);            
068.                 x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2)));
069.                 ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
070.                 ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
071.                 ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
072.                 ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
073.                 ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
074.                 ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
075.                 ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + _Width + 20);
076.                 ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
077.                 ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + _Width + 20);
078.                 ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
079.                 ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
080.                 ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
081.             }
082.             return _Width;
083.         }
084. //+------------------------------------------------------------------+

                                   .
                                   .
                                   .
                                   
136. //+------------------------------------------------------------------+
137. inline void AdjustDinamic(const string szTxt)
138.         {
139.             uint w, h;
140. 
141.             TextSetFont(def_FontName, def_FontSize * -10);
142.             TextGetSize(szTxt, w, h);
143.             m_Info.Text.Height = (uchar) h + 4;
144.             m_Info.Text.Width = (uchar) w + 4;
145.             m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32);
146.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XSIZE, m_Info.Text.Width);
147.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YSIZE, m_Info.Text.Height);
148.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? 8 : 0));
149.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5);
150.         }
151. //+------------------------------------------------------------------+

Фрагмент кода C_ElementsTrade

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

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

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

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


Заключительные замечания

В этой статье мы внесли необходимые изменения, чтобы индикатор позиции мог отображать финансовый результат. Таким образом, трейдер сможет составить представление о финансовом результате, который он получит по открытой позиции. Хотя некоторые трейдеры предпочитают обращаться к информации другого типа, я считаю, что статья достигла своей цели и познакомила вас с концепцией, о которой многие не знают даже после длительного использования 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/13391

Прикрепленные файлы |
Anexo.zip (779.24 KB)
Нейросети в трейдинге: Двухуровневая адаптация торговой политики (Окончание) Нейросети в трейдинге: Двухуровневая адаптация торговой политики (Окончание)
Статья завершает адаптацию D2Skill к алгоритмической торговле и собирает полный учебный контур. Разобраны обучение Forecast, базового Актёра и Критиков, формирование Task и Step Skills и калибровка Utility на парных траекториях. Для каждого шага приведены настройки, кодовые точки интеграции и контрольные метрики. Итоговое OOS‑сравнение одной политики с памятью и без неё показывает влияние двухуровневой памяти на результат и риск.
Интеграция MQL5 с пакетами обработки данных (Часть 9): Адаптивная оценка волатильности на основе энтропии Интеграция MQL5 с пакетами обработки данных (Часть 9): Адаптивная оценка волатильности на основе энтропии
В этой статье представлен сквозной пайплайн: сбор данных MetaTrader 5, создание признаков на основе энтропии, волатильности и тренда, обучение классификатора PyTorch и предоставление прогнозов через API Flask. Советник MQL5 на каждом тике отправляет скользящее окно цен, получает вероятность и режим волатильности, а затем адаптивно рассчитывает размер позиции и расстояние до стоп-лосса. В результате получается ясная схема интеграции инференса машинного обучения с MetaTrader 5.
От начального до среднего уровня: Очереди, списки и деревья (VI) От начального до среднего уровня: Очереди, списки и деревья (VI)
В этой статье мы вернёмся к реализации дерева. Теперь, когда мы знакомы с основными принципами работы конструкторов и деструкторов, мы наконец сможем исправить код, представленный в прошлой статье. Приготовьтесь к настоящему приключению в программировании на MQL5.
Возможности Мастера MQL5, которые вам нужно знать (Часть 93): Использование суффиксного автомата и автокодировщика в пользовательском классе управления капиталом Возможности Мастера MQL5, которые вам нужно знать (Часть 93): Использование суффиксного автомата и автокодировщика в пользовательском классе управления капиталом
В этой статье мы переходим к пользовательской реализации класса Мастера MQL5 для управления капиталом. Пользовательский класс CMoneySuffixAE создается путем объединения алгоритма суффиксного автомата и нейронной сети автокодировщика. Как и ранее, это решение можно тестировать с советниками, собранными Мастером MQL5 и настраиваемыми с различными сигналами входа и вариантами трейлинг-стопа.