[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