Обсуждение статьи "Применение OLAP в трейдинге (Часть 4): Количественный и визуальный анализ отчетов тестера"

 

Опубликована статья Применение OLAP в трейдинге (Часть 4): Количественный и визуальный анализ отчетов тестера:

Статья предлагает базовый инструментарий для OLAP-анализа отчетов тестера об одиночных проходах и результатах оптимизации в виде файлов стандартных форматов (tst и opt), а также интерактивный графический интерфейс к нему. Исходные коды MQL прилагаются.

Чтобы посмотреть общее распределение прибылей по уровням с шагом 100, выберем поле profit из статистики по оси X и агрегатор count.

Распределение прибылей по диапазонам с шагом 100 уе

Распределение прибылей всех проходов по диапазонам с шагом 100 уе

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

Прибыль vs количество трейдов

Автор: Stanislav Korotky

 
Выглядит круто! Надо пробовать. Спасибо.
 

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


Прошу Автора показать (готовый код), в качестве примера, как в представленный инструментарий добавить этот функционал?

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Библиотеки: SingleTesterCache

fxsaber, 2020.01.14 11:42

  uchar Bytes2[];
  
  if (MTTESTER::GetLastTstCache(Bytes2) != -1) // Если получилось прочитать последнюю кеш-запись одиночного прогона
  {
    const SINGLETESTERCACHE SingleTesterCache(Bytes2); // Загоняем ее в соответствующий объект.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Библиотеки: TesterCache

fxsaber, 2019.11.11 04:45

  uchar Bytes[];
  
  MTTESTER::GetLastOptCache(Bytes);
  
//  TESTERCACHE<TestCacheSymbolRecord> Cache;   // Оптимизация по символам
  TESTERCACHE<ExpTradeSummary> Cache;         // Стандартная оптимизация

  if (Cache.Load(Bytes)) // Прочитали оптимизационный кеш.
 
Прошу уточнить вопрос. SingleTesterCache и TesterCache уже подключены в статье.
 
Stanislav Korotky:
Прошу уточнить вопрос. SingleTesterCache и TesterCache уже подключены в статье.

Хочу запустить EX5, и он автоматом подхватит последний opt/tst-файл (последнее выполненное соответствующее задание в Тестере) для анализа.

 
fxsaber:

Хочу запустить EX5, и он автоматом подхватит последний opt/tst-файл (последнее выполненное соответствующее задание в Тестере) для анализа.

Вот вариант для отдельного прогона.

//+------------------------------------------------------------------+
//|                                                      TSTcube.mqh |
//|                                    Copyright (c) 2020, Marketeer |
//|                          https://www.mql5.com/en/users/marketeer |
//|               Online Analytical Processing of trading hypercubes |
//|                            https://www.mql5.com/ru/articles/6602 |
//|                            https://www.mql5.com/ru/articles/6603 |
//|                            https://www.mql5.com/ru/articles/7656 |
//|                                                   rev. 5.03.2020 |
//+------------------------------------------------------------------+

#include "ReportCubeBase.mqh"
#include <fxsaber/SingleTesterCache/SingleTesterCache.mqh>
#include <fxsaber/MultiTester/MTTester.mqh>                 // +


class TesterDeal: public Deal
{
  public:
    TesterDeal(const TradeDeal &td)
    {
      time = (datetime)td.time_create + TimeShift;
      price = td.price_open;
      string t = dealType(td.action);
      type = t == "buy" ? +1 : (t == "sell" ? -1 : 0);
      t = dealDir(td.entry);
      direction = 0;
      if(StringFind(t, "in") > -1) ++direction;
      if(StringFind(t, "out") > -1) --direction;
      volume = (double)td.volume;
      profit = td.profit;
      deal = (long)td.deal;
      order = (long)td.order;
      comment = td.comment[];
      symbol = td.symbol[];
      commission = td.commission;
      swap = td.storage;
      
      // balance - SingleTesterCache.Deals[i].reserve
    }

    static string dealType(const ENUM_DEAL_TYPE type)
    {
      return type == DEAL_TYPE_BUY ? "buy" : (type == DEAL_TYPE_SELL ? "sell" : "balance");
    }

    static string dealDir(const ENUM_DEAL_ENTRY entry)
    {
      string result = "";
      if(entry == DEAL_ENTRY_IN) result += "in";
      else if(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_OUT_BY) result += "out";
      else if(entry == DEAL_ENTRY_INOUT) result += "in out";
      return result;
    }
};

template<typename T>
class TesterReportAdapter: public BaseReportAdapter<T>
{
  protected:
    SINGLETESTERCACHE *ptrSingleTesterCache;

    virtual bool fillDealsArray() override
    {
      for(int i = 0; i < ArraySize(ptrSingleTesterCache.Deals); i++)
      {
        if(TesterDeal::dealType(ptrSingleTesterCache.Deals[i].action) == "balance")
        {
          balance += ptrSingleTesterCache.Deals[i].profit;
        }
        else
        {
          array << new TesterDeal(ptrSingleTesterCache.Deals[i]);
        }
      }
      return true;
    }

  public:
    ~TesterReportAdapter()
    {
      if(CheckPointer(ptrSingleTesterCache) == POINTER_DYNAMIC) delete ptrSingleTesterCache;
    }

    virtual bool load(const string file) override
    {
      if(StringFind(file, ".tst") > 0)
      {
        BaseReportAdapter<T>::load(file);

        if(CheckPointer(ptrSingleTesterCache) == POINTER_DYNAMIC) delete ptrSingleTesterCache;

        bool loaded = true;                                                // +
        ptrSingleTesterCache = new SINGLETESTERCACHE();
        if(file == "*.tst")                                                // +
        {                                                                  // +
          uchar Bytes2[];                                                  // +
          // Why MTTESTER::GetLastTstCacheFileName() is private?           // +
          // Print("Loading ", MTTESTER::GetLastTstCacheFileName());       // +
          if(MTTESTER::GetLastTstCache(Bytes2) != -1)                      // +
          {                                                                // +
            loaded = ptrSingleTesterCache.Load(Bytes2);                    // +
          }                                                                // +
        }
        else
        {
          loaded = ptrSingleTesterCache.Load(file);                        // *
        }
        
        if(!loaded)                                                        // *
        {
          delete ptrSingleTesterCache;
          ptrSingleTesterCache = NULL;
          return false;
        }
        size = generate();
        
        Print("Tester cache import: ", size, " trades from ", ArraySize(ptrSingleTesterCache.Deals), " deals");
      }
      return true;
    }
};

TesterReportAdapter<RECORD_CLASS> _defaultTSTReportAdapter;

Хотел вывести имя последнего tst-файла в лог, но соотв. метод приватный.

В настройках нужно выбрать файл "*.tst" (пустое имя по-прежнему запускает анализ онлайн истории счета).

Файлы:
TSTCube.mqh  5 kb
 

Для результатов оптимизации:

#include <fxsaber/MultiTester/MTTester.mqh>                 // +

...

template<typename T>
class OptCacheDataAdapter: public DataAdapter
{
  private:
    TESTERCACHE<ExpTradeSummary> Cache;
    ...
    
  public:
    OptCacheDataAdapter()
    {
      reset();
    }
    
    void load(const string optName)
    {
      bool loaded = true;                                       // +
      if(optName == "")                                         // +
      {                                                         // +
        uchar Bytes[];                                          // +
        // Why GetLastOptCacheFileName() is private?            // +
        // Print("Loading ", MTTESTER::GetLastOptCacheFileName()); // +
        MTTESTER::GetLastOptCache(Bytes);                       // +
        loaded = Cache.Load(Bytes);                             // +
      }
      else
      {
        loaded = Cache.Load(optName);                           // *
      }
      if(loaded)                                                // *
      {
        customize();
        reset();
      }
      else
      {
        cursor = -1;
      }
    }
...
Файлы:
OLAPOpts.mqh  11 kb
 

Статья понравилась, впрочем, как и все статьи о технологии OLAP. 

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

Но, это мое субъективное мнение. Спасибо за статью.

 
Stanislav Korotky:

Хотел вывести имя последнего tst-файла в лог, но соотв. метод приватный.

Не пришло в голову, что кому-нибудь это может понадобиться. Если есть еще замечания по private->public, скажите. Сделаю.

 

Публикую актуализированный исходник файла OLAPCommon.mqh, который перестал компилироваться из-за изменений в компиляторе. Также нужен TypeName.mqh - поведение typename тоже поменялось.

PS. Библиотеку от fxsaber TesterCache.mqh также нужно обновить.

Файлы:
OLAPCommon.mqh  47 kb
TypeName.mqh  2 kb
Причина обращения: