//+------------------------------------------------------------------+ //| PositionsControl.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "Position.mqh" #include "Select.mqh" //+------------------------------------------------------------------+ //| Класс-коллекция исторических позиций | //+------------------------------------------------------------------+ class CPositionsControl : public CObject { private: //--- Возвращает (1) тип позиции, (2) причину открытия по типу сделки ENUM_POSITION_TYPE PositionTypeByDeal(const CDeal *deal); ENUM_POSITION_REASON PositionReasonByDeal(const CDeal *deal); protected: CPosition m_temp_pos; // Временный объект позиции для поиска CArrayObj m_list_pos; // Список позиций //--- Возвращает объект-позицию из списка по идентификатору CPosition *GetPositionObjByID(const long id); //--- Возвращает флаг того, что позиция рыночная bool IsMarketPosition(const long id); public: //--- Создаёт и обновляет список позиций. Может быть переопределён в наследуемых классах virtual bool Refresh(void); //--- Возвращает (1) список, (2) количество позиций в списке CArrayObj *GetPositionsList(void) { return &this.m_list_pos; } int PositionsTotal(void) const { return this.m_list_pos.Total(); } //--- Распечатывает в журнале свойства всех позиций в списке и их сделок void Print(void); //--- Конструктор/деструктор CPositionsControl(void); ~CPositionsControl(); }; //+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CPositionsControl::CPositionsControl(void) { this.m_list_pos.Sort(POSITION_PROP_TIME_CLOSE_MSC); } //+------------------------------------------------------------------+ //| Деструктор | //+------------------------------------------------------------------+ CPositionsControl::~CPositionsControl() { this.m_list_pos.Shutdown(); } //+------------------------------------------------------------------+ //| Возвращает объект-позицию из списка по идентификатору | //+------------------------------------------------------------------+ CPosition *CPositionsControl::GetPositionObjByID(const long id) { //--- Устанавливаем временному объекту идентификатор позиции, а списку - флаг сортировки по идентификатору позиции this.m_temp_pos.SetID(id); this.m_list_pos.Sort(POSITION_PROP_IDENTIFIER); //--- Получаем из списка индекс объекта-позиции с указанным идентификатором (либо -1 при его отсутствии) //--- По полученному индексу получаем указатель на объект позицию из списка (либо NULL при значении индекса -1) int index=this.m_list_pos.Search(&this.m_temp_pos); CPosition *pos=this.m_list_pos.At(index); //--- Возвращаем списку флаг сортировки по времени закрытия позиции в миллисекундах и //--- возвращаем указатель на объект-позицию (либо NULL при его отсутствии) this.m_list_pos.Sort(POSITION_PROP_TIME_CLOSE_MSC); return pos; } //+------------------------------------------------------------------+ //| Возвращает флаг того, что позиция рыночная | //+------------------------------------------------------------------+ bool CPositionsControl::IsMarketPosition(const long id) { //--- В цикле по списку действующих позиций в терминале for(int i=::PositionsTotal()-1; i>=0; i--) { //--- получаем тикет позиции по индексу цикла ulong ticket=::PositionGetTicket(i); //--- Если тикет получен и позицию можно выбрать и её идентификатор равен переданному в метод - //--- это искомая рыночная позиция, возвращаем true if(ticket!=0 && ::PositionSelectByTicket(ticket) && ::PositionGetInteger(POSITION_IDENTIFIER)==id) return true; } //--- Нет такой рыночной позиции - возвращаем false return false; } //+------------------------------------------------------------------+ //| Возвращает тип позиции по типу сделки | //+------------------------------------------------------------------+ ENUM_POSITION_TYPE CPositionsControl::PositionTypeByDeal(const CDeal *deal) { if(deal==NULL) return WRONG_VALUE; switch(deal.TypeDeal()) { case DEAL_TYPE_BUY : return POSITION_TYPE_BUY; case DEAL_TYPE_SELL : return POSITION_TYPE_SELL; default : return WRONG_VALUE; } } //+------------------------------------------------------------------+ //| Возвращает причину открытия позиции по типу сделки | //+------------------------------------------------------------------+ ENUM_POSITION_REASON CPositionsControl::PositionReasonByDeal(const CDeal *deal) { if(deal==NULL) return WRONG_VALUE; switch(deal.Reason()) { case DEAL_REASON_CLIENT : return POSITION_REASON_CLIENT; case DEAL_REASON_MOBILE : return POSITION_REASON_MOBILE; case DEAL_REASON_WEB : return POSITION_REASON_WEB; case DEAL_REASON_EXPERT : return POSITION_REASON_EXPERT; default : return WRONG_VALUE; } } //+------------------------------------------------------------------+ //| Создаёт список исторических позиций | //+------------------------------------------------------------------+ bool CPositionsControl::Refresh(void) { //--- Если запросить историю сделок и ордеров не удалось - возвращаем false if(!::HistorySelect(0,::TimeCurrent())) return false; //--- Ставим списку позиций флаг сортировки по времени в миллисекундах this.m_list_pos.Sort(POSITION_PROP_TIME_MSC); //--- Объявляем переменную результата и указатель на объект позиции bool res=true; CPosition *pos=NULL; //--- В цикле по количеству сделок истории int total=::HistoryDealsTotal(); for(int i=total-1; i>=0; i--) { //--- получаем тикет очередной сделки в списке ulong ticket=::HistoryDealGetTicket(i); //--- Если тикет сделки не получен, или это не операция покупки/продажи - идём дальше ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)::HistoryDealGetInteger(ticket, DEAL_TYPE); if(ticket==0 || (deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL)) continue; //--- Получаем из сделки значение идентификатора позиции long pos_id=::HistoryDealGetInteger(ticket, DEAL_POSITION_ID); //--- Если это рыночная позиция - идём далее if(this.IsMarketPosition(pos_id)) continue; //--- Получаем указатель на объект-позицию из списка pos=this.GetPositionObjByID(pos_id); //--- Если позиции с таким идентификатором в списке ещё нет if(pos==NULL) { //--- Создаём новый объект позиции и, если объект создать не удалось, добавляем к переменной res значение false и идём далее string pos_symbol=HistoryDealGetString(ticket, DEAL_SYMBOL); pos=new CPosition(pos_id, pos_symbol); if(pos==NULL) { res &=false; continue; } //--- Если объект позиции не удалось добавить в список - добавляем к переменной res значение false, удаляем объект позиции и идём далее if(!this.m_list_pos.InsertSort(pos)) { res &=false; delete pos; continue; } } //--- Если объект сделки не удалось добавить в список сделок объекта позиции - добавляем к переменной res значение false и идём далее CDeal *deal=pos.DealAdd(ticket); if(deal==NULL) { res &=false; continue; } //--- Всё успешно. //--- В зависимости от типа сделки устанавливаем свойства позиции if(deal.Entry()==DEAL_ENTRY_IN) { pos.SetTicket(deal.Order()); pos.SetMagic(deal.Magic()); pos.SetTime(deal.Time()); pos.SetTimeMsc(deal.TimeMsc()); ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal); pos.SetTypePosition(type); ENUM_POSITION_REASON reason=this.PositionReasonByDeal(deal); pos.SetReason(reason); pos.SetPriceOpen(deal.Price()); pos.SetVolume(deal.Volume()); } if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY) { pos.SetPriceCurrent(deal.Price()); pos.SetPriceClose(deal.Price()); pos.SetTimeClose(deal.Time()); pos.SetTimeCloseMsc(deal.TimeMsc()); } if(deal.Entry()==DEAL_ENTRY_INOUT) { ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal); pos.SetTypePosition(type); pos.SetVolume(deal.Volume()-pos.Volume()); } } //--- Все исторические позиции созданы, а соответствующие сделки добавлены в списки сделок объектов-позиций //--- Устанавливаем списку позиций флаг сортировки по времени закрытия в миллисекундах this.m_list_pos.Sort(POSITION_PROP_TIME_CLOSE_MSC); //--- В цикле по созданному списку закрытых позиций устанавливаем каждой позиции значения Commissions и Fee for(int i=0; i