Пример использования библиотеки AO Core для самооптимизации

Пример использования библиотеки AO Core для самооптимизации

5 марта 2024, 11:41
Andrey Dik
0
77

AO Core

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

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

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

"EA Virt" участвует в виртуальной торговле с момента "прошлого" в истории до момента "время сейчас". Менеджер инициирует выполнение "EA Virt" столько раз, сколько указано размером популяции в "параметрах оптимизации". Затем "EA Virt" возвращает исторические результаты запуска в виде "ff result".

 "ff result" представляет собой результат функции пригодности или критерий оптимизации, который может быть любым по усмотрению пользователя. Это может быть, например, баланс, коэффициент прибыли, математическое ожидание или сложный критерий, интеграл или совокупный дифференциал, измеряемый в различные моменты времени на временной шкале "History". Таким образом, результат функции пригодности, или  "ff result" - это то, что пользователь считает важным показателем качества торговой стратегии.

Впоследствии "ff result", оценка определенного набора, передается менеджером алгоритму оптимизации.

При выполнении условия остановки менеджер передает наилучший набор торговому советнику "EA", после чего советник продолжает свою работу (торговлю) с обновленными параметрами от точки "time now" до точки повторной оптимизации "reoptimiz", где проводится повторная оптимизация на заданную глубину в истории.

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



Scheme

Figure 1.


Согласно схеме работы алгоритма оптимизации "optimization ALGO", его можно рассматривать как "black box", который функционирует автономно (действительно, для него все внешнее также является "black box"), независимо от конкретной торговой стратегии, менеджера и виртуальной стратегии. Менеджер запрашивает набор из алгоритма оптимизации и отправляет обратно оценку этого набора, которую алгоритм оптимизации использует для определения следующего набора. Этот цикл продолжается до тех пор, пока не будет найден наилучший набор параметров, удовлетворяющий требованиям пользователя. Таким образом, алгоритм оптимизации ищет оптимальные параметры, которые конкретно удовлетворяют потребностям пользователя, определенным с помощью функции пригодности в "EA Virt".

Виртуализация индикатора

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

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

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

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

Индикатор должен быть структурирован как класс (структура также будет работать), давайте назовем его "C_Stochastic". В объявлении класса основные индикаторные буферы должны быть определены как открытые поля (дополнительные вычислительные буферы могут быть закрытыми), и должна быть объявлена функция инициализации "Init", куда необходимо передать параметры индикатора.

//——————————————————————————————————————————————————————————————————————————————
class C_iStochastic
{
  public: void Init (const int InpKPeriod,       // K period
                     const int InpDPeriod,       // D period
                     const int InpSlowing)       // Slowing
  {
    inpKPeriod = InpKPeriod;
    inpDPeriod = InpDPeriod;
    inpSlowing = InpSlowing;
  }

  public: int Calculate (const int rates_total,
                         const int prev_calculated,
                         const double &high  [],
                         const double &low   [],
                         const double &close []);

  //--- indicator buffers
  public:  double ExtMainBuffer   [];
  public:  double ExtSignalBuffer [];
  private: double ExtHighesBuffer [];
  private: double ExtLowesBuffer  [];

  private: int inpKPeriod; // K period
  private: int inpDPeriod; // D period
  private: int inpSlowing; // Slowing
};
//——————————————————————————————————————————————————————————————————————————————

И, соответственно, фактический расчет индикатора в методе "Рассчитать". Расчет индикатора абсолютно ничем не отличается от индикатора в стандартной настройке терминала. Единственное отличие заключается в выделении размера для буферов индикатора и их инициализации.

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

//——————————————————————————————————————————————————————————————————————————————
int C_iStochastic::Calculate (const int rates_total,
                              const int prev_calculated,
                              const double &high  [],
                              const double &low   [],
                              const double &close [])
{
  if (rates_total <= inpKPeriod + inpDPeriod + inpSlowing) return (0);

  ArrayResize (ExtHighesBuffer, rates_total);
  ArrayResize (ExtLowesBuffer,  rates_total);
  ArrayResize (ExtMainBuffer,   rates_total);
  ArrayResize (ExtSignalBuffer, rates_total);

  ArrayInitialize (ExtHighesBuffer, 0.0);
  ArrayInitialize (ExtLowesBuffer,  0.0);
  ArrayInitialize (ExtMainBuffer,   0.0);
  ArrayInitialize (ExtSignalBuffer, 0.0);

  int i, k, start;

  start = inpKPeriod - 1;

  if (start + 1 < prev_calculated)
  {
    start = prev_calculated - 2;
    Print ("start ", start);
  }
  else
  {
    for (i = 0; i < start; i++)
    {
      ExtLowesBuffer  [i] = 0.0;
      ExtHighesBuffer [i] = 0.0;
    }
  }

  //--- calculate HighesBuffer[] and ExtHighesBuffer[]
  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double dmin =  1000000.0;
    double dmax = -1000000.0;

    for (k = i - inpKPeriod + 1; k <= i; k++)
    {
      if (dmin > low  [k]) dmin = low  [k];
      if (dmax < high [k]) dmax = high [k];
    }

    ExtLowesBuffer  [i] = dmin;
    ExtHighesBuffer [i] = dmax;
  }

  //--- %K
  start = inpKPeriod - 1 + inpSlowing - 1;

  if (start + 1 < prev_calculated) start = prev_calculated - 2;
  else
  {
    for (i = 0; i < start; i++) ExtMainBuffer [i] = 0.0;
  }

  //--- main cycle
  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double sum_low  = 0.0;
    double sum_high = 0.0;

    for (k = (i - inpSlowing + 1); k <= i; k++)
    {
      sum_low  += (close [k] - ExtLowesBuffer [k]);
      sum_high += (ExtHighesBuffer [k] - ExtLowesBuffer [k]);
    }

    if (sum_high == 0.0) ExtMainBuffer [i] = 100.0;
    else                 ExtMainBuffer [i] = sum_low / sum_high * 100;
  }

  //--- signal
  start = inpDPeriod - 1;

  if (start + 1 < prev_calculated) start = prev_calculated - 2;
  else
  {
    for (i = 0; i < start; i++) ExtSignalBuffer [i] = 0.0;
  }

  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double sum = 0.0;
    for (k = 0; k < inpDPeriod; k++) sum += ExtMainBuffer [i - k];
    ExtSignalBuffer [i] = sum / inpDPeriod;
  }

  //--- OnCalculate done. Return new prev_calculated.
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————


Виртуализация стратегии

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

Далее идут "входные" параметры советника, среди которых мы выделяем "InpKPeriod_P" и "InpUpperLevel_P". Эти параметры необходимо оптимизировать, отображая период действия индикатора "Stochastic" и его уровни.

input string   InpKPeriod_P     = "18|9|3|24";  //STO K period:    it is necessary to optimize
input string   InpUpperLevel_P  = "96|88|2|98"; //STO upper level: it is necessary to optimize

Кроме того, стоит отметить, что параметры объявляются с типом string. Эти параметры являются составными и включают значения по умолчанию, начальное значение оптимизации, шаг и конечное значение оптимизации.

При инициализации советника в рамках функции "OnInit" мы установим размер массивов параметров в соответствии с количеством оптимизируемых параметров: "Set" - набор параметров, "Range_Min" - минимальные значения параметров (начальные значения), "Range_Step" - параметр шаги, а "Range_Max" - максимальные значения параметров. Мы извлекем соответствующие значения из строковых параметров и присвоим их массивам.

//——————————————————————————————————————————————————————————————————————————————
#import "\\Market\\AO Core.ex5"
bool   Init (int colonySize, double &range_min [], double &range_max [], double &range_step []);
//------------------------------------------------------------------------------
void   Preparation    ();
void   GetVariantCalc (double &variant [], int pos);
void   SetFitness     (double value,       int pos);
void   Revision       ();
//------------------------------------------------------------------------------
void   GetVariant     (double &variant [], int pos);
double GetFitness     (int pos);
#import
//——————————————————————————————————————————————————————————————————————————————

#include <Trade\Trade.mqh>;
#include "cStochastic.mqh"


input group         "==== GENERAL ====";
sinput long         InpMagicNumber      = 132516;       //Magic Number
sinput double       InpLotSize          = 0.01;         //Lots

input group         "==== Trading ====";
input int           InpStopLoss         = 1450;         //Stoploss
input int           InpTakeProfit       = 1200;         //Takeprofit

input group         "==== Stochastic ==|value|start|step|end|==";
input string        InpKPeriod_P        = "18|9|3|24";  //STO K period   : it is necessary to optimize
input string        InpUpperLevel_P     = "96|88|2|98"; //STO upper level: it is necessary to optimize

input group         "====Self-optimization====";
sinput bool         SelfOptimization    = true;
sinput int          InpBarsOptimize     = 18000;        //Number of bars in the history for optimization
sinput int          InpBarsReOptimize   = 1440;         //After how many bars, EA will reoptimize
sinput int          InpPopSize          = 50;           //Population size
sinput int          NumberFFlaunches    = 10000;        //Number of runs in the history during optimization
sinput int          Spread              = 10;           //Spread

MqlTick Tick;
CTrade  Trade;

C_iStochastic IStoch;

double Set        [];
double Range_Min  [];
double Range_Step [];
double Range_Max  [];

double TickSize = 0.0;


/——————————————————————————————————————————————————————————————————————————————
int OnInit ()
{
  TickSize = SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE);

  ArrayResize (Set,        2);
  ArrayResize (Range_Min,  2);
  ArrayResize (Range_Step, 2);
  ArrayResize (Range_Max,  2);

  string result [];
  if (StringSplit (InpKPeriod_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED;

  Set        [0] = (double)StringToInteger (result [0]);
  Range_Min  [0] = (double)StringToInteger (result [1]);
  Range_Step [0] = (double)StringToInteger (result [2]);
  Range_Max  [0] = (double)StringToInteger (result [3]);

  if (StringSplit (InpUpperLevel_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED;

  Set        [1] = (double)StringToInteger (result [0]);
  Range_Min  [1] = (double)StringToInteger (result [1]);
  Range_Step [1] = (double)StringToInteger (result [2]);
  Range_Max  [1] = (double)StringToInteger (result [3]);

  IStoch.Init ((int)Set [0], 1, 3);

  //  set magicnumber to trade object
  Trade.SetExpertMagicNumber (InpMagicNumber);

  //---
  return (INIT_SUCCEEDED);
}
//——————————————————————————————————————————————————————————————————————————————
Кроме того, в код советника внутри функции "OnTick" мы вставляем блок, вызывающий самооптимизацию - функцию "Optimize", которая действует как "менеджер" на диаграмме на рисунке 1, инициируя процесс оптимизации. Там, где предполагалось использовать внешние переменные, которые необходимо оптимизировать, мы используем значения из массива "Set".
//——————————————————————————————————————————————————————————————————————————————
void OnTick ()
{
  //----------------------------------------------------------------------------
  if (!IsNewBar ())
  {
    return;
  }

  //----------------------------------------------------------------------------
  if (SelfOptimization)
  {
    //--------------------------------------------------------------------------
    static datetime LastOptimizeTime = 0;

    datetime timeNow  = iTime (_Symbol, PERIOD_CURRENT, 0);
    datetime timeReop = iTime (_Symbol, PERIOD_CURRENT, InpBarsReOptimize);

    if (LastOptimizeTime <= timeReop)
    {
      LastOptimizeTime = timeNow;
      Print ("-------------------Start of optimization----------------------");

      Print ("Old set:");
      ArrayPrint (Set);

      Optimize (Set,
                Range_Min,
                Range_Step,
                Range_Max,
                InpBarsOptimize,
                InpPopSize,
                NumberFFlaunches,
                Spread * SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE));

      Print ("New set:");
      ArrayPrint (Set);

      IStoch.Init ((int)Set [0], 1, 3);
    }
  }

  //----------------------------------------------------------------------------
  if (!SymbolInfoTick (_Symbol, Tick))
  {
    Print ("Failed to get current symbol tick"); return;
  }

  //data preparation------------------------------------------------------------
  MqlRates rates [];
  int dataCount = CopyRates (_Symbol, PERIOD_CURRENT, 0, (int)Set [0] + 1 + 3 + 1, rates);

  if (dataCount == -1)
  {
    Print ("Data get error");
    return;
  }

  double hi [];
  double lo [];
  double cl [];

  ArrayResize (hi, dataCount);
  ArrayResize (lo, dataCount);
  ArrayResize (cl, dataCount);

  for (int i = 0; i < dataCount; i++)
  {
    hi [i] = rates [i].high;
    lo [i] = rates [i].low;
    cl [i] = rates [i].close;
  }

  int calc = IStoch.Calculate (dataCount, 0, hi, lo, cl);
  if (calc <= 0) return;

  double buff0 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 2];
  double buff1 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 3];

  //----------------------------------------------------------------------------
  // count open positions
  int cntBuy, cntSell;
  if (!CountOpenPositions (cntBuy, cntSell))
  {
    Print ("Failed to count open positions");
    return;
  }

  //----------------------------------------------------------------------------
  // check for buy
  if (cntBuy == 0 && buff1 <= (100 - (int)Set [1]) && buff0 > (100 - (int)Set [1]))
  {
    ClosePositions (2);

    double sl = NP (Tick.bid - InpStopLoss   * TickSize);
    double tp = NP (Tick.bid + InpTakeProfit * TickSize);

    Trade.PositionOpen (_Symbol, ORDER_TYPE_BUY, InpLotSize, Tick.ask, sl, tp, "Stochastic EA");
  }

  //----------------------------------------------------------------------------
  // check for sell
  if (cntSell == 0 && buff1 >= (int)Set [1] && buff0 < (int)Set [1])
  {
    ClosePositions (1);

    double sl = NP (Tick.ask + InpStopLoss   * TickSize);
    double tp = NP (Tick.ask - InpTakeProfit * TickSize);

    Trade.PositionOpen (_Symbol, ORDER_TYPE_SELL, InpLotSize, Tick.bid, sl, tp, "Stochastic EA");
  }
}
//——————————————————————————————————————————————————————————————————————————————
Аналогично, в функции "Optimize" выполняются те же действия, которые обычно наблюдаются в сценариях тестирования алгоритмов оптимизации в серии статей "Алгоритмы оптимизации популяции":

1. Инициализация алгоритма оптимизации.
2.1. Подготовка популяции.
2.2. Получение набора параметров из алгоритма оптимизации.
2.3. Вычисление функции пригодности с переданными ей параметрами.
2.4. Обновление наилучшего решения.
2.5. Получение наилучшего решения из алгоритма.
//——————————————————————————————————————————————————————————————————————————————
void Optimize (double      &set        [],
               double      &range_min  [],
               double      &range_step [],
               double      &range_max  [],
               const int    inpBarsOptimize,
               const int    inpPopSize,
               const int    numberFFlaunches,
               const double spread)
{
  //----------------------------------------------------------------------------
  double parametersSet [];
  ArrayResize(parametersSet, ArraySize(set));

  //----------------------------------------------------------------------------
  int epochCount = numberFFlaunches / inpPopSize;

  Init(inpPopSize, range_min, range_max, range_step);

  // Optimization-------------------------------------------------------------
  for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
  {
    Preparation ();

    for (int set = 0; set < inpPopSize; set++)
    {
      GetVariantCalc (parametersSet, set);
      SetFitness     (VirtualStrategy (parametersSet, inpBarsOptimize, spread), set);
    }

    Revision ();
  }

  Print ("Fitness: ", GetFitness (0));
  GetVariant (parametersSet, 0);
  ArrayCopy (set, parametersSet, 0, 0, WHOLE_ARRAY);
}
//——————————————————————————————————————————————————————————————————————————————

Кроме того, функция "VirtualStrategy" проводит тестирование стратегии на исторических данных (на рисунке 1 она называется "EA Virt"). Для этого требуется массив параметров "set", количество баров для оптимизации "barsOptimize" и значение "spread".

Сначала выполняется этап подготовки данных. Исторические данные загружаются в массив "rates". Затем создаются массивы "hi", "lo" и "cl", необходимые для вычисления стохастика.

Далее инициализируется индикатор Stochastic, и выполняется его расчет на основе исторических данных. Если расчет завершается неудачей, функция возвращает "-DBL_MAX" (наихудшее возможное значение функции пригодности).

Впоследствии стратегия тестируется на исторических данных, следуя логике, идентичной коду основного советника. Для хранения сделок создается объект "сделки". Цикл повторяет исторические данные, где условия для открытия и закрытия позиций проверяются для каждого бара на основе значения индикатора и уровней "upLevel" и "dnLevel". Если условия выполняются, позиции открываются или закрываются.

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

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

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

//——————————————————————————————————————————————————————————————————————————————
double VirtualStrategy (double &set [], int barsOptimize, double spread)
{
  //data preparation------------------------------------------------------------
  MqlRates rates [];
  int dataCount = CopyRates(_Symbol, PERIOD_CURRENT, 0, barsOptimize + 1, rates);

  if (dataCount == -1)
  {
    Print ("Data get error");
    return -DBL_MAX;
  }

  double hi [];
  double lo [];
  double cl [];

  ArrayResize (hi, dataCount);
  ArrayResize (lo, dataCount);
  ArrayResize (cl, dataCount);

  for (int i = 0; i < dataCount; i++)
  {
    hi [i] = rates [i].high;
    lo [i] = rates [i].low;
    cl [i] = rates [i].close;
  }

  C_iStochastic iStoch;
  iStoch.Init ((int)set [0], 1, 3);

  int calc = iStoch.Calculate (dataCount, 0, hi, lo, cl);
  if (calc <= 0) return -DBL_MAX;

  //============================================================================
  //test of strategy on history-------------------------------------------------
  S_Deals deals;

  double iStMain0 = 0.0;
  double iStMain1 = 0.0;
  double upLevel  = set [1];
  double dnLevel  = 100.0 - set [1];
  double balance  = 0.0;

  //running through history-----------------------------------------------------
  for (int i = 2; i < dataCount; i++)
  {
    if (i >= dataCount)
    {
      deals.ClosPos (-1, rates [i].open, spread);
      deals.ClosPos (1, rates [i].open, spread);
      break;
    }

    iStMain0 = iStoch.ExtMainBuffer [i - 1];
    iStMain1 = iStoch.ExtMainBuffer [i - 2];

    if (iStMain0 == 0.0 || iStMain1 == 0.0) continue;

    //buy-------------------------------
    if (iStMain1 <= dnLevel && dnLevel < iStMain0)
    {
      deals.ClosPos (-1, rates [i].open, spread);

      if (deals.GetBuys () == 0) deals.OpenPos (1, rates [i].open, spread);
    }

    //sell------------------------------
    if (iStMain1 >= upLevel && upLevel > iStMain0)
    {
      deals.ClosPos (1, rates [i].open, spread);

      if (deals.GetSels () == 0) deals.OpenPos (-1, rates [i].open, spread);
    }
  }
  //----------------------------------------------------------------------------

  if (deals.histSelsCNT + deals.histBuysCNT <= 0) return -DBL_MAX;
  return deals.balance;
}
//——————————————————————————————————————————————————————————————————————————————


Поделитесь с друзьями: