Codes et pratiques élégants - page 2

 

Bonjour à tous,

Voici ma méthode pour débuguer mes codes tout en les gardant propres et bien structurés.

Au niveau global, je me crée des variables clairement identifiables comme étant des valeurs de test :


int      g_test_Int_01 = 0;
int      g_test_Int_02 = 0;
int      g_test_Int_03 = 0;
int      g_test_Int_04 = 0;

double   g_test_Dbl_01 = 0.0;
double   g_test_Dbl_02 = 0.0;
double   g_test_Dbl_03 = 0.0;
double   g_test_Dbl_04 = 0.0;
double   g_test_Dbl_05 = 0.0;
double   g_test_Dbl_06 = 0.0;

double   g_test_Ar_Dbl_01[];
double   g_test_Ar_Dbl_02[];
double   g_test_Ar_Dbl_03[];
double   g_test_Ar_Dbl_04[];

bool     g_test_Boo_01 = false;
bool     g_test_Boo_02 = false;
bool     g_test_Boo_03 = false;
bool     g_test_Boo_04 = false;

datetime g_test_DTi_01 = 0;
datetime g_test_DTi_02 = 0;
datetime g_test_DTi_03 = 0;
datetime g_test_DTi_04 = 0;

string   g_test_Str_01 = "";
string   g_test_Str_02 = "";
string   g_test_Str_03 = "";
string   g_test_Str_04 = "";

À quoi servent ces variables ?

  • Elles permettent d’éviter que des valeurs de test oubliées traînent dans le code une fois le débug terminé.

  • Comme elles sont toutes regroupées au même endroit, il suffit de commenter leur déclaration pour que le compilateur signale rapidement leur utilisation dans le code.

  • Cela facilite la maintenance : je garde toutes mes variables de test pour pouvoir reprendre rapidement mes anciens projets si une mise à jour est nécessaire.

Il me suffit alors de les réactiver globalement lorsque j’en ai besoin. Et en prime, je trouve cette méthode assez élégante — c’est justement l’idée que je voulais partager dans ce fil.

Si cela peut vous aider, tant mieux !

 
Réduire l'impact de l'erreur 4806 — ERR_INDICATOR_DATA_NOT_FOUND dans un EA

L’erreur 4806 n’est pas une petite blague. 


Elle peut survenir à n’importe quel moment de la vie d’un Expert Advisor :

au tick 1, au tick 42, ou en plein rallye de marché. 

Elle est déclenchée quand CopyBuffer() ou d’autres fonctions de récupération de données échouent 
à obtenir ce qu’elles demandent qu’il s’agisse d’un indicateur ou d’une série de prix.


ET PAS UNIQUEMENT POUR DES HISTOIRES DE SERVEUR SYNCHRONISER

À ceux qui se plaisent à répéter:

“L’erreur 4806 est uniquement due à une désynchronisation des time series avec le serveur.”

STOP. C’est faux, simpliste et carrément dangereux pour ceux qui codent sérieusement.

La vérité que certains refusent d’entendre :

  • L’erreur peut surgir même lorsque les séries sont prêtes, synchronisées, et validées.

  • Elle frappe en pleine session live, entre deux ticks, à la reprise du terminal, ou après un simple changement de symbole — peu importe que tout soit “disponible”.

  • Elle se produit dans des environnements chargés, en multi-symboles, ou quand l’EA fait des appels rapides. 💬 Et parfois... juste parce que le terminal a ses humeurs.


Idée simple :

Toujours demander la barre clôturée (shift = 1) en priorité,
puis tenter une lecture sur la barre vivante (shift = 0) dans un while temporisé.
En cas d’échec, on se replie sur la valeur stable de la barre précédente.

double temp[];
ArrayResize(temp, 2);
ArraySetAsSeries(temp, true);
ArrayInitialize(temp, EMPTY_VALUE);

// Step 1: Get previous bar (shift = 1)
int copied1 = CopyBuffer(handle, buffer_id, 1, 2, temp);
if (copied1 > 0 && temp[0] != EMPTY_VALUE)
    previous_bar = temp[0];

// Step 2: Try to get live bar (shift = 0)
while (attempts < max_attempts && !success) {
    int copied0 = CopyBuffer(handle, buffer_id, 0, 2, temp);
    if (copied0 > 0 && temp[0] != EMPTY_VALUE) {
        live_bar = temp[0];
        success = true;
    } else {
        attempts++;
        Sleep(100); // give time to buffer
    }
}

// Final decision
double output = success ? live_bar : previous_bar;




 

Quand on manipule des structures 2D ou 3D, typiquement des tableaux de  struct  ou des  array  imbriqués, le debug devient vite laborieux.


Le problème ? Le débogueur vous affiche toute l’arborescence, ce qui rend la lecture impossible dès que les dimensions dépassent quelques entrées.

Prenons cette structure simple :

struct Cell {
  long t;
  int a;
  int b;
  int c;
  double d;
};
Cell grid[10][100];

Dans le debug, vous verrez toutes les valeurs de grid[0][0..99] , puis celles de grid[1][0..99] , etc.

Mais si vous cherchez à inspecter rapidement grid[1] , vous devrez scroller comme un damné, ou plier et déplier sans fin sans jamais avoir les deux en même temps. 
Pas facile de debuger dans ces conditions 


L’astuce : créer des buffers temporaires

Pour rendre le debug lisible, créez des structures temporaires ciblées :

Cell Test01_grid[1];  // Pour capturer grid[0][x]
Cell Test02_grid[1];  // Pour capturer grid[1][x]

ce qui dans le debug, sera nettement plus accessible comme vous pouvez le voir 


 Bien entendu Test0x_grid sont à assigner avec les bonnes valeurs

et comme pour la pratique des g_test* vu plus haut, le fait de commenter les lignes de code

  //Cell Test01_grid[1];

  //Cell Test02_grid[1];

après vos tests vous permettrons de nettoyer très rapidement votre code 

Pratique et élégant

 

Avec la dernière mise à jour de MetaTrader vers la version 5200, j'ai remarqué un avertissement lié à la classe native CCanvas , et plus spécifiquement à la méthode PolygonNormalize .

L'avertissement est le suivant : 'CPoint' using assignment operator, this behavior is deprecated and will be removed in future Canvas.mqh 2898 17

Il indique que l'opérateur d'assignation pour la structure CPoint sera déprécié à l'avenir. J'ai donc cherché une solution pour corriger cette méthode.

Je vous propose une version améliorée de cette fonction qui non seulement corrige l'avertissement, mais adhère également à de meilleures pratiques de codage.

Le code original avec la correction

Voici à quoi ressemble la méthode originale après la correction de l'avertissement.

//+------------------------------------------------------------------+
//| Normalizes polygon for drawing                                   |
//+------------------------------------------------------------------+
void CCanvas::PolygonNormalize(CPoint &p[]) {
  int total = ArraySize(p);
//--- find top-left point
  int imin = 0;
  int xmin = p[0].x;
  int ymin = p[0].y;
  for(int i = 1; i < total; i++) {
    if(p[i].y > ymin)
      continue;
    if(p[i].y == ymin) {
      if(p[i].x < xmin) {
        xmin = p[i].x;
        imin = i;
      }
      continue;
    }
    xmin = p[i].x;
    ymin = p[i].y;
    imin = i;
  }
  if(imin == 0)
    return;
  for(int i = 0; i < imin; i++) {
    //CPoint tmp=p[0];
    CPoint tmp;
    tmp.x = p[0].x;
    tmp.y = p[0].y;

    ArrayCopy(p, p, 0, 1);
    p[total - 1] = tmp;
  }
}

J'ai également revu l'ensemble du code pour le rendre plus clair et plus facile à comprendre. Un code lisible est crucial, car il réduit la charge cognitive et simplifie la maintenance. J'aimerais insister sur l'importance de quelques points :

  • Des commentaires clairs qui expliquent la logique, ce qui est la base pour un bon code.

  • Une signature de méthode et des noms de variables explicites, qui aident à comprendre le code au premier coup d'œil, sans avoir à deviner à quoi servent imin , xmin , ou p .

  • Un cartouche d'information complet qui décrit précisément ce que fait la fonction, ses paramètres et son retour.

Voici une version révisée qui illustre ces bonnes pratiques :

//+------------------------------------------------------------------+
//| PolygonNormalize()
//| Normalizes a polygon by rotating its points so the top-leftmost
//| point becomes the first element in the array.
//| This avoids deprecated assignment behavior for CPoint objects.
//|
//| Parameters:
//|   polygon_points[] (CPoint&) : A reference to the array of CPoint objects.
//|
//+------------------------------------------------------------------+
void CCanvas::PolygonNormalize( CPoint &polygon_points[] ) {
  int total_points = ArraySize( polygon_points );

  // --- Find the top-leftmost point
  int top_left_index = 0;
  int min_x = polygon_points[ 0 ].x;
  int min_y = polygon_points[ 0 ].y;

  for( int i = 1; i < total_points; i++ ) {
    // If we find a point with a smaller Y coordinate, it's a new top point.
    if( polygon_points[ i ].y > min_y )
      continue;
    
    if( polygon_points[ i ].y == min_y ) {
      // If Y coordinates are equal, we choose the one with the smallest X.
      if( polygon_points[ i ].x < min_x ) {
        min_x = polygon_points[ i ].x;
        top_left_index = i;
      }
      continue;
    }
    
    // Found a new top-leftmost point.
    min_x = polygon_points[ i ].x;
    min_y = polygon_points[ i ].y;
    top_left_index = i;
  }

  // --- Rotate the array if the top-leftmost point is not the first one
  if( top_left_index == 0 )
    return;

  // Use a temporary array for rotation to prevent deprecated assignment warnings
  CPoint temporary_array[];
  ArrayResize( temporary_array, top_left_index );

  // Copy the points that need to be moved to the end of the array
  ArrayCopy( temporary_array, polygon_points, 0, 0, top_left_index );

  // Shift the remaining points to the beginning of the array
  ArrayCopy( polygon_points, polygon_points, 0, top_left_index, total_points - top_left_index );

  // Copy the points from the temporary array to the end of the original array
  ArrayCopy( polygon_points, temporary_array, total_points - top_left_index, 0, top_left_index );
}
 

Bonjour

Je voulais partager un tout petit bout de code qui parle de macro

#define IS_ASSIGNED_DOUBLE(v)   ( (v) != DBL_MIN)

qu'importe à quoi elle peut servir.

Dans certaines limites, c'est très pratique.

Déjà quand vous faite du debug en pas à pas, vous ne voyez pas le code.

Et c'est ce que je préfère le plus mais ne tombez pas dans le piège vous n'avez aucun contrôle de type par exemple

il faut qu'une macro reste simple,

surtout pas des instructions complexes qui s'enchaine, préféré alors le MQL5

 

Bonjour à tous,

Si, comme moi,

vous avez des fonctions ou méthodes susceptibles de retourner des erreurs, avec une distinction entre erreurs mineures et critiques,

voici un extrait de logique qui permet de gérer cet aspect de manière structurée et efficace :

i_Result = CHAR_MIN;

string ir_Result = "";

i_Result = this.CheckIfRequestExists(p_RequestID);



if (i_Result < ZERO_ERROR_THRESHOLD) {

    i_Msg_Hdl.Handle_Error_Code(i_Result, ir_Result);

    this.i_Msg_Hdl.Informations(StringFormat("%s : CheckIfRequestExists() returned an error.",

                                             Function_Class(__FUNCTION__)));



    if (IS_SILE_ERROR(i_Result)) return ERR_SILE_RETURN;

    if (IS_WARN_ERROR(i_Result)) return ERR_WARN_RETURN;

    if (IS_FUNC_ERROR(i_Result)) return ERR_FUNC_RETURN;

    if (IS_CRIT_ERROR(i_Result)) return ERR_CRITICAL_SYSTEM;

}


Explication rapide :

Si le retour est inférieur à un seuil défini (ZERO_ERROR_THRESHOLD), la méthode est considérée comme en erreur.

La méthode Handle_Error_Code() sert uniquement à générer un message informatif selon la catégorie de l’erreur.

La responsabilité de la gestion de l’erreur reste dans CheckIfRequestExists().


Les macros comme IS_WARN_ERROR() ou IS_CRIT_ERROR() permettent de sélectionner le bon code de retour selon la gravité.


Avantages :

Code plus lisible et modulaire, mais proche du code générique

Gestion centralisée des messages

Facilité d’extension pour d’autres types d’erreurs


Si vous avez des approches similaires ou des idées pour rendre ce genre de logique encore plus élégante,

n’hésitez pas à les partager !