Создаем новую торговую стратегию с использованием технологии разложения входов на индикаторы

6 декабря 2017, 13:20
Dmitriy Gizlyk
0
6 081

Введение

Не секрет, что только 5% трейдеров стабильно получают доход на финансовых рынках, при этом попасть в заветную цифру хотят все 100%. 

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

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

1. Создание модели для тестирования и анализа

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

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

Чтобы решить эту задачу, проведем небольшую подготовительную работу.

1.1. Создание класса виртуальных ордеров

Я использую нетинговый счет. Поэтому для открытия разнонаправленных ордеров мне нужно будет создать виртуальные ордера, которые будут отслеживаться не терминалом (в соответствии с настройками счета), а советником. Для этого создадим класс CDeal. При инициализации экземпляра класса мы будем передавать в него наименование инструмента, тип позиции, время и цену открытия, а также стоп-лосс и тейк-профит. Объем позиции я опустил намеренно, так как в данной работе он нам неинтересен. Нам важно движение цены, поэтому прибыль/убыток будем считать не в денежном выражении, а в пунктах.

Для обслуживания класс был дополнен функциями проверки статуса позиции:

  • IsClosed — возвращает логическое значение, закрыта позиция или нет;
  • Type — возвращает тип позиции;
  • GetProfit — возвращает прибыль закрытой позиции (для убыточной позиции значение будет отрицательным);
  • GetTime — возвращает время открытия позиции.

class CDeal          :  public CObject
  {
private:
   string               s_Symbol;
   datetime             dt_OpenTime;         // Time of open position
   double               d_OpenPrice;         // Price of opened position
   double               d_SL_Price;          // Stop Loss of position
   double               d_TP_Price;          // Take Profit of position
   ENUM_POSITION_TYPE   e_Direct;            // Direct of opened position
   double               d_ClosePrice;        // Price of close position
   int                  i_Profit;            // Profit of position in pips
//---
   double               d_Point;
   
public:
                     CDeal(string symbol, ENUM_POSITION_TYPE type,datetime time,double open_price,double sl_price, double tp_price);
                    ~CDeal();
   //--- Check status
   bool              IsClosed(void);
   ENUM_POSITION_TYPE Type(void)    {  return e_Direct;    }
   double            GetProfit(void);
   datetime          GetTime(void)  {  return dt_OpenTime;  }
   //---
   void              Tick(void);
  };

Поступающие тики будут обрабатываться в функции Tick, которая проверит и в случае необходимости закроет позицию по стоп-лоссу или тейк-профиту и сохранит накопленную прибыль.

void CDeal::Tick(void)
  {
   if(d_ClosePrice>0)
      return;
   double price=0;
   switch(e_Direct)
     {
      case POSITION_TYPE_BUY:
        price=SymbolInfoDouble(s_Symbol,SYMBOL_BID);
        if(d_SL_Price>0 && d_SL_Price>=price)
          {
           d_ClosePrice=price;
           i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point);
          }
        else
          {
           if(d_TP_Price>0 && d_TP_Price<=price)
             {
              d_ClosePrice=price;
              i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point);
             }
          }
        break;
      case POSITION_TYPE_SELL:
        price=SymbolInfoDouble(s_Symbol,SYMBOL_ASK);
        if(d_SL_Price>0 && d_SL_Price<=price)
          {
           d_ClosePrice=price;
           i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point);
          }
        else
          {
           if(d_TP_Price>0 && d_TP_Price>=price)
             {
              d_ClosePrice=price;
              i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point);
             }
          }
        break;
     }
  }

1.2. Создание класса для работы с индикаторами

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

class CDealsToIndicators
  {
private:
   CADX              *ADX[];
   CAlligator        *Alligator[];
   COneBufferArray   *OneBuffer[];
   CMACD             *MACD[];
   CStaticOneBuffer  *OneBufferStatic[];
   CStaticMACD       *MACD_Static[];
   CStaticADX        *ADX_Static[];
   CStaticAlligator  *Alligator_Static[];
   
   template<typename T>
   void              CleareArray(T *&array[]);

public:
                     CDealsToIndicators();
                    ~CDealsToIndicators();
   //---
   bool              AddADX(string symbol, ENUM_TIMEFRAMES timeframe, int period, string name);
   bool              AddADX(string symbol, ENUM_TIMEFRAMES timeframe, int period, string name, int &handle);
   bool              AddAlligator(string symbol,ENUM_TIMEFRAMES timeframe,uint jaw_period, uint jaw_shift, uint teeth_period, uint teeth_shift, uint lips_period, uint lips_shift, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price, string name);
   bool              AddAlligator(string symbol,ENUM_TIMEFRAMES timeframe,uint jaw_period, uint jaw_shift, uint teeth_period, uint teeth_shift, uint lips_period, uint lips_shift, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price, string name, int &handle);
   bool              AddMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price, string name);
   bool              AddMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price, string name, int &handle);
   bool              AddOneBuffer(int handle, string name);
   //---
   bool              SaveNewValues(long ticket);
   //---
   bool              Static(CArrayObj *deals);
  };

1.3. Создание советника для тестирования

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

  • ADX;
  • Alligator;
  • CCI;
  • Chaikin;
  • Force Index;
  • MACD.

Для каждого из них созданы по 3 набора параметров, данные отслеживаются на трех таймфреймах.

Стоп-лосс и тейк-профит сделок я привязал к значениям индикатора ATR и задал через соотношение прибыли к риску.

//--- input parameters
input double            Reward_Risk    =  1.0;
input int               ATR_Period     =  288;
input ENUM_TIMEFRAMES   TimeFrame1     =  PERIOD_M5;
input ENUM_TIMEFRAMES   TimeFrame2     =  PERIOD_H1;
input ENUM_TIMEFRAMES   TimeFrame3     =  PERIOD_D1;
input string            s1                =  "ADX"                ;  //---
input uint              ADX_Period1       =  14                   ;
input uint              ADX_Period2       =  28                   ;
input uint              ADX_Period3       =  56                   ;
input string            s2                =  "Alligator"          ;  //---
input uint              JAW_Period1       =  13                   ;
input uint              JAW_Shift1        =  8                    ;
input uint              TEETH_Period1     =  8                    ;
input uint              TEETH_Shift1      =  5                    ;
input uint              LIPS_Period1      =  5                    ;
input uint              LIPS_Shift1       =  3                    ;
input uint              JAW_Period2       =  26                   ;
input uint              JAW_Shift2        =  16                   ;
input uint              TEETH_Period2     =  16                   ;
input uint              TEETH_Shift2      =  10                   ;
input uint              LIPS_Period2      =  10                   ;
input uint              LIPS_Shift2       =  6                    ;
input uint              JAW_Period3       =  42                   ;
input uint              JAW_Shift3        =  32                   ;
input uint              TEETH_Period3     =  32                   ;
input uint              TEETH_Shift3      =  20                   ;
input uint              LIPS_Period3      =  20                   ;
input uint              LIPS_Shift3       =  12                   ;
input ENUM_MA_METHOD    Alligator_Method  =  MODE_SMMA            ;
input ENUM_APPLIED_PRICE Alligator_Price  =  PRICE_MEDIAN         ;
input string            s5                =  "CCI"                ;  //---
input uint              CCI_Period1       =  14                   ;
input uint              CCI_Period2       =  28                   ;
input uint              CCI_Period3       =  56                   ;
input ENUM_APPLIED_PRICE CCI_Price        =  PRICE_TYPICAL        ;
input string            s6                =  "Chaikin"            ;  //---
input uint              Ch_Fast_Period1   =  3                    ;
input uint              Ch_Slow_Period1   =  14                   ;
input uint              Ch_Fast_Period2   =  6                    ;
input uint              Ch_Slow_Period2   =  28                   ;
input uint              Ch_Fast_Period3   =  12                   ;
input uint              Ch_Slow_Period3   =  56                   ;
input ENUM_MA_METHOD    Ch_Method         =  MODE_EMA             ;
input ENUM_APPLIED_VOLUME Ch_Volume       =  VOLUME_TICK          ;
input string            s7                =  "Force Index"        ;  //---
input uint              Force_Period1     =  14                   ;
input uint              Force_Period2     =  28                   ;
input uint              Force_Period3     =  56                   ;
input ENUM_MA_METHOD    Force_Method      =  MODE_SMA             ;
input ENUM_APPLIED_VOLUME Force_Volume    =  VOLUME_TICK          ;
input string            s8                =  "MACD"               ;  //---
input uint              MACD_Fast1        =  12                   ;
input uint              MACD_Slow1        =  26                   ;
input uint              MACD_Signal1      =  9                    ;
input uint              MACD_Fast2        =  24                   ;
input uint              MACD_Slow2        =  52                   ;
input uint              MACD_Signal2      =  18                   ;
input uint              MACD_Fast3        =  48                   ;
input uint              MACD_Slow3        =  104                  ;
input uint              MACD_Signal3      =  36                   ;
input ENUM_APPLIED_PRICE MACD_Price       =  PRICE_CLOSE          ;

В блоке глобальных переменных объявим:

  • массив для хранения классов сделок Deals,
  • экземпляр класса для работы с индикаторами IndicatorsStatic,
  • переменную для хранения хэндла индикатора ATR,
  • 2 служебные переменные для хранения времени последнего обработанного бара (last_bar) и последнего закрытого ордера (last_closed_deal). Последняя нам потребуется, чтобы не перебирать на каждом тике уже закрытые ранее позиции.

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

int OnInit()
  {
//---
   last_bar=0;
   last_closed_deal=0;
//---
   Deals =  new CArrayObj();
   if(CheckPointer(Deals)==POINTER_INVALID)
      return INIT_FAILED;
//---
   IndicatorsStatic  =  new CDealsToIndicators();
   if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
      return INIT_FAILED;
//---
   atr=iATR(_Symbol,TimeFrame1,ATR_Period);
   if(atr==INVALID_HANDLE)
      return INIT_FAILED;
//---
   AddIndicators(TimeFrame1);
   AddIndicators(TimeFrame2);
   AddIndicators(TimeFrame3);
//---
   return(INIT_SUCCEEDED);
  }

Мы будем использовать одинаковый набор индикаторов на трех разных таймфреймах. Поэтому есть смысл вывести инициализацию индикаторных классов в отдельную функцию AddIndicators. В ее параметрах будем передавать требуемый таймфрейм.

bool AddIndicators(ENUM_TIMEFRAMES timeframe)
  {
   if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
     {
      IndicatorsStatic  =  new CDealsToIndicators();
      if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
         return false;
     }
   string tf_name=StringSubstr(EnumToString(timeframe),7);
   string name="ADX("+IntegerToString(ADX_Period1)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period1, name))
      return false;
   name="ADX("+IntegerToString(ADX_Period2)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period2, name))
      return false;
   name="ADX("+IntegerToString(ADX_Period3)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period3, name))
      return false;
   name="Alligator("+IntegerToString(JAW_Period1)+","+IntegerToString(TEETH_Period1)+","+IntegerToString(LIPS_Period1)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period1, JAW_Shift1, TEETH_Period1, TEETH_Shift1, LIPS_Period1, LIPS_Shift1, Alligator_Method, Alligator_Price, name))
      return false;
   name="Alligator("+IntegerToString(JAW_Period2)+","+IntegerToString(TEETH_Period2)+","+IntegerToString(LIPS_Period2)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period2, JAW_Shift2, TEETH_Period2, TEETH_Shift2, LIPS_Period2, LIPS_Shift2, Alligator_Method, Alligator_Price, name))
      return false;
   name="Alligator("+IntegerToString(JAW_Period3)+","+IntegerToString(TEETH_Period3)+","+IntegerToString(LIPS_Period3)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period3, JAW_Shift3, TEETH_Period3, TEETH_Shift3, LIPS_Period3, LIPS_Shift3, Alligator_Method, Alligator_Price, name))
      return false;
   name="MACD("+IntegerToString(MACD_Fast1)+","+IntegerToString(MACD_Slow1)+","+IntegerToString(MACD_Signal1)+") "+tf_name;
   if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast1, MACD_Slow1, MACD_Signal1, MACD_Price, name))
      return false;
   name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name;
   if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name))
      return false;
   name="MACD("+IntegerToString(MACD_Fast3)+","+IntegerToString(MACD_Slow3)+","+IntegerToString(MACD_Signal3)+") "+tf_name;
   if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast3, MACD_Slow3, MACD_Signal3, MACD_Price, name))
      return false;
   name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name;
   int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price);
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   name="CCI("+IntegerToString(CCI_Period2)+") "+tf_name;
   handle = iCCI(_Symbol, timeframe, CCI_Period2, CCI_Price);
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iCCI(_Symbol, timeframe, CCI_Period3, CCI_Price);
   name="CCI("+IntegerToString(CCI_Period3)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iForce(_Symbol, timeframe, Force_Period1, Force_Method, Force_Volume);
   name="Force("+IntegerToString(Force_Period1)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iForce(_Symbol, timeframe, Force_Period2, Force_Method, Force_Volume);
   name="Force("+IntegerToString(Force_Period2)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume);
   name="Force("+IntegerToString(Force_Period3)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   name="CHO("+IntegerToString(Ch_Slow_Period1)+","+IntegerToString(Ch_Fast_Period1)+") "+tf_name;
   handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period1, Ch_Slow_Period1, Ch_Method, Ch_Volume);
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume);
   name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period3, Ch_Slow_Period3, Ch_Method, Ch_Volume);
   name="CHO("+IntegerToString(Ch_Slow_Period3)+","+IntegerToString(Ch_Fast_Period3)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   return true;
  }

Операции, выполняемые в функции OnTick, можно разделить на 2 блока: проверка открытых позиций и открытие новых позиций.

Первый блок операций выполняется на каждом тике. В нем из массива поочередно извлекаются все ранее открытые сделки, и для каждой из них вызывается функция Tick. Она проверяет срабатывание стоп-лосса и тейк-профита позиции и, при необходимости, ордер закрывается по текущей цене с сохранением накопленной прибыли. Чтобы снова не проверять ранее закрытые сделки, в переменной last_closed_deal сохраняется номер сделки, предшествующей первой незакрытой. 

void OnTick()
  {
//---
   int total=Deals.Total();
   CDeal *deal;
   bool found=false;
   for(int i=last_closed_deal;i<total;i++)
     {
      deal  =  Deals.At(i);
      if(CheckPointer(deal)==POINTER_INVALID)
         continue;
      if(!found)
        {
         if(deal.IsClosed())
           {
            last_closed_deal=i;
            continue;
           }
         else
            found=true;
        }
      deal.Tick();
     }

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

//---
   datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   datetime cur_time=TimeCurrent();
   if(cur_bar==last_bar || (cur_time-cur_bar)>10)
      return;
   double atrs[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0)
      return;

   last_bar=cur_bar;
   double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
   double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point);
   double sl=NormalizeDouble(stops,_Digits);
   double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits);
   deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
   if(CheckPointer(deal)!=POINTER_INVALID)
      if(Deals.Add(deal))
         IndicatorsStatic.SaveNewValues(Deals.Total()-1);
   deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
   if(CheckPointer(deal)!=POINTER_INVALID)
      if(Deals.Add(deal))
         IndicatorsStatic.SaveNewValues(Deals.Total()-1);
   return;
  }

В функции OnTester будем собирать результаты тестового прохода и строить графики для анализа. Для этого вызовем функцию Static класса работы с индикаторами.

Не забываем подчистить память в функции OnDeinit!

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

2. Анализ результатов тестирования

Итак, мы создали тестовый советник. Теперь определимся с анализируемым периодом. При выборе периода надо учесть, что он должен быть достаточно долгим, чтобы обеспечить объективность анализа. И еще одно требование к периоду: он должен включать не только однонаправленные движения, но и периоды разнонаправленных трендовых движений, и боковых (флэтовых). Такой подход позволит создать торговую стратегию, способную генерировать прибыль в периоды любых движений. В моем примере проанализирована пара EURUSD за период с 1/01/2016 по 1/10/2017.

Период тестированияПараметры тестирования

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

Каждый этап тестирования будем проводить в 2 прохода с соотношением прибыль/риск равным 1/1 и 15/1. По первому проходу будем оценивать вероятность направленного движения, а по второму проходу — силу движения.

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

2.1. Первый этап

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

Аналитические графики индикатора силы с периодом 56 на таймфрейме М5

Увеличим масштаб анализируемого графика. Видим, что влияние этого фактора наблюдается в диапазоне от -0.01 до 0.01. Наблюдаемое явление одинаково верно как для сделок на покупку, так и на продажу.

Это наблюдение можно объяснить отсутствием волатильности в наблюдаемом диапазоне значений. Мы же для своей стратегии отметим запрет на открытие каких-либо ордеров в данном диапазоне.

Зависимость прибыли от значений индикатора индекса силы вблизи нулевой отметки

Добавим этот фильтр в наш советник. Для этого сначала добавим глобальную переменную для хранения хэндла индикатора.

int                  force;

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

   handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume);
   if(timeframe==TimeFrame1)
      force=handle;

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

   double atrs[];
   double force_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0)
      return;      // Some error of load indicator's data

   last_bar=cur_bar;
   double d_Step=_Point*1000;
   if(MathAbs(NormalizeDouble(force_data[0]/d_Step,0)*d_Step)<=0.01)
      return;    // Filtered by Force Index

Полный код советника приведен во вложении к статье.

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

2.2. Второй этап

После повторного тестирования советника я обратил внимание на индикатор MACD. На графике появились прибыльные зоны.

График зависимости прибыли от значений гистограммы индикатора MACD

На графике с соотношением прибыль/риск 15/1 эти зоны более ярко выражены, что может свидетельствовать о потенциале сигналов в этих диапазонах.

Диаграмма зависимости прибыли от значений гистограммы MACD (прибыль/риск = 15/1)

Добавим и этот фильтр в код нашего советника. Логика добавления фильтра аналогична приведенной в описании первого этапа.

В глобальные переменные:

int                  macd;

В функцию AddIndicators:

   name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name;
   if(timeframe==TimeFrame1)
     {
      if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name, macd))
         return false;
     }
   else
     {
      if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name))
         return false;
     }

В функцию OnTick:

   double macd_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0)
      return;

и

   double macd_Step=_Point*50;
   macd_data[0]=NormalizeDouble(macd_data[0]/macd_Step,0)*macd_Step;
   if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }
   if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035))
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }

После добавления фильтра переходим к третьему этапу тестирования.

2.3. Третий этап

На третьем этапе я обратил внимание на осциллятор Чайкина. На аналитических графиках осциллятора на тайфрейме D1 видим рост прибыли по длинным позициям при снижении значений, а при росте значений индикатора растет прибыль по коротким позициям.

Зависимость прибыли от показаний осциллятора Чайкина

Мое наблюдение подтверждается и при анализе графиков с соотношением прибыль/риск равным 15/1.

Зависимость прибыли от показаний осциллятора Чайкина.

Добавим наше наблюдение в код советника.

В глобальные переменные:

int                  cho;

В функцию AddIndicators:

   handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume);
   name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name;
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   if(timeframe==TimeFrame3)
      cho=handle;

В функцию OnTick:

   double cho_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0
      || CopyBuffer(cho,0,1,2,cho_data)<2)
      return;

и

   if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }
   if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }

Переходим к следующему этапу.

2.4. Четвертый этап

После очередного тестирования советника мое внимание вновь привлек таймфрейм D1. На этот раз я рассмотрел индикатор CCI. Его аналитические графики показали рост прибыли по коротким позициям при снижении значений индикатора и рост прибыли длинных позиций — с ростом значений индикатора. Эта тенденция наблюдалась на всех трех исследуемых периодах, но максимальная прибыль достигалась при использовании периода 14, стандартного для этого осциллятора.

Зависимость прибыли от значений индикатора CCI.

Аналитические графики, полученные при тестировании с соотношением прибыль/риск, равным 15/1, подтверждают наше наблюдение.

Зависимость прибыли от значений индикатора CCI.

Добавим и это наблюдение в код тестового советника.

В глобальные переменные:

int                  cci;

В функцию AddIndicators:

   name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name;
   int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price);
   if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      return false;
   if(timeframe==TimeFrame3)
      cci=handle;

В функцию OnTick:

   double cci_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0
      || CopyBuffer(cho,0,1,2,cho_data)<2 || CopyBuffer(cci,0,1,2,cci_data)<2)
      return;

и

   if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0 && (cci_data[1]-cci_data[0])>0)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }
   if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0 && (cci_data[1]-cci_data[0])<0)
     {
      deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
      if(CheckPointer(deal)!=POINTER_INVALID)
         if(Deals.Add(deal))
            IndicatorsStatic.SaveNewValues(Deals.Total()-1);
     }

С полным кодом советников на всех этапах можно ознакомиться во вложении к статье.

3. Создание и тестирования советника по выбранным сигналам

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

В стратегии мы использовали 4 индикатора для принятия решения о сделке и индикатор ATR для установки стоп-лосса и тейк-профита. Следовательно, во входных параметрах советника мы должны задать всю необходимую для индикаторов исходную информацию. Мани-менеджмент на данном этапе мы создавать не будем, все ордера будут использовать фиксированный объем.

//--- input parameters
input double            Lot               =  0.1                  ;
input double            Reward_Risk       =  15.0                 ;
input ENUM_TIMEFRAMES   ATR_TimeFrame     =  PERIOD_M5            ;
input int               ATR_Period        =  288                  ;
input string            s1                =  "CCI"                ;  //---
input ENUM_TIMEFRAMES   CCI_TimeFrame     =  PERIOD_D1            ;
input uint              CCI_Period        =  14                   ;
input ENUM_APPLIED_PRICE CCI_Price        =  PRICE_TYPICAL        ;
input string            s2                =  "Chaikin"            ;  //---
input ENUM_TIMEFRAMES   Ch_TimeFrame      =  PERIOD_D1            ;
input uint              Ch_Fast_Period    =  6                    ;
input uint              Ch_Slow_Period    =  28                   ;
input ENUM_MA_METHOD    Ch_Method         =  MODE_EMA             ;
input ENUM_APPLIED_VOLUME Ch_Volume       =  VOLUME_TICK          ;
input string            s3                =  "Force Index"        ;  //---
input ENUM_TIMEFRAMES   Force_TimeFrame   =  PERIOD_M5            ;
input uint              Force_Period      =  56                   ;
input ENUM_MA_METHOD    Force_Method      =  MODE_SMA             ;
input ENUM_APPLIED_VOLUME Force_Volume    =  VOLUME_TICK          ;
input string            s4                =  "MACD"               ;  //---
input ENUM_TIMEFRAMES   MACD_TimeFrame    =  PERIOD_M5            ;
input uint              MACD_Fast         =  12                   ;
input uint              MACD_Slow         =  26                   ;
input uint              MACD_Signal       =  9                    ;
input ENUM_APPLIED_PRICE MACD_Price       =  PRICE_CLOSE          ;

В глобальных переменных объявим:

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

В функции OnInit инициализируем индикаторы и зададим начальные значения переменных.

int OnInit()
  {
//---
   last_bar=0;
   last_deal=0;
//---
   atr=iATR(_Symbol,ATR_TimeFrame,ATR_Period);
   if(atr==INVALID_HANDLE)
      return INIT_FAILED;
//---
   force=iForce(_Symbol,Force_TimeFrame,Force_Period,Force_Method,Force_Volume);
   if(force==INVALID_HANDLE)
      return INIT_FAILED;
//---
   macd=iMACD(_Symbol,MACD_TimeFrame,MACD_Fast,MACD_Slow,MACD_Signal,MACD_Price);
   if(macd==INVALID_HANDLE)
      return INIT_FAILED;
//---
   cho=iChaikin(_Symbol,Ch_TimeFrame,Ch_Fast_Period,Ch_Slow_Period,Ch_Method,Ch_Volume);
   if(cho==INVALID_HANDLE)
      return INIT_FAILED;
//---
   cci=iCCI(_Symbol,CCI_TimeFrame,CCI_Period,CCI_Price);
   if(cci==INVALID_HANDLE)
      return INIT_FAILED;
//---
   MaxPeriod=fmax(Force_TimeFrame,MACD_TimeFrame);
   MaxPeriod=fmax(MaxPeriod,Ch_TimeFrame);
   MaxPeriod=fmax(MaxPeriod,CCI_TimeFrame);
   MinPeriod=fmin(Force_TimeFrame,MACD_TimeFrame);
   MinPeriod=fmin(MinPeriod,Ch_TimeFrame);
   MinPeriod=fmin(MinPeriod,CCI_TimeFrame);
//---
   return(INIT_SUCCEEDED);
  }

В функции OnDeinit закрываем используемые индикаторы.

void OnDeinit(const int reason)
  {
//---
   if(atr!=INVALID_HANDLE)
      IndicatorRelease(atr);
//---
   if(force==INVALID_HANDLE)
      IndicatorRelease(force);
//---
   if(macd==INVALID_HANDLE)
      IndicatorRelease(macd);
//---
   if(cho==INVALID_HANDLE)
      IndicatorRelease(cho);
//---
   if(cci==INVALID_HANDLE)
      IndicatorRelease(cci);
  }

Основные действия будут проводиться в функции OnTick.  В начале функции проверим наступление нового бара. Новая позиция будет открываться только на открытии нового бара по минимальному таймфрейму (я ограничил 10 секунд от открытия бара) и только если еще не открывалась позиция в пределах текущего бара по максимальному тайфрейму. Так я ограничил открытие только одного ордера на один сигнал.

void OnTick()
  {
//---
   datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,MinPeriod,SERIES_LASTBAR_DATE);
   datetime cur_max=(datetime)SeriesInfoInteger(_Symbol,MaxPeriod,SERIES_LASTBAR_DATE);
   datetime cur_time=TimeCurrent();
   if(cur_bar<=last_bar || (cur_time-cur_bar)>10 || cur_max<=last_deal)
      return;

Далее получаем данные используемых индикаторов. В случае ошибки получения данных хотя бы одного из индикаторов выходим из функции.

   last_bar=cur_bar;
   double atrs[];
   double force_data[];
   double macd_data[];
   double cho_data[];
   double cci_data[];
   if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0
      || CopyBuffer(cho,0,1,2,cho_data)<2 || CopyBuffer(cci,0,1,2,cci_data)<2)
     {
      return;
     }

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

   double force_Step=_Point*1000;
   if(MathAbs(NormalizeDouble(force_data[0]/force_Step,0)*force_Step)<=0.01)
      return;

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

После этого рассчитываем параметры для новой позиции и отправляем ордер.

Аналогичные операции проводим для короткой позиции.

   double macd_Step=_Point*50;
   macd_data[0]=NormalizeDouble(macd_data[0]/macd_Step,0)*macd_Step;
   if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0 && (cci_data[1]-cci_data[0])>0)
     {
      if(PositionSelect(_Symbol))
        {
         switch((int)PositionGetInteger(POSITION_TYPE))
           {
            case POSITION_TYPE_BUY:
              if(PositionGetDouble(POSITION_PROFIT)<=0)
                 return;
              break;
            case POSITION_TYPE_SELL:
              Trade.PositionClose(_Symbol);
              break;
           }
        }
      last_deal=cur_max;
      double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point);
      double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      double sl=NormalizeDouble(stops,_Digits);
      double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits);
      double SL=NormalizeDouble(bid-sl,_Digits);
      double TP=NormalizeDouble(ask+tp,_Digits);
      if(!Trade.Buy(Lot,_Symbol,ask,SL,TP,"New Strategy"))
         Print("Error of open BUY ORDER "+Trade.ResultComment());
     }
   if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0 && (cci_data[1]-cci_data[0])<0)
     {
      if(PositionSelect(_Symbol))
        {
         switch((int)PositionGetInteger(POSITION_TYPE))
           {
            case POSITION_TYPE_SELL:
              if(PositionGetDouble(POSITION_PROFIT)<=0)
                 return;
              break;
            case POSITION_TYPE_BUY:
              Trade.PositionClose(_Symbol);
              break;
           }
        }
      last_deal=cur_max;
      double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point);
      double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      double sl=NormalizeDouble(stops,_Digits);
      double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits);
      double SL=NormalizeDouble(ask+sl,_Digits);
      double TP=NormalizeDouble(bid-tp,_Digits);
      if(!Trade.Sell(Lot,_Symbol,bid,SL,TP,"New Strategy"))
         Print("Error of open SELL ORDER "+Trade.ResultComment());
     }
   return;
  }

С полным кодом советника можно ознакомиться во вложении.

После подготовки советника мы можем провести тестирование нашей стратегии. Чтобы отойти от "подгонки стратегии под период", расширим тестируемый период: протестируем стратегию с 1/01/2015 по 1/12/2017. Стартовый капитал тестирования будет 10 000 USD, размер сделки — 1 лот.

Тестирование стратегии. 

По результатам тестирования советник показал прибыль в 74.8% при максимальных просадках по балансу в 12.4% и по эквити в 23.8%. Всего было произведено 44 трейда (22 коротких позиции и 22 длинных). Доля прибыльных позиций составляет 18.2% и одинакова как для коротких, так и для длинных позиций. Такой низкий процент прибыльных позиций обусловлен использованием высокого соотношения ожидаемой прибыли к риску (15:1) и оставляет возможности для дальнейшего совершенствования стратегии.

Результаты тестирования стратегии.

Заключение

В статье продемонстрирована технология создания торговой стратегии "с чистого листа" с использованием метода разложения входов на индикаторы. Получившаяся в итоге стратегия способна генерировать прибыль в течение длительного периода, что подтверждено тестированием на протяжении 3 лет. Несмотря на то, что при создании стратегии использовались индикаторы из стандартной поставки MetaTrader, сигналы на совершение сделок далеки от сигналов, описанных в литературе для взятых индикаторов. Предложенная технология дает возможность творческого подхода для использования индикаторов в торговых стратегиях и не ограничивается взятыми индикаторами. Можно использовать любые пользовательские индикаторы и варианты оценки качества их сигналов.

Ссылки

  1. Раскладываем входы по индикаторам
  2. Графики и диаграммы в формате HTML

Программы, используемые в статье:

#
 Имя
Тип 
Описание 
  New_Strategy_Gizlyk.zip    
1 NewStrategy1.mq5  Советник  Советник для проведения первого этапа создания стратегии
 2 NewStrategy2.mq5   Советник  Советник для проведения второго этапа создания стратегии
 3 NewStrategy3.mq5   Советник  Советник для проведения третьего этапа создания стратегии 
 4 NewStrategy4.mq5   Советник   Советник для проведения четвертого этапа создания стратегии 
 5 NewStrategy_Final.mq5  Советник   Советник тестирования стратегии 
6 DealsToIndicators.mqh  Библиотека класса  Класс для работы с индикаторными классами
7 Deal.mqh   Библиотека класса  Класс для сохранения информации о сделке
8 Value.mqh   Библиотека класса  Класс для сохранения данных о состоянии индикаторного буфера
9 OneBufferArray.mqh  Библиотека класса  Класс для сохранения истории данных одно буферного индикатора
10 StaticOneBuffer.mqh  Библиотека класса  Класс для сбора и анализа статистики одно буферного индикатора
11 ADXValue.mqh  Библиотека класса  Класс для сохранения данных о состоянии индикатора ADX
12 ADX.mqh  Библиотека класса  Класс для сохранения истории данных индикатора ADX
13 StaticADX.mqh  Библиотека класса  Класс для сбора и анализа статистики индикатора ADX
14 AlligatorValue.mqh  Библиотека класса  Класс для сохранения данных о состоянии индикатора Alligator
15 Alligator.mqh  Библиотека класса  Класс для сохранения истории данных индикатора Alligator
16 StaticAlligator.mqh  Библиотека класса  Класс для сбора и анализа статистики индикатора Alligator
17 MACDValue.mqh  Библиотека класса  Класс для сохранения данных о состоянии индикатора MACD
18 MACD.mqh  Библиотека класса  Класс для сохранения истории данных индикатора MACD
19 StaticMACD.mqh  Библиотека класса  Класс для сбора и анализа статистики индикатора MACD
   Common.zip    
20  NewStrategy1_Report_1to1_2016-17.html  Файл интернет  Аналитические графики первого этапа создания стратегии, прибыль/риск = 1/1
21  NewStrategy1_Report_15to1_2016-17.html  Файл интернет  Аналитические графики первого этапа создания стратегии, прибыль/риск = 15/1
22  NewStrategy2_Report_1to1_2016-17.html   Файл интернет  Аналитические графики второго этапа создания стратегии, прибыль/риск = 1/1
23  NewStrategy2_Report_15to1_2016-17.html  Файл интернет  Аналитические графики второго этапа создания стратегии, прибыль/риск = 15/1
24  NewStrategy3_Report_1to1_2016-17.html   Файл интернет  Аналитические графики третьего этапа создания стратегии, прибыль/риск = 1/1
25  NewStrategy3_Report_15to1_2016-17.html   Файл интернет  Аналитические графики третьего этапа создания стратегии, прибыль/риск = 15/1
26  NewStrategy4_Report_1to1_2016-17.html   Файл интернет  Аналитические графики четвертого этапа создания стратегии, прибыль/риск = 1/1
27  NewStrategy4_Report_15to1_2016-17.html   Файл интернет  Аналитические графики четвертого этапа создания стратегии, прибыль/риск = 15/1
28  NewStrategy_Final_Report.html  Файл интернет  Отчет тестирования стратегии


Прикрепленные файлы |
Common.zip (1455.62 KB)
Торговля по уровням ДиНаполи Торговля по уровням ДиНаполи

В статье рассматривается один из вариантов практической реализации советника для торговли по уровням ДиНаполи при помощи стандартных инструментов MQL5. Протестированы результаты его работы и сделаны выводы.

Автоматический подбор перспективных сигналов Автоматический подбор перспективных сигналов

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

Как снизить риски трейдера Как снизить риски трейдера

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

Пользовательский тестер стратегий на основе быстрых математических вычислений Пользовательский тестер стратегий на основе быстрых математических вычислений

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