Bugs no código QuantConnect Lean

image1.png


Este artigo discute bugs em um projeto de código aberto que foram encontrados usando o analisador estático. Aqui estão algumas coisas simples que podem ajudá-lo a evitá-los. Por exemplo, usando construções sintáticas da linguagem desde C # 8.0. Espero que seja interessante. Leitura feliz.



QuantConnect Lean é um mecanismo de negociação algorítmica de código aberto construído para fácil pesquisa de estratégia, backtesting e negociação ao vivo. Compatível com Windows, Linux e macOS. Integra-se com os principais provedores de dados e corretoras para implementar rapidamente estratégias de negociação algorítmica.



A verificação foi realizada utilizando o analisador estático PVS-Studio . PVS-Studio é uma ferramenta para identificar erros e vulnerabilidades potenciais no código-fonte de programas escritos em C, C ++, C # e Java no Windows, Linux e macOS.



Acidentes não são acidentais



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





A expressão V3022 '_random.Next (0, 1) == 0' é sempre verdadeira. RandomValueGenerator.cs 142



A questão é que um ou outro valor é selecionado com 50% de probabilidade. No entanto, nesse caso, o método Next sempre retornará 0.



Isso ocorre porque o intervalo não inclui o segundo argumento. Ou seja, o valor que o método pode retornar ficará na faixa [0,1). Vamos consertar isso:



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





Passando parâmetros de tipo de referência



Exemplo



/// <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 O parâmetro 'array' é sempre reescrito no corpo do método antes de ser usado. SecurityPortfolioManager.cs 192



O método pega uma coleção e sobrescreve imediatamente seu valor. Concorde que isso parece bastante suspeito. Então, vamos tentar entender o que esse método deve fazer.



Com base no comentário e no nome do método, fica claro que alguma outra matriz deve ser copiada para a matriz passada. No entanto, isso não acontecerá e o valor de array fora do método atual permanecerá inalterado.



Isso acontece porque o argumento da matrizserá passado para o método por valor, não por referência. Assim, após realizar a operação de atribuição, a referência ao novo objeto será armazenada pela variável array , disponível dentro do método. O valor do argumento passado ao método permanecerá inalterado. Para corrigir isso, você precisa passar o argumento do tipo de referência por referência:



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





Uma vez que certamente estamos escrevendo um novo array no método, usamos o modificador out em vez de ref . Isso significa imediatamente que a variável receberá um valor interno.



A propósito, este caso adiciona à coleção que meu colega Andrey Karpov está coletando e sobre a qual você pode aprender no artigo " Início da coleta de erros em funções de cópia "



Liberando recursos



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 O objeto descartável 'crypt' não é descartado antes do retorno do método. Extensions.cs 510



Para entender o significado desse diagnóstico, primeiro vamos relembrar um pouco a teoria. Com sua permissão, pegarei informações da documentação para este diagnóstico:



"O coletor de lixo libera automaticamente a memória associada ao objeto controlado quando ele não é mais usado e não há referências visíveis a ele. No entanto, é impossível prever exatamente quando a coleta ocorrerá. coleta de lixo (a menos que você o chame manualmente). Além disso, o coletor de lixo não tem conhecimento de recursos não gerenciados, como identificadores, janelas ou arquivos e fluxos abertos. Dispose é normalmente usado para liberar esses recursos não gerenciados. "



Ou seja, criamos uma variável crypt do tipo SHA256Managed , que implementa a interface IDisposable . Como resultado, ao sairmos do método, os recursos potencialmente adquiridos não serão liberados.



Para evitar isso, recomendo usar . O método Dispose será chamado automaticamente quando a chave de fechamento associada à instrução using for atingida . Se parece com isso:



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





E se você não gosta de colchetes, então em C # 8.0 você pode escrever assim:



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





A diferença da opção anterior é que o método Dispose é chamado quando a chave de fechamento do método é alcançada. Este é o fim da região onde a cripta foi declarada .



Numeros reais



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 O literal '10 .25 'do tipo' double 'é comparado a um valor do tipo' int '. OpeningBreakoutAlgorithm.cs 426



Parece estranho que, na condição, o valor de uma variável do tipo int seja comparado com um literal do tipo double . Parece estranho e alguma outra variável está claramente se perguntando. E, de fato, se olharmos quais campos com um nome semelhante TimeOfDay tem , encontraremos:



public double TotalHours { get; }
      
      





Provavelmente, o código deve ser assim:



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





Lembre-se também de que você não pode comparar para igualdade direta ("==", "! =") Números de ponto flutuante. E não se esqueça da seleção de tipos .



Declaração de mudança



Dica 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 A instrução switch não cobre todos os valores do enum 'TradingDayType': EconomicEvent. TradingCalendar.cs 79 O



tipo da variável tipo é TradingDayType , e esta é uma enumeração :



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





Se você contar, verá que há 9 elementos na enumeração e apenas 8 elementos são analisados no switch.Esta situação pode surgir devido à expansão do código. Para evitar isso, sempre recomendo usar o padrão explicitamente :



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





Como você deve ter notado, a instrução return após o switch foi movida para a seção padrão . Nesse caso, a lógica do programa não mudou, mas ainda aconselho a escrever assim.



A razão para isso é a extensibilidade do código. No caso do original, você pode adicionar com segurança alguma lógica antes falsa retorno , sem suspeitar que este é o padrão ' da instrução switch . Agora tudo está claro e óbvio.



No entanto, se você acha que, no seu caso, apenas parte dos elementos de enumeração devem ser sempre processados, você pode lançar uma exceção:



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





Pessoalmente, fiquei viciado neste açúcar sintático 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
  };
};
      
      





Dica 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 Duas ou mais ramificações de caso executam as mesmas ações. SecurityCacheTests.cs 510



Dois casos diferentes retornam o mesmo valor. Neste formulário, parece muito suspeito. Imediatamente há uma sensação de que você copiou, colou e esqueceu de alterar. Portanto, eu recomendo que se a mesma lógica deve ser executada para valores diferentes, então combine o caso desta forma:



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

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

    ....
  }
}
      
      





Isso indica claramente o que queremos, além de remover a linha extra de texto. :)



Declaração If



Exemplo 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 || resolução! = Resolução.Daily || resolução! = Resolução.Hora 'é sempre verdadeira. LiveTradingDataFeedTests.cs 1431



Essa condição é sempre verdadeira. De fato, para que a condição não seja atendida, a variável de resolução deve ter o valor Resolução.Diário e Resolução.Hora ao mesmo tempo. Possível versão corrigida:



[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);
  }
  ....
}
      
      





Algumas diretrizes para a instrução if . Quando houver uma condição que consiste inteiramente de operadores "||", então, após escrever, verifique se a mesma variável não é verificada quanto à desigualdade com algo várias vezes em uma linha.



A situação é semelhante com o operador "&&". Se uma variável for verificada várias vezes quanto à igualdade com alguma coisa, então isso provavelmente é um erro lógico.



Além disso, se você escrever uma condição composta e ela contiver "&&" e "||", não hesite em colocar parênteses. Isso pode ajudá-lo a ver o erro ou evitá-lo.



Exemplo 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 A função ' Substring ' pode receber o valor '-1' enquanto um valor não negativo é esperado. Inspecione o primeiro argumento. StringExtensions.cs 311



O analisador diz que o valor -1 pode ser passado para o primeiro argumento do método Substring . Isso lançará uma exceção do tipo System.ArgumentOutOfRangeException . Vamos ver por que esse valor pode resultar. Neste exemplo, não estamos interessados ​​nas duas primeiras condições, portanto, elas serão omitidas no raciocínio.



O parâmetro startIndex é do tipo intportanto, seus valores estão no intervalo [-2147483648, 2147483647]. Portanto, para evitar o estouro dos limites da matriz, o desenvolvedor escreveu a seguinte condição:



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





Ou seja, assumiu-se que, se um valor negativo viesse, simplesmente mudamos para 0. Mas em vez de "<=" escrevemos "<", e agora o limite inferior do intervalo da variável startIndex (do ponto de vista do analisador) é -1.



Eu sugiro usar uma construção como esta em situações como esta:



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





Essa combinação é muito mais fácil de ler, pois há um valor a menos envolvido. Portanto, proponho corrigir o problema assim:



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

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





Você pode dizer que no exemplo inicial poderíamos simplesmente alterar o sinal na condição:



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





O erro também desaparece. No entanto, a lógica será semelhante a esta:



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





Concorde que parece sobrecarregado.



Exemplo 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 Possível desreferência nula. Considere inspecionar 'buyerPowerModel'. BasicTemplateFuturesAlgorithm.cs 107



V3019 Possivelmente uma variável incorreta é comparada a nula após a conversão de tipo usando a palavra-chave 'as'. Verifique as variáveis ​​'shoppingPowerModel', 'futureMarginModel'. BasicTemplateFuturesAlgorithm.cs 105



Uma peça muito curiosa. O analisador gera dois avisos de uma vez. E, de fato, eles contêm o problema e sua causa. Primeiro, vamos ver o que acontece se a condição for atendida. Como comprarPowerModel interno será estritamente nulo , ocorrerá a desreferenciação:



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





O motivo é que uma variável é confundida na condição, que é comparada a nula . Em vez de comprarPowerModel, futureMarginModel deve ser escrito explicitamente . Versão corrigida:



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)}");  
  }
  ....
}
      
      





No entanto, ainda existe um problema com a desreferenciação de buyPowerModel dentro de uma condição. Porque o futureMarginModel será nulo não apenas quando não for um FutureMarginModel , mas também quando for comprarPowerModel nulo . Portanto, sugiro esta opção:



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)}");   
  }
  ....
}
      
      





Pessoalmente, ultimamente passei a adorar escrever tais construções usando is . Portanto, o código fica menor e é mais difícil cometer um erro. Este exemplo é completamente semelhante ao exemplo acima:



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





Além disso, em C # 9.0, adicionaremos a capacidade de escrever a palavra - chave not :



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





Exemplo 4



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





V3004 A instrução 'then' é equivalente à instrução 'else'. FuturesExpiryFunctions.cs 1561



Em diferentes condições, a mesma lógica é executada. Como um dos argumentos é um literal numérico, um valor diferente pode ter que ser passado. Por exemplo:



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





Mas isso nada mais é do que uma suposição. Aqui gostaria de chamar a atenção para o fato de que ocorre um erro na inicialização do container. O tamanho dessa inicialização é de quase 2.000 linhas:



image2.png


Além disso, os fragmentos de código internos são semelhantes entre si, o que é lógico, porque a coleção é simplesmente preenchida aqui. Portanto, tenha especial cuidado ao copiar algo em áreas grandes e semelhantes. Faça mudanças imediatamente, porque então seus olhos ficarão cansados ​​e você não verá o problema.



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





A expressão V3022 'request.Parameters.Count> 0' é sempre verdadeira. GDAXBrokerage.Utility.cs 63 A



condição no operador ternário é sempre verdadeira porque essa verificação já foi realizada acima. Agora, esta é uma verificação redundante ou os operadores "&&" e "||" estão misturados na condição acima.



Para evitar isso, quando você estiver em uma condição, sempre tenha em mente quais valores você irá inseri-la.



Possível versão corrigida:



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





Exemplo 6



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

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





V3008 A variável 'MaxOrders' recebe valores duas vezes sucessivamente. Talvez seja um engano. Verifique as linhas: 244, 240. BacktestingSetupHandler.cs 244



Aqui, a variável MaxOrders recebe um valor duas vezes em uma linha. Ou seja, a lógica com condições é redundante.



Para corrigir isso, temos 2 opções. Ou removemos as atribuições nos ramos then-else ou a atribuição após a condição. Provavelmente, o código é coberto por testes e o programa funciona corretamente. Portanto, vamos deixar apenas a última atribuição. Possível versão corrigida:



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

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





Erros comuns para humanos



Erros como copiar e colar, teclas pressionadas acidentalmente, etc. serão considerados aqui. Em geral, os problemas mais comuns de imperfeição humana. Não somos máquinas, então tais situações são normais.



Recomendações gerais para eles:



  • se você copiar algo, faça alterações na cópia assim que colar;
  • conduzir uma revisão do código;
  • use ferramentas especiais que procurarão por erros para você.


Situação 1



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





V3001 Existem subexpressões idênticas '_medianMax.IsReady' à esquerda e à direita do operador '&&'. FisherTransform.cs 72



Neste exemplo, o campo IsReady deve depender de duas condições, mas na verdade depende de uma. É tudo culpa de um erro de digitação. Muito provavelmente, eles escreveram _medianMax em vez de _medianMin . Versão corrigida:



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





Situação 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 A variável 'SessionId' recebe valores duas vezes sucessivamente. Talvez seja um engano. Verifique as linhas: 182, 172. BacktestResultPacket.cs 182



A classe tem muitos campos que precisam ser inicializados - muitas linhas no construtor. Tudo é mesclado e um campo é inicializado várias vezes. Nesse caso, pode haver inicialização desnecessária ou eles se esqueceram de inicializar algum outro campo.



Se estiver interessado, você pode ver outros erros encontrados por esta regra de diagnóstico.



Situação 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 Análise empírica. É possível que um erro de digitação esteja presente no literal da string. A palavra 'pontuação' é suspeita. InsightJsonConverterTests.cs 209



Desculpe pelo código grande e assustador. Campos diferentes têm os mesmos valores aqui. Este é um erro clássico da família de copiar e colar. Copiado, pensativo, esquecido de fazer alterações - esse é o erro.



Situação 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 A operação '-' é executada 2 ou mais vezes em sucessão. Considere inspecionar a expressão '- -shares'. OpeningBreakoutAlgorithm.cs 328 Operador unário



"-" aplicado duas vezes em uma linha. Assim, o valor passado ao método MarketOrder permanecerá inalterado. É muito difícil dizer quantas contras unárias devem ser deixadas aqui. Talvez o operador de decremento de prefixo "-" seja geralmente necessário aqui, mas a tecla de espaço foi acidentalmente pressionada . As opções estão escuras, portanto, uma das opções corrigidas possíveis:



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





Situação 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 A variável '_factory' é atribuída a si mesma. ZipEntryNameSubscriptionDataSourceReader.cs 50



Paul _factory duas vezes recebe o mesmo valor. Existem apenas quatro campos na classe, então isso provavelmente é apenas um erro de digitação. Versão corrigida:



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





Conclusão



Existem muitos lugares onde você pode cometer erros. Alguns percebemos e corrigimos imediatamente. Parte disso é corrigido para revisão de código e eu recomendo atribuir parte a ferramentas especiais.



Além disso, se você gostou desse formato, escreva sobre ele. Eu farei outra coisa semelhante. Obrigado!





Se você deseja compartilhar este artigo com um público que fala inglês, por favor, use o link de tradução: Nikolay Mironov. Falando sobre erros no código Lean QuantConnect .



All Articles