English 中文 Español Deutsch 日本語 Português
Паттерны с примерами (Часть I): Кратная вершина

Паттерны с примерами (Часть I): Кратная вершина

MetaTrader 5Торговые системы | 12 мая 2021, 15:29
5 744 25
Evgeniy Ilin
Evgeniy Ilin

Содержание


Введение

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


О разворотных паттернах

Лично для меня разворотные паттерны имеют слишком расплывчатое определение, и, что самое главное, не имеют под собой никакой математики от слова "совсем". Вообще если честно любой паттерн, не имеет под собой никакой математики, и единственная математика, которую мы можем предложить в данной связи — это статистика. Статистика является единственным критерием истины, но статистика составляется на основе реальной торговли. Понятно, что нет таких источников, которые могли бы с максимальной точностью предоставить данную статистику, а если и могли бы, то стали бы они это делать ради какого-то исследования в рамках одной торговой площадки, при этом понимая, что выгоды им с этого никакой. Ответ, я думаю, всем очевиден. Единственным выходом из данной ситуации является тестирование по истории и визуализация в тестере стратегий. Данный подход, конечно, уступает в качестве, но у него есть неоспоримый плюс — скорость и количество данных. 

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


Почему кратная вершина и что в ней такого интересного?

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

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


Можно ли расширить концепцию двойной вершины?

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

Tree

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

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

В роли базовых паттернов здесь выступают:

  1. Двойная вершина
  2. Тройная вершина
  3. Голова и плечи

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

Double extremum

На рисунке все линии которые нам понадобятся, пронумерованы и означают следующее:

  1. Сопротивление тренда
  2. Вспомогательная линия для определения пессимистичной вершины (кто-то ее считает шеей, я считаю что это неверно, но я могу ошибаться)
  3. Линия шеи
  4. Оптимистичная мишень (она же является тейк профитом для торговли)
  5. Максимально допустимый уровень стоп-лосса (выставляется по дальней вершине)
  6. Линия оптимистичного прогноза (равна движению предшествующего тренда)

Пессимистичная мишень считается относительно точки пересечения линии шеи с ближнего края к рынку, для этого берется расстояние между "1" и "2", которое обозначено как "t" и откладывается еще раз в сторону предполагаемого разворота. Минимум оптимистичной мишени считается так же, только уже откладывается расстояние между "5" и "3", которое обозначено как "s".


Пишем код для визуализации кратной вершины

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

Карта класса

Начнем с того, что будет в классе:

class ExtremumsPatternFamilySearcher//класс имитирующий независимого поисковика формации
   {
   private:
   int BarsM;//использовать баров на графике
   int MinimumSeriesBarsM;//минимальное количество баров подряд для детекта вершины
   int TopsM;//количество вершин в паттерне
   int PointsPessimistM;//минимальное расстояние в пунктах до ближайшей мишени
   double RelativeUnstabilityM;//максимальное превышение размера головы относительно минимального плеча
   double RelativeUnstabilityMinM;//минимальное превышение размера головы относительно минимального плеча
   double RelativeUnstabilityTimeM;//максимальное превышение размеров плечей и головы
   bool bAbsolutelyHeadM;//требуется ли ярко выраженная голова
   bool bRandomExtremumsM;//рандомный выбор экстремумов
     


   struct Top//данные вершины
      {
      datetime Datetime0;//время ближайшей свечи к рынку
      datetime Datetime1;//время следующей свечи
      int Index0;//индекс ближайшей свечи к рынку
      int Index1;//индекс следующей свечи   
      datetime DatetimeExtremum;//время вершины
      int IndexExtremum;//индекс вершины
      double Price;//цена вершины
      bool bActive;//активна ли вершина (если нет то ее не существует)
      };
   
   struct Line//линия 
      {
      double Price0;//цена ближайшей свечи к рынку, к которой привязана линия
      datetime Time0;//время ближайшей свечи к рынку, к которой привязана линия
      double Price1;//цена дальней свечи, к которой привязана линия
      datetime Time1;//время дальней свечи, к которой привязана линия
      datetime TimeX;//время точки X
      int Index1;//индекс с левого края
      bool DirectionOfFormation;//направление 
      double C;//свободный коэффициент в уравнении
      double K;//коэффициент пропорциональности
   
      void CalculateKC()//посчитаем неизвестные в уравнении
         {
         if ( Time0 != Time1 ) K=double(Price0-Price1)/double(Time0-Time1);
         else K=0.0;
         C=double(Price1)-K*double(Time1);
         }
      
      double Price(datetime T)//функция линии в зависимости от времени
         {
         return K*T+C;
         }
      };
   
   public:   
   
   ExtremumsPatternFamilySearcher(int BarsI,int MinimumSeriesBarsI,int TopsI,int PointsPessimistI, double RelativeUnstabilityI,
   double RelativeUnstabilityMinI,double RelativeUnstabilityTimeI,bool bAbsolutelyHeadI,bool bRandomExtremumsI)//параметрический конструктор
      {
      BarsM=BarsI;
      MinimumSeriesBarsM=MinimumSeriesBarsI;
      TopsM=TopsI;
      PointsPessimistM=PointsPessimistI;
      RelativeUnstabilityM=RelativeUnstabilityI;
      RelativeUnstabilityMinM=RelativeUnstabilityMinI;
      RelativeUnstabilityTimeM=RelativeUnstabilityTimeI;
      bAbsolutelyHeadM=bAbsolutelyHeadI;
      bRandomExtremumsM=bRandomExtremumsI;
      bPatternFinded=bFindPattern();
      }
      
   int FormationDirection;//направление формации ( кратная вершина или дно или вообще отсутствует ) ( -1,1,0 )      
   bool bPatternFinded;//найден ли паттерн в процессе создания
   Top TopsUp[];//необходимые верхние экстремумы
   Top TopsDown[];//необходимые нижние экстремумы
   Top TopsUpAll[];//все верхние экстремумы
   Top TopsDownAll[];//все нижние экстремумы
   int RandomIndexUp[];//массив для рандомного выбора индекса вершин
   int RandomIndexDown[];//массив для рандомного выбора индекса впадин
   Top StartTop;//там где начинается формация ( дальняя вершина от рынка )
   Top EndTop;//там где заканчивается формация ( ближняя вершина от рынка )
   Line Neck;//шея
   Top FarestTop;//вершина наиболее удаленная от шеи ( понадобится для определения головы или размера формации ) или что то же самое что голова
   Line OptimistLine;//линия оптимистичного прогноза
   Line PessimistLine;//линия пессимистичного прогноза
   Line BorderLine;//линия на краю паттерна
   Line ParallelLine;//линия параллельная сопротивлению тренда
   
      
   private:
   void SetTopsSize();//установка размеров массивов с вершинами
   bool SearchFirstUps();//поиск вершин
   bool SearchFirstDowns();//поиск впадин
   void CalculateMaximum(Top &T,int Index0,int Index1);//вычисление максимальной цены между двумя барами
   void CalculateMinimum(Top &T,int Index0,int Index1);//вычисление минимальной цены между двумя барами
   bool PrepareExtremums();//подготовим экстремумы
   bool IsExtremumsAbsolutely();//проконтролируем приоритет вершин
   void DirectionOfFormation();//определение направления формации
   void FindNeckUp(Top &TStart,Top &TEnd);//найдем шею для бычьего паттерна
   void FindNeckDown(Top &TStart,Top &TEnd);//найдем шею для медвежьего патерна
   void SearchFarestTop();//найдем дальнюю от шеи вершину
   bool bBalancedExtremums();//первичная балансировка экстремумов ( чтобы сильно не разнились )
   bool bBalancedExtremumsHead();//если в паттерне больше 2 вершин то можно производить проверку на ярко выраженную голову
   bool bBalancedExtremumsTime();//будем требовать, чтобы экстремумы не сильно были удалены по времени относительно минимального удаления
   bool bBalancedHead();//сбалансируем голову ( иначе говоря потребуем чтобы она была не первой и не последней в списке вершин, если их больше трех )
   bool CorrectNeckUpLeft();//скорректируем шею так, чтобы найти пересечение цены с ней ( так мы создадим предпосылки предшествующему тренду ) 
   bool CorrectNeckDownLeft();//аналогично только для дна
   int CorrectNeckUpRight();//скорректируем шею так, чтобы найти пересечение цены с ней справа или в текущем положении цены, что одно и то же ( для определения точки входа )
   int CorrectNeckDownRight();//аналогично только для дна
   void SearchLineOptimist();//вычислим линию оптимистичного прогноза
   bool bWasTrend();//определим, предшествовал ли тренд определению паттерна ( в данном случае считаем линию оптимистичной мишени началом тренда )
   void SearchLineBorder();//определим сопротивление или поддержку тренда ( обычно наклонная линия )
   void CalculateParallel();//определим линию параллельную сопротивлению или поддержке ( пересекается в минимуме или максимуме паттерна с шеей )
   bool bCalculatePessimistic();//посчитаем линию пессимистичной мишени
   bool bFindPattern();//произведем все вышеописанные действия
   int iFindEnter();//найдем пересечение с шеей
   public:
   void CleanAll();//очистка объектов
   void DrawPoints();//рисование точек
   void DrawNeck();//нарисуем шею
   void DrawLineBorder();//линия на границе
   void DrawParallel();//линия параллельная границе
   void DrawOptimist();//линия оптимистичного прогноза
   void DrawPessimist();//линия пессимистичного прогноза
   };

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

Определение вершин и впадин

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

bool ExtremumsPatternFamilySearcher::SearchFirstUps()//найдем вершины
   {
   int NumUp=0;//количество найденных вершин
   int NumDown=0;//количество найденных впадин
   bool bDown=false;//вспомогательная булевая которая говорит о том найден ли сегмент медвежьих свечей
   bool bUp=false;//вспомогательная булевая которая говорит о том найден ли сегмент бычьих свечей
   bool bNextUp=true;//можно ли переходить к поиску следующей вершины
   bool bNextDown=true;//можно ли переходить к поиску следующей впадины
   
   for(int i=0;i<ArraySize(TopsUp);i++)//перед поиском выставим все необходимые вершины в неактивное состояние 
      {
      TopsUp[i].bActive=false;
      }
   for(int i=0;i<ArraySize(TopsUpAll);i++)//перед поиском выставим все вершины в неактивное состояние 
      {
      if (!TopsUpAll[i].bActive) break;
      TopsUpAll[i].bActive=false;
      }
               
   
   for(int i=0;i<BarsM;i++)
      {
      if ( i+MinimumSeriesBarsM-1 < BarsM )//если оставшихся баров хватает для определения экстремума и можно начать поиск следующей вершины
         {
         if ( bNextUp )//если разрешено искать следующую вершину
            {
            bDown=true;
            for(int j=i;j<i+MinimumSeriesBarsM;j++)//определим первые экстремумы для верхних вершин
               {
               if ( Open[j]-Close[j] < 0 )//если из выбранных свечей была хотя бы одна вверх
                  {
                  bDown=false;
                  break;
                  }
               }
            if ( bDown )
               {
               TopsUpAll[NumUp].Datetime0=Time[i+MinimumSeriesBarsM-1];
               TopsUpAll[NumUp].Index0=i+MinimumSeriesBarsM-1;
               bNextUp=false;
               }
            }        
         }

      if ( MinimumSeriesBarsM+i < BarsM && bDown )//если оставшихся баров хватает для определения второй половины экстремума и предыдущая половина найдена
         {
         bUp=true;                  
         for(int j=i;j<MinimumSeriesBarsM+i;j++)//определим последующие свечи в обратном направлении
            {
            if ( Open[j]-Close[j] > 0 )//если из выбранных свечей была хотя бы одна вниз
               {
               bUp=false;
               break;
               }
            }
         if ( bUp )
            {
            TopsUpAll[NumUp].Datetime1=Time[i];
            TopsUpAll[NumUp].Index1=i;
            TopsUpAll[NumUp].bActive=true;
            bNextUp=false;
            }   
         } 
      //после чего зарегистрируем найденную формацию как вершину если она таковой является
      if ( bDown && bUp )
         {
         CalculateMaximum(TopsUpAll[NumUp],TopsUpAll[NumUp].Index0,TopsUpAll[NumUp].Index1);//посчитаем максимум между двумя барами
         bNextUp=true;
         bDown=false;
         bUp=false;
         NumUp++;
         }
      }
   if ( NumUp >= TopsM ) return true;//если найдены вершины в нужном количестве
   else return false;
   }

Впадины определяются зеркально:

bool ExtremumsPatternFamilySearcher::SearchFirstDowns()//найдем впадины
   {
   int NumUp=0;
   int NumDown=0;
   bool bDown=false;//вспомогательная булевая, которая говорит о том найден ли сегмент медвежьих свечей
   bool bUp=false;//вспомогательная булевая, которая говорит о том найден ли сегмент бычьих свечей
   bool bNextUp=true;//можно ли переходить к поиску следующей вершины
   bool bNextDown=true;//можно ли переходить к поиску следующей впадины

   for(int i=0;i<ArraySize(TopsDown);i++)//перед поиском выставим все необходимые впадины в неактивное состояние 
      {
      TopsDown[i].bActive=false;
      }
   for(int i=0;i<ArraySize(TopsDownAll);i++)//перед поиском выставим все впадины в неактивное состояние 
      {
      if (!TopsDownAll[i].bActive) break;
      TopsDownAll[i].bActive=false;
      }

   for(int i=0;i<BarsM;i++)
      {
      if ( i+MinimumSeriesBarsM-1 < BarsM )//если оставшихся баров хватает для определения экстремума и можно начать поиск следующей вершины
         {
         if ( bNextDown )//если разрешено искать следующую впадину
            {
            bUp=true;               
            for(int j=i;j<i+MinimumSeriesBarsM;j++)//определим первые экстремумы для верхних вершин
               {
               if ( Open[j]-Close[j] > 0 )//если из выбранных свечей была хотя бы одна вниз
                  {
                  bUp=false;
                  break;
                  }
               }
            if ( bUp )
               {
               TopsDownAll[NumDown].Datetime0=Time[i+MinimumSeriesBarsM-1];
               TopsDownAll[NumDown].Index0=i+MinimumSeriesBarsM-1;
               bNextDown=false;
               }
            }        
         }

      if ( MinimumSeriesBarsM+i < BarsM && bUp )//если оставшихся баров хватает для определения второй половины экстремума и предыдущая половина найдена
         {   
         bDown=true;                              
         for(int j=i;j<MinimumSeriesBarsM+i;j++)//определим последующие свечи в обратном направлении
            {
            if ( Open[j]-Close[j] < 0 )//если из выбранных свечей была хотя бы одна вверх
               {
               bDown=false;
               break;
               }
            }
         if ( bDown )
            {
            TopsDownAll[NumDown].Datetime1=Time[i];
            TopsDownAll[NumDown].Index1=i;
            TopsDownAll[NumDown].bActive=true;
            bNextDown=false;              
            }
         } 
      //после чего зарегистрируем найденную формацию как впадину если она таковой является
      if ( bDown && bUp )
         {
         CalculateMinimum(TopsDownAll[NumDown],TopsDownAll[NumDown].Index0,TopsDownAll[NumDown].Index1);//посчитаем экстремум между двумя барами
         bNextDown=true;
         bDown=false;
         bUp=false;            
         NumDown++;
         }
      }
      
   if ( NumDown == TopsM ) return true;//если найдены впадины в нужном количестве
   else return false;
   }

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

Searching tops & bottoms

Сначала ищем движение 1, потом после него движение 2, а под номером 3 уже идет определение самой вершины или впадины. Для "3" логика вынесена в отдельные две функции которые выглядят вот так:

void ExtremumsPatternFamilySearcher::CalculateMaximum(Top &T,int Index0,int Index1)//если найдены 2 промежуточные точки то найдем между ними максимум
   {
   double MaxValue=High[Index0];
   datetime MaxTime=Time[Index0];
   int MaxIndex=Index0;
   for(int i=Index0;i<=Index1;i++)
      {
      if ( High[i] >  MaxValue )
         {
         MaxValue=High[i];
         MaxTime=Time[i];
         MaxIndex=i;
         }
      }
   T.DatetimeExtremum=MaxTime;
   T.IndexExtremum=MaxIndex;
   T.Price=MaxValue;
   }
   
void ExtremumsPatternFamilySearcher::CalculateMinimum(Top &T,int Index0,int Index1)//если найдены 2 промежуточные точки то найдем между ними минимум
   {
   double MinValue=Low[Index0];
   datetime MinTime=Time[Index0];
   int MinIndex=Index0;
   for(int i=Index0;i<=Index1;i++)
      {
      if ( Low[i] <  MinValue ) 
         {
         MinValue=Low[i];
         MinTime=Time[i];
         MinIndex=i;
         }
      } 
   T.DatetimeExtremum=MinTime;
   T.IndexExtremum=MinIndex;
   T.Price=MinValue;      
   }

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

Выбор вершин, с которыми будем работать

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

bool ExtremumsPatternFamilySearcher::PrepareExtremums()//назначим те вершины с которыми будем работать
   {
   int Quantity;//вспомогательный счетчик для рандомных вершин
   int PrevIndex;//вспомогательный индекс для соблюдения порядка следования индексов(только увеличение)
   
   for(int i=0;i<TopsM;i++)//просто выберем ближайшие вершины к рынку
      {
      TopsUp[i]=TopsUpAll[i];
      TopsDown[i]=TopsDownAll[i];
      }
   return true;   
   }

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

Choose tops & bottoms

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

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

Определение направления формации

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

void ExtremumsPatternFamilySearcher::DirectionOfFormation()//определим это двойная вершина(1) или двойное дно(-1) ( только в случае если и все впадины и все вершины нашлись ) а если не нашлись то "0"
   {
   if ( TopsDown[0].DatetimeExtremum > TopsUp[0].DatetimeExtremum && TopsDown[ArraySize(TopsDown)-1].bActive )
      {
      StartTop=TopsDown[ArraySize(TopsDown)-1];
      EndTop=TopsDown[0];    
      FormationDirection=-1;
      }
   else if ( TopsDown[0].DatetimeExtremum < TopsUp[0].DatetimeExtremum && TopsUp[ArraySize(TopsUp)-1].bActive )
      {
      StartTop=TopsUp[ArraySize(TopsUp)-1];
      EndTop=TopsUp[0]; 
      FormationDirection=1;  
      }
   else FormationDirection=0;   
   }

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

  1. Кратная вершина
  2. Кратное дно

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

Фильтры для отбрасывания некорректных паттернов

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

bool ExtremumsPatternFamilySearcher::IsExtremumsAbsolutely()//потребуем чтобы экстремумы которорые мы выбрали были самыми крайними
   {
   if ( bRandomExtremumsM )//проводим проверку только в том случае если у нас рандомный их выбор(в остальных случаях считаем данную проверку пройденной)
      {
      if ( FormationDirection == 1 )
         {
         int StartIndex=RandomIndexUp[0];
         int EndIndex=RandomIndexUp[ArraySize(RandomIndexUp)-1];
         for(int i=StartIndex+1;i<EndIndex;i++)//проверим все вершины которые между выбранными
            {
            for(int j=0;j<ArraySize(TopsUp);j++)
               {
               if ( TopsUpAll[i].Price >= TopsUp[j].Price )
                  {
                  for(int k=0;k<ArraySize(RandomIndexUp);k++)
                     {
                     if ( i != RandomIndexUp[k] ) return false;
                     }
                  }
               }
            }
         return true;
         }
      else if ( FormationDirection == -1 )
         {
         int StartIndex=RandomIndexDown[0];
         int EndIndex=RandomIndexDown[ArraySize(RandomIndexDown)-1];
         for(int i=StartIndex+1;i<EndIndex;i++)//проверим все вершины которые между выбранными
            {
            for(int j=0;j<ArraySize(TopsDown);j++)
               {
               if ( TopsDownAll[i].Price <= TopsDown[j].Price )
                  {
                  for(int k=0;k<ArraySize(RandomIndexDown);k++)
                     {
                     if ( i != RandomIndexDown[k] ) return false;
                     }
                  }
               }
            }
         return true;      
         }
      else return false;      
      }
   else
      {
      return true;
      }
   }

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

Контроль неучтенных вершин


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

После того как подготовительные процедуры выполнены, можно приступать к поиску шеи. Разные трейдеры строят шею по-разному. Я условно выделил несколько типов построения:

  1. Визуально с наклоном (не по теням)
  2. Визуально, горизонтально (не по теням)
  3. По наивысшей или наинизшей точке, с наклоном (по теням)
  4. По наивысшей или наинизшей точке, горизонтально (по теням)

Исходя из соображений безопасности и повышения шансов на прибыль, я считаю, что следует выбирать вариант 4. Данный выбор обусловлен следующими соображениями:

  • Более явное нахождение начала разворотного движения
  • Проще реализовать в коде
  • Однозначность определения угла наклона ( горизонтально )

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

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

void ExtremumsPatternFamilySearcher::FindNeckUp(Top &TStart,Top &TEnd)//найдем линию шеи исходя из двух крайних вершин ( для классической кратной вершины )
   {
   double PriceMin=Low[TStart.IndexExtremum];
   datetime TimeMin=Time[TStart.IndexExtremum];
   for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)//определим нижнюю точку
      {
      if ( Low[i] < PriceMin )
         {
         PriceMin=Low[i];
         TimeMin=Time[i];
         }
      }
   //определим точки привязки и все параметры уравнения линии
   Neck.Price0=PriceMin;
   Neck.TimeX=TimeMin;
   Neck.Time0=Time[0];
   Neck.Price1=PriceMin;
   Neck.Time1=TStart.DatetimeExtremum;
   Neck.DirectionOfFormation=true;
   Neck.CalculateKC();
   }
   
void ExtremumsPatternFamilySearcher::FindNeckDown(Top &TStart,Top &TEnd)//найдем линию шеи исходя из двух крайних впадин ( для классического кратного дна )
   {
   double PriceMax=High[TStart.IndexExtremum];
   datetime TimeMax=Time[TStart.IndexExtremum];
   for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)//определим нижнюю точку
      {
      if ( High[i] > PriceMax )
         {
         PriceMax=High[i];
         TimeMax=Time[i];         
         }
      }
   //определим точки привязки и все параметры уравнения линии
   Neck.Price0=PriceMax;
   Neck.TimeX=TimeMax;
   Neck.Time0=Time[0];
   Neck.Price1=PriceMax;
   Neck.Time1=TStart.DatetimeExtremum;
   Neck.DirectionOfFormation=false;
   Neck.CalculateKC();
   }

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

Шея

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

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

void ExtremumsPatternFamilySearcher::SearchFarestTop()//определим вершину на максимальном удалении
   {
   double MaxTranslation;//временная переменная для определения верхней вершины
   if ( FormationDirection == 1 )//если мы рассматриваем кратную вершину
      {
      MaxTranslation=TopsUp[0].Price-Neck.Price0;//временная переменная для определения верхней вершины
      FarestTop=TopsUp[0];
      for(int i=1;i<ArraySize(TopsUp);i++)
         {
         if ( TopsUp[i].Price-Neck.Price0 > MaxTranslation ) 
            {
            MaxTranslation=TopsUp[i].Price-Neck.Price0;
            FarestTop=TopsUp[i];
            }
         }      
      }
   if ( FormationDirection == -1 )//если мы рассматриваем кратное дно
      {
      MaxTranslation=Neck.Price0-TopsDown[0].Price;//временная переменная для определения верхней вершины
      FarestTop=TopsDown[0];      
      for(int i=1;i<ArraySize(TopsDown);i++)
         {
         if ( Neck.Price0-TopsDown[i].Price > MaxTranslation ) 
            {
            MaxTranslation=Neck.Price0-TopsDown[0].Price;
            FarestTop=TopsDown[i];
            }
         }      
      }
   }

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

bool ExtremumsPatternFamilySearcher::bBalancedExtremums()//сбалансируем вершины
   {
   double Lowest;//нижняя вершина для кратной вершины
   double Highest;//верхняя вершина для кратного дна
   double AbsMin;//расстояние от шеи до ближайшей вершины
   if ( FormationDirection == 1 )//для кратной вершины
      {
      Lowest=TopsUp[0].Price;
      for(int i=1;i<ArraySize(TopsUp);i++)//найдем самую нижнюю вершину
         {
         if ( TopsUp[i].Price < Lowest ) Lowest=TopsUp[i].Price;
         }
      AbsMin=Lowest-Neck.Price0;//определим расстояние от нижней вершины до шеи
      if ( AbsMin == 0.0 ) return false;
      if ( ((FarestTop.Price - Neck.Price0)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;//если голова слишком больше минимального плеча       
      }
   else if ( FormationDirection == -1 )//для кратного дна
      {
      Highest=TopsDown[0].Price;
      for(int i=1;i<ArraySize(TopsDown);i++)//найдем самую верхнюю вершину
         {
         if ( TopsDown[i].Price > Highest ) Highest=TopsDown[i].Price;
         }
      AbsMin=Neck.Price0-Highest;//определим расстояние от верхней вершины до шеи
      if ( AbsMin == 0.0 ) return false;
      if ( ((Neck.Price0-FarestTop.Price)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;//если голова слишком больше минимального плеча          
      }
   else return false;
   return true;   
   }

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

Контроль вертикальных размеров

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

  • K = (Max - Min)/Min
  • K <= RelativeUnstabilityM

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

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

bool ExtremumsPatternFamilySearcher::bBalancedExtremumsTime()//сбалансируем размеры плечей и головы по горизонтальной оси
   {
   double Lowest;//минимальное расстояние между вершинами
   double Highest;//максимальное расстояние между вершинами
   if ( FormationDirection == 1 )//для кратной вершины
      {
      Lowest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum;
      Highest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum;
      for(int i=1;i<ArraySize(TopsUp)-1;i++)//найдем самую нижнюю вершину
         {
         if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum < Lowest ) Lowest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum;
         if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum > Highest ) Highest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum;
         }
      if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;//если ширина одной из волн слишком выделяется
      }
   else if ( FormationDirection == -1 )//для кратного дна
      {   
      Lowest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum;
      Highest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum;
      for(int i=1;i<ArraySize(TopsDown)-1;i++)//найдем самую нижнюю вершину
         {
         if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum < Lowest ) Lowest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum;
         if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum > Highest ) Highest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum;
         }
      if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;//если ширина одной из волн слишком выделяется   
      }
   else return false;
   return true;
   }

Для проверки можно взять схожий показатель, и точно так же визуально это все можно изобразить графически:

Контроль горизонтального размера

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

  • K = (Max - Min)/Min
  • K <= RelativeUnstabilityTimeM

Линия шеи обязательно должна пересечься с ценой слева, потому что это будет означать, что этому мог предшествовать тренд:

bool ExtremumsPatternFamilySearcher::CorrectNeckUpLeft()//дальше необходимо скорректировать линию шеи так чтобы она нашла пересечение с ценой слева
   {
   bool bCrossNeck=false;//обозначает пересечена ли шея
   if ( Neck.DirectionOfFormation )//если шея найдена у двойной вершины
      {
      for(int i=StartTop.Index1;i<BarsM;i++)//определим точку пересечения
         {
         if ( High[i] >= FarestTop.Price )//если движение уходит за пределы формации то это фейковая формация
            {
            return false;
            }         
         if ( Close[i] < Neck.Price0 && Open[i] < Neck.Price0 && High[i] < Neck.Price0 && Low[i] < Neck.Price0   )
            {
            Neck.Time1=Time[i];
            Neck.Index1=i;
            return true;
            }
         }
      }
   return false;
   }
   
bool ExtremumsPatternFamilySearcher::CorrectNeckDownLeft()//дальше необходимо скорректировать линию шеи так чтобы она нашла пересечение с ценой слева
   {
   bool bCrossNeck=false;//обозначает пересечена ли шея
   if ( !Neck.DirectionOfFormation )//если шея найдена у двойного дна
      {
      for(int i=StartTop.Index1;i<BarsM;i++)//определим точку пересечения
         {
         if ( Low[i] <= FarestTop.Price )//если движение уходит за пределы формации то это фейковая формация
            {
            return false;
            }         
         if ( Close[i] > Neck.Price0 && Open[i] > Neck.Price0 && High[i] > Neck.Price0 && Low[i] > Neck.Price0 )
            {
            Neck.Time1=Time[i];
            Neck.Index1=i;
            return true;
            }
         }
      }
   return false;
   }

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

Контроль пересечения слева и справа

Синими рамками выделены сегменты рынка, где мы осуществляем данный контроль. Оба сегмента находятся за паттерном, слева и справа от крайних вершин. 

Остается всего две проверки:

  1. Паттерн нам нужен именно такой, который пересекает линию шеи в текущий момент (то есть на нулевой свече)
  2. Необходимо, чтобы паттерну предшествовало движение больше либо равное размеру самого паттерна

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

Пересечение с нулевой свечей (проверка пересечения справа) считается так:

int ExtremumsPatternFamilySearcher::CorrectNeckUpRight()//дальше необходимо скорректировать линию шеи так чтобы она нашла пересечение с ценой справа
   bool bCrossNeck=false;//обозначает пересечена ли шея
   if ( Neck.DirectionOfFormation )//если шея найдена у двойной вершины
      {
      for(int i=EndTop.IndexExtremum;i>1;i--)//определим точку пересечения
         {
         if ( High[i] > FarestTop.Price || Low[i] < Neck.Price0 )//если движение уходит за пределы формации то это фейковая формация
            {
            return -1;
            }         
         }
      }
      
   if ( Close[0] <= Neck.Price0 )
      {
      Neck.Time0=Time[0];
      return 1;
      }      
   return 0;
   }

int ExtremumsPatternFamilySearcher::CorrectNeckDownRight()//дальше необходимо скорректировать линию шеи так чтобы она нашла пересечение с ценой справа
   {
   bool bCrossNeck=false;//обозначает пересечена ли шея
   if ( !Neck.DirectionOfFormation )//если шея найдена у двойного дна
      {
      for(int i=EndTop.IndexExtremum;i>1;i--)//определим точку пересечения
         {
         if ( Low[i] < FarestTop.Price || High[i] > Neck.Price0  )//если движение уходит за пределы формации то это фейковая формация
            {
            return -1;
            }         
         }
      }
      
   if ( Close[0] >= Neck.Price0 )
      {
      Neck.Time0=Time[0];
      return 1;
      }   
      
   return 0;
   }
<

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

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

bool ExtremumsPatternFamilySearcher::bWasTrend()//нашли ли движение предшествующее формации ( одновременно здесь перемещаем точку привязки в пересечение )
   {
   bool bCrossOptimist=false;//обозначает пересечена ли шея
   if ( FormationDirection == 1 )//если оптимистичный прогноз у двойной вершины
      {
      for(int i=Neck.Index1;i<BarsM;i++)//определим точку пересечения
         {
         if ( High[i] > Neck.Price0 )//если движение уходит за пределы шеи то это фейковое движение
            {
            return false;
            }         
         if ( Low[i] < OptimistLine.Price0 )
            {
            OptimistLine.Time1=Time[i];
            return true;
            }
         }
      }
   else if ( FormationDirection == -1 )//если оптимистичный прогноз у двойного дна
      {
      for(int i=Neck.Index1;i<BarsM;i++)//определим точку пересечения
         {
         if ( Low[i] < Neck.Price0 )//если движение уходит за пределы шеи то это фейковое движение
            {
            return false;
            }         
         if ( High[i] > OptimistLine.Price0 )
            {
            OptimistLine.Time1=Time[i];
            return true;
            }
         }      
      }
   return false;
   }

Визуально работу последнего предиката так же можно изобразить графически:

Предшествующее движение

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

Смотрим на результат в визуализаторе тестера стратегий MetaTrader 5:

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

тройная вершина в визуализаторе тестера стратегий MetaTrader 5

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


Идеи на будущее

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

  1. Другие интересные паттерны
  2. Иные методы обнаружения формации иного типа
  3. Торговля на истории и сбор статистики для разных инструментов и таймфреймов
  4. Паттернов очень много и все я не знаю (поэтому потенциально могу рассмотреть ваш паттерн)
  5. Уровни также будут освещены (так как уровни используются повсеместно для обнаружения разворотов)


    Заключение

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

    Прикрепленные файлы |
    Prototype.zip (311.2 KB)
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (25)
    WME Ukraine/ lab. of Internet-trading
    Alexandr Plys | 30 мая 2021 в 13:49
    Dmitry Bogdanov:

    :)

    а выставить "барьер" в стакане - это что? неужели не чье-то решение?

    а почему вдруг кто-то решил выставить что-то в стакан?

    Вот именно вашего решения ни в коем случае во внимание не возьмут.
    На биржевых рынках очень желательно торговать по волнам, так сказать, как "рыба-прилипала".
    А на внутреннем, вы можете решать многое, да хоть Луну приближать, но только что это будет значит и каков результат будет?
    Читайте книги и переходите на реальный рынок.
    Хотя бы, дивиденды получайте, иначе ваш депозит изгрызут обязательно.

    Вот, к примеру, в 2007 году ещё блистал Теле.... (брокер такой).
    Один из моих учеников, пострадал от ночной просадки (кратковременно) USDCAD в 45 пунктов (тогда пятого знака ещё не было), причём ни в одном источнике на рынках такого движения не было.
    Что ответил этот блистательный "бро":
    - это движение создали внутренние клиенты компании. В 4:00 утра (примерно).
    В результате пострадали многие клиенты.
    А что же в реальности?
    А ничего. Ночью, в четыре утра, ничего на реальных рынках не случилось.
    Внутри бассейна всегда можно бурю создать и на смартфон снять как катастрофу в океане, но разве это будет так?
    Это вот такой способ депозиты есть.
    Ну что же, вот такие они продавцы демо-счетов.

    Evgeniy Ilin
    Evgeniy Ilin | 30 мая 2021 в 15:15
    Dmytryi Nazarchuk:

    Есть хоть какое то статистическое обоснование прогностических способностей паттерна? В чем смысл его идентификации на графике, если на нем невозможно построить статистически значимый прогноз? Паттерн идентифицируется только после его полного построения - а дальше? Есть статистика или в чем вообще смысл?

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

    Анатолий Лукин
    Анатолий Лукин | 28 окт. 2021 в 21:47
    Здравствуйте! Оч понравилась статья, с нетерпением ждем продолжения!)
    Evgeniy Ilin
    Evgeniy Ilin | 31 окт. 2021 в 21:56
    Анатолий Лукин #:
    Здравствуйте! Оч понравилась статья, с нетерпением ждем продолжения!)

    приветствую, будет конечно, сейчас просто занят веткой по теорверу, а после следующей статьи по теорверу будет вторая часть этой ветки

    andrik377
    andrik377 | 2 дек. 2021 в 07:53
    @Evgeniy Ilin Математика всех паттернов есть здесь "Булковски - Энциклопедия ценовых моделей". На торрентах имеется.
    Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента
    В статье начинаем новый большой раздел библиотеки по работе с графикой. Сегодня создадим объект состояний мышки, базовый объект всех графических элементов и класс объекта-формы графических элементов библиотеки.
    Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции Прочие классы в библиотеке DoEasy (Часть 72): Отслеживание и фиксация параметров объектов-чартов в коллекции
    В статье завершим работу над классами объектов-чартов и их коллекцией. Сделаем автоматическое отслеживание изменения свойств чартов и их окон, а также сохранение новых параметров в свойства объекта. Такая доработка позволит в будущем сделать событийный функционал для всей коллекции чартов.
    Комбинаторика и теория вероятностей для трейдинга (Часть I): Основы Комбинаторика и теория вероятностей для трейдинга (Часть I): Основы
    В данной серии статей будем искать практическое применение теории вероятностей для описания процесса торговли и ценообразования. В первой статье мы познакомимся с основами комбинаторики и теории вероятностей, и разберем первый пример применения фракталов в рамках теории вероятности.
    Советы профессионального программиста (Часть II): Организация хранения и обмена параметров между экспертом, скриптами и внешними программами Советы профессионального программиста (Часть II): Организация хранения и обмена параметров между экспертом, скриптами и внешними программами
    Советы профессионального программиста о методах, приемах и вспомогательных инструментах, облегчающих программирование. Речь пойдет о параметрах, которые можно восстанавливать после перезапуска (закрытия) терминала. Все примеры — реально работающие куски кода из моего проекта Cayman.