[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 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()));
     }
*/
  }
//+------------------------------------------------------------------+
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

Thank’s man. it was very good and solved my problem.