Моделирование рынка: В единстве — сила (III)
Введение
Всем привет и добро пожаловать в новую статью из серии, посвящённой созданию системы репликации/моделирования.
В предыдущей статье «Моделирование рынка: в единстве сила (II)» я показал, как мы изменим работу наших приложений, чтобы имитировать взаимодействие с торговым сервером. В той статье мы реализовали новый класс: C_InServer. Его назначение как раз и состоит в том, чтобы позволить нам легко моделировать работу наших приложений так, чтобы было совершенно незаметно, кто отвечает на наши запросы: реальный сервер или симулированный сервер. Я знаю, что для многих из вас всё это может показаться довольно сложным, тогда как для других это будет лишь ещё одним этапом в том, что мы реализуем.
На самом деле с помощью класса C_InServer мы сможем очень легко адаптировать наши приложения и получить желаемый результат. Как вы, наверное, уже догадываетесь, нам не придётся реализовывать ничего нового ни в советнике, ни в индикаторе позиции. По крайней мере, на этом первом этапе потребуется лишь очень тонко изменить код. Итак, без лишних предисловий перейдём к тому, что нас интересует.
Решим две задачи сразу
Ещё до завершения реализации советника и индикатора позиции для использования в системе репликации/моделирования мы уже можем сделать код обоих полностью работоспособным и подготовить его для этой системы. Это возможно благодаря классу C_InServer. К сожалению, я забыл показать одну функцию, которая нам тоже понадобится, поскольку советник может использовать символ, связанный со счётом типа HEDGING. Как вы уже знаете, на счёте этого типа мы можем держать открытыми более одной позиции. Поэтому нам нужно добавить в класс C_InServer фрагмент, приведённый ниже.
127. //+------------------------------------------------------------------+ 128. inline const ulong _PositionGetTicket(int arg) 129. { 130. #define macro_ERROR { Print("Error in accessing the database..."); return 0; } 131. 132. bool bRet; 133. 134. if (!m_IsReplay) 135. return PositionGetTicket(arg); 136. 137. ZeroMemory(m_Info); 138. if (!ExecRequestOfData("SELECT * FROM tb_Replay AS tb WHERE tb.history = 0;")) macro_ERROR 139. for (int c = 0; (bRet = GetRegisterOfRequest(m_Info)) && (c < arg); c++); 140. 141. return (bRet ? m_Info.ticket : 0); 142. 143. #undef macro_ERROR 144. } 145. //+------------------------------------------------------------------+
Фрагмент класса C_InServer
Я не буду здесь повторять весь код, потому что в этом нет смысла. Достаточно добавить показанный фрагмент, и мы сможем приступить к работе с советником и индикатором позиции, чтобы посмотреть, как в итоге будет выглядеть код обоих. Начнём с кода советника, приведённого ниже.
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.135" 007. #property link "https://www.mql5.com/pt/articles/13555" 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; //Выбор типа контракта для ордера 015. //+------------------------------------------------------------------+ 016. C_Orders *Orders = NULL; 017. C_Terminal *Terminal = NULL; 018. //+------------------------------------------------------------------+ 019. int OnInit() 020. { 021. Orders = new C_Orders(0xC0DEDAFE78514269); 022. 023. return INIT_SUCCEEDED; 024. } 025. //+------------------------------------------------------------------+ 026. void OnTick() {} 027. //+------------------------------------------------------------------+ 028. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 029. { 030. int handle; 031. ulong ul; 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. Terminal = new C_Terminal(0, 0, user00); 041. for (int count = (*Orders)._PositionsTotal() - 1; count >= 0; count--) 042. { 043. ul = (*Orders)._PositionGetTicket(count); 044. if ((*Orders)._PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) 045. { 046. ChartIndicatorDelete(0, 0, IntegerToString(ul)); 047. continue; 048. } 049. handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul); 050. ChartIndicatorAdd(0, 0, handle); 051. IndicatorRelease(handle); 052. } 053. } 054. case CHARTEVENT_CUSTOM + evChartTrade_At_EA: 055. EventChartCustom(0, evEA_At_ChartTrade, user00, 0, ""); 056. break; 057. } 058. } 059. //+------------------------------------------------------------------+ 060. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) 061. { 062. if (Terminal == NULL) return; 063. static ulong ticket = 0; 064. switch (trans.type) 065. { 066. case TRADE_TRANSACTION_HISTORY_ADD: 067. EventChartCustom(0, evUpdate_Position, trans.position, 0, ""); 068. ticket = (trans.order != trans.position ? trans.position : 0); 069. break; 070. case TRADE_TRANSACTION_REQUEST: 071. if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action) 072. { 073. case TRADE_ACTION_DEAL: 074. if (ticket > 0) EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 075. else 076. { 077. int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order); 078. ChartIndicatorAdd(0, 0, handle); 079. IndicatorRelease(handle); 080. } 081. ticket = 0; 082. break; 083. case TRADE_ACTION_SLTP: 084. EventChartCustom(0, evUpdate_Position, request.position, 0, ""); 085. break; 086. } 087. break; 088. }; 089. } 090. //+------------------------------------------------------------------+ 091. void OnDeinit(const int reason) 092. { 093. ulong ul; 094. 095. switch (reason) 096. { 097. case REASON_REMOVE: 098. case REASON_INITFAILED: 099. EventChartCustom(0, evEA_At_ChartTrade, -1, 0, ""); 100. break; 101. } 102. if (Terminal != NULL) 103. { 104. for (int count = (*Orders)._PositionsTotal() - 1; count >= 0; count--) 105. { 106. ul = (*Orders)._PositionGetTicket(count); 107. if ((*Orders)._PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue; 108. ChartIndicatorDelete(0, 0, IntegerToString(ul)); 109. } 110. } 111. delete Orders; 112. delete Terminal; 113. } 114. //+------------------------------------------------------------------+
Советник
Обратите внимание, что код остается таким же, как и раньше, и не требует дополнительных изменений для достижения поставленной цели. То же самое относится и к индикатору позиции, код которого приведен ниже.
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.135" 008. #property link "https://www.mql5.com/pt/articles/13555" 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\Order System\C_InServer.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. tickSize; 027. char digits; 028. bool bIsBuy; 029. }m_Infos; 030. //+------------------------------------------------------------------+ 031. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL; 032. C_InServer *Order = NULL; 033. //+------------------------------------------------------------------+ 034. bool CheckCatch(ulong ticket) 035. { 036. double vv; 037. 038. ZeroMemory(m_Infos); 039. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 040. if (!(*Order)._PositionSelectByTicket(m_Infos.ticket)) return false; 041. if (ChartWindowFind(0, m_Infos.szShortName) >= 0) 042. { 043. m_Infos.ticket = 0; 044. return false; 045. } 046. m_Infos.szSymbol = (*Order)._PositionGetString(POSITION_SYMBOL); 047. m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 048. m_Infos.tickSize = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 049. vv = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 050. m_Infos.var = m_Infos.tickSize / vv; 051. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 052. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 053. 054. return true; 055. } 056. //+------------------------------------------------------------------+ 057. inline void ProfitNow(void) 058. { 059. double ask, bid, value; 060. 061. ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK); 062. bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID); 063. if (Open != NULL) 064. { 065. (*Open).ViewValue(value = (m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask)); 066. (*Take).ViewValue(value, false); 067. (*Stop).ViewValue(value, false); 068. } 069. } 070. //+------------------------------------------------------------------+ 071. int OnInit() 072. { 073. Order = new C_InServer(); 074. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 075. if (!CheckCatch(user00)) 076. { 077. ChartIndicatorDelete(0, 0, def_ShortName); 078. return INIT_FAILED; 079. } 080. 081. return INIT_SUCCEEDED; 082. } 083. //+------------------------------------------------------------------+ 084. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 085. { 086. ProfitNow(); 087. 088. return rates_total; 089. } 090. //+------------------------------------------------------------------+ 091. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 092. { 093. double volume; 094. 095. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 096. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 097. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 098. switch (id) 099. { 100. case CHARTEVENT_CUSTOM + evUpdate_Position: 101. if (lparam != m_Infos.ticket) break; 102. if (!(*Order)._PositionSelectByTicket(m_Infos.ticket)) 103. { 104. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 105. return; 106. }; 107. if (Open == NULL) Open = new C_ElementsTrade( 108. m_Infos.ticket, 109. m_Infos.szSymbol, 110. evMsgClosePositionEA, 111. clrRoyalBlue, 112. m_Infos.digits, 113. m_Infos.tickSize, 114. StringFormat("%I64u : Position opening price.", m_Infos.ticket), 115. m_Infos.bIsBuy = ((*Order)._PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) 116. ); 117. if (Take == NULL) Take = new C_ElementsTrade( 118. m_Infos.ticket, 119. m_Infos.szSymbol, 120. evMsgCloseTakeProfit, 121. clrForestGreen, 122. m_Infos.digits, 123. m_Infos.tickSize, 124. StringFormat("%I64u : Take Profit price.", m_Infos.ticket), 125. m_Infos.bIsBuy 126. ); 127. if (Stop == NULL) Stop = new C_ElementsTrade( 128. m_Infos.ticket, 129. m_Infos.szSymbol, 130. evMsgCloseStopLoss, 131. clrFireBrick, 132. m_Infos.digits, 133. m_Infos.tickSize, 134. StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), 135. m_Infos.bIsBuy 136. ); 137. volume = (*Order)._PositionGetDouble(POSITION_VOLUME); 138. (*Open).UpdatePrice(0, m_Infos.priceOpen = (*Order)._PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var); 139. (*Take).UpdatePrice(m_Infos.priceOpen, (*Order)._PositionGetDouble(POSITION_TP), volume, m_Infos.var, (*Order)._PositionGetDouble(POSITION_SL)); 140. (*Stop).UpdatePrice(m_Infos.priceOpen, (*Order)._PositionGetDouble(POSITION_SL), volume, m_Infos.var, (*Order)._PositionGetDouble(POSITION_TP)); 141. ProfitNow(); 142. break; 143. } 144. ChartRedraw(); 145. }; 146. //+------------------------------------------------------------------+ 147. void OnDeinit(const int reason) 148. { 149. delete Order; 150. delete Open; 151. delete Take; 152. delete Stop; 153. } 154. //+------------------------------------------------------------------+
Индикатор позиции
Таким образом, у нас уже есть корректно работающая основа системы. Что касается индикатора позиции, его можно считать полностью завершенным: его уже можно использовать как в симуляторе, так и на реальном или демо-счете. Что касается советника, то пока нам не потребуется изменять его основной код. Тем не менее нам нужно завершить реализацию класса C_Orders, поскольку именно там будет происходить то самое «волшебство», которое позволит нам по-настоящему смоделировать торговый сервер. Пока что нам удалось смоделировать лишь часть того, что нам нужно.
Пока завершена только часть, которая обрабатывает TRADEACTIONSLTP, но мы пока не можем полноценно её протестировать поскольку для проверки такого типа запроса нам нужна открытая позиция, сохраненная в базе данных.
На этом этапе нам следует остановиться и немного подумать. Не потому, что TRADE_ACTION_DEAL особенно сложно реализовать, а из-за самой природы того, что нам предстоит реализовать. Мы используем TRADE_ACTION_DEAL как для открытия, так и для закрытия позиции. Однако настоящая сложность заключается в том, что нужно определить, что именно мы пытаемся сделать на двух разных типах счетов.
То есть независимо от того, используем ли мы счет типа HEDGING или счет типа NETTING, TRADE_ACTION_DEAL должен распознать тип счета и определить, что нужно сделать с открытой позицией. На счете типа NETTING, если пользователь не закрывает позицию, TRADE_ACTION_DEAL должен пересчитать среднюю цену открытой позиции, а затем обновить базу данных. На счете типа HEDGING, напротив, он должен просто создать новую позицию. Обратите внимание, что один и тот же запрос требует обновить позицию на счете типа NETTING и создать новую на счете типа HEDGING. Поэтому TRADE_ACTION_DEAL реализовать несколько сложнее. Давайте двигаться спокойно, шаг за шагом. Мы будем реализовывать TRADE_ACTION_DEAL постепенно, пока не добьемся его правильной работы. И только после этого мы оптимизируем уже созданный код. Чтобы разделить эти два вопроса, перейдем к новому разделу.
TRADE_ACTION_DEAL: миссия
Чтобы избежать чрезмерно длинного и утомительного кода, мы слегка изменим то, что у нас уже есть. Так его будет немного проще и приятнее реализовывать и понимать. Посмотрите на текущее состояние исходного кода в следующем фрагменте.
067. //+------------------------------------------------------------------+ 068. ulong SimulateServer(void) 069. { 070. MqlTradeResult TradeResult; 071. MqlTradeTransaction TradeTrans; 072. bool bResult = false; 073. 074. ZeroMemory(TradeResult); 075. ZeroMemory(TradeTrans); 076. 077. switch (m_Base.TradeRequest.action) 078. { 079. case TRADE_ACTION_SLTP: 080. bResult = ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 081. m_Base.TradeRequest.sl, 082. m_Base.TradeRequest.tp, 083. m_Base.TradeRequest.position)); 084. TradeTrans.type = TRADE_TRANSACTION_REQUEST; 085. TradeResult.retcode = TRADE_RETCODE_DONE; 086. if (bResult) 087. { 088. OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult); 089. return m_Base.TradeRequest.position; 090. } 091. break; 092. case TRADE_ACTION_DEAL: 093. break; 094. } 095. TradeResult.retcode = TRADE_RETCODE_INVALID; 096. OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult); 097. return 0; 098. } 099. //+------------------------------------------------------------------+ 100. ulong SendToPhysicalServer(void) 101. { 102. MqlTradeCheckResult TradeCheck; 103. MqlTradeResult TradeResult; 104. 105. ZeroMemory(TradeCheck); 106. ZeroMemory(TradeResult); 107. if (_Symbol == def_SymbolReplay) 108. return SimulateServer(); 109. if (!OrderCheck(m_Base.TradeRequest, TradeCheck)) 110. { 111. PrintFormat("Order System - Check Error: %d", GetLastError()); 112. return 0; 113. } 114. m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult); 115. if (TradeResult.retcode != TRADE_RETCODE_DONE) 116. { 117. PrintFormat("Order System - Send Error: %d", TradeResult.retcode); 118. return 0; 119. }; 120. 121. return TradeResult.order; 122. } 123. //+------------------------------------------------------------------+
Фрагмент C_Orders
Теперь мы извлечем часть приведенного выше кода, чтобы получилось так, как показано в следующем фрагменте.
067. //+------------------------------------------------------------------+ 068. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result) 069. { 070. } 071. //+------------------------------------------------------------------+ 072. void SimulateServer(MqlTradeTransaction &trans, MqlTradeResult &result) 073. { 074. switch (m_Base.TradeRequest.action) 075. { 076. case TRADE_ACTION_SLTP: 077. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 078. m_Base.TradeRequest.sl, 079. m_Base.TradeRequest.tp, 080. m_Base.TradeRequest.position)) ? TRADE_RETCODE_DONE : TRADE_RETCODE_INVALID); 081. trans.type = TRADE_TRANSACTION_REQUEST; 082. result.order = m_Base.TradeRequest.position; 083. break; 084. case TRADE_ACTION_DEAL: 085. Decode_TRADE_ACTION_DEAL(trans, result); 086. break; 087. } 088. OnTradeTransaction(trans, m_Base.TradeRequest, result); 089. } 090. //+------------------------------------------------------------------+ 091. ulong SendToPhysicalServer(void) 092. { 093. MqlTradeCheckResult TradeCheck; 094. MqlTradeResult TradeResult; 095. MqlTradeTransaction TradeTrans; 096. 097. ZeroMemory(TradeCheck); 098. ZeroMemory(TradeResult); 099. ZeroMemory(TradeTrans); 100. if (_Symbol == def_SymbolReplay) SimulateServer(TradeTrans, TradeResult); else 101. { 102. if (!OrderCheck(m_Base.TradeRequest, TradeCheck)) 103. { 104. PrintFormat("Order System [%s] - Check Error: %d", m_Base.TradeRequest.symbol, GetLastError()); 105. return 0; 106. } 107. m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult); 108. } 109. if (TradeResult.retcode != TRADE_RETCODE_DONE) 110. { 111. PrintFormat("Order System [%s] - Send Error: %d", m_Base.TradeRequest.symbol, TradeResult.retcode); 112. return 0; 113. }; 114. 115. return TradeResult.order; 116. } 117. //+------------------------------------------------------------------+
Фрагмент C_Orders
Результат получается гораздо интереснее, поскольку код обрабатывает ошибки почти одинаково как при подключении к реальному серверу, так и при его симуляции. Чтобы сообщения были понятнее, поскольку они могут поступать как с реального сервера, так и с имитированного, я добавил к каждому из них символ, связанный с соответствующим запросом. Таким образом, даже если вы используете систему репликации/моделирования одновременно с торговлей на реальном счете, вы сможете определить источник каждого сообщения, отображаемого в терминале MetaTrader 5, по символу, указанному рядом с ним. Так решение выглядит аккуратнее и им удобнее пользоваться. Теперь мы можем сосредоточиться на одной вполне конкретной части кода: Decode_TRADE_ACTION_DEAL, реализация которой начинается со строки 68.
Начнём с самого основного: открытия позиции по рынку. Когда это произойдет, Decode_TRADE_ACTION_DEAL получит ряд данных из функции ToMarket. Нам останется только интерпретировать их и создать новую запись в базе данных. И всё. Пока что моделирование не будет полностью точным, так как мы не будем использовать все возможные настройки. Это будет сделано, когда мы реализуем систему отложенных ордеров. Пока что мы будем работать только с тем, что у нас уже есть. Чтобы приступить к тестированию, мы реализуем код, приведенный в следующем фрагменте.
67. //+------------------------------------------------------------------+ 68. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result) 69. { 70. trans.type = TRADE_TRANSACTION_REQUEST; 71. if (m_Base.TradeRequest.position == 0) 72. { 73. struct stLocal 74. { 75. ulong value; 76. }Info; 77. 78. if ((result.retcode = (ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay AS tb WHERE tb.history = 0;")) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR) == TRADE_RETCODE_ERROR) return; 79. if ((result.retcode = (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; 80. result.order = Info.value + 10; 81. if ((result.retcode = (ExecCommandSQL(StringFormat("INSERT INTO tb_Replay (ticket, type, volume, price, sl, tp, history) values (%d, %d, %f, %f, %f, %f, %d);", 82. result.order, 83. m_Base.TradeRequest.type, 84. m_Base.TradeRequest.volume, 85. m_Base.TradeRequest.price, 86. m_Base.TradeRequest.sl, 87. m_Base.TradeRequest.tp, 88. 0 89. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; 90. } 91. } 92. //+------------------------------------------------------------------+
Фрагмент C_Orders
Этот фрагмент кода делает именно то, что нам нужно: он позволяет советнику имитировать открытие позиции по рынку с помощью того же взаимодействия между индикатором мыши и индикатором Chart Trade, которое мы показывали в предыдущих статьях, когда советник был подключён к реальному торговому серверу. Просто добавив этот фрагмент в код класса C_Orders, мы сможем протестировать не только открытие позиции, но и изменение значений, которые будет отображать индикатор позиции. Вот это самое интересное.
Как только всё появится на графике, система сможет делать то, чего многие из вас ждали: открывать позиции, перемещать уровни тейк-профита и стоп-лосса, а также отображать результат по позиции. Разумеется, мы только начинаем дорабатывать систему репликации/моделирования, поскольку пока сможем выполнять лишь эти операции — так же, как если бы были подключены к реальному серверу.
Результат, полученный после компиляции всего кода, можно увидеть в следующих анимациях.

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

Как вы можете заметить, работа системы практически идентична той, что была бы при подключении к реальному серверу. Однако на данный момент мы можем только открывать позиции. Позиция по-прежнему не будет закрываться, если цена достигнет линии тейк-профита или стоп-лосса. Мы также не можем закрыть её, нажав кнопку закрытия, расположенную на линии цены открытия. Всё остальное работает отлично и именно так, как и ожидалось. Тем не менее системе всё ещё требуются некоторые улучшения. Помните, что это была лишь первая проверка; пока мы не реализуем ничего особенно амбициозного. Однако, если вы оставите этот фрагмент в показанном виде, то сможете имитировать счет типа HEDGING, даже если символ соответствует счету типа NETTING. Таким образом, мы реализуем это ещё до того, как попытаемся сделать это по-настоящему.
Отлично. Мы уже знаем, что сможем использовать систему с символами, предназначенными для счетов типа HEDGING, поскольку новые позиции можно открывать даже по символам, настроенным для счетов типа NETTING. Однако мы должны правильно различать оба случая. Возможно, вы подумаете, что это безумие, но это не так. Так делается уже очень давно. Нам нужно лишь, чтобы класс C_Orders получал данные, необходимые для различения символа, предназначенного для счета типа NETTING, от символа, предназначенного для счета типа HEDGING. Это очень просто. Если вы изучите код системы репликации/моделирования, то найдёте функцию SetSymbolInfos, расположенную в классе C_FileTicks. В этой функции мы задаём значение SYMBOL_TRADE_CALC_MODE, чтобы указать, как будет рассчитываться позиция. Однако, в отличие от системы, подключенной к реальному счету, здесь мы можем отложить некоторые критерии расчета и использовать только два значения, чтобы правильно различать типы счетов.
В системе репликации/моделирования уже реализован механизм различения этих двух типов счетов, но теперь нам нужно реализовать его и в классе C_Orders. Вы можете сделать это иначе, не так, как я покажу. Тем не менее то, как я это сделаю, также должно создать или, точнее говоря, имитировать некоторую задержку между запросом пользователя и ответом сервера. Такая задержка существует всегда, в том числе из-за расстояния. Помните, что сигнал передаётся не мгновенно: проходит некоторое время с момента его отправки с вашего устройства до момента поступления на сервер. Чтобы частично смоделировать эту задержку, мы изменим код класса C_Orders, как показано ниже.
092. //+------------------------------------------------------------------+ 093. void SimulateServer(MqlTradeTransaction &trans, MqlTradeResult &result) 094. { 095. ENUM_SYMBOL_CALC_MODE Account = (ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE); 096. 097. switch (m_Base.TradeRequest.action) 098. { 099. case TRADE_ACTION_SLTP: 100. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 101. m_Base.TradeRequest.sl, 102. m_Base.TradeRequest.tp, 103. m_Base.TradeRequest.position)) ? TRADE_RETCODE_DONE : TRADE_RETCODE_INVALID); 104. trans.type = TRADE_TRANSACTION_REQUEST; 105. result.order = m_Base.TradeRequest.position; 106. break; 107. case TRADE_ACTION_DEAL: 108. Decode_TRADE_ACTION_DEAL(trans, result, Account == SYMBOL_CALC_MODE_EXCH_STOCKS); 109. break; 110. } 111. OnTradeTransaction(trans, m_Base.TradeRequest, result); 112. } 113. //+------------------------------------------------------------------+
Фрагмент класса C_Orders
Обратите внимание, что в строке 95 мы получаем нужные нам данные. Этот запрос будет выполняться каждый раз, когда мы будем моделировать сервер. Затем мы будем использовать это же значение в строке 108. Я делаю это, чтобы добавить небольшую задержку остальным вызовам. Я знаю, что строка 95 выполнится очень быстро, но, если вы хотите добиться более реалистичной задержки, можете добавить вызов Sleep перед выполнением строки 97. Решение остаётся за вами, уважаемый читатель. Пока я не хочу слишком усложнять объяснение и затруднять его понимание. Обратите также внимание, что в строке 108 мы будем передавать не числовое значение, а логическое. Проще говоря, мы будем передавать значение true, если это счет типа NETTING, и false, если это счет типа HEDGING.
Отлично. Теперь вернёмся к методу Decode_TRADE_ACTION_DEAL. Хотя этот метод и работал, его текущая реализация не вполне подходит, поскольку в поле тикета могут появляться повторяющиеся значения. Это связано с тем, что предыдущая версия предназначалась только для проверки работоспособности системы. После этого нам нужно исправить код. Новый фрагмент, в который уже включены некоторые дополнительные инструкции, приведён ниже.
067. //+------------------------------------------------------------------+ 068. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result, const bool IsNetting) 069. { 070. struct stLocal 071. { 072. ulong value; 073. }Info; 074. C_InServer *UseLocal; 075. 076. trans.type = TRADE_TRANSACTION_REQUEST; 077. if ((IsNetting) && (m_Base.TradeRequest.position == 0)) 078. { 079. result.retcode = (ExecRequestOfData("SELECT tb.ticket FROM tb_Replay AS tb WHERE tb.history = 0;") ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 080. result.retcode = (result.retcode != TRADE_RETCODE_DONE ? result.retcode : (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)); 081. m_Base.TradeRequest.position = (result.retcode == TRADE_RETCODE_DONE ? Info.value : 0); 082. } 083. if (m_Base.TradeRequest.position == 0) 084. { 085. if ((result.retcode = (ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay;")) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR) == TRADE_RETCODE_ERROR) return; 086. if ((result.retcode = (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; 087. result.order = Info.value + 10; 088. result.retcode = (ExecCommandSQL(StringFormat("INSERT INTO tb_Replay (ticket, type, volume, price, sl, tp, history) values (%d, %d, %f, %f, %f, %f, %d);", 089. result.order, 090. m_Base.TradeRequest.type, 091. m_Base.TradeRequest.volume, 092. m_Base.TradeRequest.price, 093. m_Base.TradeRequest.sl, 094. m_Base.TradeRequest.tp, 095. 0 096. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 097. } else 098. { 099. UseLocal = new C_InServer(); 100. 101. (*UseLocal)._PositionSelectByTicket(m_Base.TradeRequest.position); 102. if (UseLocal._PositionGetInteger(POSITION_TYPE) == m_Base.TradeRequest.type) 103. { 104. m_Base.TradeRequest.price = NormalizeDouble(((*UseLocal)._PositionGetDouble(POSITION_PRICE_OPEN) * (*UseLocal)._PositionGetDouble(POSITION_VOLUME) + 105. m_Base.TradeRequest.price * m_Base.TradeRequest.volume) / 106. (UseLocal._PositionGetDouble(POSITION_VOLUME) + m_Base.TradeRequest.volume), (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 107. m_Base.TradeRequest.sl = NormalizeDouble((m_Base.TradeRequest.sl + (*UseLocal)._PositionGetDouble(POSITION_SL)) / 2, (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 108. m_Base.TradeRequest.tp = NormalizeDouble((m_Base.TradeRequest.tp + (*UseLocal)._PositionGetDouble(POSITION_TP)) / 2, (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 109. m_Base.TradeRequest.volume += (*UseLocal)._PositionGetDouble(POSITION_VOLUME); 110. 111. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET volume = %f, price = %f, sl = %f, tp = %f;", 112. m_Base.TradeRequest.volume, 113. m_Base.TradeRequest.price, 114. m_Base.TradeRequest.sl, 115. m_Base.TradeRequest.tp 116. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 117. if (result.retcode == TRADE_RETCODE_DONE) 118. { 119. trans.type = TRADE_TRANSACTION_HISTORY_ADD; 120. trans.order = m_Base.TradeRequest.position; 121. trans.position = m_Base.TradeRequest.position; 122. OnTradeTransaction(trans, m_Base.TradeRequest, result); 123. } 124. } 125. 126. delete UseLocal; 127. } 128. } 129. //+------------------------------------------------------------------+
Фрагмент класса C_Orders
А теперь будьте внимательны, потому что здесь всё начинает немного усложняться. Тем не менее, нет причин ни для тревоги, ни для того, чтобы бежать в горы, пытаясь спастись от возможной лавины деталей. На первый взгляд то, что мы здесь делаем, может показаться несколько запутанным, но на самом деле всё не так уж сложно. Обратите внимание, что в строке 74 мы объявляем локальную переменную. Это позволит нам получить доступ к базе данных, не мешая никаким другим обращениям к ней, которые может выполнять советник.
В строке 77 мы выполняем проверку, чтобы определить, работаем ли мы со счетом типа HEDGING или со счетом типа NETTING, поскольку этот класс должен выполнять разные операции для каждого типа счета. Мы также проверяем, содержит ли поле position тикет. Если запрос поступает из ToMarket, это поле будет равно 0, в отличие от случая, когда вызов поступает из ClosePosition. Позже мы рассмотрим этот второй случай. Если условие в строке 77 выполняется, мы должны получить тикет открытой позиции. Это выполняется в строке 81.
А теперь будьте особенно внимательны, потому что этот момент важен. Если мы работаем на счете типа NETTING и запрос не возвращает ни одной открытой позиции, result.retcode получит значение TRADE_RETCODE_ERROR. Если же запрос возвращает открытую позицию, ее тикет будет сохранен в m_Base.TradeRequest.position. Это важно, потому что в строке 83 мы будем проверять именно эту переменную.
Таким образом, если мы находимся на счете типа HEDGING или запрос не возвращает ни одной открытой позиции на счете типа NETTING, условие в строке 83 будет выполнено, и мы продолжим открытие позиции. Поскольку ранее могло быть закрыто несколько позиций, в строке 85 мы подсчитываем записи в таблице, чтобы сгенерировать новый тикет. Таким образом мы избегаем ситуации, когда две позиции или операции получают один и тот же тикет. Остальное работает так, как мы уже видели ранее.
Если же указана позиция, условие в строке 83 не будет выполнено, и управление перейдет к строке 97. Это может происходить при двух типах вызовов: один исходит из ClosePosition, а другой — из ToMarket. В последнем случае мы можем закрыть позицию, развернуть её — то есть закрыть и открыть другую в противоположном направлении — или увеличить её объём в том же направлении, чтобы пересчитать её среднюю цену. Поскольку речь идёт о трёх разных операциях, мы должны обрабатывать их по отдельности, так как каждая из них даёт разный результат.
В предыдущем фрагменте мы начали со случая, когда пользователь запрашивает увеличение объёма позиции в том же направлении, то есть пересчёт её средней цены открытия. Здесь нам нужно решить, что делать с ценами тейк-профита и стоп-лосса. В данной реализации я исхожу из того, что эти цены также будут пересчитываться как среднее значение. Однако не все платформы обязательно работают именно так. Причина проста. Когда трейдер увеличивает позицию, чтобы пересчитать её среднюю цену, некоторые платформы сохраняют исходные цены тейк-профита и стоп-лосса исходной позиции. Таким образом они сохраняют исходную логику стратегии, и эти цены могут выступать точками частичного закрытия для нового открытого объёма.
Поскольку я исхожу из того, что сохранение исходных цен тейк-профита и стоп-лосса не является частью стратегии, я сделаю так, чтобы система тоже пересчитывала среднее значение этих цен. Уважаемый читатель, при желании вы можете изменить это поведение. Однако не забудьте также изменить индикатор позиции, чтобы он отображал результирующие цены. В противном случае у вас могут возникнуть проблемы при использовании системы репликации/моделирования.
Теперь, когда мы выяснили, как будем пересчитывать эти цены, можно перейти к следующему пункту. Обратите внимание, что пока мы ещё не закрываем позицию. Система лишь открывает её и обновляет по мере взаимодействия пользователя с ней. Закрытие позиции с помощью вызова, поступающего из ClosePosition, во многом похоже на её разворот. Разница между этими двумя операциями заключается в запрашиваемом объёме. Поэтому мы можем обрабатывать их одним и тем же блоком кода, если будем проверять этот объём, чтобы определить, должен ли запрос ограничиваться закрытием открытой позиции или же, помимо её закрытия, должна быть открыта новая позиция в противоположном направлении. Лично я обычно не разворачиваю позицию в ходе сделки. Я предпочитаю закрыть её и дождаться нового входа. По этой причине я не могу с полной уверенностью утверждать, изменяется ли тикет позиции при её развороте. Тем не менее, в данной реализации мы закроем текущую позицию и откроем новую в противоположном направлении, когда операция будет предполагать разворот позиции. Для этого нам нужно заменить предыдущий фрагмент на приведённый ниже.
067. //+------------------------------------------------------------------+ 068. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result, const bool IsNetting) 069. { 070. #define macro_NewPosition { \ 071. if ((result.retcode = (ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay;")) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR) == TRADE_RETCODE_ERROR) return; \ 072. if ((result.retcode = (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; \ 073. result.order = Info.value + 10; \ 074. result.retcode = (ExecCommandSQL(StringFormat("INSERT INTO tb_Replay (ticket, type, volume, price, sl, tp, history) values (%d, %d, %f, %f, %f, %f, %d);", \ 075. result.order, \ 076. m_Base.TradeRequest.type, \ 077. m_Base.TradeRequest.volume, \ 078. m_Base.TradeRequest.price, \ 079. m_Base.TradeRequest.sl, \ 080. m_Base.TradeRequest.tp, \ 081. 0 \ 082. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); \ 083. } 084. 085. #define macro_History if (result.retcode == TRADE_RETCODE_DONE) { \ 086. trans.type = TRADE_TRANSACTION_HISTORY_ADD; \ 087. trans.order = m_Base.TradeRequest.position; \ 088. trans.position = m_Base.TradeRequest.position; \ 089. OnTradeTransaction(trans, m_Base.TradeRequest, result); } 090. 091. struct stLocal 092. { 093. ulong value; 094. }Info; 095. C_InServer *UseLocal; 096. 097. trans.type = TRADE_TRANSACTION_REQUEST; 098. if ((IsNetting) && (m_Base.TradeRequest.position == 0)) 099. { 100. result.retcode = (ExecRequestOfData("SELECT tb.ticket FROM tb_Replay AS tb WHERE tb.history = 0;") ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 101. result.retcode = (result.retcode != TRADE_RETCODE_DONE ? result.retcode : (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)); 102. m_Base.TradeRequest.position = (result.retcode == TRADE_RETCODE_DONE ? Info.value : 0); 103. } 104. if (m_Base.TradeRequest.position == 0) macro_NewPosition else 105. { 106. UseLocal = new C_InServer(); 107. 108. (*UseLocal)._PositionSelectByTicket(m_Base.TradeRequest.position); 109. if (UseLocal._PositionGetInteger(POSITION_TYPE) == m_Base.TradeRequest.type) 110. { 111. m_Base.TradeRequest.price = NormalizeDouble(((*UseLocal)._PositionGetDouble(POSITION_PRICE_OPEN) * (*UseLocal)._PositionGetDouble(POSITION_VOLUME) + 112. m_Base.TradeRequest.price * m_Base.TradeRequest.volume) / 113. (UseLocal._PositionGetDouble(POSITION_VOLUME) + m_Base.TradeRequest.volume), (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 114. m_Base.TradeRequest.sl = NormalizeDouble((m_Base.TradeRequest.sl + (*UseLocal)._PositionGetDouble(POSITION_SL)) / 2, (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 115. m_Base.TradeRequest.tp = NormalizeDouble((m_Base.TradeRequest.tp + (*UseLocal)._PositionGetDouble(POSITION_TP)) / 2, (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 116. m_Base.TradeRequest.volume += (*UseLocal)._PositionGetDouble(POSITION_VOLUME); 117. 118. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET volume = %f, price = %f, sl = %f, tp = %f;", 119. m_Base.TradeRequest.volume, 120. m_Base.TradeRequest.price, 121. m_Base.TradeRequest.sl, 122. m_Base.TradeRequest.tp 123. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 124. macro_History; 125. } else 126. { 127. m_Base.TradeRequest.volume = m_Base.TradeRequest.volume - (*UseLocal)._PositionGetDouble(POSITION_VOLUME); 128. 129. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET history = 1 WHERE ticket = %d;", 130. m_Base.TradeRequest.position 131. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 132. macro_History; 133. trans.type = TRADE_TRANSACTION_REQUEST; 134. macro_NewPosition; 135. } 136. 137. delete UseLocal; 138. } 139. #undef macro_History 140. #undef macro_NewPosition 141. } 142. //+------------------------------------------------------------------+
Фрагмент C_Orders
Этот фрагмент кода реализует как разворот позиции — закрытие текущей позиции и открытие другой в противоположном направлении, — так и закрытие конкретной позиции при нажатии на кнопку, расположенную на линии цены её открытия. Однако кнопка Chart Trade, предназначенная для закрытия всех позиций, пока ещё не сможет их закрыть. Причина проста: мы не записываем в базу данных магическое число, полученное в запросе. Это значение позволяет определить, какой советник открыл каждую позицию, и используется при поиске позиций, которые нужно закрыть. На данном этапе у нас есть два варианта. Уважаемый читатель, вам нужно выбрать тот вариант, который лучше всего соответствует вашим потребностям. Первый вариант заключается в изменении базы данных, чтобы сохранять магическое число советника для каждой позиции. Второй вариант заключается в том, чтобы исключить проверку магического числа при выборе позиций, которые будут закрыты. Давайте посмотрим, как можно реализовать каждый из этих вариантов. Помните, что вам нужно выбрать один из этих вариантов; реализовывать оба решения не имеет никакого смысла.
Убрать проверку магического числа
Чтобы убрать эту проверку и сделать так, чтобы при нажатии кнопки Chart Trade закрывались все позиции, вам нужно лишь изменить приведённый ниже фрагмент кода.
221. //+------------------------------------------------------------------+ 222. void CloseAllsPosition(void) 223. { 224. for (int count = _PositionsTotal() - 1; count >= 0; count--) 225. { 226. if (_PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue; 227. if (_PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue; 228. ClosePosition(_PositionGetInteger(POSITION_TICKET)); 229. } 230. }; 231. //+------------------------------------------------------------------+
Фрагмент C_Orders
Всё, что вам нужно сделать, — это удалить строку 227, которая, как вы можете заметить, зачёркнута в этом фрагменте. Однако, хотя это решение работает очень хорошо, оно не позволит фиксировать и различать операции, автоматически выполняемые каждым советником. В той же базе данных, которую создаст система репликации/моделирования, вы сможете посмотреть, когда каждый из советников выполнял ту или иную операцию.
Сопоставив эти данные — а это очень просто сделать с помощью SQL, — вы сможете выявить моменты, в которых несколько советников одинаково интерпретируют рынок, даже если они используют разные таймфреймы или торговые модели. Без сомнения, такое сопоставление данных окажется весьма полезным для многих разработчиков торговых сетапов. Однако для этого нам придётся изменить некоторые детали. Посмотрим, как это сделать, в следующем разделе.
Добавление магического числа
Магическое число всегда присутствует в запросах, которые получает класс C_Orders. Чтобы использовать его при выборе и закрытии позиций, мы должны сохранять его в каждой записи таблицы. Для этого достаточно изменить код в следующем фрагменте.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "..\Defines.mqh" 05. #include "..\SQL\C_ReplayDataBase.mqh" 06. //+------------------------------------------------------------------+ 07. class C_InServer : public C_ReplayDataBase 08. { 09. private : 10. bool m_IsReplay; 11. struct stLocal 12. { 13. ulong numberMagic, 14. ticket; 15. int type; 16. double volume, 17. price, 18. sl, 19. tp; 20. }m_Info; 21. public : 22. //+------------------------------------------------------------------+ 23. C_InServer() 24. :C_ReplayDataBase(), 25. m_IsReplay(_Symbol == def_SymbolReplay) 26. { 27. ZeroMemory(m_Info); 28. ExecCommandSQL("CREATE TABLE IF NOT EXISTS tb_Replay ( magic, ticket, type, volume, price, sl, tp, history );"); 29. } 30. //+------------------------------------------------------------------+ . . . 49. //+------------------------------------------------------------------+ 50. inline const long _PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER arg) 51. { 52. if (!m_IsReplay) 53. return PositionGetInteger(arg); 54. 55. switch (arg) 56. { 57. case POSITION_TICKET : return (long) m_Info.ticket; 58. case POSITION_TIME : break; 59. case POSITION_TIME_MSC : break; 60. case POSITION_TIME_UPDATE : break; 61. case POSITION_TIME_UPDATE_MSC : break; 62. case POSITION_TYPE : return (long) m_Info.type; 63. case POSITION_MAGIC : return (long) m_Info.numberMagic; 64. case POSITION_IDENTIFIER : break; 65. } 66. 67. return 0; 68. } 69. //+------------------------------------------------------------------+
Фрагмент из C_InServer
Обратите внимание, что сначала, в строке 13, мы объявляем член, который будет хранить магическое число. Затем, в строке 28, мы добавляем столбец magic в инструкцию CREATE TABLE. Обратите внимание на одну деталь: чтобы это новое определение применилось, база данных не должна уже существовать. В противном случае таблица сохранит прежнюю структуру, и столбец не будет создан. Вы также можете добавить столбец magic в существующую таблицу с помощью команды ALTER TABLE. Если вы не знаете, как это сделать, обратитесь к документации по SQL.
Наконец, в строке 63 мы считываем столбец magic из выбранной записи и возвращаем это значение как магическое число позиции. После внесения этого изменения нам также нужно изменить класс C_Orders, чтобы записывать это число при создании соответствующей записи и обновлять его при необходимости. Эти изменения ещё проще, как показывает следующий фрагмент.
67. //+------------------------------------------------------------------+ 68. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result, const bool IsNetting) 69. { 70. #define macro_NewPosition { \ 71. if ((result.retcode = (ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay;")) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR) == TRADE_RETCODE_ERROR) return; \ 72. if ((result.retcode = (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; \ 73. result.order = Info.value + 10; \ 74. result.retcode = (ExecCommandSQL(StringFormat("INSERT INTO tb_Replay (magic, ticket, type, volume, price, sl, tp, history)" \ 75. "values (%I64u, %d, %d, %f, %f, %f, %f, %d);", \ 76. m_Base.TradeRequest.magic, \ 77. result.order, \ 78. m_Base.TradeRequest.type, \ 79. m_Base.TradeRequest.volume, \ 80. m_Base.TradeRequest.price, \ 81. m_Base.TradeRequest.sl, \ 82. m_Base.TradeRequest.tp, \ 83. 0 \ 84. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); \ 85. } 86. 87. . 88. . 89. .
Фрагмент из C_Orders
Посмотрите, как легко их реализовать. Их даже не нужно объяснять или снова показывать весь код, потому что решение уже реализовано. Таким образом, при нажатии кнопки Chart Trade для закрытия всех позиций класс сможет выбрать записи, магическое число которых соответствует советнику, и закрыть эти позиции. Кроме того, теперь вы сможете использовать нескольких советников в системе репликации/моделирования и создать базу данных, с помощью которой можно будет подробно проанализировать каждого из них. В каждой записи будет сохраняться магический номер советника, выполнившего операцию, поэтому вы сможете сопоставлять их данные за один и тот же период и по одному и тому же символу. Таким образом, будет легко определить, в каком сценарии лучше использовать того или иного из них.
Я не собираюсь здесь сравнивать систему репликации/моделирования со средством тестирования стратегий 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/13555
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
От начального до среднего уровня: Перегрузка операторов (IV)
Пайплайны Codex: от Python до MQL5 для выбора индикаторов — анализ ETF XLF по нескольким кварталам с применением машинного обучения
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования