Español Português
preview
Репликация и моделирование рынка: Большой финал

Репликация и моделирование рынка: Большой финал

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

Введение

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

В предыдущей статье «Моделирование рынка: В единстве — сила (III)» мы практически завершили этот этап разработки системы репликации/моделирования. Однако ещё осталось показать, как внести небольшую корректировку и некоторые необходимые изменения, чтобы систему репликации/моделирования можно было использовать в рамках подходящей методики обучения. В этой статье мы завершим рассмотрение этих аспектов, так как в следующей я хочу перейти к другой теме. Возможно, уважаемый читатель, вы считаете, что мы ещё не реализовали все элементы, необходимые для использования системы репликации/моделирования. Однако вы убедитесь, что все они уже разработаны или практически завершены. Поскольку в этой статье я хочу завершить данный этап, давайте сразу перейдём к тому, что нас интересует.


Настроить количество знаков

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

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

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

105. //+------------------------------------------------------------------+
106.         int SetSymbolInfos(void)
107.             {
108.                 int iRet;
109.                 
110.                 CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, iRet = (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5));
111.                 CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
112.                 CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
113.                 
114.                 return iRet;
115.             }
116. //+------------------------------------------------------------------+
117.     public    :
118. //+------------------------------------------------------------------+
119.         C_FileTicks()
120.             {
121.                 ArrayResize(m_Ticks.Rate, def_BarsDiary);
122.                 m_Ticks.nRate = -1;
123.                 m_Ticks.nTicks = 0;
124.                 m_Ticks.Rate[0].time = 0;
125.             }
126. //+------------------------------------------------------------------+
127.         bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume)
128.             {
129.                 C_FileBars       *pFileBars;
130.                 C_Simulation     *pSimulator = NULL;
131.                 int              iMem = m_Ticks.nTicks,
132.                                  iRet = -1;
133.                 MqlRates         rate[1];
134.                 MqlTick          local[];
135.                 bool             bInit = false;
136.                 
137.                 pFileBars = new C_FileBars(szFileNameCSV);
138.                 ArrayResize(local, def_MaxSizeArray);
139.                 Print("Converting bars to ticks. Please wait...");
140.                 while ((*pFileBars).ReadBar(rate) && (!_StopFlag))
141.                 {
142.                     if (!bInit)
143.                     {
144.                         m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX);
145.                         pSimulator = new C_Simulation(SetSymbolInfos());
146.                         bInit = true;
147.                     }
                   .
                                   .
                                   .
165.             }
166. //+------------------------------------------------------------------+
167.         datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
168.             {
169.                 int         MemNRates,
170.                             MemNTicks,
171.                             nDigits,
172.                             nShift;
173.                 datetime dtRet = TimeCurrent();
174.                 MqlRates RatesLocal[],
175.                             rate;
176.                 MqlTick    TicksLocal[];
177.                 bool        bNew;
178.                 
179.                 MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
180.                 nShift = MemNTicks = m_Ticks.nTicks;
181.                 if (!Open(szFileNameCSV)) return 0;
182.                 if (!ReadAllsTicks()) return 0;            
183.                 rate.time = 0;
184.                 nDigits = SetSymbolInfos(); 
185.                 m_Ticks.bTickReal = true;
186.                 for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
187.                 {
188.                     if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0];
189.                     if (!BuildBar1Min(c0, rate, bNew)) continue;
190.                     if (bNew)
191.                     {
192.                         if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
193.                         {
194.                             nShift = MemShift;
195.                             ArrayResize(TicksLocal, def_MaxSizeArray);
196.                             C_Simulation *pSimulator = new C_Simulation(nDigits);
197.                             if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0)
198.                                 nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
199.                             delete pSimulator;
200. 

Фрагмент кода из C_FileTicks

Этот фрагмент относится к классу C_FileTicks. В строке 110 мы задаём значение для свойства SYMBOL_DIGITS. Однако при этом индикатор позиции отображает значения странным образом. Кроме того, свойство SYMBOL_DIGITS также используется в строке 184, чтобы указать симулятору в строке 196, как следует нормализовать значения, которые будут сгенерированы. То же самое происходит в строке 145. Хорошо. Мы изменим эту часть, как показано в следующем фрагменте.

105. //+------------------------------------------------------------------+
106.         int SetSymbolInfos(void)
107.             {
108.                 CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
109.                 CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
110.                 
111.                 return (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5);
112.             }
113. //+------------------------------------------------------------------+
114.     public    :
115. //+------------------------------------------------------------------+
116.         C_FileTicks()
117.             {
118.                 ArrayResize(m_Ticks.Rate, def_BarsDiary);
119.                 m_Ticks.nRate = -1;
120.                 m_Ticks.nTicks = 0;
121.                 m_Ticks.Rate[0].time = 0;
122.             }
123. //+------------------------------------------------------------------+
124.         bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume)
125.             {
126.                 C_FileBars   *pFileBars;
127.                 C_Simulation *pSimulator = NULL;
128.                 int          iMem = m_Ticks.nTicks,
129.                              iRet = -1;
130.                 MqlRates     rate[1];
131.                 MqlTick      local[];
132.                 bool         bInit = false;
133.                 
134.                 pFileBars = new C_FileBars(szFileNameCSV);
135.                 ArrayResize(local, def_MaxSizeArray);
136.                 Print("Converting bars to ticks. Please wait...");
137.                 while ((*pFileBars).ReadBar(rate) && (!_StopFlag))
138.                 {
139.                     if (!bInit)
140.                     {
141.                         m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX);
142.                         pSimulator = new C_Simulation(SetSymbolInfos());
143.                         bInit = true;
144.                     }
                   .
                                   .
                                   .
162.             }
163. //+------------------------------------------------------------------+
164.         datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
165.             {
166.                 int      MemNRates,
167.                          MemNTicks,
168.                          nDigits,
169.                          nShift;
170.                 datetime dtRet = TimeCurrent();
171.                 MqlRates RatesLocal[],
172.                          rate;
173.                 MqlTick  TicksLocal[];
174.                 bool     bNew;
175.                 
176.                 MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
177.                 nShift = MemNTicks = m_Ticks.nTicks;
178.                 if (!Open(szFileNameCSV)) return 0;
179.                 if (!ReadAllsTicks()) return 0;            
180.                 rate.time = 0;
181.                 nDigits = SetSymbolInfos(); 
182.                 m_Ticks.bTickReal = true;
183.                 for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
184.                 {
185.                     if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0];
186.                     if (!BuildBar1Min(c0, rate, bNew)) continue;
187.                     if (bNew)
188.                     {
189.                         if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
190.                         {
191.                             nShift = MemShift;
192.                             ArrayResize(TicksLocal, def_MaxSizeArray);
193.                             C_Simulation *pSimulator = new C_Simulation(nDigits);
194.                             if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0)
195.                                 nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
196.                             delete pSimulator;
197. 

Фрагмент кода из C_FileTicks

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

060. //+------------------------------------------------------------------+
061. inline bool Configs(const string szInfo)
062.             {
063.                 const string szList[] = {
064.                             "PATH",
065.                             "POINTSPERTICK",
066.                             "VALUEPERPOINTS",
067.                             "VOLUMEMINIMAL",
068.                             "LOADMODEL",
069.                             "ACCOUNT",
070.                             "MAXTICKSPERBAR",
071.                             "DIGITS"
072.                                                 };
073.                 string     szRet[];
074.                 char        cWho;
075.                 
076.                 if (StringSplit(szInfo, '=', szRet) == 2)
077.                 {
078.                     StringTrimRight(szRet[0]);
079.                     StringTrimLeft(szRet[1]);
080.                     for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
081.                     switch (cWho)
082.                     {
083.                         case 0:
084.                             m_GlPrivate.szPath = szRet[1];
085.                             return true;
086.                         case 1:
087.                             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
088.                             return true;
089.                         case 2:
090.                             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
091.                             return true;
092.                         case 3:
093.                             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
094.                             return true;
095.                         case 4:
096.                             m_GlPrivate.ModelLoading = StringInit(szRet[1]);
097.                             m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading);
098.                             return true;
099.                         case 5:
100.                             if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true;
101.                             else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false;
102.                             else
103.                             {
104.                                 Print("Entered account type is not invalid.");                                
105.                                 return false;
106.                             }
107.                             return true;
108.                         case 6:
109.                             m_GlPrivate.MaxTickVolume = (int) MathAbs(StringToInteger(szRet[1]));
110.                             return true;
111.                         case 7:                                    
112.                             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, (int) MathAbs(StringToInteger(szRet[1])));
113.                             return true;
114. 
115.                     }
116.                     Print("Variable >>", szRet[0], "<< not defined.");
117.                 }else
118.                     Print("Configuration definition >>", szInfo, "<< invalidates.");
119.                     
120.                 return false;
121.             }
122. //+------------------------------------------------------------------+

Фрагмент кода из C_ConfigService

Обратите внимание, как это просто. В строке 71 мы добавляем строку в верхнем регистре, которую должен использовать пользователь. После этого нам остаётся только добавить соответствующую обработку этой строки, что мы и делаем в строке 111. Далее, в строке 112, мы указываем, где будет использоваться это значение. Поскольку строка была обработана корректно, в строке 113 мы возвращаем true, чтобы гарантировать инициализацию сервиса репликация/моделирование.

В приведённом ниже коде показано, как пользователь может задать это значение в файле конфигурации.

01. [Config]
02. Path = WDO
03. PointsPerTick = 0.5
04. ValuePerPoints = 5.0
05. VolumeMinimal = 1.0
06. Account = NETTING
07. Digits = 3
08. 
09. [Bars]
10. WDON22_M1_202206140900_202206141759
11. 
12. [ Ticks -> Bars]
13. 
14. [ Bars -> Ticks ]
15. WDON22_M1_202206150900_202206151759
16. 
17. [Ticks]
18. 

Файл конфигурации

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

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

Digits = 1

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

Просто, не правда ли? Следующая проблема, которую я хочу объяснить, может возникнуть, если вы не примете должных мер предосторожности при работе с SQL. Если вы измените код, пытаясь сделать те или иные вещи, это может привести к повреждению базы данных. Чтобы объяснить, как этого избежать, перейдём к новому разделу.


Как избежать повреждения базы данных

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\Defines.mqh"
05. #include "..\SQL\C_ReplayDataBase.mqh"
06. //+------------------------------------------------------------------+
07. #resource "Script.sql" as string SQL_01
08. //+------------------------------------------------------------------+
09. class C_InServer : public C_ReplayDataBase
10. {
11.     private    :
12.         bool    m_IsReplay;
13.         struct stLocal
14.         {
15.             ulong   numberMagic,
16.                     ticket;
17.             int     type;
18.             double  volume,
19.                     price,
20.                     sl,
21.                     tp;
22.         }m_Info;
23.     public    :
24. //+------------------------------------------------------------------+
25.         C_InServer()
26.             :C_ReplayDataBase(),
27.             m_IsReplay(_Symbol == def_SymbolReplay)
28.         {
29.             ZeroMemory(m_Info);
30.             ExecResourceSQL(SQL_01);
31.         }
32. //+------------------------------------------------------------------+

Фрагмент C_InServer

Скрипт показан ниже.

CREATE TABLE IF NOT EXISTS tb_Replay ( 
    magic NOT NULL DEFAULT 0,      -- ID Expert Advisor
    ticket PRIMARY KEY NOT NULL,   -- Position Ticket
    type NOT NULL,                 -- Position type
    volume NOT NULL,               -- Position volume
    price NOT NULL,                -- Opening point
    sl NOT NULL DEFAULT 0,         -- Stop loss point
    tp NOT NULL DEFAULT 0,         -- Take profit point
    history NOT NULL DEFAULT 0     -- If false, the position is open
);

SQL-скрипт

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

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

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


Тейк-профит и стоп-лосс

Здесь есть один вопрос, который вызывает сомнения у многих начинающих трейдеров. Даже некоторые трейдеры с определённым опытом работы на рынке понимают этот вопрос, мягко говоря, неверно. Линии тейк-профита и стоп-лосса — это не отложенные ордера. Это ордера особого типа. Некоторые платформы и даже некоторые трейдеры делают этот вопрос гораздо более запутанным, чем он есть на самом деле. Я упоминаю об этом, потому что здесь, в Бразилии, многие аналитики и профессиональные трейдеры, использующие другие платформы, с большим трудом понимают то, что я объясню ниже. Неверно утверждать, что нужно корректировать так называемый OFFSET, чтобы линия стоп-лосса не закрывала позицию. Это одно из самых распространённых заблуждений, которые распространяют трейдеры и аналитики рынка в Бразилии. Так называемый OFFSET, о котором многие говорят, существует только у одного типа ордеров — BUY STOP LIMIT или SELL STOP LIMIT, как вы можете видеть в выделенной части следующего изображения.

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

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

В системе есть несколько мест, где можно реализовать необходимую проверку. Некоторые из них подходят для этого лучше других, поскольку вам потребуется передавать между приложениями цену закрытия и уровни тейк-профита и стоп-лосса. Худшим местом для её реализации был бы советник. Каждый разрабатываемый вами советник должен был бы включать один и тот же код для проверки тейк-профита и стоп-лосса при использовании советника в системе репликации/моделирования. Это, безусловно, худший вариант. Можем ли мы выполнить проверку в контрольном индикаторе? Да, могли бы, но есть варианты получше. А как насчёт индикатора Chart Trade? Это тоже был бы вполне разумный вариант, хотя у нас всё ещё есть более подходящие альтернативы.

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

Хорошо. После того как решение принято, давайте посмотрим, что нужно сделать, чтобы код индикатора позиции действительно мог «СИМУЛИРОВАТЬ» то, что сделал бы реальный торговый сервер. Вы можете увидеть это в следующем фрагменте.

018. //+------------------------------------------------------------------+
019. struct st00
020. {
021.     ulong   ticket;
022.     string  szShortName,
023.             szSymbol;
024.     double  priceOpen,
025.             var,
026.             sl, tp,
027.             tickSize;
028.     char    digits;
029.     bool    bIsBuy;
030. }m_Infos;
031. //+------------------------------------------------------------------+
                                   .
                                   .
                                   .
071. //+------------------------------------------------------------------+
072. int OnInit()
073. {
074.     ZeroMemory(m_Infos);
075.     Order = new C_InServer();
076.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
077.     if (!CheckCatch(user00))
078.     {
079.         ChartIndicatorDelete(0, 0, def_ShortName);
080.         return INIT_FAILED;
081.     }
082. 
083.     return INIT_SUCCEEDED;
084. }
085. //+------------------------------------------------------------------+
086. int OnCalculate (const int rates_total, const int prev_calculated, const datetime &time[], const double &open[],
087.                  const double &high[], const double &low[], const double &close[], const long &tick_volume[],
088.                  const long &volume[], const int &spread[])
089. {
090.     ProfitNow();
091.     
092.     if ((close[rates_total - 1] == m_Infos.sl) || (close[rates_total - 1] == m_Infos.tp))
093.         EventChartCustom(0, evMsgClosePositionEA, m_Infos.ticket, 0, m_Infos.szSymbol);
094.     
095.     return rates_total;
096. }
097. //+------------------------------------------------------------------+
098. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
099. {
                                   .
                                   .
                                   .
144.             volume = (*Order)._PositionGetDouble(POSITION_VOLUME);
145.             (*Open).UpdatePrice(0, m_Infos.priceOpen = (*Order)._PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var);
146.             (*Take).UpdatePrice(m_Infos.priceOpen, m_Infos.tp = (*Order)._PositionGetDouble(POSITION_TP), volume, m_Infos.var, (*Order)._PositionGetDouble(POSITION_SL));
147.             (*Stop).UpdatePrice(m_Infos.priceOpen, m_Infos.sl = (*Order)._PositionGetDouble(POSITION_SL), volume, m_Infos.var, (*Order)._PositionGetDouble(POSITION_TP));
148.             ProfitNow();

Фрагмент индикатора позиции

Теперь давайте разберём, что делает этот код. В строке 26 я задаю новые значения, которые мы будем использовать в индикаторе. В строке 74 я инициализирую структуру данных нулевыми значениями, чтобы гарантировать, что переменные не содержат мусорных значений. Далее, в строках 146 и 147, мы получаем значения тейк-профита и стоп-лосса. До этого момента мы не сделали ничего особенно сложного. А теперь обратите особое внимание на то, что происходит внутри функции OnCalculate, расположенной в строке 86.

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

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

Вернёмся теперь к строке 92, потому что именно в ней кроется ключ к этим проверкам. Если вы поймёте, что происходит в этой строке, то сможете создать индикатор отложенных ордеров. Кроме того, если вам удастся это реализовать, вы также сможете добиться его корректной работы. Чтобы рассмотреть этот вопрос отдельно, перейдём к новому разделу. Хотя заголовок посвящён другой теме, на самом деле мы разберём, что происходит в строке 92. Поэтому, если вы всё ещё не до конца понимаете, как условие в строке 92 запускает событие в строке 93, будьте очень внимательны.


Типы отложенных ордеров и как заставить их работать

Хотя название этого раздела и подсказывает, что я буду непосредственно рассматривать отложенные ордера, на самом деле мы будем заниматься не совсем этим. Сначала нам нужно понять, что происходит в строке 92 последнего фрагмента предыдущего раздела. Как вы можете заметить, здесь я сравниваю цену закрытия с двумя другими уровнями: тейк-профитом и стоп-лоссом. Что ж. Если любое из этих условий выполняется, срабатывает событие, в результате чего позиция закрывается во время репликации/моделирования. Но что произошло бы, если бы вместо проверки на равенство мы проверяли другое условие? Что бы произошло? Например, предположим, что мы изменим этот индикатор позиции так, чтобы вместо открытой позиции он отображал отложенный ордер. Можем ли мы смоделировать открытие позиции?

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

Это первый тип отложенного ордера: ордер BUY LIMIT. Принцип его работы следующий: когда цена закрытия войдёт в ЗЕЛЁНУЮ зону, то есть когда она станет меньше или равна цене ордера, будет выполнена покупка по рынку. Мы можем смоделировать это поведение с помощью следующей проверки внутри OnCalculate:

092.     if (close[rates_total - 1] <= m_Infos.open)
093.         EventChartCustom(...See the article for details...);

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

Так же, как и в случае с BUY LIMIT, у нас есть новая фигура, показанная ниже.

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

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

092.     if (close[rates_total - 1] >= m_Infos.open)
093.         EventChartCustom(...See the article for details...);

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

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

Однако есть ещё один вопрос, который стоит рассмотреть отдельно, чтобы не запутать вас, уважаемый читатель. Итак, перейдём к новому разделу.


BUY STOP LIMIT и SELL STOP LIMIT

Хорошо. Если вы поняли, как моделировать предыдущие типы, то эти типы будет реализовать ещё проще. Это может показаться сложным, но на самом деле всё не так уж сложно. Вам нужно лишь понять, что именно следует сделать, и выполнить задуманное.

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


На обоих изображениях показано, как с точки зрения сервера устроены ордер BUY STOP LIMIT и ордер SELL STOP LIMIT. На самом деле, если вы разместите такой ордер на графике MetaTrader 5, то увидите, что появятся не три, а четыре линии. Почему? Дело в том, что три из этих четырёх линий представляют один из ордеров, рассмотренных в предыдущем разделе. Четвёртая линия, отображаемая на графике, соответствует уровню срабатывания. При достижении этого уровня срабатывает один из четырёх типов ордеров, которые мы рассмотрели ранее; он может либо исполниться немедленно, либо остаться отложенным. Именно здесь вступает в действие так называемый OFFSET.

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

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

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


Показать историю

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

Значения, равные нулю, указывают на то, что это открытая позиция. После получения данных из записи вам останется лишь добавить на график несколько объектов типов OBJ_ARROW_BUY и OBJ_ARROW_SELL, а также объект OBJ_TREND. Благодаря этому история сделок будет отображаться непосредственно на графике. Вы также можете создать эти объекты в момент закрытия позиции. В любом случае способ создания этих объектов почти не меняется. Всё зависит от того, что вы действительно хотите и что вам нужно разработать.


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

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

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

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

Если вы не знаете, как скомпилировать полный код системы репликации/моделирования, я включу во вложения все приложения и необходимые файлы, чтобы у вас был хотя бы один пример. Таким образом, вам, возможно, будет интересно изучить и научиться программировать те части, которых пока ещё не хватает, но которые были объяснены в этой последней статье. В остальном желаю вам большой удачи. Увидимся в следующей статье, где я начну разбирать другую тему. Возможно, в будущем я обновлю эту систему репликации/моделирования, но только БОГ знает, сделаю ли я это на самом деле. 😂😁👍 Крепко всех обнимаю. Увидимся в следующей статье.

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

Прикрепленные файлы |
Anexo_136.zip (4382.11 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Нейросети в трейдинге: Адаптация прогноза при смене рыночного режима (OpenCL примитивы) Нейросети в трейдинге: Адаптация прогноза при смене рыночного режима (OpenCL примитивы)
Статья продолжает практическую реализацию фреймворка OMPB для адаптации прогнозных моделей к смене рыночного режима. Рассматриваем вычислительную декомпозицию фреймворка и реализуем недостающие OpenCL-примитивы: ограниченный Disagreement, распространение градиента Mismatch и поэлементный расчёт KL-дивергенции для байесовского слоя. Полученные компоненты образуют параллельную GPU-основу без переноса больших промежуточных тензоров на CPU и подготавливают сборку полноценного OMPB-слоя в библиотеке нейронных сетей MQL5.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
От начального до среднего уровня: Перегрузка операторов (V) От начального до среднего уровня: Перегрузка операторов (V)
В этой статье мы рассмотрим, как изменить код, чтобы реализовать решение, совершенно отличающееся от того, что многие считают возможным в MQL5. Важное замечание: чтобы правильно понять данный материал, необходимо хорошо усвоить понятия, рассмотренные в предыдущих статьях.