Моделирование временных рядов с помощью пользовательских символов по заданным законам распределения

Aleksey Zinovik | 14 августа, 2018

Содержание

Введение

Торговый терминал MetaTrader 5 позволяет создавать и использовать в работе пользовательские символы. Трейдер имеет возможность тестировать собственные валютные пары и другие финансовые инструменты. В статье будут предложены способы создания и удаления пользовательских символов, генерации тиков и баров по заданным законам распределения.

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

Создание и удаление пользовательских символов

В статье показан способ создания пользовательских символов в окне "Символы" терминала MetaTrader 5, на основе уже существующих символов. 

Мы предлагаем автоматизировать этот процесс с помощью простого скрипта с минимальными настройками.

Скрипт имеет 4 входных параметра:

  • имя пользовательского символа,
  • краткое имя валютной пары или финансового инструмента,
  • полное имя валютной пары или финансового инструмента,
  • краткое имя базовой валюты или финансового инструмента, если символ создается на основе базового символа.
Приведем код скрипта (скрипт приложен к статье в файле CreateSymbol.mq5):

//+------------------------------------------------------------------+
//|                                                 CreateSymbol.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property script_show_inputs 
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
input string SName="ExampleCurrency";
input string CurrencyName="UCR";
input string CurrencyFullName="UserCurrency";
input string BaseName="EURUSD";
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   ResetLastError();
//cоздаем символ
   if(!CustomSymbolCreate(SName,"\\Forex"))
     {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
         Print("Символ ",SName," уже существует!");
      else
         Print("Ошибка создания символа. Код ошибки: ",GetLastError());
     }
   else
     {
      if(BaseName=="")//создаем новый
        {
         //свойства типа String
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && //базовая валюта
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&                         //валюта прибыли
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"USD",""))&&                         //валюта маржи
            (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyName,""))&&                      //строковое описание символа (полное имя)
            (SetProperty(SName,SYMBOL_BASIS,"","")) &&                                     //имя базового актива для производного инструмента
            (SetProperty(SName,SYMBOL_FORMULA,"","")) &&                                   //формула для построения цены пользовательского символа
            (SetProperty(SName,SYMBOL_ISIN,"","")) &&                                      //имя торгового символа в системе ISIN
            (SetProperty(SName,SYMBOL_PAGE,"","")) &&                                      //адрес интернет страницы с информацией по символу
            //свойсва типа Integer
            (SetProperty(SName,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID,"")) &&             //построение графиковпо цене Bid
            (SetProperty(SName,SYMBOL_SPREAD,3,"")) &&                                     //спред
            (SetProperty(SName,SYMBOL_SPREAD_FLOAT,true,"")) &&                            //плавающий спред
            (SetProperty(SName,SYMBOL_DIGITS,5,"")) &&                                     //точность
            (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,10,"")) &&                           //размер стакана цен
            (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,White,""))&&                        //цвет фона, которым подсвечивается символ в Обзоре рынка
            (SetProperty(SName,SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_FULL,""))&&             //тип исполнения ордеров: полный доступ
            (SetProperty(SName,SYMBOL_TRADE_EXEMODE,SYMBOL_TRADE_EXECUTION_INSTANT,""))&&  //режим заключения сделок: немедленное исполнение
            (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,SYMBOL_ORDERS_GTC,""))&&              //срок действия  StopLoss и TakeProfit ордеров: дейсвтительные до отмены
            (SetProperty(SName,SYMBOL_FILLING_MODE,SYMBOL_FILLING_FOK,""))&&               //режим исполнения ордера: все или ничего
            (SetProperty(SName,SYMBOL_EXPIRATION_MODE,SYMBOL_EXPIRATION_GTC,""))&&         //режим истечения ордера: не ограничено по времени, до явной отмены
            (SetProperty(SName,SYMBOL_ORDER_MODE,127,"")) &&                               //типы ордеров: все типы ордеров
            (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,SYMBOL_CALC_MODE_FOREX,""))&&        //способ вычисления стоимости контракта
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,false,""))&&                   //режим расчета хеджированной маржи по наибольшей стороне
            (SetProperty(SName,SYMBOL_SWAP_MODE,SYMBOL_SWAP_MODE_POINTS,""))&&             //модель расчета свопа: расчет свопа в пунктах
            (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,WEDNESDAY,"")) &&                 //день недели для начисления тройного свопа
            (SetProperty(SName,SYMBOL_OPTION_MODE,0,"")) &&                                //тип опциона
            (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,"")) &&                               //право опциона
            (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,"")) &&                          //минимальный отступ в пунктах от текущей цены закрытия для установки Stop ордеров
            (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,"")) &&                         //дистанция заморозки торговых операций (в пунктах)
            (SetProperty(SName,SYMBOL_START_TIME,0,"")) &&                                 //дата начала торгов по инструменту (обычно используется для фьючерсов)
            (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,"")) &&                            //дата окончания торгов по инструменту (обычно используется для фьючерсов)
            //свойсва типа Double
            (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,"")) &&                              //цена исполнения опциона
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,"")) &&                    //минимально допустимое значение цены на сессию 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,"")) &&                    //максимально допустимое значение цены на сессию
            (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,"")) &&                   //цена поставки на текущую сессию
            (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,"")) &&                     //накопленный купонный доход (для облигаций)
            (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,"")) &&                           //номинальная стоимость (для облигаций)
            (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,"")) &&                       //коэффициент ликвидности (используется для collateral-инструментов)
            (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0.00001,"")) &&                      //минимальное изменение цены
            (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,1,"")) &&                           //стоимость тика
            (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,100000,"")) &&                   //размер торгового контракта
            (SetProperty(SName,SYMBOL_POINT,0.00001,"")) &&                                //значение одного пункта
            (SetProperty(SName,SYMBOL_VOLUME_MIN,0.01,"")) &&                              //минимальный объем для заключения сделки
            (SetProperty(SName,SYMBOL_VOLUME_MAX,500.00,"")) &&                            //максимальный объем для заключения сделки
            (SetProperty(SName,SYMBOL_VOLUME_STEP,0.01,"")) &&                             //минимальный шаг изменения объема для заключения сделки
            (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,"")) &&                               //максимально допустимый для данного символа совокупный объем открытой позиции и отложенных ордеров  в одном направлении (покупка или продажа) 
            (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,"")) &&                             //начальная (инициирующая) маржа 
            (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,"")) &&                         //поддерживающая маржа 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED,100000,"")) &&                         //размер контракта или маржи для одного лота разнонаправленных позиций по одному символу 
            (SetProperty(SName,SYMBOL_SWAP_LONG,-0.7,"")) &&                               //значение свопа в покупку
            (SetProperty(SName,SYMBOL_SWAP_SHORT,-1,"")))                                  //значение свопа в продажу
            Print("Символ ",SName," создан успешно");
         else
            Print("Ошибка в установке свойств символа. Код ошибки: ",GetLastError());
        }
      else//создаем на основе базового
        {
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && 
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyFullName,"")) && 
            (SetProperty(SName,SYMBOL_BASIS,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_FORMULA,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_ISIN,"",BaseName)) && 
            (SetProperty(SName,SYMBOL_PAGE,"",BaseName)) && 

            (SetProperty(SName,SYMBOL_CHART_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SPREAD,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SPREAD_FLOAT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_DIGITS,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_EXEMODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_FILLING_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_EXPIRATION_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_ORDER_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_OPTION_MODE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_START_TIME,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,BaseName)) && 

            (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_POINT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_MIN,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_MAX,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_STEP,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_MARGIN_HEDGED,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_LONG,0,BaseName)) && 
            (SetProperty(SName,SYMBOL_SWAP_SHORT,0,BaseName)))
            Print("Символ ",SName," создан успешно");
         else
            Print("Ошибка в установке свойств символа. Код ошибки: ",GetLastError());
        }
      if(SymbolSelect(SName,true))
         Print("Символ ",SName," выбран в Market Watch");
      else
         Print("Ошибка в выборе символа в Market Watch. Код ошибки: ",GetLastError());
     }
  }
//функция установки свойств символа
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_STRING SProp,string PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetString(SymName,SProp,PropValue))
         return true;
      else
         Print("Ошибка в установке свойства символа: ",SProp," .Код ошибки: ",GetLastError());
     }
   else
     {
      string SValue=SymbolInfoString(BaseSymName,SProp);
      if(CustomSymbolSetString(SymName,SProp,SValue))
         return true;
      else
         Print("Ошибка в установке свойства  символа: ",SProp," .Код ошибки: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_INTEGER IProp,long PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetInteger(SymName,IProp,PropValue))
         return true;
      else
         Print("Ошибка в установке свойства символа: ",IProp," .Код ошибки: ",GetLastError());
     }
   else
     {
      long IValue=SymbolInfoInteger(BaseSymName,IProp);
      if(CustomSymbolSetInteger(SymName,IProp,IValue))
         return true;
      else
         Print("Ошибка в установке свойства символа: ",IProp," .Код ошибки: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SetProperty(string SymName,ENUM_SYMBOL_INFO_DOUBLE DProp,double PropValue,string BaseSymName)
  {
   ResetLastError();
   if(BaseSymName=="")
     {
      if(CustomSymbolSetDouble(SymName,DProp,PropValue))
         return true;
      else
         Print("Ошибка в установке свойства символа: ",DProp," .Код ошибки: ",GetLastError());
     }
   else
     {
      double DValue=SymbolInfoDouble(BaseSymName,DProp);
      if(CustomSymbolSetDouble(SymName,DProp,DValue))
         return true;
      else
         Print("Ошибка в установке свойства символа: ",DProp," .Код ошибки: ",GetLastError());
     }
   return false;
  }
//+------------------------------------------------------------------+

Рассмотрим код скрипта более подробно. Сначала происходит попытка создания символа с помощью функции CustomSymbolCreate:

if(!CustomSymbolCreate(SName,"\\Forex"))
     {
      if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))
         Print("Символ ",SName," уже существует!");
      else
         Print("Ошибка создания символа. Код ошибки: ",GetLastError());
     }

Символ создается в папке Custom/Forex. Если вы хотите создать собственную подпапку (пользовательскую группу) в папке Custom, укажите ее наименование во втором параметре функции CustomSymbolCreate. 

Далее выполняется установка свойств созданного символа. Если параметр BaseName не задан, выполняется установка параметров символа заданных пользователем. Для примера указаны свойства валютной пары EURUSD:

    //свойства типа String
         if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && //базовая валюта
            (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&&                         //валюта прибыли
            (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"UCR",""))&&                         //валюта маржи
...

Для удобства свойства разделены на группы, сначала задаются свойства типа String, потом Integer, а затем Double. В случае успешного задания свойств в журнал записывается сообщение об удачном создании символа, иначе в журнал записывается код ошибки, возникшей при установке свойств символа. 

Если значение параметра BaseName непустое, то свойства создаваемого символа копируются из свойств базового символа, имя которого задано параметром BaseName, например это могут валютные пары EURUSD, USDCAD, GBPUSD и другие.

За установку свойств символа отвечает функция SetProperty, которая описана после кода основой функции скрипта. Данная функция не используется в других скриптах, поэтому мы не стали выносить ее в отдельный подключаемый класс.

Для свойств типа String, Integer и Double созданы отдельные экземпляры функции SetProperty. Для установки свойств пользовательского символа используются функции CustomSymbolSetStringCustomSymbolSetInteger, CustomSymbolSetDouble. Для получения свойств базового символа используются функции SymbolInfoStringSymbolInfoIntegerSymbolInfoDouble

Созданный пользовательский символ после успешной установки свойств выбирается в Обзоре рынка функцией SymbolSelect:

  if(SymbolSelect(SName,true))
         Print("Символ ",SName," выбран в Market Watch");
      else
         Print("Ошибка в выборе символа в Market Watch. Код ошибки: ",GetLastError());

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

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

Попытка удаления символа

Рис. 1. Попытка удаления символа, когда он выбран в Обзоре рынка

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

//+------------------------------------------------------------------+
//|                                                 DeleteSymbol.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property script_show_inputs 
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
input string SName="ExampleCurrency";
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   ResetLastError();
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//если символ существует
     {
      if(!CustomSymbolDelete(SName))//пытаемся удалить
        {
         if(SymbolInfoInteger(SName,SYMBOL_SELECT))//если он выбран в Обзоре рынка
           {
            if(SymbolSelect(SName,false))//Пытаемся отключить и удалить
              {
               if(!CustomSymbolDelete(SName))
                  Print("Ошибка при удалении символа ",SName," Код ошибки: ",GetLastError());
               else
                  Print("Символ ",SName," успешно удален");
              }
            else
              {
               //пытаемся закрыть графики с символом
               int i=0;
               long CurrChart=ChartFirst();
               int i_id=0;
               long ChartIDArray[];
               while(CurrChart!=-1)
                 {
                  //проходим по списку графиков и сохраняем идентификаторы открытых графиков символа SName
                  if(ChartSymbol(CurrChart)==SName)
                    {
                     ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1);
                     ChartIDArray[i_id]=CurrChart;
                     i_id++;
                    }
                  CurrChart=ChartNext(CurrChart);
                 }
               //закрываем все графики символа SName
               for(i=0;i<i_id;i++)
                 {
                  if(!ChartClose(ChartIDArray[i]))
                    {
                     Print("Ошибка при закрытии графика символа ",SName,". Код ошибки: ",GetLastError());
                     return;
                    }
                 }
               //отключаем и удаляем символ 
               if(SymbolSelect(SName,false))
                 {
                  if(!CustomSymbolDelete(SName))
                     Print("Ошибка при удалении символа ",SName," Код ошибки: ",GetLastError());
                  else
                     Print("Символ ",SName," успешно удален");
                 }
               else
                  Print("Ошибка при отключении символа ",SName," в Обзоре рынка. Код ошибки: ",GetLastError());
              }//end else SymbolSelect 
           } //end if(SymbolSelect(SName,false))
         else
            Print("Ошибка при удалении символа ",SName," Код ошибки: ",GetLastError());
        }
      else
         Print("Символ ",SName," успешно удален");
     }
   else
      Print("Символ ",SName," не существует");
  }
//+------------------------------------------------------------------+

Рассмотрим порядок работы скрипта:

  • сначала проверяется наличие символа с именем SName,
  • если символ найден, выполняется попытка удалить символ с помощью функции CustomSymbolDelete,
  • если удалить символ не удалось, пытаемся отключить его в Обзоре рынка с помощью функции SimbolSelect,
  • если отключить символ в Обзоре рынка не удалось, закрываем все открытые графики по символу.
Для этого проходим по всем открытым графикам и сохраняем идентификаторы графиков, открытых по символу с именем SName:
               while(CurrChart!=-1)
                 {
                  //проходим по списку графиков и сохраняем идентификаторы открытых графиков символа SName
                  if(ChartSymbol(CurrChart)==SName)
                    {
                     ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1);
                     ChartIDArray[i_id]=CurrChart;
                     i_id++;
                    }
                  CurrChart=ChartNext(CurrChart);
                 }

Закрываем все графики с идентификаторами, сохраненными в массиве ChartIDArray:

              for(i=0;i<i_id;i++)
                 {
                  if(!ChartClose(ChartIDArray[i]))
                    {
                     Print("Ошибка при закрытии графика символа ",SName,". Код ошибки: ",GetLastError());
                     return;
                    }
                 }
  • после закрытия всех графиков, выполняем попытку повторно отключить символ из Обзора рынка и удаляем символ, иначе в журнал записывается сообщение об ошибке.

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

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

Генерация тиков и баров

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

Приведем код скрипта для генерации баров (файл скрипта GetCandle.mq5):

//+------------------------------------------------------------------+
//|                                                    GetСandle.mq5 |
//|                                                  Aleksey Zinovik |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Aleksey Zinovik"
#property link      ""
#property version   "1.00"
#property script_show_inputs 

#include </Math/Stat/Beta.mqh>
#include </Math/Stat/Binomial.mqh>
#include </Math/Stat/Cauchy.mqh>
#include </Math/Stat/ChiSquare.mqh>
#include </Math/Stat/Exponential.mqh>
#include </Math/Stat/F.mqh>
#include </Math/Stat/Gamma.mqh>
#include </Math/Stat/Geometric.mqh>
#include </Math/Stat/Hypergeometric.mqh>
#include </Math/Stat/Logistic.mqh>
#include </Math/Stat/Lognormal.mqh>
#include </Math/Stat/NegativeBinomial.mqh>
#include </Math/Stat/NoncentralBeta.mqh>
#include </Math/Stat/NoncentralChiSquare.mqh>
#include </Math/Stat/NoncentralF.mqh>
#include </Math/Stat/NoncentralT.mqh>
#include </Math/Stat/Normal.mqh>
#include </Math/Stat/Poisson.mqh>
#include </Math/Stat/T.mqh>
#include </Math/Stat/Uniform.mqh>
#include </Math/Stat/Weibull.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum Distribution
  {
   Beta,
   Binomial,
   Cauchy,
   ChiSquare,
   Exponential,
   F,
   Gamma,
   Geometric,
   Hypergeometric,
   Logistic,
   Lognormal,
   NegativeBinomial,
   NoncentralBeta,
   NoncentralChiSquare,
   NoncentralF,
   NoncentralT,
   Normal,
   Poisson,
   T,
   Uniform,
   Weibull
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
/*input params*/
input string SName="ExampleCurrency";
input datetime TBegin=D'2018.01.01 00:00:00';    //Время начала генерации баров
input datetime TEnd=D'2018.02.01 00:00:00';      //Время окончания генерации баров
input int BarForReplace=1000;                    //Количество баров, после которых выполняется замена баров
input double BaseOCHL=1;                         //Базовое значение цены OCHL
input double dOCHL=0.001;                        //Масштабный коэффицент изменения цены OCHL
input ulong BaseRealVol=10000;                   //Базовое значение объема
input ulong dRealVol=100;                        //Масштабный коэффицент изменения реального объема
input ulong BaseTickVol=100;                     //Базовое значение объема
input ulong dTickVol=10;                         //Масштабный коэффицент изменения тикового объема
input ulong BaseSpread=0;                        //Базовое значение спреда
input ulong dSpread=1;                           //Масштабный коэффицент изменения спреда
input Distribution DistOCHL=Normal;              //Тип распределения для цен OCHL
input Distribution DistRealVol = Normal;         //Тип распределения для реального объема
input Distribution DistTickVol = Normal;         //Тип распределения для тикового объема
input Distribution DistSpread = Uniform;         //Тип распределения для спреда
input bool DiffCandle=false;                     //Генерировать свечи различных типов
input double DistOCHLParam1=0;                   //Параметр 1 распределения для цен OCHL
input double DistOCHLParam2=1;                   //Параметр 2 распределения для цен OCHL
input double DistOCHLParam3=0;                   //Параметр 3 распределения для цен OCHL
input double DistRealParam1=0;                   //Параметр 1 распределения для реального объема
input double DistRealParam2=1;                   //Параметр 2 распределения для реального объема
input double DistRealParam3=0;                   //Параметр 3 распределения для реального объема
input double DistTickParam1=0;                   //Параметр 1 распределения для тикового объема
input double DistTickParam2=1;                   //Параметр 2 распределения для тикового объема
input double DistTickParam3=0;                   //Параметр 3 распределения для тикового объема
input double DistSpreadParam1=0;                 //Параметр 1 распределения для спреда
input double DistSpreadParam2=50;                //Параметр 2 распределения для спреда
input double DistSpreadParam3=0;                 //Параметр 3 распределения для спреда
input bool FiveDayOfWeek=true;                   //true - не формировать тики в выходные дни
/*----input params----*/
int i_bar=0;                                     //счетчик ,минутных баров
MqlRates MRatesMin[];                            //массив для хранения баров барами
MqlDateTime  StructCTime;                        //структура для работы с временем
int DistErr=0;                                   //номер ошибки
bool IsErr=false;                                //ошибка
double DistMass[4];                              //массив для хранения сгенерированных значений OCHL
int ReplaceBar=0;                                //количество замененных баров
double BValue[1];                                //массив для копирования последней цены Close
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   int i=0;                                      //счетчик цикла
   double MaxVal,MinVal;                         //значения High и Low
   int i_max,i_min;                              //индексы наибольшего и наименьшего значения массива DistMass 
   datetime TCurrent=TBegin;
   BValue[0]=BaseOCHL;
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//если символ существует
     {
      while(TCurrent<=TEnd)
        {
         if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6)))
              {
               if(StructCTime.day_of_week==0)
                  TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               else
                  TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               if(TCurrent>=TEnd)
                 {
                  if(ReplaceBar==0)
                     Print("В заданном диапазоне нет торгов");
                  return;
                 }
              }
           }
         ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
         MRatesMin[i_bar].open=0;
         MRatesMin[i_bar].close=0;
         MRatesMin[i_bar].high=0;
         MRatesMin[i_bar].low=0;
         //заполняем Open      
         if(i_bar>0)
            MRatesMin[i_bar].open=MRatesMin[i_bar-1].close;
         else
           {
            if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1))
               MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            else
               MRatesMin[i_bar].open=BValue[0];
           }
         //генерируем цены High, Low
         MaxVal=2.2250738585072014e-308;
         MinVal=1.7976931348623158e+308;
         i_max=0;
         i_min=0;
         for(i=0;i<3;i++)
           {
            DistMass[i]=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            if(IsErrCheck(DistErr)) return;
            if(MaxVal<DistMass[i])
              {
               MaxVal=DistMass[i];
               i_max=i;
              }
            if(MinVal>DistMass[i])
              {
               MinVal=DistMass[i];
               i_min=i;
              }
           }
         if(MaxVal<MRatesMin[i_bar].open)
            MRatesMin[i_bar].high=MRatesMin[i_bar].open;
         else
            MRatesMin[i_bar].high=MaxVal;
         if(MinVal>MRatesMin[i_bar].open)
            MRatesMin[i_bar].low=MRatesMin[i_bar].open;
         else
            MRatesMin[i_bar].low=MinVal;
         //заполняем Close
         for(i=0;i<3;i++)
            if((i!=i_max) && (i!=i_min))
              {
               MRatesMin[i_bar].close=DistMass[i];
               break;
              }
         //генерируем объем, спред
         MRatesMin[i_bar].real_volume=(long)(BaseRealVol+dRealVol*GetDist(DistRealVol,DistRealParam1,DistRealParam2,DistRealParam3));
         if(IsErrCheck(DistErr)) return;
         MRatesMin[i_bar].tick_volume=(long)(BaseTickVol+dTickVol*GetDist(DistTickVol,DistTickParam1,DistTickParam2,DistTickParam3));
         if(IsErrCheck(DistErr)) return;
         MRatesMin[i_bar].spread=(int)(BaseSpread+dSpread*GetDist(DistSpread,DistSpreadParam1,DistSpreadParam2,DistSpreadParam3));
         if(IsErrCheck(DistErr)) return;
         //записываем время
         MRatesMin[i_bar].time=TCurrent;
         if(DiffCandle)
           {
            i=MathRand()%5;
            switch(i)
              {
               case 0://Доджи
                 {
                  MRatesMin[i_bar].close=MRatesMin[i_bar].open;
                  break;
                 }
               case 1://Молот
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                    }
                  break;
                 }
               case 2://Звезда
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                    }
                  break;
                 }
               case 3://Марибозу
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                    {
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                    }
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                       {
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                       }
                    }
                  break;
                 }
               default: break;
              }
           }
         //проверяем не пора ли заменять бары  
         if(i_bar>=BarForReplace-1)
           {
            ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time);
            TCurrent=TCurrent+60;
            BValue[0]=MRatesMin[i_bar].close;
            i_bar=0;
            ArrayFree(MRatesMin);
           }
         else
           {
            i_bar++;
            TCurrent=TCurrent+60;
           }
        }
      if(i_bar>0)
        {
         i_bar--;
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time);
        }
     }
   else
      Print("Символ ",SName," не существует");
  }
//+------------------------------------------------------------------+

void ReplaceHistory(datetime DBegin,datetime DEnd)
  {
   ReplaceBar=CustomRatesReplace(SName,DBegin,DEnd,MRatesMin);
   if(ReplaceBar<0)
      Print("Ошибка замены баров. Код ошибки: ",GetLastError());
   else
      PrintFormat("Ценовая история за период: %s по %s сформирована успешно. Создано %i баров, добавлено (заменено) %i баров",TimeToString(DBegin),TimeToString(DEnd),i_bar+1,ReplaceBar);
  }
//+------------------------------------------------------------------+

double GetDist(Distribution d,double p1,double p2,double p3)
  {
   double res=0;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
   switch(d)
     {
/*Бета распределение*/
      //p1,p2 - первый и второй параметр 
      case Beta: {res=MathRandomBeta(p1,p2,DistErr); break;}
/*Биноминальное распределение*/
      //p1 - количество испытаний, p2 - вероятность успеха для каждого испытания    
      case Binomial: {res=MathRandomBinomial(p1,p2,DistErr); break;};
/*Распределение Коши*/
      //p1 - коэффициент сдвига, p2 - коэффициент масштаба
      case Cauchy: {res=MathRandomCauchy(p1,p2,DistErr); break;};
/*Распределение Хи-квадрат*/
      //p1 - число степеней свободы
      case ChiSquare: {res=MathRandomChiSquare(p1,DistErr); break;};
/*Экспоненциальное распределение*/
      //p1 - параметр(лямбда) распределения 
      case Exponential: {res=MathRandomExponential(p1,DistErr); break;};
/*Распределение Фишера*/
      //p1, p2 - числа степеней свободы
      case F: {res=MathRandomF(p1,p2,DistErr); break;};
/*Гамма распределение*/
      //p1 - параметр распределения(целое число), p2 - коэффициент масштаба
      case Gamma: {res=MathRandomGamma(p1,p2,DistErr); break;};
/*Геометрическое распределение*/
      //p1 - вероятность успеха (появления события в опыте)
      case Geometric: {res=MathRandomGeometric(p1,DistErr); break;};
/*Гипергеометрическое распределение*/
      //p1 - общее количество объектов, p2 - количество объектов с желаемой характеристикой, p3 - количество объектов в выборке
      case Hypergeometric: {res=MathRandomHypergeometric(p1,p2,p3,DistErr); break;};
/*Логистическое распределение*/
      //p1 - математическое ожидание,p2 - коэффициент масштаба 
      case Logistic: {res=MathRandomLogistic(p1,p2,DistErr); break;};
/*Логнормальое распределение*/
      //p1 -логарифм математического ожидания,p2 - логорифм среднеквадратичного отклонения
      case Lognormal: {res=MathRandomLognormal(p1,p2,DistErr); break;};
/*Отрицательное биноминальное распределение*/
      //p1 - количество успешных испытаний, p2 - вероятность успеха  
      case NegativeBinomial: {res=MathRandomNegativeBinomial(p1,p2,DistErr); break;};
/*Нецентральное Бета распределение*/
      //p1,p2 - первый и второй параметр, p3 - параметр нецентральности       
      case NoncentralBeta: {res=MathRandomNoncentralBeta(p1,p2,p3,DistErr); break;};
/*Нецентральное распределение Хи-квадрат*/
      //p1 - число степеней свободы, p2 - параметр нецентральности
      case NoncentralChiSquare: {res=MathRandomNoncentralChiSquare(p1,p2,DistErr); break;};
/*Нецентральное F распределение*/
      //p1, p2 - числа степеней свободы, p3 - параметр нецентральности
      case NoncentralF: {res=MathRandomNoncentralF(p1,p2,p3,DistErr); break;};
/*Нецентральное T распределение*/
      //p1 - число степеней свободы, p2 - параметр нецентральности
      case NoncentralT: {res=MathRandomNoncentralT(p1,p2,DistErr); break;};
/*Нормальное распределение*/
      //p1 - математическое ожидание, p2 - среднеквадратичное отклонение
      case Normal: {res=MathRandomNormal(p1,p2,DistErr); break;};
/*Распределение Пуассона*/
      //p1 - математическое ожидание
      case Poisson: {res=MathRandomPoisson(p1,DistErr); break;};
/*Распределение Стьюдента*/
      //p1 - число степеней свободы
      case T: {res=MathRandomT(p1,DistErr); break;};
/*Равномерное распределение*/
      //p1 - нижняя граница диапазона, p2 - верхняя граница диапазона
      case Uniform: {res=MathRandomUniform(p1,p2,DistErr); break;};
/*Распределение Вейбулла*/
      //p1 - параметр формы, p2 - параметр масштаба
      case Weibull: {res=MathRandomWeibull(p1,p2,DistErr); break;};
     }
   if(DistErr!=0)
      return -1;
   else
      return res;
  }
//+------------------------------------------------------------------+
bool IsErrCheck(int Err)
  {
//проверяем на ошибку при генерации псевдослучайных чисел
   switch(DistErr)
     {
      case(1):
        {
         MessageBox("Заданные параметры распределения не являются действительными числами","Ошибка входных параметров",MB_ICONWARNING);
         return true;
        }
      case(2):
        {
         MessageBox("Заданные параметры распределения являются недопустимыми","Ошибка входных параметров",MB_ICONWARNING);
         return true;
        }
      case(4):
        {
         MessageBox("Ошибка деления на ноль","Ошибка входных параметров",MB_ICONWARNING);
         return true;
        }
     }
   return false;
  }
//+------------------------------------------------------------------+

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

С помощью псевдослучайных чисел генерируются цены Close, High, Low, реальный объем, тиковый объем, спред по следующей формуле:

form1 (1)

где P(i) - значение параметра, Base - базовое значение параметра, step - масштабный коэффициент (шаг) изменения псевдослучайной величины, DistValue(i) - сгенерированная псевдослучайная величина, распределенная по заданному закону. Параметры Base и step задаются пользователем. В качестве примера приведем код для формирования значения цены открытия:

         if(i_bar>0)
            MRatesMin[i_bar].open=MRatesMin[i_bar-1].close;
         else
           {
            if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1))
               MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits);
            else
               MRatesMin[i_bar].open=BValue[0];
           }

Если до запуска скрипта бары отсутствуют или по какой-то причине функция CopyClose не смогла скопировать последний бар ценовой истории, цена открытия первого бара формируется по описанной выше формуле:

Для последующих баров цена Open равна предыдущей цене Close, т.е. новый бар открывается на закрытии предыдущего. 

За генерацию значения псевдослучайной величины отвечает функция GetDist, на вход которой поступает тип распределения и значения его параметров.

Для обработки ошибок, возникающих при генерации псевдослучайной величины, создана функция IsErrCheck. На вход функции поступает код ошибки, который определяется при выполнении функции GetDist. Если  возникнет ошибка, выполнения скрипта прерывается, в журнал запишется сообщение об ошибке. Код функций GetDist и IsErrCheck приведен в конце скрипта.   

Скрипт генерирует минутные тики и обладает следующими особенностями:

1) Тики генерируются только в заданном диапазоне времени, имеется возможность не генерировать тики в выходные дни (входной параметр FiveDayOfWeek=true)

Отключение генерации тиков в выходные дни реализовано в следующем коде:

if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6)))
              {
               if(StructCTime.day_of_week==0)
                  TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               else
                  TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec);
               if(TCurrent>=TEnd)
                 {
                  if(ReplaceBar==0)
                     Print("В заданном диапазоне нет торгов");
                  return;
                 }
              }
           }

Если текущее время попадает на выходной день, оно сдвигается до следующего торгового дня. 

2) Скрипт позволяет заменять бары частями, освобождая память после замены сгенерированных баров

Количество баров, после замены  которых массив сгенерированных тиков обнуляется, задается в переменой BarForReplace. Замена баров реализована в слудеющем коде:

if(i_bar>=BarForReplace)
           {
            ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
            i_bar=0;
            ArrayFree(MRatesMin);
           }

Для замены баров создана функция ReplaceHistory, код которой приведен в конце скрипта. Замена баров выполняется функцией CustomRatesReplace. После замены баров счетчик баров обнуляется и освобождается буфер динамического массива MRatesMin, хранящего созданные бары. 

3) Скрипт позволяет генерировать свечи различных типов

Генерация различных типов свечей реализована следующим образом (параметр DiffCande = true):

 if(DiffCandle)
           {
            i=MathRand()%5;
            switch(i)
              {
               case 0://Доджи
                 {
                  MRatesMin[i_bar].close=MRatesMin[i_bar].open;
                  break;
                 }
               case 1://Молот
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                    }
                  break;
                 }
               case 2://Звезда
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                    }
                  break;
                 }
               case 3://Марибозу
                 {
                  if(MRatesMin[i_bar].open>MRatesMin[i_bar].close)
                    {
                     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
                     MRatesMin[i_bar].low=MRatesMin[i_bar].close;
                    }
                  else
                    {
                     if(MRatesMin[i_bar].open<MRatesMin[i_bar].close)
                       {
                        MRatesMin[i_bar].high=MRatesMin[i_bar].close;
                        MRatesMin[i_bar].low=MRatesMin[i_bar].open;
                       }
                    }
                  break;
                 }
               default: break;
              }
           }

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

Продемонстрируем работу скрипта, для этого запустим скрипт со следующими входными параметрами:

input

Рис. 1. Входные параметры скрипта

В результате работы скрипта появится новый символ ExampleCurrency. В процессе работы скрипта сгенерирован 33121 минутный бар. На рисунке 2 приведен фрагмент  минутного графика символа ExampleCurrency.

ExChart

Рис. 2. Минутный график символа ExampleCurrency

Иногда минутных баров может быть недостаточно для тестирования советника или индикатора и тестирования выполняется на реальных или смоделированных тиках. 

Рассмотрим скрипт, моделирующий тики и формирующий минутные бары на основе смоделированных тиков. Полный код скрипта приведен в файле GetTick.mq5, приложенном к статье. Приведем и рассмотрим код функции OnStart():

void OnStart()
  {
   if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//если символ существует
     {
      MqlDateTime  StructCTime;
      long TBeginMSec=(long)TBegin*1000;      //время начала генерации тиков в мс
      long TEndMSec=(long)TEnd*1000;          //время окончания генерации тиков в мс
      int ValMsec=0;                          //переменная для генерации случайного сдвига по времени в мс
      int SumSec=0;                           //счетчик секунд
      int SumMSec=0;                          //счетчик миллисекунд
      int PrevTickCount=0;                    //переменная для хранения предыдущего количесвто тиков в минуте      
      datetime TCurrent=TBegin;
      bool   NewMinute=false;
       //скопируем значение цены, с которой будет начинаться генерация тиков
      if(CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1)
         BValue[0]=Base;
      //заполняем структуру LastTick
      LastTick.ask=BValue[0];
      LastTick.bid=BValue[0];
      LastTick.last=BValue[0];
      LastTick.volume=baseVol;

      while(TBeginMSec<=TEndMSec)
        {
         if(FiveDayOfWeek)
           {
            TimeToStruct(TCurrent,StructCTime);
            if((StructCTime.day_of_week==0) || (StructCTime.day_of_week==6))
              {
               if(StructCTime.day_of_week==0)
                 {
                  TCurrent=TCurrent+86400;
                  TBeginMSec=TBeginMSec+86400000;
                 }
               else
                 {
                  TCurrent=TCurrent+2*86400;
                  TBeginMSec=TBeginMSec+2*86400000;
                 }
               if(TBeginMSec>=TEndMSec)
                  break;
              }
           }
         GetTick(TCurrent,TBeginMSec);
         if(IsErrCheck(DistErr)) return;
         i_tick++;

         if(RandomTickTime)
           {
            //генерируем случайный сдвиг времени
            
            ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);
            SumSec=SumSec+ValMsec;
            SumMSec=SumMSec+ValMsec;
            if(i_tick-PrevTickCount>=MaxTickInMinute)
              {
               TimeToStruct(TCurrent,StructCTime);
               StructCTime.sec=0;
               TCurrent=StructToTime(StructCTime)+60;
               TBeginMSec=TBeginMSec+60000-SumSec+ValMsec;
               SumSec=0;
               SumMSec=0;
               NewMinute=true;
              }
            else
              {
               if(SumSec>=60000)
                 {
                  //обнулим счетчик тиков в минуту
                  SumSec=SumSec-60000*(SumSec/60000);
                  NewMinute=true;
                 }
               //сформирование новое время тика    
               TBeginMSec=TBeginMSec+ValMsec;
               if(SumMSec>=1000)
                 {
                  TCurrent=TCurrent+SumMSec/1000;
                  SumMSec=SumMSec-1000*(SumMSec/1000);
                 }
              }
           }
         else
           {
            TBeginMSec=TBeginMSec+60000/MaxTickInMinute;
            SumSec=SumSec+60000/MaxTickInMinute;
            SumMSec=SumMSec+60000/MaxTickInMinute;
            if(SumMSec>=1000)
              {
               TCurrent=TCurrent+SumMSec/1000;
               SumMSec=SumMSec-1000*(SumMSec/1000);
              }
            if(SumSec>=60000)
              {
               SumSec=SumSec-60000*(SumSec/60000);
               NewMinute=true;
              }
           }
         if(NewMinute)
           {
            //добавим новый бар в массив 
            ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
            if(ArraySize(MRatesMin)==1)//если первая минута
              {
               MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick;
              }
            else
              {
               MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount;
              }
            MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);
            if(ValHigh>MRatesMin[i_bar].open)
               MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits);
            else
               MRatesMin[i_bar].high=MRatesMin[i_bar].open;
            if(ValLow<MRatesMin[i_bar].open)
               MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits);
            else
               MRatesMin[i_bar].low=MRatesMin[i_bar].open;
            MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume;
            MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);
            TimeToStruct(MTick[i_tick-1].time,StructCTime);
            StructCTime.sec=0;
            MRatesMin[i_bar].time=StructToTime(StructCTime);
            i_bar++;
            PrevTickCount=i_tick;
            ValHigh=2.2250738585072014e-308;
            ValLow=1.7976931348623158e+308;
            NewMinute=false;
            if(i_bar>=BarForReplace)
              {
               ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
               LastTick.bid=MTick[i_tick-1].bid;
               LastTick.ask=MTick[i_tick-1].ask;
               LastTick.last=MTick[i_tick-1].last;
               LastTick.volume=MTick[i_tick-1].volume;
               i_tick=0;
               i_bar=0;
               PrevTickCount=0;
               ArrayFree(MRatesMin);
               ArrayFree(MTick);
              }
           }
        }//end while
      if(i_bar>0)
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
     }
   else
      Print("Символ ",SName," не существует");
  }

В начале функции OnStart() выполняется инициализация переменных. Генерация тиков и баров выполняется в основном цикле работы скрипта:

while(TBeginMSec<=TEndMSec)
{
...
}

В начале цикла в случае, если параметр FiveDayOfWeek = true, выполняется отключение генерации тиков в выходные дни, время тика при этом сдвигается на 1 день (если время тика соответствует воскресенью) или 2 дня (если время тика соответствует субботе):

if(FiveDayOfWeek)
{
...
}

Далее выполняется генерация тика с помощью функции GetTick:

 GetTick(TCurrent,TBeginMSec);
	if(IsErrCheck(DistErr)) return;
 	i_tick++;

Если при генерации тика возникла ошибка (значение функции IsErrCheck = true), выполнение скрипта прервется. Функция IsErrCheck описана выше в коде скрипта GetCandle.

Рассмотрим функцию  GetTick:

void GetTick(datetime TDate,long TLong)
  {
   ArrayResize(MTick,ArraySize(MTick)+1);
//заполняем новое время  
   MTick[i_tick].time=TDate;
   MTick[i_tick].time_msc=TLong;
//заполняем текущий тик значениями предыдущего
   if(ArraySize(MTick)>1)
     {
      MTick[i_tick].ask=MTick[i_tick-1].ask;
      MTick[i_tick].bid=MTick[i_tick-1].bid;
      MTick[i_tick].volume=MTick[i_tick-1].volume;
      MTick[i_tick].last=MTick[i_tick-1].last;
     }
   else
     {
      MTick[i_tick].ask=LastTick.ask;
      MTick[i_tick].bid=LastTick.bid;
      MTick[i_tick].last=LastTick.last;
      MTick[i_tick].volume=LastTick.volume;
     }
//заполняем текущий тик  
   if(RandomTickValue)
     {
      double RBid=MathRandomUniform(0,1,DistErr);
      double RAsk=MathRandomUniform(0,1,DistErr);
      double RVolume=MathRandomUniform(0,1,DistErr);
      if(RBid>=0.5)
        {
         if(i_tick>0)
            MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
         MTick[i_tick].last=MTick[i_tick].bid;
         MTick[i_tick].flags=10;
         if(RAsk>=0.5)
           {
            MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
            MTick[i_tick].flags=MTick[i_tick].flags+4;
           }
         if(RVolume>=0.5)
           {
            MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
            MTick[i_tick].flags=MTick[i_tick].flags+16;
           }
        }
      else
        {
         if(RAsk>=0.5)
           {
            MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
            MTick[i_tick].flags=4;
            if(RVolume>=0.5)
              {
               MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
               MTick[i_tick].flags=MTick[i_tick].flags+16;
              }
           }
        }
     }//end if(RandomTickValue)
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
   else
     {
      MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
      MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep;
      MTick[i_tick].last=MTick[i_tick].bid;
      MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol);
      MTick[i_tick].flags=30;
     }//end if(RandomTickValue)  
  //сохраняем наибольшее и наименьшее значение для формирования минутных баров  
   if(MTick[i_tick].bid>ValHigh)
      ValHigh=MTick[i_tick].bid;
   if(MTick[i_tick].bid<ValLow)
      ValLow=MTick[i_tick].bid;
  }//end 

На вход функции поступает время тика в формате datetime и в миллисекундах. Сначала текущий тик заполняется значениями предыдущего, далее значения текущего тика будут изменены следующим образом:

1) Если значение параметра RandomTickValue = true, каждый из параметров Ask, Bid и Volume будет изменен с вероятностью 0.5. Для этого генерируются 3 равномерно распределенные случайные величины:

      double RBid=MathRandomUniform(0,1,DistErr);
      double RAsk=MathRandomUniform(0,1,DistErr);
      double RVolume=MathRandomUniform(0,1,DistErr);

Если RBid>0.5, RAsk>0.5 - цены Bid и/или Ask изменяется по формуле (1), описанной выше для скрипта GetCandle. Изменение объема по формуле (1) выполняется, если изменилась цена Ask или Bid и параметр RVolume > 0.5. Цене Last присваивается значение цены Bid при ее изменении.

2) Если значение параметра RandomTickValue = false, значения параметров Ask, Bid и Volume вычисляются по формуле (1). 

Значение флага тиков (flags) устанавливается следующим образом:

  • изменена цена Bid - flags=flags+2
  • изменена цена Ask - flags=flags+4
  • изменена цена Last - flags=flags+8
  • изменена цена Volume - flags=flags+16

После изменения цен Ask, Bid или Volume в переменные ValHigh и ValLow сохраняются значения максимальной и минимальной цены Bid. Значение переменных ValHigh и ValLow используются для формирования цен High и Low минутного бара. 

Продолжим рассмотрения кода функции OnStart().

После генерации текущего тика выполняется формирование времени появления нового тика:

1) Если параметр RandomTickTime = true, новое время тика формируется следующим образом:

ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);

Далее выполняется проверка наступления нового минутного бара, а также корректировка текущего времени на сформированное количество секунд. Количество тиков, которой может сформироваться в течение одного минутного бара, ограничено переменной MaxTickInMinute. Если число сгенерированных тиков превышает значение переменой MaxTickInMinute, то счетчики секунд (SumSec) и миллисекунд (SumMSec) обнуляются и формируется новый минутный бар (NewMinute = true). 

2) Если параметр RandomTickTime = false, то в каждом минутном баре генерируются одинаковое количество тиков, заданное в переменной  MaxTickInMinute.

Формирование минутного бара на основе сгенерированных тиков происходит следующим образом:

  • массив минутных баров увеличиваться на 1

 ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);

  • формируется значение цены открытия текущего бара и тикового объема:

            if(ArraySize(MRatesMin)==1)//если первая минута
              {
               MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick;
              }
            else
              {
               MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits);
               MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount;
              }

При формировании первого минутного бара цене открытия присваивается цена Bid последнего тика из предыдущей замены тиков (замена тиков и баров выполняется функцией ReplaceHistory) или базовое значение цены Bid (параметр Base), если замена тиков и баров ранее не выполнялась. При формировании последующих минутных баров цене открытия присваивается нормализованное значение цены Bid последнего тика (цены закрытия) предыдущей минуты. Под нормализацией понимается округление до точности измерения цены символа текущего графика, на котором запущен скрипт. 

  • формируется значение цены Close - нормализованная цена Bid последнего тика:

MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);

  • формируются значение цен High и Low. При этом учитывается, что значение цены открытия может быть больше наибольшего (ValHigh) или меньше наименьшего(ValLow) значения цены Bid сгенерированных тиков:

 if(ValHigh>MRatesMin[i_bar].open)
     MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits);
 else
     MRatesMin[i_bar].high=MRatesMin[i_bar].open;
 if(ValLow<MRatesMin[i_bar].open)
     MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits);
 else
     MRatesMin[i_bar].low=MRatesMin[i_bar].open;

MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume;
MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);

Объему текущего бара присваивается значение объема последнего тика, спред вычисляется как разность между ценами Bid и Ask последнего тика. 

TimeToStruct(MTick[i_tick-1].time,StructCTime);
StructCTime.sec=0;
MRatesMin[i_bar].time=StructToTime(StructCTime);

На этом процесс формирования параметров текущего минутного бара заканчивается, увеличивается счетчик минутных баров (переменная i_bar), переменным ValHigh и ValLow присваиваются минимальное и максимальное значения типа данных double, сбрасывается флаг минутного бара (NewMinute). Далее проверятся, не пора ли заменить сформированное количество минутных баров и тиков. Количество баров, после формирования которых выполняется их замена, устанавливается в переменной BarForReplace

После выхода из основного цикла выполняется замена оставшихся баров, если такие имеются:

     if(i_bar>0)
         ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);

В функции ReplaceHistory реализована замена тиков и баров. Замену тиков выполняет функция CustomTicksReplace, замену баров функция CustomRatesReplace.

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

Моделирование тренда

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

Для этих целей был написаны скрипты GetCandleTrend (приложен к статье в файле GetCandleTrend.mq5) и GetTickTrend (приложен к статье в файле GetTickTrend.mq5) позволяющие моделировать возрастающие или убывающие тренды по заданному закону движение цен. Скрипт GetCandleTrend предназначен для генерации возрастающих или убывающих минутных баров, скрипт GetTickTrend генерирует возрастающие или убывающие тики, затем, аналогично скрипту GetCandleTrend формирует минутные бары. 

Рассмотрим работу скрипта GetCandleTrend. Формирование минутных баров происходит аналогично скрипту GetCandle, поэтому рассмотрим только способ формирования тренда. Входные данные скрипта содержат следующие параметры тренда:

input TrendModel TModel = Linear;                //Модель тренда
input TrendType TType =  Increasing;             //Тип тренда (возрастающий/убывающий,случайный)
input double RandomTrendCoeff=0.5;               //Трендовый коэффицент(при RandomTrendCoeff<0.5 преобладает нисходящий тренд, при RandomTrendCoeff>0.5 - восходящий)
input double Coeff1=0.1;                         //Коэффициент k1 трендовой модели
input double Coeff2=0.1;                         //Коэффициент k2 трендовой модели
input double Coeff3=0.1;                         //Коэффициент k3 трендовой модели
input int CountCandle=60;                        //Интервал случайного изменения направления тренда (в барах)

Пользователю предлагается выбрать модель тренда, заданную в перечислении TrendModel:

enum TrendModel
  {
   Linear,
   Hyperbolic,
   Exp,
   Power,
   SecondOrderPolynomial,
   LinearAndPeriodic,
   LinearAndStochastic
  };

и тип тренда, заданный в перечислении TrendType:

enum TrendType
  {
   Increasing,
   Decreasing,
   Random
  };

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

Модель трендаФормула
ЛинейнаяLinear
Гиперболическаяf2
Экспоненциальнаяf3
Степеннаяf4
Параболическаяf5
Линейно-периодическаяf6
Линейно-стохастическаяf7

T(i) - текущее значение тренда, k1, k2, k3 - коэффициенты, влияющие на скорость роста(убывания) тренда, N(0,1) - случайная величина, распределенная по нормальному закону с нулевым математическим ожиданием и единичной дисперсией.

В качестве типа тренда предлагается выбрать возрастающий, убывающий или случайный тренд. Под случайным трендом понимается тренд, направление которого меняется через заданное количество свечей (количество свечей устанавливается параметром CountCandle). 

Формирование тренда реализовано в функции ChooseTrend:

double ChooseTrend()
  {
   switch(TType)
     {
      case 0: return NormalizeDouble(BValue[0]+dOCHL*(GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
      case 1: return NormalizeDouble(BValue[0]+dOCHL*(-GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
      case 2:
        {
         if((i_trend%CountCandle==0) && (i_trend!=0))
           {
            if(i_bar!=0)
               BValue[0]=MRatesMin[i_bar-1].close;
            LastRand=MathRandomUniform(0,1,DistErr);
            i_trend=0;
           }
         if(LastRand>RandomTrendCoeff)
            return NormalizeDouble(BValue[0]+dOCHL*(GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
         else
            return NormalizeDouble(BValue[0]+dOCHL*(-GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits);
        }
      default:return 0;
     }
  }

При случайном тренде, за каждые N минутных свечей, заданных параметром CountCandle, генерируются случайная величина LastRand, равномерно распределенная в интервале от 0 до 1. Если значение LastRand больше параметра RandomTrendCoeff, тренд будет возрастающим, иначе убывающим. Параметр RandomTrendCoeff позволяет варьировать вероятность изменения тренда. Если RandomTrendCoeff<0.5 будет преобладать возрастающий тренд, если RandomTrendCoeff>0.5 - убывающий. 

Формирование тренда заданной модели реализовано в функции GetModelTrend:

double GetModelTrend()
  {
   switch(TModel)
     {
      case Linear: return Coeff1+Coeff2*i_trend;
      case Hyperbolic:
        {
         if(i_trend==0)
            return Coeff1;
         else
            return Coeff1+Coeff2/i_trend;
        }
      case Exp:
        {
         if(i_trend==0)
            return Coeff1;
         else
            return Coeff1+MathExp(Coeff2*i_trend);
        }
      case Power:return Coeff1+MathPow((double)i_trend,Coeff2);
      case SecondOrderPolynomial:return Coeff1+Coeff2*i_trend+Coeff3*i_trend*i_trend;
      case LinearAndPeriodic: return Coeff1*i_trend+sin(Coeff2*i_trend)+cos(Coeff3*i_trend);
      case LinearAndStochastic:
        {
         LastValue=Coeff1*i_trend+MathSqrt(Coeff2*(1-MathPow(exp(-Coeff3),2)))*MathRandomNormal(0,1,DistErr)+exp(-Coeff3)*LastValue;
         return LastValue;
        }
      default:
         return -1;
     }
  }

В данной функции реализованы модели тренда, приведеные в таблице 1.

Рассмотрим формирование ценовых графиков по различным трендовым моделям. Сформируем линейный тренд, для этого запустим скрипт GetTrendCandle со следующими параметрами:

input2

Рис. 3. Параметры скрипта GetTrendCandle

После выполнения скрипта откроем минутный график символа ExampleCurrency:

Linear

Рис. 4. Минутный график символа ExampleCurrency для линейной модели тренда

Из графика видим, что сгенерировался линейный тренд, варьируя коэффициенты k1 и k2 можно изменять угол наклона (скорость возрастания/убывания) тренда. 

В параметрах скрипта укажем гиперболическую модель тренда: TModel = Hyperbolic, Coeff1 = 1, Coeff2 = 1000. Запустив скрипт, получим следующий график:

Params

Рис. 4. Минутный график символа ExampleCurrency для гиперболической модели тренда

Особенностью данной модели является следующие: из-за того, что гиперболическая функция относится к классу обратных функций при выборе возрастающего тренда (TType = Increasing) - тренд будет убывающим. 

Рассмотрим экспоненциальную модель тренда: TModel =Exp, Coeff1 = 1, Coeff2 = 0,1. Запустив скрипт, получим следующий график:

exp

Рис. 5. Минутный график символа ExampleCurrency для экспоненциальной модели тренда

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

Рассмотрим другие модели тренда:

Степенная модель тренда:  TModel =Power, Coeff1 = 1, Coeff2 = 2. 

Power

Рис. 6. Минутный график символа ExampleCurrency для степенной модели тренда

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

Параболическая модель тренда:  TModel = SecondOrderPolynomial, Coeff1 = 1, Coeff2 = 0,05, Coeff3 = 0,05. 

Parab

Рис. 7. Минутный график символа ExampleCurrency для параболической модели тренда

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

Линейно-периодическая модель тренда:  TModel = LinearAndPeriodic, Coeff1 = 0.05, Coeff2 = 0,1, Coeff3 = 0,1.

Periodic

Рис. 8. Минутный график символа ExampleCurrency для линейно-периодической модели тренда 

В линейно-периодической модели тренд, возрастая или убывая, меняет свое направление по периодическому закону. 

Линейно-стохастическая модель тренда:  TModel = LinearAndStochastic, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1.

Stoh

Рис. 8. Минутный график символа ExampleCurrency для линейно-стохастической модели тренда 

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

Рассмотренные выше трендовые модели обладают заданными свойствами только на минутных интервалах, на интервалах М15, М30, H1 и выше ценовые графики, сформированные по этим моделям, выглядят как линейные функции. Для того, чтобы получить тренд, меняющий свое направление на других интервалах, кроме M1, необходимо выбрать случайный тип тренда (TType =  Random) и указать количество минутных свечей, через которые будет выполнена попытка смены направления тренда.

Запустим скрипт со следующими параметрами:  TModel = LinearAndStochastic, TType =  Random, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1, RandomTrendCoeff=0.5, CountCandle=60. Получим следующий график на интервале H1:

H1_1

Рис. 9 Часовой график символа ExampleCurrency при случайном изменении тренда

Установим параметр RandomTrendCoeff = 0.7 и запустим скрипт:

H1_low

Рис. 10 Часовой график символа ExampleCurrency при случайном изменении тренда c RandomTrendCoeff = 0.7

Видим, что наблюдается убывающий тренд, изменим RandomTrendCoeff = 0.3 и получим возрастающий тренд:

H1_high

Рис. 10 Часовой график символа ExampleCurrency при случайном изменении тренда c RandomTrendCoeff = 0.3

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

Скрипт GetTickTrend позволяет генерировать тики, а из них формировать минутные бары и обладает теми же возможностями, что и скрипт GetCandleTrend.

Моделирование графических паттернов

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

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

pattern2

Рис. 11. Паттерн "Двойная вершина"

pattern2

Рис. 12. Паттерн "Двойное дно"

Для создания паттернов будет использовать скрипт GetCandleTrend. Паттерны будет формировать на периоде H1. Для формирования каждого паттерна нам нужно четыре раза запустить скрипт GetCandleTrend с различными входными параметрами. Выберем следующие временные интервалы, обозначенные на рисунках 11 и 12 как t1-t5:

Для генерации паттерна "Двойная вершина" установим следующие настройки скрипта:

  • Время начала генерации баров: 00:00 02.01.2018
  • Время окончания генерации баров: 12:00 02.01.2018
  • Модель тренда: LinearAndStochastic
  • Тип тренда: Random
  • Трендовый коэффициент: 0.15 
  • Коэффициент k1 трендовой модели: 0.15
  • Коэффициент k2 трендовой модели: 1 
  • Коэффициент k3 трендовой модели: 1 
  • Интервал случайного изменения направления тренда: 60
Остальные настройки оставим по умолчанию и запустим скрипт. Далее для второго, третьего и четвертого запуска скрипта изменим следующие настройки:

Запуск №2:

  • Время начала генерации баров: 13:00 02.01.2018
  • Время окончания генерации баров: 12:00 03.01.2018
  • Трендовый коэффициент: 0.85 
Запуск №3:

  • Время начала генерации баров: 13:00 03.01.2018
  • Время окончания генерации баров: 12:00 04.01.2018
  • Трендовый коэффициент: 0.15 
Запуск №4:

  • Время начала генерации баров: 13:00 04.01.2018
  • Время окончания генерации баров: 00:00 05.01.2018
  • Трендовый коэффициент: 0.85 

В результате, после четырех запусков скрипта GetCandleTrend, будет получен ценовой график, показанный на рисунке 13.

2high

Рис. 13. Смоделированный паттерн "Двойная вершина" на периоде H1

Аналогично смоделируем паттерн "Двойное дно". Для этого запустим скрипт GetCandleTrend четыре раза с настройками, указанными для паттерна "Двойная вершина", изменив только трендовый коэффициент: для первого запуска 0.85, для последующих 0.15, 0.85, 0.15. Результат работы скрипта показан на рисунке 14.

2low

Рис. 14. Смоделированный паттерн "Двойное дно" на периоде H1

Аналогично можно моделировать и другие паттерны. Наиболее реалистичные паттерны получаются на минутном графике. Для формирования паттернов на других временных интервалах необходимо в параметре "Интервал случайного изменения направления тренда" скрипта GetCandleTrend указать количество минутных свечей, содержащихся в выбранном интервале, например для интервала H1 - 60, для интервала H4 - 240.

Заключение

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

1) Создание и удаление пользовательских символов

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

2) Генерация тиков и баров

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

3) Моделирования тренда

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

4) Моделирование графических паттернов

Показан пример использования скрипта GetCandleTrend для создания паттернов "Двойная вершина" и "Двойное дно".

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