Bogues dans QuantConnect Lean Code

image1.png


Cet article traite des bogues dans un projet open source détecté à l'aide de l'analyseur statique. Voici quelques conseils simples qui peuvent vous aider à les éviter. Par exemple, en utilisant des constructions de langage syntaxique depuis C # 8.0. J'espère que ce sera intéressant. Bonne lecture.



QuantConnect Lean est un moteur de trading algorithmique open source conçu pour faciliter la recherche stratégique, le backtesting et le trading en direct. Compatible avec Windows, Linux et macOS. S'intègre aux principaux fournisseurs de données et sociétés de courtage pour déployer rapidement des stratégies de trading algorithmiques.



Le contrôle a été effectué à l'aide de l'analyseur statique PVS-Studio . PVS-Studio est un outil d'identification des erreurs et des vulnérabilités potentielles dans le code source des programmes écrits en C, C ++, C # et Java sous Windows, Linux et macOS.



Les accidents ne sont pas accidentels



public virtual DateTime NextDate(....)
{
  ....
  // both are valid dates, so chose one randomly
  if (   IsWithinRange(nextDayOfWeek, minDateTime, maxDateTime) 
      && IsWithinRange(previousDayOfWeek, minDateTime, maxDateTime))
  {
    return _random.Next(0, 1) == 0  // <=
           ? previousDayOfWeek
           : nextDayOfWeek;
  }
  ....
}
      
      





L' expression V3022 '_random.Next (0, 1) == 0' est toujours vraie. RandomValueGenerator.cs 142



Le fait est que l'une ou l'autre valeur est sélectionnée avec une probabilité de 50%. Cependant, dans ce cas, la méthode Next retournera toujours 0.



Cela est dû au fait que la plage n'inclut pas le deuxième argument. Autrement dit, la valeur que la méthode peut renvoyer sera dans la plage [0,1). Corrigeons ceci:



public virtual DateTime NextDate(....)
{
  ....
  // both are valid dates, so chose one randomly
  if (   IsWithinRange(nextDayOfWeek, minDateTime, maxDateTime) 
      && IsWithinRange(previousDayOfWeek, minDateTime, maxDateTime))
  {
    return _random.Next(0, 2) == 0
           ? previousDayOfWeek
           : nextDayOfWeek;
  }
  ....
}
      
      





Passage des paramètres de type de référence



Exemple



/// <summary>
/// Copy contents of the portfolio collection to a new destination.
/// </summary>
/// <remarks>
/// IDictionary implementation calling the underlying Securities collection
/// </remarks>
/// <param name="array">Destination array</param>
/// <param name="index">Position in array to start copying</param>
public void CopyTo(KeyValuePair<Symbol, SecurityHolding>[] array, int index)
{
  array = new KeyValuePair<Symbol, SecurityHolding>[Securities.Count];
  var i = 0;
  foreach (var asset in Securities)
  {
    if (i >= index)
    {
      array[i] = new KeyValuePair<Symbol,SecurityHolding>(asset.Key,
                                                          asset.Value.Holdings);
    }
    i++;
  }
}
      
      





V3061 Le paramètre 'array' est toujours réécrit dans le corps de la méthode avant d'être utilisé. SecurityPortfolioManager.cs 192



La méthode prend une collection et écrase immédiatement sa valeur. Convenez que cela semble plutôt suspect. Essayons donc de comprendre ce que cette méthode devrait faire.



À partir du commentaire et du nom de la méthode, il devient clair qu'un autre tableau doit être copié dans le tableau passé. Cependant, cela ne se produira pas et la valeur de array en dehors de la méthode actuelle restera inchangée.



Cela se produit parce que l'argument tableausera transmis à la méthode par valeur et non par référence. Ainsi, après avoir effectué l'opération d'affectation, la référence au nouvel objet sera stockée par la variable tableau , disponible à l'intérieur de la méthode. La valeur de l'argument passé à la méthode restera inchangée. Pour résoudre ce problème, vous devez passer l'argument du type de référence par référence:



public void CopyTo(KeyValuePair<Symbol, SecurityHolding>[] out array, // <=
                   int index)
{
  array = new KeyValuePair<Symbol, SecurityHolding>[Securities.Count];
  ....
}
      
      





Puisque nous écrivons certainement un nouveau tableau dans la méthode, nous utilisons le modificateur out au lieu de ref . Cela signifie immédiatement que la variable se verra attribuer une valeur à l'intérieur.



En passant, cette affaire reconstitue la collection que mon collègue Andrey Karpov rassemble et dont vous pouvez apprendre dans l'article " Commencer à collecter les erreurs dans les fonctions de copie ".



Libérer des ressources



public static string ToSHA256(this string data)
{
  var crypt = new SHA256Managed();
  var hash = new StringBuilder();
  var crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(data), 
                                 0, 
                                 Encoding.UTF8.GetByteCount(data));
  foreach (var theByte in crypto)
  {
    hash.Append(theByte.ToStringInvariant("x2"));
  }
  return hash.ToString();
}
      
      





V3114 L' objet IDisposable 'crypt' n'est pas supprimé avant le retour de la méthode. Extensions.cs 510



Pour comprendre la signification de ce diagnostic, rappelons d'abord un peu la théorie. Avec votre permission, je vais prendre des informations de la documentation pour ce diagnostic:



"Le garbage collector libère automatiquement la mémoire associée à l'objet contrôlé s'il n'est plus utilisé et qu'il n'y a pas de références visibles à celui-ci. Cependant, il est impossible de prédire quand exactement la collecte aura lieu. garbage collection (sauf si vous l'appelez manuellement). De plus, le garbage collector n'a aucune connaissance des ressources non gérées telles que les descripteurs, les fenêtres ou les fichiers et flux ouverts. Dispose est généralement utilisé pour libérer ces ressources non gérées. "



Autrement dit, nous avons créé une crypte variable de type SHA256Managed , qui met en œuvre la IDisposable l' interface . Par conséquent, lorsque nous quittons la méthode, les ressources potentiellement acquises ne seront pas libérées.



Pour éviter cela, je recommande d'utiliser using . La méthode Dispose sera appelée automatiquement lorsque l'accolade de fermeture associée à l'instruction using est atteinte . Cela ressemble à ceci:



public static string ToSHA256(this string data)
{
  using (var crypt = new SHA256Managed())
  {
    var hash = new StringBuilder();
    ....
  }
}
      
      





Et si vous n'aimez pas les accolades, alors en C # 8.0, vous pouvez écrire comme ceci:



public static string ToSHA256(this string data)
{
  using var crypt = new SHA256Managed();
  var hash = new StringBuilder();
  ....
}
      
      





La différence par rapport à l'option précédente est que la méthode Dispose est appelée lorsque l'accolade fermante de la méthode est atteinte. C'est la fin de la région où crypt est déclaré .



Nombres réels



public bool ShouldPlot
{
  get
  {
    ....
    if (Time.TimeOfDay.Hours < 10.25) return true;
    ....
  }
}

public struct TimeSpan : IComparable, 
                         IComparable<TimeSpan>, 
                         IEquatable<TimeSpan>, 
                         IFormattable
{
  ....
  public double TotalHours { get; }
  public int Hours { get; }
  ....
}
      
      





V3040 Le littéral '10 .25 'de type' double 'est comparé à une valeur de type' int '. OpeningBreakoutAlgorithm.cs 426



Il semble étrange que, dans la condition, la valeur d'une variable de type int soit comparée à un littéral de type double . Cela semble étrange et une autre variable se suggère évidemment. Et, en effet, si nous regardons quels champs avec un nom similaire TimeOfDay a , nous trouverons:



public double TotalHours { get; }
      
      





Très probablement, le code devrait ressembler à ceci:



public bool ShouldPlot
{
  get
  {
    ....
    if (Time.TimeOfDay.TotalHours < 10.25) return true;
    ....
  }
}
      
      





Souvenez-vous également que vous ne pouvez pas comparer pour l'égalité directe ("==", "! =") Les nombres à virgule flottante. Et n'oubliez pas le casting de type .



Instruction Switch



Astuce 1



public IEnumerable<TradingDay> GetDaysByType(TradingDayType type,
                                             DateTime start, 
                                             DateTime end)
{
  Func<TradingDay, bool> typeFilter = day =>
  {
    switch (type)        // <=
    {
      case TradingDayType.BusinessDay:
        return day.BusinessDay;
      case TradingDayType.PublicHoliday:
        return day.PublicHoliday;
      case TradingDayType.Weekend:
        return day.Weekend;
      case TradingDayType.OptionExpiration:
        return day.OptionExpirations.Any();
      case TradingDayType.FutureExpiration:
        return day.FutureExpirations.Any();
      case TradingDayType.FutureRoll:
        return day.FutureRolls.Any();
      case TradingDayType.SymbolDelisting:
        return day.SymbolDelistings.Any();
      case TradingDayType.EquityDividends:
        return day.EquityDividends.Any();
    };
    return false;
  };
  return GetTradingDays(start, end).Where(typeFilter);
}
      
      





V3002 L'instruction switch ne couvre pas toutes les valeurs de l'énumération 'TradingDayType': EconomicEvent. TradingCalendar.cs 79 Le



type de la variable de type est TradingDayType , ce qui est un ENUM :



public enum TradingDayType
{
  BusinessDay,
  PublicHoliday,
  Weekend,
  OptionExpiration,
  FutureExpiration,
  FutureRoll,
  SymbolDelisting,
  EquityDividends,
  EconomicEvent
}
      
      





Si vous comptez, vous verrez qu'il y a 9 éléments dans l'énumération, et seulement 8 éléments sont analysés dans le commutateur . Cette situation pourrait survenir en raison de l'expansion du code. Pour éviter cela, je recommande toujours d'utiliser explicitement default :



public IEnumerable<TradingDay> GetDaysByType(TradingDayType type,
                                             DateTime start, 
                                             DateTime end)
{
  Func<TradingDay, bool> typeFilter = day =>
  {
    switch (type)
    {
      ....
      default:
        return false;
    };
  };
  return GetTradingDays(start, end).Where(typeFilter);
}
      
      





Comme vous l'avez peut-être remarqué, l' instruction return après le passage du commutateur dans la section par défaut . Dans ce cas, la logique du programme n'a pas changé, mais je vous conseille quand même d'écrire de cette façon.



La raison en est l'extensibilité du code. Dans le cas de l'original, vous pouvez ajouter en toute sécurité une logique avant de renvoyer false , sans soupçonner qu'il s'agit de la valeur par défaut de l' instruction switch . Maintenant, tout est clair et évident.



Cependant, si vous pensez que dans votre cas, seule une partie des éléments d'énumération doit toujours être traitée, vous pouvez lever une exception:



default:
  throw new CustomExeption("Invalid enumeration element");
      
      





Personnellement, je suis devenu accro à ce sucre syntaxique C # 8.0:



Func<TradingDay, bool> typeFilter = day =>
{
  return type switch
  {
    TradingDayType.BusinessDay      => day.BusinessDay,
    TradingDayType.PublicHoliday    => day.PublicHoliday,
    TradingDayType.Weekend          => day.Weekend,
    TradingDayType.OptionExpiration => day.OptionExpirations.Any(),
    TradingDayType.FutureExpiration => day.FutureExpirations.Any(),
    TradingDayType.FutureRoll       => day.FutureRolls.Any(),
    TradingDayType.SymbolDelisting  => day.SymbolDelistings.Any(),
    TradingDayType.EquityDividends  => day.EquityDividends.Any(),
    _ => false
  };
};
      
      





Astuce 2



public string[] GetPropertiesBy(SecuritySeedData type)
{
  switch (type)
  {
    case SecuritySeedData.None:
      return new string[0];

    case SecuritySeedData.OpenInterest:
      return new[] { "OpenInterest" };  // <=

    case SecuritySeedData.OpenInterestTick:
      return new[] { "OpenInterest" };  // <=

    case SecuritySeedData.TradeTick:
      return new[] {"Price", "Volume"};

    ....

    case SecuritySeedData.Fundamentals:
      return new string[0];

    default:
      throw new ArgumentOutOfRangeException(nameof(type), type, null);
  }
}
      
      





V3139 Deux ou plusieurs branches de cas exécutent les mêmes actions. SecurityCacheTests.cs 510



Deux cas différents renvoient la même valeur. Sous cette forme, cela semble très suspect. On a immédiatement l'impression que vous avez copié, collé et oublié de changer. Par conséquent, je recommande que si la même logique doit être appliquée pour différentes valeurs, combinez le cas comme ceci:



public string[] GetPropertiesBy(SecuritySeedData type)
{
  switch (type)
  {
    case SecuritySeedData.None:
      return new string[0];

    case SecuritySeedData.OpenInterest:
    case SecuritySeedData.OpenInterestTick:
      return new[] { "OpenInterest" };

    ....
  }
}
      
      





Cela indique clairement ce que nous voulons, et supprime la ligne de texte supplémentaire. :)



Si déclaration



Exemple 1



[TestCaseSource(nameof(DataTypeTestCases))]
public void HandlesAllTypes<T>(....) where T : BaseData, new()
{
  ....
  if (   symbol.SecurityType != SecurityType.Equity
      || resolution != Resolution.Daily
      || resolution != Resolution.Hour)
  {
    actualPricePointsEnqueued++;
    dataPoints.Add(dataPoint);
  }
  ....
}
      
      





V3022 Expression 'symbol.SecurityType! = SecurityType.Equity || résolution! = Resolution.Daily || resolution! = Resolution.Hour 'est toujours vrai. LiveTradingDataFeedTests.cs 1431



Cette condition est toujours vraie. En effet, pour que la condition ne soit pas remplie, la variable de résolution doit avoir la valeur Resolution.Daily et Resolution.Hour en même temps. Version corrigée possible:



[TestCaseSource(nameof(DataTypeTestCases))]
public void HandlesAllTypes<T>(....) where T : BaseData, new()
{
  ....
  if (   symbol.SecurityType != SecurityType.Equity
      || (   resolution != Resolution.Daily 
          && resolution != Resolution.Hour))
  {
    actualPricePointsEnqueued++;
    dataPoints.Add(dataPoint);
  }
  ....
}
      
      





Quelques lignes directrices pour l' instruction if . Quand il y a une condition qui consiste entièrement en opérateurs "||", puis après l'écriture, vérifiez si la même variable est vérifiée pour l' inégalité pour quelque chose plusieurs fois de suite.



La situation est similaire avec l'opérateur "&&". Si une variable est vérifiée plusieurs fois pour l' égalité avec quelque chose, il s'agit probablement d'une erreur logique.



Aussi, si vous écrivez une condition composée, et qu'elle contient "&&" et "||", alors n'hésitez pas à mettre des parenthèses. Cela peut vous aider à voir l'erreur ou à l'éviter.



Exemple 2



public static string SafeSubstring(this string value, 
                                   int startIndex,
                                   int length)
{
  if (string.IsNullOrEmpty(value))
  {
    return value;
  }

  if (startIndex > value.Length - 1)
  {
    return string.Empty;
  }

  if (startIndex < -1)
  {
    startIndex = 0;
  }

  return value.Substring(startIndex, 
                         Math.Min(length, value.Length - startIndex));
}
      
      





V3057 La fonction 'Substring' peut recevoir la valeur '-1' alors qu'une valeur non négative est attendue. Inspectez le premier argument. StringExtensions.cs 311



L'analyseur indique que la valeur -1 peut être passée au premier argument de la méthode Substring . Cela lèvera une exception de type System.ArgumentOutOfRangeException . Voyons pourquoi une telle valeur peut se révéler. Dans cet exemple, nous ne sommes pas intéressés par les deux premières conditions, elles seront donc omises dans le raisonnement.



Le paramètre startIndex est de type intpar conséquent, ses valeurs sont dans la plage [-2147483648, 2147483647]. Par conséquent, pour éviter de dépasser les limites du tableau, le développeur a écrit la condition suivante:



if (startIndex < -1)
{
  startIndex = 0;
}
      
      





Autrement dit, on a supposé que si une valeur négative arrivait, nous la changions simplement en 0. Mais au lieu de "<=" nous avons écrit "<", et maintenant la limite inférieure de la plage de la variable startIndex (du point de vue de l'analyseur) est -1.



Je suggère d'utiliser une construction comme celle-ci dans des situations comme celle-ci:



if (variable < value)
{
  variable = value;
}
      
      





Cette combinaison est beaucoup plus facile à lire, car il y a une valeur en moins impliquée. Par conséquent, je propose de résoudre le problème comme ceci:



public static string SafeSubstring(....)
{
  ....
  if (startIndex < 0)
  {
    startIndex = 0;
  }

  return value.Substring(startIndex, 
                         Math.Min(length, value.Length - startIndex));
}
      
      





Vous pouvez dire que dans l'exemple initial, nous pourrions simplement changer le signe dans la condition:



if (startIndex <= -1)
{
  startIndex = 0;
}
      
      





L'erreur disparaît également. Cependant, la logique ressemblera à ceci:



if (variable <= value - 1)
{
  variable = value;
}
      
      





Convenez qu'il a l'air dépassé.



Exemple 3



public override void OnEndOfAlgorithm()
{
  var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
  var futureMarginModel = buyingPowerModel as FutureMarginModel;
  if (buyingPowerModel == null)
  {
    throw new Exception($"Invalid buying power model. " +
                        $"Found: {buyingPowerModel.GetType().Name}. " +
                        $"Expected: {nameof(FutureMarginModel)}");  
  }
  ....
}
      
      





V3080 Déréférencement nul possible. Pensez à inspecter «purchasePowerModel». BasicTemplateFuturesAlgorithm.cs 107



V3019 Il est possible qu'une variable incorrecte soit comparée à null après la conversion de type en utilisant le mot clé 'as'. Vérifiez les variables 'purchasePowerModel', 'futureMarginModel'. BasicTemplateFuturesAlgorithm.cs 105



Une pièce très curieuse. L'analyseur génère deux avertissements à la fois. Et en fait, ils contiennent le problème et sa cause. Voyons d'abord ce qui se passe si la condition est remplie. Étant donné que buyPowerModel à l' intérieur sera strictement nul , un déréférencement se produira:



$"Found: {buyingPowerModel.GetType().Name}. "
      
      





La raison en est qu'une variable est mélangée dans la condition, qui est comparée à null . Au lieu d' acheterPowerModel, futureMarginModel doit être écrit explicitement . Version corrigée:



public override void OnEndOfAlgorithm()
{
  var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
  var futureMarginModel = buyingPowerModel as FutureMarginModel;
  if (futureMarginModel == null)
  {
    throw new Exception($"Invalid buying power model. " +
                        $"Found: {buyingPowerModel.GetType().Name}. " +
                        $"Expected: {nameof(FutureMarginModel)}");  
  }
  ....
}
      
      





Cependant, il reste un problème avec le déréférencement d' achat de PowerModel dans une condition. Parce que futureMarginModel sera nul non seulement lorsqu'il ne s'agit pas d'un FutureMarginModel , mais également lorsque le BuyPowerModel est nul . Par conséquent, je suggère cette option:



public override void OnEndOfAlgorithm()
{
  var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
  var futureMarginModel = buyingPowerModel as FutureMarginModel;
  if (futureMarginModel == null)
  {
    string foundType =    buyingPowerModel?.GetType().Name 
                       ?? "the type was not found because the variable is null";
    throw new Exception($"Invalid buying power model. " +
                        $"Found: {foundType}. " +
                        $"Expected: {nameof(FutureMarginModel)}");   
  }
  ....
}
      
      





Personnellement, ces derniers temps, j'ai adoré écrire de telles constructions en utilisant is . Ainsi, le code devient moins et il est plus difficile de se tromper. Cet exemple est complètement similaire à l'exemple ci-dessus:



public override void OnEndOfAlgorithm()
{
  var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
  if (!(buyingPowerModel is FutureMarginModel futureMarginModel))
  {
    ....
  }
  ....
}
      
      





De plus, en C # 9.0, nous ajouterons la possibilité d'écrire le mot - clé not :



public override void OnEndOfAlgorithm()
{
  var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
  if (buyingPowerModel is not FutureMarginModel futureMarginModel)
  {
    ....
  }
  ....
}
      
      





Exemple 4



public static readonly Dictionary<....> 
  FuturesExpiryDictionary = new Dictionary<....>()
{
  ....
  if (twoMonthsPriorToContractMonth.Month == 2)
  {
    lastBusinessDay = FuturesExpiryUtilityFunctions
                      .NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
  }
  else
  {
    lastBusinessDay = FuturesExpiryUtilityFunctions
                      .NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
  }
  ....
}
      
      





V3004 L' instruction «then» équivaut à l'instruction «else». FuturesExpiryFunctions.cs 1561



Dans des conditions différentes, la même logique est exécutée. Étant donné que l'un des arguments est un littéral numérique, une valeur différente peut devoir être transmise. Par exemple:



public static readonly Dictionary<....> 
  FuturesExpiryDictionary = new Dictionary<....>()
{
  ....
  if (twoMonthsPriorToContractMonth.Month == 2)
  {
    lastBusinessDay = FuturesExpiryUtilityFunctions
                      .NthLastBusinessDay(twoMonthsPriorToContractMonth, 2);
  }
  else
  {
    lastBusinessDay = FuturesExpiryUtilityFunctions
                      .NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
  }
  ....
}
      
      





Mais ce n'est rien de plus qu'une hypothèse. Ici, je voudrais attirer votre attention sur le fait qu'une erreur se produit lors de l'initialisation du conteneur. La taille de cette initialisation est de près de 2000 lignes:



image2.png


De plus, les fragments de code à l'intérieur sont similaires les uns aux autres, ce qui est logique, car la collection est simplement remplie ici. Par conséquent, soyez particulièrement prudent lorsque vous copiez quelque chose dans des zones étendues et similaires. Faites des changements tout de suite, car vos yeux se fatiguent et vous ne verrez pas le problème.



Exemple 5



public AuthenticationToken GetAuthenticationToken(IRestRequest request)
{
  ....
  if (request.Method == Method.GET && request.Parameters.Count > 0)
  {
    var parameters = request.Parameters.Count > 0
                     ? string.Join(....)
                     : string.Empty;
    url = $"{request.Resource}?{parameters}";
  }
}
      
      





L' expression V3022 'request.Parameters.Count> 0' est toujours vraie. GDAXBrokerage.Utility.cs 63 La



condition de l'opérateur ternaire est toujours vraie car cette vérification a déjà été effectuée ci-dessus. Maintenant, c'est soit un contrôle redondant, soit les opérateurs "&&" et "||" sont mélangés dans la condition ci-dessus.



Pour éviter cela, lorsque vous êtes dans une condition, gardez toujours à l'esprit les valeurs que vous allez saisir.



Version corrigée possible:



public AuthenticationToken GetAuthenticationToken(IRestRequest request)
{
  ....
  if (request.Method == Method.GET && request.Parameters.Count > 0)
  {
    var parameters = string.Join(....);
    url = $"{request.Resource}?{parameters}";
  }
}
      
      





Exemple 6



public bool Setup(SetupHandlerParameters parameters)
{
  ....
  if (job.UserPlan == UserPlan.Free)
  {
    MaxOrders = 10000;
  }
  else
  {
    MaxOrders = int.MaxValue;
    MaximumRuntime += MaximumRuntime;
  }

  MaxOrders = job.Controls.BacktestingMaxOrders; // <=
  ....
}
      
      





V3008 La variable 'MaxOrders' reçoit des valeurs deux fois de suite. C'est peut-être une erreur. Vérifiez les lignes: 244, 240. BacktestingSetupHandler.cs 244



Ici, la variable MaxOrders reçoit une valeur deux fois de suite. Autrement dit, la logique avec conditions est redondante.



Pour résoudre ce problème, nous avons 2 options. Nous supprimons les affectations dans les branches then-else ou l'affectation après la condition. Très probablement, le code est couvert par des tests et le programme fonctionne correctement. Par conséquent, nous ne laisserons que la dernière affectation. Version corrigée possible:



public bool Setup(SetupHandlerParameters parameters)
{
  ....
  if (job.UserPlan != UserPlan.Free)
  {
    MaximumRuntime += MaximumRuntime;
  }

  MaxOrders = job.Controls.BacktestingMaxOrders;
  ....
}
      
      





Erreurs courantes pour les humains



Les erreurs telles que copier-coller, touches accidentellement pressées, etc. seront prises en compte ici. En général, les problèmes les plus courants de l'imperfection humaine. Nous ne sommes pas des machines, de telles situations sont donc normales.



Recommandations générales pour eux:



  • si vous copiez quelque chose, apportez des modifications à la copie dès que vous la collez;
  • effectuer un examen du code;
  • utilisez des outils spéciaux qui rechercheront les erreurs à votre place.


Situation 1



public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
  private readonly Minimum _medianMin;
  private readonly Maximum _medianMax;
  public override bool IsReady => _medianMax.IsReady && _medianMax.IsReady;
}
      
      





V3001 Il existe des sous-expressions identiques '_medianMax.IsReady' à gauche et à droite de l'opérateur '&&'. FisherTransform.cs 72



Dans cet exemple, le champ IsReady doit dépendre de deux conditions, mais dépend en fait d'une seule. Tout est la faute d'une faute de frappe. Très probablement, ils ont écrit _medianMax au lieu de _medianMin . Version corrigée:



public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
  private readonly Minimum _medianMin;
  private readonly Maximum _medianMax;
  public override bool IsReady => _medianMin.IsReady && _medianMax.IsReady;
}
      
      





Situation 2



public BacktestResultPacket(....) : base(PacketType.BacktestResult)
{
  try
  {
    Progress = Math.Round(progress, 3);
    SessionId = job.SessionId; // <=
    PeriodFinish = endDate;
    PeriodStart = startDate;
    CompileId = job.CompileId;
    Channel = job.Channel;
    BacktestId = job.BacktestId;
    Results = results;
    Name = job.Name;
    UserId = job.UserId;
    ProjectId = job.ProjectId;
    SessionId = job.SessionId; // <=
    TradeableDates = job.TradeableDates;
  }
  catch (Exception err) 
  {
    Log.Error(err);
  }
}
      
      





V3008 La variable 'SessionId' reçoit des valeurs deux fois de suite. C'est peut-être une erreur. Vérifiez les lignes: 182, 172. BacktestResultPacket.cs 182



La classe a de nombreux champs qui doivent être initialisés - de nombreuses lignes dans le constructeur. Tout est fusionné et un champ est initialisé plusieurs fois. Dans ce cas, il peut y avoir une initialisation inutile ici, ou ils ont oublié d'initialiser un autre champ.



Si vous êtes intéressé, vous pouvez consulter les autres erreurs détectées par cette règle de diagnostic.



Situation 3



private const string jsonWithScore =
            "{" +
            "\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
            "\"group-id\":\"a02be50f56a8496b9ba995d19a904ada\"," +
            "\"source-model\":\"mySourceModel-1\"," +
            "\"generated-time\":1520711961.00055," +
            "\"created-time\":1520711961.00055," +
            "\"close-time\":1520711961.00055," +
            "\"symbol\":\"BTCUSD XJ\"," +
            "\"ticker\":\"BTCUSD\"," +
            "\"type\":\"price\"," +
            "\"reference\":9143.53," +
            "\"reference-final\":9243.53," +
            "\"direction\":\"up\"," +
            "\"period\":5.0," +
            "\"magnitude\":0.025," +
            "\"confidence\":null," +
            "\"weight\":null," +
            "\"score-final\":true," +
            "\"score-magnitude\":1.0," +
            "\"score-direction\":1.0," +
            "\"estimated-value\":1113.2484}";

private const string jsonWithExpectedOutputFromMissingCreatedTimeValue =
            "{" +
            "\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
            "\"group-id\":\"a02be50f56a8496b9ba995d19a904ada\"," +
            "\"source-model\":\"mySourceModel-1\"," +
            "\"generated-time\":1520711961.00055," +
            "\"created-time\":1520711961.00055," +
            "\"close-time\":1520711961.00055," +
            "\"symbol\":\"BTCUSD XJ\"," +
            "\"ticker\":\"BTCUSD\"," +
            "\"type\":\"price\"," +
            "\"reference\":9143.53," +
            "\"reference-final\":9243.53," +
            "\"direction\":\"up\"," +
            "\"period\":5.0," +
            "\"magnitude\":0.025," +
            "\"confidence\":null," +
            "\"weight\":null," +
            "\"score-final\":true," +
            "\"score-magnitude\":1.0," +
            "\"score-direction\":1.0," +
            "\"estimated-value\":1113.2484}";
      
      





V3091 Analyse empirique. Il est possible qu'une faute de frappe soit présente dans le littéral de chaîne. Le mot «score» est suspect. InsightJsonConverterTests.cs 209



Désolé pour le code volumineux et effrayant. Ici, différents champs ont les mêmes valeurs. Il s'agit d'une erreur classique de la famille des copier-coller. Copié, réfléchi, oublié d'apporter des modifications - c'est l'erreur.



Situation 4



private void ScanForEntrance()
{
  var shares = (int)(allowedDollarLoss/expectedCaptureRange);
  ....
  if (ShouldEnterLong)
  {
    MarketTicket = MarketOrder(symbol, shares);
    ....
  }
  else if (ShouldEnterShort)
  {
    MarketTicket = MarketOrder(symbol, - -shares); // <=
    ....
    StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice);
    ....
  }
  ....
}
      
      





V3075 L'opération «-» est exécutée deux fois ou plus de suite. Pensez à inspecter l'expression '- -shares'. OpeningBreakoutAlgorithm.cs 328 Opérateur unaire



"-" appliqué deux fois de suite. Ainsi, la valeur transmise à la méthode MarketOrder restera inchangée. Il est très difficile de dire combien de contre unaires il faut laisser ici. Peut-être que l'opérateur de décrémentation du préfixe "-" est généralement nécessaire ici, mais la touche espace a été accidentellement frappée . Les options sont sombres, donc l'une des options corrigées possibles:



private void ScanForEntrance()
{
  ....
  if (ShouldEnterLong)
  {
    MarketTicket = MarketOrder(symbol, shares);
    ....
  }
  else if (ShouldEnterShort)
  {
    MarketTicket = MarketOrder(symbol, -shares);
    ....
    StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice);
    ....
  }
  ....
}
      
      





Situation 5



private readonly SubscriptionDataConfig _config;
private readonly DateTime _date;
private readonly bool _isLiveMode;
private readonly BaseData _factory;

public ZipEntryNameSubscriptionDataSourceReader(
    SubscriptionDataConfig config, 
    DateTime date,
    bool isLiveMode)
{
  _config = config;
  _date = date;
  _isLiveMode = isLiveMode;
  _factory = _factory = config.GetBaseDataInstance(); // <=
}
      
      





V3005 La variable '_factory' est affectée à elle-même. ZipEntryNameSubscriptionDataSourceReader.cs 50



Paul _factory deux fois reçoit la même valeur. Il n'y a que quatre champs dans la classe, il ne s'agit donc probablement que d'une faute de frappe. Version corrigée:



public ZipEntryNameSubscriptionDataSourceReader(....)
{
  _config = config;
  _date = date;
  _isLiveMode = isLiveMode;
  _factory = config.GetBaseDataInstance();
}
      
      





Conclusion



Il existe de nombreux endroits où vous pouvez faire des erreurs. Certains que nous remarquons et corrigeons immédiatement. Une partie est corrigée pour la révision du code, et je recommande d'attribuer une partie à des outils spéciaux.



Aussi, si vous avez aimé ce format, écrivez-le. Je vais faire une autre chose similaire. Je vous remercie!





Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien de traduction: Nikolay Mironov. Parler des erreurs dans le code Lean QuantConnect .



All Articles