[MQL4/5] How to save/load/sort dynamic object collections?

 

I wasn't able to find any documentation on how to override the CObject virtual methods Load, Save, and Compare to make use of(CList and CArrayObj) load, save, and sort features - so I managed to hack my way through it. I'm posting it here in case anyone else is interested in using the Save/Load/Sort methods in CArrayObj and CList. Also, if anyone has a better way please let me know.

#property strict
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
class MyObj : public CObject
{
public:
   string         name;
   double         price;
   datetime       time;
   virtual bool   Save(const int file_handle) override   // MUST BE OVERRIDDEN FOR LOAD/SAVE          
   { 
      if(!FileWriteString(file_handle,name,10)) return false;
      if(!FileWriteDouble(file_handle,price))   return false; 
      if(!FileWriteLong(file_handle,(long)time))return false;  
      return(true);   
   }
   virtual bool   Load(const int file_handle) override   // MUST BE OVERRIDDEN FOR LOAD/SAVE                   
   { 
      name = FileReadString(file_handle,10);
      price= FileReadDouble(file_handle); 
      time = (datetime)FileReadLong(file_handle);  
      return(true);   
   }
   virtual int    Compare(const CObject *node,const int mode=0)const override // MUST BE OVERRIDDEN FOR SORT
   {
      MyObj *other = (MyObj*)node; 
      if(this.price > other.price) return 1;
      if(this.price < other.price) return -1;
      return 0;
   }
   string ToString(){ return name+" - "+string(time)+" - "+string(price);}
};
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
class MyObjArray : public CArrayObj
{
public:
   virtual bool CreateElement(const int index) override //MUST BE OVERRIDDEN FOR LOAD/SAVE
   {
      m_data[index] = new MyObj;
      return true;
   }
   MyObj* operator[](const int i)const{return (MyObj*)At(i);}
   bool Load()
   { 
      int h = FileOpen("MyTest.bin",FILE_READ|FILE_BIN);
      bool res = CArrayObj::Load(h);
      FileClose(h);
      return res;
   }
   bool Save()
   { 
      int h = FileOpen("MyTest.bin",FILE_WRITE|FILE_BIN);
      bool res = CArrayObj::Save(h);
      FileClose(h);
      return res;
   }
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
//---
   srand(GetTickCount());
   MyObjArray *arr = new MyObjArray;
   int num = 1;
   for(int i=0;i<SymbolsTotal(false)&&i<10;i++)
   {
      MyObj *obj = new MyObj;
      obj.name = SymbolName(i,false);
      obj.price= double(rand()%1000);
      obj.time = TimeCurrent();
      arr.Add(obj);
   }
   arr.Save();
   delete arr;
   arr = new MyObjArray;
   arr.Load();
   arr.Sort();
   for(int i=0;i<arr.Total();i++)
      Print(arr[i].ToString());   
   delete arr;
}
//+------------------------------------------------------------------+
 

...and here is an example for CList

//+------------------------------------------------------------------+
//|                                             ObjArrayLoadSave.mq4 |
//|                                                      nicholishen |
//|                            http://www.reddit.com/u/nicholishenFX |
//+------------------------------------------------------------------+
#property copyright "nicholishen"
#property link      "http://www.reddit.com/u/nicholishenFX"
#property version   "1.00"
#property strict
#include <Arrays\List.mqh>
//+------------------------------------------------------------------+
class MyObj : public CObject
{
public:
   string         name;
   double         price;
   datetime       time;
   virtual bool   Save(const int file_handle) override   // MUST BE OVERRIDEN FOR LOAD/SAVE          
   { 
      if(!FileWriteString(file_handle,name,10))    return false;
      if(!FileWriteDouble(file_handle,price))   return false; 
      if(!FileWriteLong(file_handle,(long)time))return false;  
      return(true);   
   }
   virtual bool   Load(const int file_handle) override   // MUST BE OVERRIDEN FOR LOAD/SAVE                   
   { 
      name = FileReadString(file_handle,10);
      price= FileReadDouble(file_handle); 
      time = (datetime)FileReadLong(file_handle);  
      return(true);   
   }
   virtual int    Compare(const CObject *node,const int mode=0)const override // MUST BE OVERRIDEN TO SORT
   {
      MyObj *other = (MyObj*)node; 
      if(this.price > other.price) return 1;
      if(this.price < other.price) return -1;
      return 0;
   }
   string ToString(){ return name+" - "+string(time)+" - "+string(price);}
};
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
class MyList : public CList
{
public:
   virtual CObject* CreateElement() override //MUST BE OVERRIDEN FOR LOAD/SAVE
   {
      return new MyObj;
   }
   bool Load()
   { 
      int h = FileOpen("MyTest.bin",FILE_READ|FILE_BIN);
      bool res = CList::Load(h);
      FileClose(h);
      return res;
   }
   bool Save()
   { 
      int h = FileOpen("MyTest.bin",FILE_WRITE|FILE_BIN);
      bool res = CList::Save(h);
      FileClose(h);
      return res;
   }
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
//---
   srand(GetTickCount());
   MyList *list = new MyList;
   
   for(int i=0;i<SymbolsTotal(false)&&i<10;i++)
   {
      MyObj *obj = new MyObj;
      obj.name = SymbolName(i,false);
      obj.price= double(rand()%1000);
      obj.time = TimeCurrent();
      list.Add(obj);
   }
   list.Save();
   delete list;
   list = new MyList;
   list.Load();
   list.Sort(0);
   for(MyObj *obj=list.GetFirstNode(); obj!=NULL; obj=list.GetNextNode())
      Print(obj.ToString());   
   delete list;
}
//+------------------------------------------------------------------+
 

Hi,

First thanks nicholishen for the sample code.

I try to use it but I have 2 errors.

//+------------------------------------------------------------------+
//|                                             CTradeParamsNode.mqh |
//|                                   Copyright 2017, Pierre Rougier |
//|                           https://www.mql5.com/en/users/pierre8r |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Pierre Rougier"
#property link      "https://www.mql5.com/en/users/pierre8r"
#property version   "1.00"

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CTradeParamsNode : public CObject
  {
private:
   datetime          _OpenOrderAtThisDateTime;
   int               _MagicNumber;
   int               _TakeProfitInPips;
   int               _StopLossInPips;
   double            _NumberOfLotsToOpen;

public:
   void              CTradeParamsNode(datetime OpenOrderAtThisDateTime,int MagicNumber,int TakeProfitInPips,int StopLossInPips,
                                      double NumberOfLotsToOpen);

   virtual int       Compare(const CObject *node,const int mode=0)const override; // MUST BE OVERRIDEN TO SORT

   datetime          Get_OpenOrderAtThisDateTime(void){return this._OpenOrderAtThisDateTime;};
   int               Get_MagicNumber(void){return this._MagicNumber;};
   int               Get_TakeProfitInPips(void){return this._TakeProfitInPips;};
   int               Get_StopLossInPips(void){return this._StopLossInPips;};
   double            Get_NumberOfLotsToOpen(void){return this._NumberOfLotsToOpen;};

  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CTradeParamsNode::CTradeParamsNode(datetime OpenOrderAtThisDateTime,int MagicNumber,int TakeProfitInPips,int StopLossInPips,
                                        double NumberOfLotsToOpen)
   :_OpenOrderAtThisDateTime(OpenOrderAtThisDateTime),_MagicNumber(MagicNumber),_TakeProfitInPips(_TakeProfitInPips),_StopLossInPips(StopLossInPips),
     _NumberOfLotsToOpen(NumberOfLotsToOpen)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int  CTradeParamsNode::Compare(const CObject *node,const int mode=0)const override // MUST BE OVERRIDEN TO SORT
  {
   CTradeParamsNode *other=(CTradeParamsNode*)node;
   if(this.Get_OpenOrderAtThisDateTime() > other.Get_OpenOrderAtThisDateTime()) return 1;
   if(this.Get_OpenOrderAtThisDateTime()< other.Get_OpenOrderAtThisDateTime()) return -1;
   return 0;
  }
//+------------------------------------------------------------------+


'CTradeParamsNode.mqh'  CTradeParamsNode.mqh    1       1
'Object.mqh'    Object.mqh      1       1
'StdLibErr.mqh' StdLibErr.mqh   1       1
'Get_OpenOrderAtThisDateTime' - call non-const method for constant object       CTradeParamsNode.mqh    51      12
'Get_OpenOrderAtThisDateTime' - call non-const method for constant object       CTradeParamsNode.mqh    52      12
2 error(s), 0 warning(s)                3       1
 

I think, i found my error.


   datetime          Get_OpenOrderAtThisDateTime(void)const{return this._OpenOrderAtThisDateTime;};
 

There are no more compilation errors, but the program is looping.

All source code comes with the attached file.


2018.01.24 17:35:17.015 eaTimeTrades (EURUSD,H1)        Params : DateTime 2017.11.20 08:10:00 - MagicNumber: 2014 - TakeProfit: 0 - StopLoss: 30 - NumberOfLotsToOpen: 6.0
2018.01.24 17:35:17.015 eaTimeTrades (EURUSD,H1)        Params : DateTime 2017.11.20 08:10:00 - MagicNumber: 2014 - TakeProfit: 0 - StopLoss: 30 - NumberOfLotsToOpen: 6.0
2018.01.24 17:35:17.015 eaTimeTrades (EURUSD,H1)        Params : DateTime 2017.11.20 08:10:00 - MagicNumber: 2014 - TakeProfit: 0 - StopLoss: 30 - NumberOfLotsToOpen: 6.0
2018.01.24 17:35:17.015 eaTimeTrades (EURUSD,H1)        Params : DateTime 2017.11.20 08:10:00 - MagicNumber: 2014 - TakeProfit: 0 - StopLoss: 30 - NumberOfLotsToOpen: 6.0
2018.01.24 17:35:17.015 eaTimeTrades (EURUSD,H1)        Params : DateTime 2017.11.20 08:10:00 - MagicNumber: 2014 - TakeProfit: 0 - StopLoss: 30 - NumberOfLotsToOpen: 6.0
2018.01.24 17:35:17.015 eaTimeTrades (EURUSD,H1)        Params : DateTime 2017.11.20 08:10:00 - MagicNumber: 2014 - TakeProfit: 0 - StopLoss: 30 - NumberOfLotsToOpen: 6.0
2018.01.24 17:35:17.015 eaTimeTrades (EURUSD,H1)        Params : DateTime 2017.11.20 08:10:00 - MagicNumber: 2014 - TakeProfit: 0 - StopLoss: 30 - NumberOfLotsToOpen: 6.0
//+------------------------------------------------------------------+
//|                                                 eaTimeTrades.mq5 |
//|                                   Copyright 2017, Pierre Rougier |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Pierre Rougier"
#property link      "https://www.mql5.com"
#property version   "1.02"

#include "libLogger_V_01_01.mq5"
#include<Arrays\List.mqh>
#include "CTradesParamsList.mqh"

//CList p_int_List=new CTradesParamsList;
CTradesParamsList trades_params_list;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

// CTradeParamsNode(datetime OpenOrderAtThisDateTime,int MagicNumber,int TakeProfitInPips,int StopLossInPips,double NumberOfLotsToOpen);
   CTradeParamsNode  *p_trades_params_node=new CTradeParamsNode(D'2017.11.16 06:01:00',2018,10,20,2);
   trades_params_list.Add(p_trades_params_node);

// CTradeParamsNode(int MagicNumber,int TakeProfitInPips,int StopLossInPips,double NumberOfLotsToOpen,datetime OpenOrderAtThisDateTime);
   *p_trades_params_node=new CTradeParamsNode(D'2017.11.08 10:05:00',2015,5,20,5);
   trades_params_list.Add(p_trades_params_node);

// CTradeParamsNode(int MagicNumber,int TakeProfitInPips,int StopLossInPips,double NumberOfLotsToOpen,datetime OpenOrderAtThisDateTime);
   *p_trades_params_node=new CTradeParamsNode(D'2017.11.20 08:10:00',2014,20,30,6);
   trades_params_list.Add(p_trades_params_node);

   trades_params_list.Sort(0);

   for(CTradeParamsNode *trade_params_node=trades_params_list.GetFirstNode(); trade_params_node!=NULL; trade_params_node=trades_params_list.GetNextNode())
     {
      Print(trade_params_node.ToString());
     }

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
/*
//--- Define some MQL5 Structures we will use for our trade
   MqlTick last_tick;      // To be used for getting recent/latest price quotes
   MqlTradeRequest mrequest;  // To be used for sending our trade requests
   MqlTradeResult mresult;    // To be used to get our trade results
   MqlRates mrate[];          // To be used to store the prices, volumes and spread of each bar


   if(SymbolInfoTick(Symbol(),last_tick))
     {
      if(!m_isOpenedOrderPreviously)
        {
         if((last_tick.time==m_OpenFirstOrderAtThisDateTime))
           {
            Log(DEBUG,__FILE__,__FUNCTION__,__LINE__,
                " Ask :"+DoubleToString(last_tick.ask)+" Bid :"+DoubleToString(last_tick.bid)+" Time :"+TimeToString(last_tick.time));
            Log(DEBUG,__FILE__,__FUNCTION__,__LINE__,
                " Digits :"+IntegerToString(_Digits)+" Point :"+DoubleToString(_Point));

            ZeroMemory(mrequest);
            mrequest.action=TRADE_ACTION_DEAL;                                  // immediate order execution
            mrequest.price=NormalizeDouble(last_tick.ask,_Digits);           // latest ask price
            mrequest.sl = NormalizeDouble(last_tick.ask - m_StopLossInPips*_Point,_Digits); // Stop Loss
            mrequest.tp = NormalizeDouble(last_tick.ask+m_TakeProfitInPips*_Point,_Digits); // Take Profit
            mrequest.symbol=_Symbol;                                            // currency pair
            mrequest.volume=m_NumberOfLotsToOpen;                                                 // number of lots to trade
            mrequest.magic=m_MagicNumber;                                         // Order Magic Number
            mrequest.type = ORDER_TYPE_BUY;                                       // Buy Order
            mrequest.type_filling = ORDER_FILLING_FOK;                            // Order execution type
            mrequest.deviation=100;                                               // Deviation from current price
            //--- send order
            OrderSend(mrequest,mresult);
            // get the result code
            if(mresult.retcode==10009 || mresult.retcode==10008) //Request is completed or order placed
              {
               Log(DEBUG,__FILE__,__FUNCTION__,__LINE__,"A Buy order has been successfully placed with Ticket#:"+IntegerToString(mresult.order));
               Log(DEBUG,__FILE__,__FUNCTION__,__LINE__,
                   " Ask :"+DoubleToString(last_tick.ask)+" Bid :"+DoubleToString(last_tick.bid)+" Time :"+TimeToString(last_tick.time));
              }
            else
              {
               Log(ERROR,__FILE__,__FUNCTION__,__LINE__,"The Buy order request could not be completed - Error:"+IntegerToString(GetLastError()));
               ResetLastError();
               return;
              }
            m_isOpenedOrderPreviously=true;
           }
        }
     }
   else
     {
      Log(FATAL,__FILE__,__FUNCTION__,__LINE__,IntegerToString(GetLastError()));
     }
*/
  }
//+------------------------------------------------------------------+
Files:
 
Pierre Rougier:

There are no more compilation errors, but the program is looping.

All source code comes with the attached file.



for(CTradeParamsNode *trade_params_node=trades_params_list.GetFirstNode(); trade_params_node!=NULL; trade_params_node=trades_params_node.Next())
     {
      Print(trade_params_node.ToString());
     }
 

I got another version to use CArrayObj with my own Object Class

  1. Array/Order.mqh : contain CObjectOrder (new class) derived from CObject 
  2. Array/CArrayObjOrder.mqh: copy from CArrayObj.mqh and then replace all reference of 
    CArrayObj to CArrayObjOrder and
    CObject to CObjectOrder 
  3. Then just use CArrayObjOrder or CObjectOrder as replacement for CArrayObj and CObject 

 
Thanks, This give me a lots help.
 

nicholish en Also, if anyone has a better way please let me know.

nicholish en, thank you for your example... just one add-in to it (that works for me - (?) though I lack explanation for such behavior yet):

if MyObjArrayis being declared globally & used in different functions (e.g. OnInit & OnCalculate in indicator) & deleted in OnDeinit, then:

MyObjArray *arr;

int OnInit (){
   
   arr = new MyObjArray;
   
   int num = 1;

   for(int i=0;i<SymbolsTotal(false)&&i<10;i++) {   
      // ...  each obj assigning & pushing to arr...   
      Print ("pushed  " , arr[i].ToString()); // <<<<<<<<< MEMBER-METHOD CALL
   }
}

and in OnCalculate:


int OnCalculate (/*...*/ )  {
//---   
for  (int  i=0 ; i<arr.Total(); i++)      
   Print ("pop " , GetPointer (arr)[i].ToString()); // <<<<<<<<< MEMBER-METHOD CALL

/*   int i=0;
   while (i<arr.Total()) {
      arr.Next();
      i++;
      Print(arr[i].ToString());  // invalid pointer access     
   }
*/       

//--- return value of prev_calculated for next call   
return (rates_total);
  }
in the second function - member-method is called only with GetPointer, otherwise "invalid pointer access "-error... while in the first function (where the arr is filled) its elements' member function can still be called from the object itself
GetPointer - Общие функции - Справочник MQL4
GetPointer - Общие функции - Справочник MQL4
  • docs.mql4.com
GetPointer - Общие функции - Справочник MQL4
 

After a lot of confusion, it seems the proper way to save/load CArrayObj is to create two new classes like the following modified code. The object array has to be pre-Reserve() and is large enough to load all data.

#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
class MyObj : public CObject
{
public:
   string         name;
   double         price;
   datetime       time;
   bool   Save(const int file_handle)    // MUST BE OVERRIDDEN FOR LOAD/SAVE          
   { 
      if(!FileWriteString(file_handle,name,10)) return false;
      if(!FileWriteDouble(file_handle,price))   return false; 
      if(!FileWriteLong(file_handle,(long)time))return false;  
      return true;
   }
   bool   Load(const int file_handle)    // MUST BE OVERRIDDEN FOR LOAD/SAVE                   
   { 
      ResetLastError();
      name = FileReadString(file_handle,10);
      price= FileReadDouble(file_handle); 
      time = (datetime)FileReadLong(file_handle);  
      if (0 != GetLastError())
         return false;
      return true;
   }
   int    Compare(const CObject *node,const int mode=0) const  // MUST BE OVERRIDDEN FOR SORT
   {
      MyObj *other = (MyObj*)node; 
      if(this.price > other.price) return 1;
      if(this.price < other.price) return -1;
      return 0;
   }
};

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
class MyObjArray : public CArrayObj
{
public:
   bool CreateElement(const int index)  //MUST BE OVERRIDDEN FOR LOAD/SAVE
   {
       // assume array has been pre-Reserve() and is large enough.
       // dont use Insert() or Add() because these will increase the internal counter twice!
       m_data[index] = new MyObj;
       return (NULL != m_data[index]);
   }
   
   // Save() and Load() is already implemented!
};
 
Thanks nicholis