Проект советника - страница 3

 
Alexey Volchanskiy:

И потом ковыряйся глазами, к чему относятся эти 4 скобки внизу.

Кстати, меня очень нервирует, когда вложенность более двух уровней.  Стараюсь так никогда не писать, раскидывая код по функциям.

И даже когда два уровня вложенности - обязательно после каждой закрывающей скобки - пишу комментарии, какой блок она зарывает (скажем, дублирую заголовок цикла).

Что касается стиля, то вот мой код выбора исторической позиции для МТ5 (по указанному магику, символу, с указанным диапазоном дат):

int CMT5TradeHistory::Select(ulong ulMagic,ECurrencySymbol csSymbol,datetime dtFrom = MIN_DATETIME,datetime dtTill = NEVER_EXPIRES)
{
   ASSERT(dtFrom <= dtTill);

   // Очистим список ядер позиции
   m_aoPosCores.Clear();
   
   // Запросим историю ордеров и сделок
   if(HistorySelect(dtFrom,dtTill)!=true)
      return(WRONG_VALUE);
   
   // Соберем тикеты исторических позиций
   // Просмотрим все сделки выхода, и выпишем оттуда тикеты позиций.
   int iHistoryDealsTotal=HistoryDealsTotal();

   CArrayLong   alHistoryPosIDs;
   int iI = WRONG_VALUE;
   ulong ulCurTicket = 0;
   long lCurPosID = 0;
   long lCurMagic = 0;
   long lCurEntry = 0;
   string strCurSymbol;
   
   for(iI=0;iI<iHistoryDealsTotal; ++iI)
      {
      ulCurTicket = HistoryDealGetTicket(iI);
      
      if(ulCurTicket == 0)
         return(WRONG_VALUE);
      
      // Получим направление сделки   
      if(HistoryDealGetInteger(ulCurTicket,DEAL_ENTRY,lCurEntry)!=true)
         {
         TRACE_INTEGER("Не удалось получить направление сделки ! Тикет: ",ulCurTicket);
         continue;
         };
      
      // Проверим направление сделки
      if(lCurEntry != DEAL_ENTRY_OUT)
         continue;
      
      // Получим магик сделки
      if(HistoryDealGetInteger(ulCurTicket,DEAL_MAGIC,lCurMagic)!=true)
         {
         TRACE_INTEGER("Не удалось получить магик сделки ! Тикет: ",ulCurTicket);
         continue;
         };
         
      // Проверим магик
      if(ulMagic != NULL && lCurMagic != ulMagic)
         {
         //TRACE_INTEGER("Сделка не подходит ! Имеет неверный магик ! Magic сделки: ",lCurMagic);
         //TRACE_INTEGER("Требуемый Magic : ",ulMagic);
         continue;
         };
      
      // Получим символ сделки
      if(HistoryDealGetString(ulCurTicket,DEAL_SYMBOL,strCurSymbol)!=true)
         {
         TRACE_INTEGER("Не удалось получить символ ордера ! Тикет: ",ulCurTicket);
         continue;
         };
      
      // Проверим символ
      if(csSymbol != CS_UNKNOWN)
         if(csSymbol == CS_CURRENT)
            {
            if(_Symbol2CurrencyEnum(strCurSymbol) != _Symbol2CurrencyEnum(Symbol()))
               {
               //TRACE2("Symbol выбираемой позиции: ",_Enum2CurrencySymbol(csSymbol));
               //TRACE2("Выбранный ордер имеет неверный символ: ",strCurSymbol);
               continue;
               };
            }
         else 
            {
            if(_Symbol2CurrencyEnum(strCurSymbol) != csSymbol)
               {
               //TRACE2("Symbol выбираемой позиции: ",_Enum2CurrencySymbol(csSymbol));
               //TRACE2("Выбранный ордер имеет неверный символ ! Символ ордера: ",poiBuffer.GetSymbolString());
               continue;
               };
            };

      // Получим ID позиции
      if(HistoryDealGetInteger(ulCurTicket,DEAL_POSITION_ID,lCurPosID)!=true)
         {
         TRACE_INTEGER("Не удалось получить ID позиции ! Тикет: ",ulCurTicket);
         continue;
         };
         
      // Проверим ID позиции
      if(lCurPosID <= NULL)
         continue;        
         
      if(alHistoryPosIDs.Add(lCurPosID)!=true)
         return(WRONG_VALUE);
      };  // цикл перебора всех сделок
   
   // Здесь ID всех позиций собраны в массиве alHistoryPosIDs, необходимо убрать повторения (в позиции может быть много ордеров)
   CArrayLong alUnicalHistoryPosIDs;   
   
   if(_DeleteDoubles(GetPointer(alHistoryPosIDs),GetPointer(alUnicalHistoryPosIDs))!=true)
      return(WRONG_VALUE);

   TRACE_INTEGER("Уникальных ID позиций в истории: ",alUnicalHistoryPosIDs.Total());

   // Здесь массив alUnicalHistoryPosIDs заполнен уникальными ID позиций в истории.
   // Заполним ядра позиции
   CMT5HistoryPositionInfoCore* phpiHistPosCore = NULL;
   
   for(iI=0;iI<alUnicalHistoryPosIDs.Total(); ++iI)
      {
      //TRACE_INTEGER("Выберем позицию: ",iI);
      
      // Выберем очередной тикет
      lCurPosID = alUnicalHistoryPosIDs.At(iI);

      // Позиция является нужной компонентой 
      ASSERT(phpiHistPosCore == NULL);
      
      phpiHistPosCore = new CMT5HistoryPositionInfoCore;
      
      if(phpiHistPosCore == NULL)
         {
         m_aoPosCores.Clear();
         ASSERT_DSC(false,"Не удалось создать объект CMT5HistoryPositionInfoCore по new");
         return(WRONG_VALUE);
         };
      
      ASSERT_MYPOINTER(phpiHistPosCore);

      if(phpiHistPosCore.SelectByID(lCurPosID)!=true)
         {
         TRACE("Не удалось создать выбрать позицию ! Возможно, позиция открыта, и еще не полностью в истории.");
         TRACE_INTEGER("ID невыбранной позиции: ",lCurPosID);
         delete phpiHistPosCore;
         phpiHistPosCore = NULL;
         continue;
         };
      
      ASSERT(phpiHistPosCore.GetTPCOpenTime() > MIN_DATETIME && phpiHistPosCore.GetTPCOpenTime() < phpiHistPosCore.GetTPCCloseTime() && phpiHistPosCore.GetTPCCloseTime() < NEVER_EXPIRES);
      
      // Найдена и выбрана еще одна компонента позиции
      if(m_aoPosCores.Add(phpiHistPosCore) == false)
         {
         delete phpiHistPosCore;
         m_aoPosCores.Clear();
         ASSERT_DSC(false,"Не удалось добавить новый объект в список ядер позиции");
         return(WRONG_VALUE);
         };
      
      phpiHistPosCore = NULL;   
      }; // цикл перебора уникальных PosID

   // TRACE_INTEGER("Ядер в выбранной позиции: ",m_aoPosCores.Total());     
   
   return(m_aoPosCores.Total());
};

При этом сам класс истории - это наследник абстрактного  интерфейса CTradeHistoryI:

class CTradeHistoryI: public CMyObject
{
public:
   void CTradeHistoryI() {    SetMyObjectType(MOT_TRADE_HISTORY_I); };
   virtual void ~CTradeHistoryI() {};
   
   // Выбор существующей истории. 
   // Указывается магик и символ, по которому выбираются исторические ордера, а также промежуток времени, в котором необходимо искать их.
   // Если ulMagic = 0 - выбираются все позиции по всем магикам.
   // Если ECurrencySymbol = CS_UNKNOWN - выбираются все позиции по всем символам
   // Если ECurrencySymbol = CS_CURRENT - запрашивается функция Symbol(), и выбираются все позиции по этому символу
   // Возвращает число компонент позиции внутри истории (может быть нулевым если ничего не найдено) или WRONG_VALUE в случае ошибок
   // NOTE !!! 
   // При выборе - отложенные ордера не учитываются.
   virtual int Select(ulong ulMagic = 0,ECurrencySymbol csSymbol = CS_CURRENT,datetime dtFrom = MIN_DATETIME,datetime dtTill = NEVER_EXPIRES) = 0;

   virtual uint GetTotalComponents() const = 0;  // Получение общего числа компонент
   virtual CHistoryPosComponentI* GetComponent(uint uiComponentIdx) const = 0;
   
   // Расширенный интерфейс
   virtual void Sort(ESortTPCMode stmMode = STM_BY_OPEN_TIME_A) = 0;
   
   
   // Функция ищет внутри истории компоненту с указанным тикетом. 
   // В случае, если ее нет - возвращается false.
   // Если компонента найдена - возвращается true, и uiComponentIdx устанавливается на индекс компоненты внутри позиции.
   virtual bool FindComponentByTicket(long lTicket,uint &uiComponentIdx) const = 0;
};

Выбрав необходимую историю - можно пересчитать ее компоненты (позици для МТ5 или ордера для МТ4), и получить интерфейс на любую компоненту в виде абстрактного интерфейса:

class CTradePosComponentI: public CMyObject
{
public:
   void CTradePosComponentI() {    SetMyObjectType(MOT_TRADEPOS_COMPONENT_I); };
   virtual void ~CTradePosComponentI() {};
   
   // Основной интерфейс
   virtual long               GetTPCTicket()       const = 0;
   virtual long               GetTPCMagic()        const = 0;
   virtual ECurrencySymbol    GetTPCSymbol()       const = 0;
   virtual ENUM_POSITION_TYPE GetTPCType()         const = 0;
   virtual datetime           GetTPCOpenTime()     const = 0;
   virtual double             GetTPCVolume()       const = 0;
   virtual double             GetTPCOpenPrice()    const = 0;
   virtual double             GetTPCStopLoss()     const = 0;
   virtual double             GetTPCTakeProfit()   const = 0;
   virtual string             GetTPCCommentary()   const = 0;
   
   virtual bool               IsTPCInUnloss() const { if(GetTPCStopLoss() <= 0 || GetTPCStopLoss() == EMPTY_VALUE) return(false); if(GetTPCType() == POSITION_TYPE_BUY) { if(GetTPCStopLoss() >= GetTPCOpenPrice()) return(true); } else { if(GetTPCStopLoss() <= GetTPCOpenPrice())return(true); }; return (false); };
   virtual double             GetTPDistance() const { if(GetTPCTakeProfit() == 0 || GetTPCTakeProfit() == EMPTY_VALUE) return(EMPTY_VALUE); if(GetTPCType() == POSITION_TYPE_BUY) return(GetTPCTakeProfit() - GetTPCOpenPrice()); return(GetTPCOpenPrice() - GetTPCTakeProfit());  };
   virtual double             GetSLDistance() const { if(GetTPCStopLoss() == 0 || GetTPCStopLoss() == EMPTY_VALUE) return(EMPTY_VALUE); if(GetTPCType() == POSITION_TYPE_BUY) return(GetTPCOpenPrice()- GetTPCStopLoss()); return(GetTPCStopLoss() - GetTPCOpenPrice());  };
};

class CHistoryPosComponentI: public CTradePosComponentI
{
public:
   void CHistoryPosComponentI() {    SetMyObjectType(MOT_HISTORYPOS_COMPONENT_I); };
   virtual void ~CHistoryPosComponentI() {};

   virtual datetime           GetTPCCloseTime()    const = 0;
   virtual double             GetTPCClosePrice()   const = 0;
   virtual double             GetTPCProfit()       const = 0;  // Возвращает профит по исторической позиции
   
   virtual bool               IsProfitClosePrice() const = 0;   // Возвращает true, если цена зарытия отличается от цены открытия в прибыльную сторону   
   
   // Возвращает профит исторической позиции для случая, когда бы ход цены (в сторону профита) был бы равен dPriceMove, а лот был бы единичным.
   // Функция используется для расчета лота для такой же позиции с нужным ходом цены 
   // Рекомендуется отнимать от цены двойной спред.
   virtual double             CalculateOneLotProfit(double dPriceMove) const = 0;  
  
};

Для МТ4 - есть соответствующие классы истории, также наследующиеся от этих интерфейсов - таким образом, заодно обеспечивается и кропссплатформенность - советнику совершенно не надо выяснять, где он работает, вся работа с историей ведется через абстрактные интерфейсы.

 
Vitaly Muzichenko:

Не писать функции которые всегда постоянны и никогда не изменяются в таком стиле

Пишите их сжато, всё-равно в них никогда никто не смотрит, а строк занимает в два раза меньше

Раз эти функции не меняются, то зачем вы там кучу ненужных фигурных скобок понаставили?  Уберите их, и всё само сожмётся.  А то ваш пример выглядит нелепо: сами же размазали код, а потом выдумываете костыли, чтобы его сократить.
 
Alexey Navoykov:
Раз эти функции не меняются, то зачем вы там кучу ненужных фигурных скобок понаставили?  Уберите их, и всё само сожмётся.  А то ваш пример выглядит нелепо: сами же размазали код, а потом выдумываете костыли, чтобы его сократить.

Согласен, можно выпилить ещё 3 строки, и сократить код, но цель была не выложить код к использованию, он по сути даже не мой, а сократить, и таких функций можно поставить пять в один экран, а не одну. После этого программы читаются легче, и не нужно 150 раз скролить. И вес файла уменьшается.

 
George Merts:

хорошая работа, мне нравиться, но мне не нравиться ООП и стараюсь обходиться без него. как и не нравятся процессоры, с разделением потоков (пример 4 ядра и 8 потоков). должно быть понятно что разделение и любая виртуализация это потеря производительности и потеря машинного времени на ее осуществление, будь то разделение потоков в ядре или виртуализация функций в коде.

Vitaly Muzichenko:

Согласен, можно выпилить ещё 3 строки, и сократить код, но цель была не выложить код к использованию, он по сути даже не мой, а сократить, и таких функций можно поставить пять в один экран, а не одну. После этого программы читаются легче, и не нужно 150 раз скролить. И вес файла уменьшается.

краткость сестра таланта, я думаю так лучше звучит.

с уважением.
 
Vitaly Muzichenko:

Рабочий экран 27"

Перечитывать отправлять не буду, процитирую: "Не писать функции которые всегда постоянны и никогда не изменяются в таком стиле"

Зачем ковырять глазами функцию, которая пишется один раз при выходе платформы, и никогда не будет меняться в будущем? Вы часто меняете/редактируете код в функциях получения размера лота, количество ордеров и типичных? Тогда зачем её растягивать на 3 экрана 32" монитора?

P.S. Код приложен выковырянный с кодобазы.


Встречный вопрос )) У меня такие функции лежат в файле MyFunc.mqh, не вижу ни малейшего смысла его сжимать. Зачем, сэкономить 10-20 Кб на диске? И если честно, мне от такого кодестайла становится плохо ))

 
Alexey Volchanskiy:

Встречный вопрос )) У меня такие функции лежат в файле MyFunc.mqh, не вижу ни малейшего смысла его сжимать. Зачем, сэкономить 10-20 Кб на диске? И если честно, мне от такого кодестайла становится плохо ))

так же пользуюсь подключаемыми файлами, это удобно. особенно когда пишешь под заказ, много функций одинаковых и писать одно и тоже несколько глупо, подключил файл и функция в советнике.
по мне так код должен быть понятным, коротким, быстрым в работе, работать при любых условиях без ошибок.


с уважением.

 
Alexey Volchanskiy:

Встречный вопрос )) У меня такие функции лежат в файле MyFunc.mqh, не вижу ни малейшего смысла его сжимать. Зачем, сэкономить 10-20 Кб на диске? И если честно, мне от такого кодестайла становится плохо ))

Человек же написал, что у него принцип "всё своё ношу с собой", весь код советника запихнут в один файл.  Соответственно в каждый советник он копипастит все эти функции.
Вот и считай: 1000 советников x 10 кб = 10 Мб - уже есть повод задуматься об экономии ))
 
Alexey Volchanskiy:

Встречный вопрос )) У меня такие функции лежат в файле MyFunc.mqh, не вижу ни малейшего смысла его сжимать. Зачем, сэкономить 10-20 Кб на диске? И если честно, мне от такого кодестайла становится плохо ))

Мне тоже, но как-то давно пришёл к такому выводу, что код должен быть компактный в тех местах, где в него никогда не смотрят, он никогда не правится, и правится не будет.

Разбрасывать пользовательский код по инклудам, это создать ещё одну дополнительную головную боль, чтоб перетащить файл в другой терминал, или им поделиться, нужно таскать не один файл, а несколько. Можно конечно перенести во все терминалы инклудники, но если в одном что подправил или добавил, тогда нужно во всех сделать замену на новый.

Советники и индикаторы настолько малы, что не имеет смысла что-то куда отдалять от тела программы. Вернее они не малы, а однофайловые, это-же не сайт в 10 000 страниц, где без класса и инклудов не обойтись. Тем более, сейчас есть структуры, и их вполне достаточно, чтоб писать компактные на 100% работоспособные коды.

 
George Merts:

Кстати, меня очень нервирует, когда вложенность более двух уровней.  Стараюсь так никогда не писать, раскидывая код по функциям.

И даже когда два уровня вложенности - обязательно после каждой закрывающей скобки - пишу комментарии, какой блок она зарывает (скажем, дублирую заголовок цикла).

Что касается стиля, то вот мой код выбора исторической позиции для МТ5 (по указанному магику, символу, с указанным диапазоном дат):

При этом сам класс истории - это наследник абстрактного  интерфейса CTradeHistoryI:

Выбрав необходимую историю - можно пересчитать ее компоненты (позици для МТ5 или ордера для МТ4), и получить интерфейс на любую компоненту в виде абстрактного интерфейса:

Для МТ4 - есть соответствующие классы истории, также наследующиеся от этих интерфейсов - таким образом, заодно обеспечивается и кропссплатформенность - советнику совершенно не надо выяснять, где он работает, вся работа с историей ведется через абстрактные интерфейсы.


Выглядит вкусно, можно еще посмотреть TRACE_*** и ASSERT?

 
Vitaly Muzichenko:

чтоб перетащить файл в другой терминал, или им поделиться, нужно таскать не один файл, а несколько. Можно конечно перенести во все терминалы инклудники, но если в одном что подправил или добавил, тогда нужно во всех сделать замену на новый

Рекомендую использовать символические ссылки или junction-связи для папки MQL. Все терминалы будут смотреть в одну папку.
Причина обращения: