Discussion of article "Applying OLAP in trading (part 4): Quantitative and visual analysis of tester reports"

 

New article Applying OLAP in trading (part 4): Quantitative and visual analysis of tester reports has been published:

The article offers basic tools for the OLAP analysis of tester reports relating to single passes and optimization results. The tool can work with standard format files (tst and opt), and it also provides a graphical interface. MQL source codes are attached below.

To view the general distribution of profits by levels in increments of 100, select the 'profit' field from the statistics along the X axis and the 'count' aggregator.

Distribution of profits by ranges in increments of 100 units

Distribution of all profits by ranges in increments of 100 units

By using the 'identity' aggregator, we can evaluate the influence of the number of trades to profit. Generally, this aggregator enables the visual evaluation of many other dependencies.

Profit vs number of trades

Profit vs number of trades

Author: Stanislav Korotky

 
That looks cool! I'll have to try it. Thank you.
 

When parsing the code from the article, I had a constant feeling that I would not be able to get even close to such a high level of skill. Unfortunately, I could not understand it because of my incompetence.


I ask the author to show (ready code), as an example, how to add this functionality to the presented toolkit?

Forum on trading, automated trading systems and testing trading strategies.

Libraries: SingleTesterCache

fxsaber, 2020.01.14 11:42 AM

  uchar Bytes2[];
  
  if (MTTESTER::GetLastTstCache(Bytes2) != -1) // If it was possible to read the last cache record of a single run
  {
    const SINGLETESTERCACHE SingleTesterCache(Bytes2); // Drive it into the corresponding object.

Forum on trading, automated trading systems and testing trading strategies

Libraries: TesterCache

fxsaber, 2019.11.11 04:45 pm.

  uchar Bytes[];
  
  MTTESTER::GetLastOptCache(Bytes);
  
// TESTERCACHE<TestCacheSymbolRecord> Cache; // Symbol Optimisation
  TESTERCACHE<ExpTradeSummary> Cache;         // Standard optimisation

  if (Cache.Load(Bytes)) // Read the optimisation cache.
 
Please clarify the question. SingleTesterCache and TesterCache are already connected in the article.
 
Stanislav Korotky:
Please clarify the question. SingleTesterCache and TesterCache are already connected in the article.

I want to run EX5 and it will automatically pick up the last opt/tst-file (the last executed corresponding task in Tester) for analysis.

 
fxsaber:

I want to run EX5 and it will automatically pick up the last opt/tst file (the last executed corresponding job in Tester) for analysis.

Here is a variant for a separate run.

//+------------------------------------------------------------------+
//|TSTcube.mqh |
//|Copyright (c) 2020, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//| Online Analytical Processing of trading hypercubes |
//| https://www.mql5.com/en/articles/6602 |
//| https://www.mql5.com/en/articles/6603 |
//| https://www.mql5.com/en/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;

I wanted to output the name of the last tst-file to the log, but the corresponding method is private.

In the settings you should select the file "*.tst" (an empty name still starts the analysis of the online account history).

Files:
TSTCube.mqh  5 kb
 

For the results of the optimisation:

#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;
      }
    }
...
Files:
OLAPOpts.mqh  11 kb
 

I liked the article, as well as all articles about OLAP technology.

Personally, I lacked the flight of thought, - philosophical formulations of the essence, giving an idea of the whole approach, - of the covered space of tasks and potential. And the potential is huge. I am saddened by the multiple and insignificant details that waste the reader's time. They say - there is such a variable there, and there is such a variable here..... I realise that this is necessary for beginners copying solutions, but still.... No technology is static, and if your classes and methods are used now, then in the future, someone will want to change everything for himself, and for him the usefulness of the article will decrease in proportion to the number of private entities. Write more about the approach as a whole - about its present and future. You need to be more global with such approaches.

But, this is my subjective opinion. Thank you for the article.

 
Stanislav Korotky:

I wanted to output the name of the last tst file to the log, but the corresponding method is private.

It didn't occur to me that someone might need it. If you have more comments on private->public, tell me. Will do.

 

Hello Sir,

I tried to compile the file 'OLAPGUI_Opts.mq5' but there are 17 errors in some include files.

Kind Regards,
Ben

 
Szabo Bence #:

Hello Sir,

I tried to compile the file 'OLAPGUI_Opts.mq5' but there are 17 errors in some include files.

Kind Regards,
Ben

You should always show exact errors.