Реализация фактора Януса в MQL5

Francis Dube | 16 мая, 2023

Введение

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

Обратная связь

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

Рис. 1. Положительная обратная связь при восходящем тренде

Рисунок 1 иллюстрирует положительную обратную связь при восходящем тренде


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

Рис. 2. Положительная обратная связь при нисходящем тренде

На рисунке 2 показана положительная обратная связь при нисходящем тренде

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

Рис. 3. Отрицательная обратная связь

Рис. 3. Отрицательная обратная связь на рынке

Движение капитала

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

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


Расчеты Януса

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

В книге Фактор Януса - Руководство по диалектике рынка для следующих за трендом Андерсон описывает различные показатели акций, рассчитанные на основе доходности, которые он использует, чтобы получить представление о поведении рынка. 


Библиотека janus.mqh

Данные и подпрограммы, общие для всех вычислений, связанных с Janus, содержатся в janus.mqh. Включаемый файл содержит объявления для трех пользовательских типов:

Класс CSymboldata обрабатывает данные символов и связанные с ними операции. 

//+------------------------------------------------------------------+
//|Class which manage the single Symbol                              |
//+------------------------------------------------------------------+
class CSymbolData
  {

private:
   string            m_name;    // name of the symbol
   ENUM_TIMEFRAMES   m_timeframe;// timeframe
   int               m_length;  // length for copy rates
   MqlRates          m_rates[]; // store rates
   datetime          m_first;   // first date on server or local history

   datetime          SetFirstDate(void)
     {
      datetime first_date=-1;
      if((datetime)SymbolInfoInteger(m_name,SYMBOL_TIME)>0)
         first_date=(datetime)SeriesInfoInteger(m_name,m_timeframe,SERIES_FIRSTDATE);
      //---
      if(first_date==WRONG_VALUE || first_date==0)
        {
         if(TerminalInfoInteger(TERMINAL_CONNECTED))
           {
            while(!SeriesInfoInteger(m_name,m_timeframe,SERIES_SERVER_FIRSTDATE,first_date) && !IsStopped())
               Sleep(10);
           }
        }
      //---
#ifdef DEBUG
      Print(m_name," FirstDate ",first_date);
#endif
      return first_date;
     }

public:
                     CSymbolData(string name,ENUM_TIMEFRAMES tf=PERIOD_CURRENT)
     {
      m_name = name;
      m_length = 0;
      m_timeframe = tf;
      SymbolSelect(m_name,true);
     }

                    ~CSymbolData(void)
     {
      ArrayFree(m_rates);
     }
   datetime          GetFirstDate(void)
     {
      m_first = SetFirstDate();
      return m_first;
     }
   string            GetName(void)
     {
      return m_name;
     }

   int               GetLength(void)
     {
      return m_length;
     }

   void              SetLength(const int set_length)
     {
      if(set_length>0)
        {
         m_length=set_length;
         ArrayResize(m_rates,m_length,m_length);
         ArraySetAsSeries(m_rates,true);
        }
     }

   bool              Update(void)
     {
      int copied = CopyRates(m_name,m_timeframe,0,m_length,m_rates);
#ifdef DEBUG
      Print("copied ", copied, " requested ", m_length);
#endif
      //--
      return copied == m_length;
     };

   MqlRates          GetRateAtPos(const int i)
     {
      if(i<0 || i>=m_length)
        {
#ifdef DEBUG
         Print("Array out of range ",i,".Size of array is ",m_length);
#endif
         return (i<0)?m_rates[0]:m_rates[m_length-1];
        }
      return m_rates[i];
     }
  };
CSymbolCollection представляет собой контейнер объектов CSymboldata, который представляет набор анализируемых символов. Код для обоих классов был адаптирован из листинга кодовой базы MQL5. Были внесены изменения в направление индексации базовых буферов. Следует отметить, что все классы реализуют формат индексации справа налево с нулевым индексом, указывающим на последний бар. 

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

//+------------------------------------------------------------------+
//| Class that mange the collection of symbols                       |
//+------------------------------------------------------------------+
class CSymbolCollection
  {

private:

   int               m_calculation_length; // global length
   ENUM_TIMEFRAMES   m_collection_timeframe;  // timeframe of data
   string            m_raw_symbols;    // delimited symbol list
   datetime          m_synced_first;  // synced first bar opentime for all symbols
   bool              m_synced;        // flag of whether all symbols are synchronized
   CSymbolData       *m_collection[]; // Collection of Symbol Pointer
   int               m_collection_length; // Collection of Symbol Length
   //+------------------------------------------------------------------+
   //|                                                                  |
   //+------------------------------------------------------------------+
   bool               CheckSymbolBars(const string __sym)
     {
      int bars=-1;
      bars=iBarShift(__sym,m_collection_timeframe,m_synced_first)+1;//SeriesInfoInteger(__sym,PERIOD_CURRENT,SERIES_BARS_COUNT);
#ifdef DEBUG
      Print("Bars found in history for ",__sym," ",bars);
#endif
      if(bars>=m_calculation_length)
         return(true);
      //---
      return(SyncSymbol(__sym));
     }
   //+------------------------------------------------------------------+
   //|                                                                  |
   //+------------------------------------------------------------------+
   bool               SyncSymbol(const string __sym)
     {
      //--- load data step by step
      bool downloaded=false;
      datetime times[1];
      int bars=-1;
      
     /* if(MQLInfoInteger(MQL_PROGRAM_TYPE)==PROGRAM_INDICATOR)
        {
#ifdef DEBUG
         Print(" cannot download ",__sym," history from an indicator");
#endif
         return(downloaded);
        }*/
          
#ifdef DEBUG
      Print(" downloading ",__sym," history");
#endif
      while(!IsStopped() && !downloaded && TerminalInfoInteger(TERMINAL_CONNECTED))
        {
         //---
         while(!SeriesInfoInteger(__sym,m_collection_timeframe,SERIES_SYNCHRONIZED) && !IsStopped())
            Sleep(5);
         //---
         bars=Bars(__sym,PERIOD_CURRENT);
         if(bars>=m_calculation_length)
           {
            downloaded=true;
            break;
           }
         //--- copying of next part forces data loading
         if(CopyTime(__sym,m_collection_timeframe,m_calculation_length-1,1,times)==1)
           {
            downloaded=true;
            break;
           }
         //---
         Sleep(5);
        }
#ifdef DEBUG
      if(downloaded)
         Print(bars," ",__sym," bars downloaded ");
      else
         Print("Downloading ",__sym," bars failed");
#endif
      return(downloaded);
     }

public:

                     CSymbolCollection(const ENUM_TIMEFRAMES tf=PERIOD_CURRENT)
     {
      m_raw_symbols="";
      m_collection_length = 0;
      m_calculation_length = -1;
      m_synced_first=0;
      m_synced=false;
      m_collection_timeframe=tf;
     }

                    ~CSymbolCollection(void)
     {

      for(int i=0; i<m_collection_length; i++)
        {
         if(CheckPointer(m_collection[i])==POINTER_DYNAMIC)
            delete m_collection[i];
        }
     }
   //+------------------------------------------------------------------+
   //|return the set timeframe for bars stored in the collection        |
   //+------------------------------------------------------------------+
   ENUM_TIMEFRAMES   GetTimeFrame(void)
     {
      return(m_collection_timeframe);
     }
   
   //+------------------------------------------------------------------+
   //|Checks the history available and syncs it across all symbols      |
   //+------------------------------------------------------------------+
   bool              CheckHistory(const int size)
     {
      if(size<=0)
         return(false);

      int available=iBarShift(NULL,m_collection_timeframe,m_synced_first)+1;

      if(available<size)
         m_calculation_length=available;
      else
         m_calculation_length=size;

#ifdef DEBUG
      Print("synced first date is ", m_synced_first);
      Print("Proposed size of history ",m_calculation_length);
#endif

      if(m_calculation_length<=0)
         return(false);

      ResetLastError();

      for(int i=0; i<m_collection_length; i++)
        {
         m_synced=CheckSymbolBars(m_collection[i].GetName());
         if(!m_synced)
           {
            Print("Not Enough history data for ", m_collection[i].GetName(), " > ", GetLastError());
            return(m_synced);
           }
         m_collection[i].SetLength(m_calculation_length);
        }

      return m_synced;

     }


   //+------------------------------------------------------------------+
   //| Add a symbol by name to the collection                           |
   //+------------------------------------------------------------------+
   int               Add(string name)
     {
      CSymbolData *ref = new CSymbolData(name,m_collection_timeframe);
      datetime f=ref.GetFirstDate();
      int found=GetIndex(name);
      if(f==WRONG_VALUE || found>-1)
        {
#ifdef DEBUG
         if(f==WRONG_VALUE)
            Print("Failed to retrieve information for symbol ",name,". Symbol removed from collection");
         if(found>-1)
            Print("Symbol ",name,"already part of collection");
#endif
         delete ref;
         return(m_collection_length);
        }
      ArrayResize(m_collection, m_collection_length + 1,1);
      m_collection[m_collection_length] = ref;
      //---
      if(f>m_synced_first)
         m_synced_first=f;
      //---

      return(++m_collection_length);

     }
   //+------------------------------------------------------------------+
   //|Return symbol name                                                |
   //+------------------------------------------------------------------+
   string            GetSymbolNameAtPos(int pos)
     {
      return m_collection[pos].GetName();
     }
   //+------------------------------------------------------------------+
   //|return index of symbol                                            |
   //+------------------------------------------------------------------+
   int               GetIndex(const string symbol_name)
     {
      for(int i=0; i<m_collection_length; i++)
        {
         if(symbol_name==m_collection[i].GetName())
            return(i);
        }
      //---fail
      return(-1);
     }
   //+------------------------------------------------------------------+
   //| Return Collection length                                         |
   //+------------------------------------------------------------------+
   int               GetCollectionLength(void)
     {
      return m_collection_length;
     }

   //+------------------------------------------------------------------+
   //| Update every currency rates                                      |
   //+------------------------------------------------------------------+
   bool              Update(void)
     {

      int i;
      for(i = 0; i < m_collection_length; i++)
        {
         bool res = m_collection[i].Update();
         if(res==false)
           {
            Print("missing data on " + m_collection[i].GetName());
            return false;
           }
        }
      return true;
     }
   //+------------------------------------------------------------------+
   //|                                                                  |
   //+------------------------------------------------------------------+
   int               GetHistoryBarsLength(void)
     {
      return m_calculation_length;
     }
   //+------------------------------------------------------------------+
   //| Return MqlRates of currency at position                          |
   //+------------------------------------------------------------------+
   MqlRates          GetRateAtPos(int pos, int i)
     {
      return m_collection[pos].GetRateAtPos(i);
     }

   //+------------------------------------------------------------------+
   //| Return Open price of currency at position                        |
   //+------------------------------------------------------------------+
   double            GetOpenAtPos(int pos, int i)
     {
      return m_collection[pos].GetRateAtPos(i).open;
     }

   //+------------------------------------------------------------------+
   //| Return Close price of currency at position                       |
   //+------------------------------------------------------------------+
   double            GetCloseAtPos(int pos, int i)
     {
      return m_collection[pos].GetRateAtPos(i).close;
     }

   //+------------------------------------------------------------------+
   //| Return High price of currency at position                        |
   //+------------------------------------------------------------------+
   double            GetHighAtPos(int pos, int i)
     {
      return m_collection[pos].GetRateAtPos(i).high;
     }

   //+------------------------------------------------------------------+
   //| Return Low price of currency at position                         |
   //+------------------------------------------------------------------+
   double            GetLowAtPos(int pos, int i)
     {
      return m_collection[pos].GetRateAtPos(i).low;
     }

   //+------------------------------------------------------------------+
   //| Return Median price of currency at position                      |
   //+------------------------------------------------------------------+
   double            GetMedianAtPos(int pos, int i)
     {
      return (GetHighAtPos(pos,i) + GetLowAtPos(pos, i))/2;
     }

   //+------------------------------------------------------------------+
   //| Return Typical price of currency at position                     |
   //+------------------------------------------------------------------+
   double            GetTypicalAtPos(int pos, int i)
     {
      return (GetHighAtPos(pos,i) + GetLowAtPos(pos, i) + GetCloseAtPos(pos,i))/3;
     }

   //+------------------------------------------------------------------+
   //| Return Weighted price of currency at position                    |
   //+------------------------------------------------------------------+
   double            GetWeightedAtPos(int pos, int i)
     {
      return (GetHighAtPos(pos,i) + GetLowAtPos(pos, i) + GetCloseAtPos(pos,i) * 2)/4;
     }
  };
//+------------------------------------------------------------------+

 

Код начинается с трех пользовательских перечислений, описанных ниже.

#include<Math\Stat\Math.mqh>
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum ENUM_INDEX_TYPE
  {
   INDEX_FOREX_MAJORS=0,//forex majors only list
   INDEX_CUSTOM,//custom symbol list
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum ENUM_PRICE
  {
   CLOSE=0,//close price
   MEDIAN,//median price
   TYPICAL,//typical price
   WEIGHTED//weighted price
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum ENUM_DIFF_TYPE
  {
   DIFF_PERCENT=0,//percent difference
   DIFF_LOG//log difference
  };

Перечисление
Детали
Настройки
ENUM_INDEX_TYPE
Тип символов, включенных в анализируемую коллекцию
INDEX_FOREX - создать коллекцию символов, состоящую из всех основных форекс-валют. Набор из 28 символов.
INDEX_CUSTOM - указать набор символов.
ENUM_PRICE
разрешить выбор ценового ряда для использования в расчетах. Информация о ценах хранится в виде рядов с последними ценами с индексом 0.

CLOSE - цена закрытия
MEDIAN - медианная цена - high+low/2
TYPICAL - типичная цена - high+low+close/3
WEIGHTED - взвешенная цена high+low+close+close/4

 ENUM_DIFF_TYPE
 Различные методы дифференцирования, которые можно применить к выбранному ценовому ряду.
DIFF_LOG - использовать логарифмическую разность
DIFF_PERCENT - использовать разность в процентах

Чтобы использовать класс CJanus, необходимо освоить пять методов. После создания объекта CJanus и перед вызовом любого метода сначала должна быть вызвана функция-член Initialize. Метод делает две важные вещи - инициализирует объект CSymbolCollection и заполняет его выбранными символами, которые составят наш индекс. Затем он проверяет и синхронизирует бары истории символов в коллекции.

//+------------------------------------------------------------------+
//|Janus class for calculating janus family of indicators.           |
//+------------------------------------------------------------------+
class CJanus
  {
private:
   CSymbolCollection* m_symbol_list;    //object container of symbols
   ENUM_PRICE         m_price_type;     //applied price for calculations
   ENUM_DIFF_TYPE     m_diff_type;      //method of differencing applied
   ENUM_INDEX_TYPE    m_index_type;     //type of index
   int                m_hist_size;      // synchronized size of history across all selected symbols in collection
   ENUM_TIMEFRAMES    m_list_timeframe; //timeframe for bars to be used in calculations
   //---private methods
   double            market_return(const uint barshift, const uint symbolshift);
   void              market_offense_defense(const uint barshift,const uint symbolshift,const uint rs_period,double& out_offense,double& out_defense);
   double            rs(const uint barshift,const uint symbolshift,const uint rs_period,uint lag=0);
   double            rs_off_def(const uint barshift, const uint symbolshift,const uint lag,const double median,const double index_offense, const double index_defense, double &array[]);
   //---
public:
   //constructor
                     CJanus(void):m_symbol_list(NULL),
                     m_price_type(WRONG_VALUE),
                     m_diff_type(WRONG_VALUE),
                     m_index_type(WRONG_VALUE),
                     m_list_timeframe(WRONG_VALUE),
                     m_hist_size(0)
     {

     }
   // destructor
                    ~CJanus(void)
     {
      if(CheckPointer(m_symbol_list)==POINTER_DYNAMIC)
         delete m_symbol_list;
     }
   //public methods
   bool              Initialize(const ENUM_PRICE set_price_type, const ENUM_DIFF_TYPE set_diff_type, const ENUM_INDEX_TYPE set_index_type, ENUM_TIMEFRAMES set_timeframe,const int history_size, const string symbol_list);
   bool              Update(void);
   int               HistorySize(void);
   string            GetSymbolAt(const int sym_ind);
   int               GetSymbolsTotal(void);
   double            CalculateReturn(const uint barshift, const string symbol__);
   double            CalculateBenchMarkReturn(const uint barshift);
   double            CalculateSummedIndexReturn(const uint barshift);
   void              CalculateBenchMarkOffenseDefense(const uint barshift,const uint rs_period,double& out_offense,double& out_defense);
   void              CalculateMarketOffenseDefense(const uint barshift,const string symbol__,const uint rs_period,double& out_offense,double& out_defense);
   double            CalculateRelativeStrength(const uint barshift,const string symbol__,const uint rs_period,uint lag=0);
   void              CalculateRelativeStrengthLeaderLaggard(const uint barshift,const uint rs_period,const double rs_percent_top,double& leader,double& laggard);
   double            CalculateRelativeStrengthSpread(const uint barshift,const uint rs_period,const double rs_percent_top);
   double            CalculateRelativeStrengthSpreadChange(const uint barshift,const uint rs_period,const double rs_percent_top);

  };

Входные параметры метода Initialize() поясняются в таблице ниже.

Параметры
Детали
 Тип данных
set_price_type
Перечисление, устанавливающее базовый ценовой ряд, который будет использоваться для всех расчетов
ENUM_PRICE
set_diff_type
Метод дифференцирования, применяемый к ценовому ряду
ENUM_DIFF_TYPE
set_index_type
Тип коллекции символов - либо пользовательский, либо только основные валюты форекс. Если выбран пользовательский, символы должны быть указаны в параметрах symbol_list
ENUM_INDEX_TYPE
set_timeframe
Таймфрейм баров, которые будут использоваться в расчетах
ENUM_TIMEFRAME

history_size
Максимальное количество баров, которое должно быть запрошено. Поскольку мы имеем дело с несколькими символами, это способ гарантировать, что все символы в коллекции имеют одинаковое количество доступных исторических данных баров.
integer

 symbol_list
Если для set_index_type установлено значение INDEX_CUSTOM, пользователь должен указать разделенный запятыми список символов, которые составят набор символов для анализа.
 string
Другие важные методы:

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


Измерение производительности

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

//+------------------------------------------------------------------+
//|private method that calculates the returns                        |
//+------------------------------------------------------------------+
double CJanus::market_return(const uint barshift, const uint symbolshift)
  {
   double curr,prev;
   curr=0;
   prev=1.e-60;

   switch(m_price_type)
     {
      case CLOSE:
         curr=m_symbol_list.GetCloseAtPos(symbolshift,barshift);
         prev=m_symbol_list.GetCloseAtPos(symbolshift,barshift+1);
         break;
      case MEDIAN:
         curr=m_symbol_list.GetMedianAtPos(symbolshift,barshift);
         prev=m_symbol_list.GetMedianAtPos(symbolshift,barshift+1);
         break;
      case TYPICAL:
         curr=m_symbol_list.GetTypicalAtPos(symbolshift,barshift);
         prev=m_symbol_list.GetTypicalAtPos(symbolshift,barshift+1);
         break;
      case WEIGHTED:
         curr=m_symbol_list.GetWeightedAtPos(symbolshift,barshift);
         prev=m_symbol_list.GetWeightedAtPos(symbolshift,barshift+1);
         break;
      default:
         return WRONG_VALUE;
     }

   if(prev==0)
      return(WRONG_VALUE);

   switch(m_diff_type)
     {
      case DIFF_PERCENT:
         return(((curr-prev)/prev)*100);
      case DIFF_LOG:
         return(MathLog(curr/prev));
      default:
         return(WRONG_VALUE);
     }
  }
//+------------------------------------------------------------------+
//|public method to calculate returns for single bar                 |
//+------------------------------------------------------------------+
double CJanus::CalculateReturn(const uint barshift, const string symbol_)
  {
   int sshift=m_symbol_list.GetIndex(symbol_);
   if(sshift>-1)
      return(market_return(barshift,sshift));
   else
      return(WRONG_VALUE);
  }

Код индикатора IndexReturns, приведенный ниже, отображает доходность символа графика, а также эталонную доходность.

//+------------------------------------------------------------------+
//|                                                 IndexReturns.mq5 |
//|                        Copyright 2023, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   2
//--- plot IndexReturns
#property indicator_label1  "IndexReturns"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot Returns
#property indicator_label2  "Returns"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
#include<Janus.mqh>
//inputs
input ENUM_PRICE AppliedPrice=CLOSE;
input ENUM_DIFF_TYPE AppliedDiffType=DIFF_LOG;
input ENUM_INDEX_TYPE SelectIndexType=INDEX_FOREX_MAJORS;
input string BenchMarkSymbols="";
input int MaxBars = 300;
//--- indicator buffers
double         IndexReturnsBuffer[];
double         ReturnsBuffer[];
CJanus *janus;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,IndexReturnsBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,ReturnsBuffer,INDICATOR_DATA);
  
   ArraySetAsSeries(IndexReturnsBuffer,true);
   ArraySetAsSeries(ReturnsBuffer,true);  
//--- 
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0);
   PlotIndexSetString(1,PLOT_LABEL,_Symbol+" Returns");
//---
   IndicatorSetInteger(INDICATOR_DIGITS,8);
   IndicatorSetString(INDICATOR_SHORTNAME,"IndexReturns("+_Symbol+")");
//---
   janus=new CJanus();   
//--- 
   if(!janus.Initialize(AppliedPrice,AppliedDiffType,SelectIndexType,PERIOD_CURRENT,MaxBars,BenchMarkSymbols))
     return(INIT_FAILED);     
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|Custom indicator deinitialization function                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
 {
//--- 
  switch(reason)
   {
    case REASON_INITFAILED:
      ChartIndicatorDelete(ChartID(),ChartWindowFind(),"IndexReturns("+_Symbol+")");
       break;
    default:
       break;
   } 
//---
   if(CheckPointer(janus)==POINTER_DYNAMIC)
      delete janus;       
 }   
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   int limit;
   if(prev_calculated<=0)
     {
      limit=janus.HistorySize()-2;
      PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,rates_total-limit+1);
      PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,rates_total-limit+1);
     } 
   else 
     limit=rates_total-prev_calculated;
   //---  
   if(!janus.Update())
     return(prev_calculated); 
     
   for(int bar=limit;bar>=1;bar--)
    {
     ReturnsBuffer[bar]=janus.CalculateReturn(bar,_Symbol);
     IndexReturnsBuffer[bar]=janus.CalculateBenchMarkReturn(bar);
    }    
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Индикатор IndexReturns

Индикатор IndexReturns

Эталон

Чтобы рассчитать эталонную доходность, мы используем индивидуальную доходность символов для каждого соответствующего момента времени, чтобы получить медианное значение. Этот расчет производится с помощью метода CalculateBenchmarkReturns(). 

//+------------------------------------------------------------------+
//|public method to calculate index returns                          |
//+------------------------------------------------------------------+
double CJanus::CalculateBenchMarkReturn(const uint barshift)
  {
   double sorted[];
   int size=m_symbol_list.GetCollectionLength();

   if(size<=0)
      return(WRONG_VALUE);

   ArrayResize(sorted,size);

   for(int i=0; i<size; i++)
     {
      sorted[i]=market_return(barshift,i);
     }

   if(!ArraySort(sorted))
     {
      Print("sorting error ",__LINE__," ",__FUNCTION__);
      return(0);
     }

   return(MathMedian(sorted));
  }


Атака и защита

Чтобы лучше понять относительную производительность, Андерсон придумал для символа понятие очков атаки (offense) и защиты (defense). Очки атаки — это производительность, достигнутая, когда эталонная доходность выше средней. 

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

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

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

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

//+------------------------------------------------------------------+
//|public method to calculate Index offense and defense scores       |
//+------------------------------------------------------------------+
void CJanus::CalculateBenchMarkOffenseDefense(const uint barshift,const uint rs_period,double& out_offense,double& out_defense)
  {
   out_defense=out_offense=WRONG_VALUE;

   double index[],sorted[],median,i_offense,i_defense;
   median=i_offense=i_defense=0;

   ArrayResize(index,rs_period);
   ArrayResize(sorted,rs_period);

   int begin=0;

   for(int i=0; i<(int)rs_period; i++)
     {
      index[i]=CalculateBenchMarkReturn(barshift+i);
      if(i>=begin)
         sorted[i-begin]=index[i];
     }

   if(!ArraySort(sorted))
     {
      Print("sorting error ",__LINE__," ",__FUNCTION__);
      return;
     }

   median=MathMedian(sorted);

   i_offense=1.e-30;
   i_defense=-1.e-30;

   for(int i=begin; i<(int)rs_period; i++)
     {
      if(index[i]>=median)
         i_offense+=index[i]-median;
      else
         i_defense+=index[i]-median;
     }

   if(i_offense<0 || i_defense>0)
     {
#ifdef DEBUG
      Print("error invalid figures ","Offensive ",i_offense," Defensive ",i_defense);
#endif
      return;
     }

   out_offense=i_offense;
   out_defense=i_defense;

   return;
  }

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

Аналогичные вычисления выполняются с использованием эталонной доходности защиты. Метод CalculateSymbolOffenseDefense() реализует подсчет как очков атаки, так и очков защиты.

//+------------------------------------------------------------------+
//|public method to calculate market offense and defense                                                                  |
//+------------------------------------------------------------------+
void CJanus::CalculateSymbolOffenseDefense(const uint barshift,const string symbol__,const uint rs_period,double &out_offense,double &out_defense)
  {
   out_defense=out_offense=0.0;
   int sshift=m_symbol_list.GetIndex(symbol__);
   if(sshift>-1)
      market_offense_defense(barshift,sshift,rs_period,out_offense,out_defense);
   else
      return;
  }
//+------------------------------------------------------------------+
//|private method that calculates market defense and offense values  |
//+------------------------------------------------------------------+
void CJanus::market_offense_defense(const uint barshift,const uint symbolshift,const uint rs_period,double& out_offense,double& out_defense)
  {
   out_defense=out_offense=0.0;

   double index[],sorted[],median,i_offense,i_defense;
   median=i_offense=i_defense=0;

   ArrayResize(index,rs_period);
   ArrayResize(sorted,rs_period);

   int begin=0;

   for(int i=0; i<(int)rs_period; i++)
     {
      index[i]=CalculateBenchMarkReturn(barshift+i);
      if(i>=begin)
         sorted[i-begin]=index[i];
     }

   if(!ArraySort(sorted))
     {
      Print("sorting error ",__LINE__," ",__FUNCTION__);
      return;
     }

   median=MathMedian(sorted);

   i_offense=1.e-30;
   i_defense=-1.e-30;

   for(int i=begin; i<(int)rs_period; i++)
     {
      if(index[i]>=median)
         i_offense+=index[i]-median;
      else
         i_defense+=index[i]-median;
     }

   if(i_offense<0 || i_defense>0)
     {
#ifdef DEBUG
      Print("error invalid figures ","Offensive ",i_offense," Defensive ",i_defense);
#endif
      return;
     }

   double m_offense,m_defense;
   m_offense=m_defense=0;

   for(int i=0; i<(int)rs_period; i++)
     {
      if(index[i]>=median)
         m_offense+=market_return(barshift+i,symbolshift);
      else
         m_defense+=market_return(barshift+i,symbolshift);
     }

   out_defense= (m_defense/i_defense) * 100;
   out_offense= (m_offense/i_offense) * 100;

  }

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

В приведенном ниже коде описывается скрипт OffensiveDefensiveScatterPlot. Он имеет те же входные данные, что и приведенный выше индикатор, и рисует графики очков атаки и защиты в виде анимации.

//+------------------------------------------------------------------+
//|                                OffensiveDefensiveScatterPlot.mq5 |
//|                        Copyright 2023, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include <Graphics\Graphic.mqh>
#include<Janus.mqh>

input ENUM_PRICE AppliedPrice=CLOSE;
input ENUM_DIFF_TYPE AppliedDiffType=DIFF_LOG;
input ENUM_INDEX_TYPE SelectIndexType=INDEX_FOREX_MAJORS;
input uint AppliedPeriod = 25;
input string BenchMarkSymbols="";
input int MaxBars = 50;

CJanus janus;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   if(!janus.Initialize(AppliedPrice,AppliedDiffType,SelectIndexType,PERIOD_CURRENT,MaxBars,BenchMarkSymbols))
     {
      Print("error with janus object");
      return;
     }
//---
   janus.Update();
//---
   double y[];
   double x[];

   int size=janus.GetSymbolsTotal();

   ArrayResize(x,size);
   ArrayResize(y,size);

   long chart=0;
   string name="OffenseDefense";

   for(int k=MaxBars-(int)AppliedPeriod-1; k>=0; k--)
     {
      for(int i=0; i<size; i++)
        {
         string ssy=janus.GetSymbolAt(i);
         janus.CalculateSymbolOffenseDefense(k,ssy,AppliedPeriod,y[i],x[i]);
        }

      CGraphic graphic;
      if(ObjectFind(chart,name)<0)
         graphic.Create(chart,name,0,0,0,780,380);
      else
         graphic.Attach(chart,name);
      //---
      graphic.CurveAdd(x,y,ColorToARGB(clrBlue),CURVE_POINTS,"DefensiveOffensive ");
      //---
      graphic.CurvePlotAll();
      //---
      graphic.Update();
      Sleep(1*1000);
      graphic.Destroy();
      ChartRedraw();

     }

   ChartSetInteger(0,CHART_SHOW,true);

  }
//+------------------------------------------------------------------+

Ниже приведена диаграмма, полученная с помощью скрипта

Диаграмма разброса атаки/защиты


Относительная сила

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

Формула относительной силы

 Вывод этой формулы задокументирован в книге Андерсона. Относительная сила вычисляется методом CalculateRelativeStrength().

//+------------------------------------------------------------------+
//|public method to calculate relative strength                      |
//+------------------------------------------------------------------+
double CJanus::CalculateRelativeStrength(const uint barshift,const string symbol__,const uint rs_period,uint lag=0)
  {
   int sshift=m_symbol_list.GetIndex(symbol__);
   if(sshift>-1)
      return(rs(barshift,sshift,rs_period,lag));
   else
      return(WRONG_VALUE);
  }


//+------------------------------------------------------------------+
//|private method that calculates the relative strength              |
//+------------------------------------------------------------------+
double CJanus::rs(const uint barshift,const uint symbolshift,const uint rs_period,uint lag=0)
  {
   if(lag>=rs_period)
      return(WRONG_VALUE);

   double index[],sorted[],median,i_offense,i_defense;
   median=i_offense=i_defense=0;

   ArrayResize(index,rs_period);
   ArrayResize(sorted,rs_period);

   int begin=(int)lag;

   for(int i=0; i<(int)rs_period; i++)
     {
      index[i]=CalculateBenchMarkReturn(barshift+i);
      if(i>=begin)
         sorted[i-begin]=index[i];
     }

   if(!ArraySort(sorted))
     {
      Print("sorting error ",__LINE__," ",__FUNCTION__);
      return(EMPTY_VALUE);
     }

   median=MathMedian(sorted);

   i_offense=1.e-30;
   i_defense=-1.e-30;

   for(int i=begin; i<(int)rs_period; i++)
     {
      if(index[i]>=median)
         i_offense+=index[i]-median;
      else
         i_defense+=index[i]-median;
     }

   if(i_offense<0 || i_defense>0)
     {
#ifdef DEBUG
      Print("error invalid figures ","Offensive ",i_offense," Defensive ",i_defense);
#endif
      return(WRONG_VALUE);
     }

   return(rs_off_def(barshift,symbolshift,lag,median,i_offense,i_defense,index));
  }

Приведенный ниже код индикатора отображает относительную силу символа графика.

//+------------------------------------------------------------------+
//|                                             RelativeStrength.mq5 |
//|                        Copyright 2023, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot RelativeStrength
#property indicator_label1  "RelativeStrength"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
#include<Janus.mqh>
//inputs
input ENUM_PRICE AppliedPrice=CLOSE;
input ENUM_DIFF_TYPE AppliedDiffType=DIFF_LOG;
input ENUM_INDEX_TYPE SelectIndexType=INDEX_FOREX_MAJORS;
input uint AppliedPeriod = 7;
input string BenchMarkSymbols="";
input int MaxBars = 300;
//--- indicator buffers
double         RelativeStrengthBuffer[];
//---
CJanus *janus;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,RelativeStrengthBuffer,INDICATOR_DATA);
//---    
   ArraySetAsSeries(RelativeStrengthBuffer,true);
//---   
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
   IndicatorSetString(INDICATOR_SHORTNAME,"RS("+_Symbol+")("+string(AppliedPeriod)+")");
//---
   janus=new CJanus();   
//--- 
   if(!janus.Initialize(AppliedPrice,AppliedDiffType,SelectIndexType,PERIOD_CURRENT,MaxBars,BenchMarkSymbols))
     return(INIT_FAILED);     
//---
   
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|Custom indicator deinitialization function                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
 {
//--- 
  switch(reason)
   {
    case REASON_INITFAILED:
      ChartIndicatorDelete(ChartID(),ChartWindowFind(),"RS("+_Symbol+")("+string(AppliedPeriod)+")");
       break;
    default:
       break;
   } 
//---
   if(CheckPointer(janus)==POINTER_DYNAMIC)
      delete janus;       
 }     
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],p
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   int limit;
   if(prev_calculated<=0)
     {
      limit=janus.HistorySize()-int(AppliedPeriod+2);
      PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,rates_total-limit+1);
     } 
   else 
     limit=rates_total-prev_calculated;
   //---  
   if(!janus.Update())
     return(prev_calculated); 
     
   for(int i=limit;i>=1;i--)
    {
     RelativeStrengthBuffer[i]=janus.CalculateRelativeStrength(i,_Symbol,AppliedPeriod);
    } 
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Индикатор относительной силы

Индикатор относительной силы

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

Максимумы и минимумы относительной силы

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

//+------------------------------------------------------------------+
//|                                    RelativeStrenghtBestWorst.mq5 |
//|                        Copyright 2023, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   2
//--- plot Upper
#property indicator_label1  "Upper"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot Lower
#property indicator_label2  "Lower"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
#include<Janus.mqh>
//inputs
input ENUM_PRICE AppliedPrice=CLOSE;
input ENUM_DIFF_TYPE AppliedDiffType=DIFF_LOG;
input ENUM_INDEX_TYPE SelectIndexType=INDEX_FOREX_MAJORS;
input uint AppliedPeriod = 25;
input string BenchMarkSymbols="";
input int MaxBars = 300;
//--- indicator buffers
double         UpperBuffer[];
double         LowerBuffer[];
double rsv[];

CJanus *janus;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,UpperBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,LowerBuffer,INDICATOR_DATA);
//---   
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0);
//--- 
   ArraySetAsSeries(UpperBuffer,true);
   ArraySetAsSeries(LowerBuffer,true);
//---    
   IndicatorSetString(INDICATOR_SHORTNAME,"RS_Upperlower("+_Symbol+")("+string(AppliedPeriod)+")");
//---   
   janus=new CJanus();   
//--- 
   if(!janus.Initialize(AppliedPrice,AppliedDiffType,SelectIndexType,PERIOD_CURRENT,MaxBars,BenchMarkSymbols))
     return(INIT_FAILED);  
     
   ArrayResize(rsv,janus.GetSymbolsTotal());     
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|Custom indicator deinitialization function                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
 {
//--- 
  switch(reason)
   {
    case REASON_INITFAILED:
      ChartIndicatorDelete(ChartID(),ChartWindowFind(),"RS_Upperlower("+_Symbol+")("+string(AppliedPeriod)+")");
       break;
    default:
       break;
   } 
//---
   if(CheckPointer(janus)==POINTER_DYNAMIC)
      delete janus;       
 }       
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   int limit;
   if(prev_calculated<=0)
     {
      limit=janus.HistorySize()-int(AppliedPeriod+2);
      PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,rates_total-limit+1);
     } 
   else 
     limit=rates_total-prev_calculated;
   //---  
   if(!janus.Update())
     return(prev_calculated); 
     
   for(int i=limit;i>=1;i--)
    {
      for(int k=0;k<ArraySize(rsv);k++)
        {
         string sym=janus.GetSymbolAt(k);
         rsv[k]= janus.CalculateRelativeStrength(i,sym,AppliedPeriod);
        }
        
      ArraySort(rsv);
      
     UpperBuffer[i]=rsv[ArraySize(rsv)-1];
     LowerBuffer[i]=rsv[0];    
    } 
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+


Лидеры и отстающие по относительной силе

Когда относительная производительность лучших и худших исполнителей усредняется, мы получаем относительную силу лидеров и относительную силу отстающих соответственно. Эти значения указывают на лучшее время для торговли в зависимости от состояния обратной связи. Относительная сила (rs) лидеров и отстающих рассчитывается путем указания количества лучших и худших исполнителей для расчета среднего значения. 

//+------------------------------------------------------------------+
//|public method to calculate relative strength leaders and laggards |
//+------------------------------------------------------------------+
void CJanus::CalculateRelativeStrengthLeaderLaggard(const uint barshift,const uint rs_period,const double rs_percent_top,double& leader,double& laggard)
  {
   leader=laggard=0;
   
   uint lag=rs_period;

   double sorted[];
   int iwork[],k,n,isub;
   k=isub=-1;

   int size=m_symbol_list.GetCollectionLength();

   ArrayResize(sorted,size);
   ArrayResize(iwork,size);

   for(int i=0; i<size; i++)
     {
      sorted[i]=rs(barshift,uint(i),rs_period,lag);
      iwork[i]=i;
     }

   MathQuickSortAscending(sorted,iwork,0,size-1);

   k=(int)(rs_percent_top*(size+1))-1;
   if(k<0)
      k=0;
   n=k+1;

   while(k>=0)
     {
      isub=iwork[k];
      for(uint i=0; i<lag; i++)
        {
         laggard+=market_return(barshift+i,isub);
        }
      isub=iwork[size-1-k];
      for(uint i=0; i<lag; i++)
        {
         leader+=market_return(barshift+i,isub);
        }
      --k;
     }
   leader/=n*lag;
   laggard/=n*lag;

   return;
  }

Входной параметр rs_percent_top для метода CalculateRelativeStrengthLeadersLaggards() обозначает долю символов, используемых для расчета усредненной относительной силы. Например, установка rs_percent_top на 0,1 означает, что верхние и нижние 10% символов будут использоваться при расчете лидеров и отстающих. 

Ниже скриншот индикатора, отображающего лидеров и отстающих (Leaders and Laggards)

Лидеры и отстающие


Разброс

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

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

Разброс относительной силы реализован в методе CalculateRelativeStrengthSpread().

//+------------------------------------------------------------------+
//|public method to calculate the relative strength spread.          |
//+------------------------------------------------------------------+
double CJanus::CalculateRelativeStrengthSpread(const uint barshift,const uint rs_period,const double rs_percent_top)
  {
   double sorted[],width,div;
   int k=0;
   int size=m_symbol_list.GetCollectionLength();

   width=div=0;
   ArrayResize(sorted,size);

   for(int i=0; i<size; i++)
     {
      sorted[i]=rs(barshift,uint(i),rs_period);
     }

   if(!ArraySort(sorted))
     {
      Print("sorting error ",__LINE__," ",__FUNCTION__);
      return(WRONG_VALUE);
     }

   k=(int)(rs_percent_top*(size+1))-1;

   if(k<0)
      k=0;
   double n=double(k+1);

   while(k>=0)
     {
      width+=sorted[size-1-k]-sorted[k];
      --k;
     }

   return(width/=n);
  }

Индикатор разброса относительной силы

Индикатор разброса относительной силы

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

//+------------------------------------------------------------------+
//|public method to calculate relative strength leaders and laggards |
//+------------------------------------------------------------------+
void CJanus::CalculateRelativeStrengthLeaderLaggard(const uint barshift,const uint rs_period,const double rs_percent_top,double& leader,double& laggard)
  {
   leader=laggard=0;
   
   uint lag=1;

   double sorted[];
   int iwork[],k,n,isub;
   k=isub=-1;

   int size=m_symbol_list.GetCollectionLength();

   ArrayResize(sorted,size);
   ArrayResize(iwork,size);

   for(int i=0; i<size; i++)
     {
      sorted[i]=rs(barshift,uint(i),rs_period,lag);
      iwork[i]=i;
     }

   MathQuickSortAscending(sorted,iwork,0,size-1);

   k=(int)(rs_percent_top*(size+1))-1;
   if(k<0)
      k=0;
   n=k+1;

   while(k>=0)
     {
      isub=iwork[k];
      for(uint i=0; i<lag; i++)
        {
         laggard+=market_return(barshift+i,isub);
        }
      isub=iwork[size-1-k];
      for(uint i=0; i<lag; i++)
        {
         leader+=market_return(barshift+i,isub);
        }
      --k;
     }
   leader/=n*lag;
   laggard/=n*lag;

   return;
  }


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

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


Заключение

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

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

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

Имя файла Тип Описание
Mql5/include/Janus.mqh Включаемый файл  Включаемый файл с определениями классов CSymbolData, CSymbolCollection и CJanus
Mql5/scripts/OffensiveDefensiveScatterPlot.mq5 Скрипт Скрипт, рисующий анимированный точечный график очков атаки/защиты
Mql5/indicators/IndexReturns.mq5 Индикатор Код индикатора, отображающего символы и эталонную доходность
Mql5/indicators/RelativeStrengthBestWorst.mq5
Индикатор Индикатор, показывающий график самых высоких и самых низких значений относительной силы.
Mql5/indicators/RelativeStrength.mq5
Индикатор Индикатор относительной силы символа
Mql5/indicators/RelativeStrengthSpread.mq5 Индикатор Индикатор разброса относительной силы
Mql5/indicatorr/RssLeaderlaggards.mq5 Индикатор Индикатор, отображающий лидеров и отстающих по относительной силе