Projet du conseiller - page 3

 
Alexey Volchanskiy:

Et ensuite, il faut chercher à quoi correspondent ces quatre crochets en bas.

D'ailleurs, cela me rend très nerveux lorsque l'imbrication est supérieure à deux niveaux. J'essaie de ne jamais écrire de cette façon, en répartissant le code sur les fonctions.

Et même lorsqu'il y a deux niveaux d'imbrication, assurez-vous d'écrire des commentaires après chaque crochet fermant - quel bloc il enterre (disons, un en-tête de boucle en double).

En ce qui concerne le style, voici mon code pour sélectionnerune position historique pour MT5 (par un magicien, un symbole, avec une plage de dates spécifiée) :

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());
};

La classe historique elle-même est un descendant de l'interface abstraite 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;
};

En sélectionnant l'historique requis - vous pouvez recalculer ses composants (positions pour MT5 ou ordres pour MT4), et obtenir une interface à n'importe quel composant comme une interface abstraite :

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;  
  
};

Pour MT4 - il y a des classes d'historique correspondantes également héritées de ces interfaces - ainsi, en même temps, la multiplateforme est fournie - un EA n'a pas besoin de trouver où il travaille, tout le travail avec l'historique est fait par les interfaces abstraites.

 
Vitaly Muzichenko:

N'écrivez pas de fonctions qui sont toujours constantes et ne changent jamais dans ce style

Rédigez-les de manière concise, personne ne les regarde jamais de toute façon, et elles prennent deux fois moins de lignes.

Puisque ces fonctions ne changent jamais, pourquoi y mettre un tas de crochets inutiles ? Enlevez-les et tout rétrécira de lui-même. Parce que votre exemple est absurde : vous avez vous-même brouillé le code et vous inventez ensuite des béquilles pour le réduire.
 
Alexey Navoykov:
Puisque ces fonctions ne changent pas, pourquoi avoir mis un tas de crochets inutiles à cet endroit ? Enlevez-les, et tout sera compressé. Parce que votre exemple est absurde : vous avez vous-même brouillé le code et vous inventez ensuite des béquilles pour le réduire.

Je suis d'accord, vous pouvez couper 3 lignes de plus, et raccourcir le code, mais le but n'était pas de mettre le code à utiliser, il n'est en fait même pas le mien, mais de raccourcir, et de telles fonctions peuvent être mis cinq dans un écran, pas un. Ensuite, les programmes sont plus faciles à lire et vous n'avez pas besoin de faire défiler 150 fois les pages. Et le poids du dossier diminue.

 
George Merts:

Beau travail, j'aime bien, mais je n'aime pas la POO et j'essaie de m'en passer. Je n'aime pas les processeurs avec division des threads (comme 4 cœurs et 8 threads). Il doit être clair que la division et toute virtualisation est une perte de performance et une perte de temps machine pour son implémentation, que ce soit la division des threads dans le noyau ou la virtualisation des fonctions dans le code.

Vitaly Muzichenko:

Je suis d'accord, vous pouvez couper 3 lignes de plus, et raccourcir le code, mais le but n'était pas de mettre le code à utiliser, il n'est en fait même pas le mien, mais de raccourcir, et de telles fonctions peuvent être mis cinq dans un écran, pas un. Ensuite, les programmes sont plus faciles à lire et vous n'avez pas besoin de faire défiler 150 fois les pages. Et le poids du dossier est réduit.

La brièveté est la sœur du talent, je pense que ça sonne mieux.

Sincèrement.
 
Vitaly Muzichenko:

Écran de travail de 27 pouces

Je ne vais pas le relire, je vais juste le citer :"N'écrivez pas de fonctions qui sont toujours constantes et ne changent jamais dans ce style"

Pourquoi s'acharner sur une fonction qui n'est écrite qu'une fois lors de la sortie de la plate-forme et qui ne changera jamais à l'avenir ? Modifiez-vous souvent le code dans les fonctions pour obtenir la taille du lot, le nombre d'ordres et les caractéristiques ? Alors pourquoi l'étirer sur 3 écrans d'un moniteur 32" ?

P.S. Le code ci-joint est forgé à partir de kodobase.


Question de comptoir ;))) J'ai de telles fonctions dans le fichier MyFunc.mqh, je ne vois pas le moindre intérêt à le compresser. Pourquoi, pour économiser 10-20 Ko sur le disque ? Et franchement, ce genre de flux codé me rend malade ;))

 
Alexey Volchanskiy:

Question de comptoir ;))) J'ai de telles fonctions dans le fichier MyFunc.mqh, je ne vois pas le moindre intérêt à le compresser. Pourquoi, pour économiser 10-20 Ko sur le disque ? Pour être honnête, ce codestream me rend malade ;)).

J'utilise aussi les fichiers include, c'est pratique. Surtout quand on écrit un script personnalisé, il y a beaucoup de fonctions identiques et c'est idiot d'écrire une seule et même chose, il suffit d'inclure le fichier et la fonction est dans votre EA.
Pour ma part, le code doit être clair, court, rapide à travailler et doit fonctionner dans toutes les conditions sans erreur.


Sincèrement.

 
Alexey Volchanskiy:

Question de comptoir ;))) J'ai de telles fonctions dans le fichier MyFunc.mqh, je ne vois pas le moindre intérêt à le compresser. Pourquoi, pour économiser 10-20 Ko sur le disque ? Et franchement, un tel flux de données me rend malade ;))

Le programmeur a écrit que le principe "je porte tout moi-même" ; l'ensemble du code de l'Expert Advisor est entassé dans un seul fichier. En conséquence, il copie toutes ces fonctions dans chaque EA.
Donc, comptez : 1000 EAs x 10 Kb = 10 Mb - il faut déjà penser à économiser ;))
 
Alexey Volchanskiy:

Question de comptoir ;))) J'ai de telles fonctions dans le fichier MyFunc.mqh, je ne vois pas le moindre intérêt à le compresser. Pourquoi, pour économiser 10-20 Ko sur le disque ? Et franchement, ce genre de flux codé me rend malade;))

Moi aussi, mais il y a longtemps que je suis arrivé à la conclusion que le code doit être compact dans des endroits où on ne le regarde jamais, où il n'est jamais corrigé et ne le sera jamais.

La dispersion du code utilisateur avec tous ces emplacements est un casse-tête supplémentaire, car vous devrez glisser et déposer les fichiers dans différents terminaux ou les partager. Bien sûr, vous pouvez transférer les includniks dans tous les terminaux, mais si vous modifiez ou ajoutez quelque chose dans un terminal, alors tous les includniks doivent être remplacés par un nouveau.

Les conseillers experts et les indicateurs sont si petits qu'il n'y a aucun intérêt à les éloigner du corps du programme. Pour être plus correct, ils ne sont pas petits, ils sont en fichier unique, ce n'est pas comme un site de 10 000 pages où vous ne pouvez pas vous passer des classes et des inludes. De plus, il existe maintenant des structures, et elles sont suffisantes pour écrire un code compact et 100% exploitable.

 
George Merts:

D'ailleurs, cela me rend très nerveux lorsque l'imbrication se fait sur plus de deux niveaux. J'essaie de ne jamais l'écrire de cette façon, en répartissant le code sur les fonctions.

Et même lorsqu'il y a deux niveaux d'imbrication, veillez à écrire des commentaires après chaque parenthèse fermante, pour indiquer quel bloc elle enterre (par exemple, un en-tête de boucle en double).

En ce qui concerne le style, voici mon code pour sélectionner uneposition historique pour MT5 (par magik spécifié, symbole, avec une plage de dates spécifiée) :

La classe historique elle-même est un descendant de l'interface abstraite CTradeHistoryI :

En sélectionnant l'historique requis - vous pouvez recalculer ses composants (positions pour MT5 ou ordres pour MT4), et obtenir une interface à n'importe quel composant comme une interface abstraite :

Pour MT4, il existe des classes d'historique correspondantes qui héritent également de ces interfaces - ainsi, en même temps, la multiplateforme est assurée - le conseiller expert n'a pas besoin de savoir où il travaille, tout le travail avec l'historique est effectué par le biais d'interfaces abstraites.


Cela a l'air bien, mais pouvons-nous également nous pencher sur TRACE_*** et ASSERT ?

 
Vitaly Muzichenko:

Pour glisser et déposer un fichier vers un autre terminal, ou pour le partager, vous devez faire glisser non pas un seul fichier, mais plusieurs. Vous pouvez, bien sûr, transférer les inludes vers tous les terminaux, mais si vous modifiez ou ajoutez quelque chose dans un terminal, vous devez alors le remplacer par un nouveau dans tous les terminaux.

Je recommande d'utiliser des liens symboliques ou des liens de jonction pour le dossier MQL. Tous les terminaux se retrouveront dans un seul dossier.