QuantConnectリーンコードのバグ

image1.png


この記事では、静的アナライザーを使用して検出されたオープンソースプロジェクトのバグについて説明します。これらを回避するのに役立つ簡単なことがいくつかあります。たとえば、C#8.0以降の言語の構文構造を使用します。それが面白いことを願っています。幸せな読書。



QuantConnect Leanは、簡単な戦略調査、バックテスト、およびライブ取引のために構築されたオープンソースのアルゴリズム取引エンジンです。Windows、Linux、macOSと互換性があります。主流のデータプロバイダーや証券会社と統合して、アルゴリズムによる取引戦略を迅速に展開します。



チェックは、PVS-Studio静的アナライザーを使用して実行されました PVS-Studioは、Windows、Linux、およびmacOS上のC、C ++、C#、およびJavaで記述されたプログラムのソースコードのエラーと潜在的な脆弱性を特定するためのツールです。



事故は偶然ではありません



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





V3022式 '_random.Next(0、1)== 0'は常に真です。RandomValueGenerator.cs 142要点



は、いずれかの値が50%の確率で選択されるということでした。ただし、この場合、Nextメソッド は常に0を返し



ます。これは、範囲に2番目の引数が含まれていないためです。つまり、メソッドが返すことができる値は[0,1)の範囲になります。これを修正しましょう:



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





参照タイプパラメータの受け渡し







/// <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パラメータ 'array'は、使用される前に常にメソッド本体で書き換えられます。 SecurityPortfolioManager.cs 192



メソッドはコレクションを取得し、すぐにその値を上書きします。これはかなり疑わしいように見えることに同意します。それでは、このメソッドが何をすべきかを理解してみましょう。



コメントとメソッドの名前から、渡された配列に他の配列をコピーする必要があることが明らかになります。ただし、これは発生せず、現在のメソッド外配列の値は 変更されません。



これは、配列引数が原因で発生します 参照ではなく、値によってメソッドに渡されます。したがって、割り当て操作を実行した後、新しいオブジェクトへの参照は、メソッド内で使用可能な配列変数によって格納され ます。メソッドに渡される引数の値は変更されません。これを修正するには、参照タイプ引数を参照で渡す必要があります。



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





確かにメソッドに新しい配列を書き込んでいるのでrefの代わりに out修飾子を使用し ます。これはすぐに、変数に内部の値が割り当てられることを意味します。 ちなみに、このケースでは、同僚のAndrey Karpovが収集しているコレクションを補充します。これについては、「コピー機能でのエラーの収集の開始」の記事から学ぶことができます







リソースを解放する



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





V3114IDisposableオブジェクト 'crypt'は、メソッドが戻る前に破棄されません。 Extensions.cs 510



この診断の意味を理解するために、最初に理論を少し思い出してみましょう。あなたの許可を得て、この診断のドキュメントから情報を取得します。



「ガベージコレクターは、監視対象オブジェクトが使用されなくなり、オブジェクトへの参照が表示されなくなると、監視対象オブジェクトに関連付けられたメモリを自動的に解放します。ただし、収集がいつ行われるかを正確に予測することはできません。ガベージコレクション(手動で呼び出さない限り)。また、ガベージコレクターは、ハンドル、ウィンドウ、開いているファイルやストリームなどのアンマネージリソースを認識していません。通常、Disposeメソッドは 、このようなアンマネージリソースを解放するために使用されます。



つまりIDisposableインターフェイスを実装する SHA256Managedタイプの crypt変数を作成しました その結果、メソッドを終了しても、取得された可能性のあるリソースは解放されません。 これを防ぐために、を使用することをお勧め ます。 処分に関連した右中括弧ときにメソッドが自動的に呼び出されます使用して文が到達しました 次のようになります。







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





また、中括弧が気に入らない場合は、C#8.0で次のように記述できます。



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





前のオプションとの違いはメソッドの閉じ中括弧に達したときにDisposeメソッドが 呼び出されることです。これは、cryptが宣言されている領域の終わりです



実数



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「double」タイプの「10.25」リテラルは、「int」タイプの値と比較されます。OpeningBreakoutAlgorithm.cs 426



条件で、int型の変数の値がdouble型のリテラルと比較 されるのは奇妙に見え ます。それは奇妙に見え、他のいくつかの変数は明らかにそれ自体を示唆しています。実際、TimeOfDayと同じ名前のフィールドを見ると 、次のことがわかります。



public double TotalHours { get; }
      
      





ほとんどの場合、コードは次のようになります。



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





また、直接等しい( "=="、 "!=")浮動小数点数を比較できないことにも注意して くださいそして、タイプキャスティングを忘れないでください



スイッチステートメント



ヒント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 switchステートメントは、「TradingDayType」列挙型のすべての値をカバーしているわけではありません:EconomicEvent。TradingCalendar.cs 79



変数 タイプのタイプTradingDayTypeであり、これは 列挙です



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





数えると、列挙には9つの要素があり、スイッチで8つの要素のみ分析されていることがわかります 。この状況は、コードの拡張が原因で発生する可能性があります。これを防ぐために、デフォルトを明示的に使用することを常にお勧めし ます



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





お気づきかもしれませんが スイッチがデフォルトのセクションに移動した 後の returnステートメント。この場合、プログラムのロジックは変更されていませんが、このように作成することをお勧めします。 この理由は、コードの拡張性です。オリジナルのケースでは、安全にする前に、いくつかのロジックを追加することができ 、戻り偽であると疑うない、 のデフォルトの」switch文。今、すべてが明確で明白です。 ただし、自分の場合、列挙要素の一部のみを常に処理する必要があると思われる場合は、例外をスローできます。











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





個人的に、私はこの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
  };
};
      
      





ヒント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);
  }
}
      
      





V31392つ以上のケースブランチが同じアクションを実行します。SecurityCacheTests.cs 5102



つの異なる ケースが同じ値を返します。この形式では、非常に疑わしいように見えます。すぐにコピー、貼り付け、変更を忘れたような気がします。したがって、異なる値に対して同じロジックを実行する必要がある場合は、次のようにケースを組み合わせることをお勧めします



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

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

    ....
  }
}
      
      





これは、必要なものを明確に示し、さらにテキストの余分な行を削除します。:)



Ifステートメント



例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式 'symbol.SecurityType!= SecurityType.Equity ||解像度!= Resolution.Daily || Resolution!= Resolution.Hour 'は常に真です。 LiveTradingDataFeedTests.cs1431



この条件は常に真です。結局のところ、条件が満たされないようにするには、解像度変数 Resolution.DailyResolution.Hourに同時に設定する必要があります 。可能な修正バージョン:



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





ifステートメントのいくつかのガイドライン 。完全に演算子「||」で構成される条件がある場合は、書き込んだ後、同じ変数が連続て数回何か不等式であるかどうかをチェックします



状況は「&&」演算子と同様です。変数が何かと等しいかどうかを数回チェックする場合、 これはおそらく論理エラーです。



また、複合条件を記述し、それに「&&」と「||」が含まれている場合は、かっこを付けることを躊躇しないでください。これは、エラーを確認するか、エラーを回避するのに役立ちます。



例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サブストリング」関数は、負でない値が予想されるときに「-1」値を受け取る可能性があります。最初の引数を調べます。 StringExtensions.cs 311



アナライザは、と言う値が-1渡すことができるの最初の引数にサブストリングの方法 。これにより、System.ArgumentOutOfRangeExceptionタイプの例外がスローされ ます。そのような値がなぜ判明するのか見てみましょう。この例では、最初の2つの条件には関心がないため、推論では省略します。



パラメータ startIndexint型です したがって、その値は[-2147483648、2147483647]の範囲にあります。したがって、配列境界のオーバーフローを防ぐために、開発者は次の条件を記述しました。



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





つまり、負の値が来た場合は、単に0に変更すると想定されていました。ただし、「<=」の代わりに「<」と記述したため、(アナライザーの観点から)startIndex変数の範囲の下限 は-1になります。



このような状況では、このような構造を使用することをお勧めします。



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





関係する値が1つ少ないため、この組み合わせははるかに読みやすくなります。したがって、私はこのような問題を修正することを提案します:



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

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





最初の例では、条件の符号を簡単に変更できると言えます。



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





エラーも消えます。ただし、ロジックは次のようになります。



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





それが圧倒されているように見えることに同意します。



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





V3080nullの逆参照の可能性。 'buyingPowerModel'を調べることを検討してください。 BasicTemplateFuturesAlgorithm.cs 107



V3019'as 'キーワードを使用した型変換後に、誤った変数がnullと比較される可能性があります。変数「buyingPowerModel」、「futureMarginModel」を確認してください。 BasicTemplateFuturesAlgorithm.cs105



非常に興味深い作品。アナライザーは、一度に2つの警告を生成します。そして実際、それらには問題とその原因が含まれています。まず、条件が満たされた場合に何が起こるかを見てみましょう。以来 buyingPowerModelの内部が厳密になり 、ヌル、逆参照すると、発生します。



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





その理由は、変数が条件で混同され、nullと比較されるため です。BuyPowerModelの代わりに、 futureMarginModelを明示的に記述する必要があります 修正されたバージョン:



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





ただし、条件内でbuyingPowerModelを参照解除することには問題が残って います。結局のところ、 futureMarginModelはなり ヌルそうでないときだけでなく FutureMarginModelでなく、とき buyingPowerModelがある ヌルしたがって、私はこのオプションを提案します:



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





個人的には、最近、isを使ってそのような構成を書くのが好きになりました これにより、コードが少なくなり、間違いを犯しにくくなります。この例は、上記の例と完全に似ています。



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





さらに、C#9.0では、notキーワードを記述する機能を追加します



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





例4



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





V3004「then」ステートメントは「else」ステートメントと同等です。FuturesExpiryFunctions.cs 1561



異なる条件下で、同じロジックが実行されます。引数の1つが数値リテラルであるため、別の値を渡す必要がある場合があります。例えば:



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





しかし、これは単なる仮定にすぎません。ここで、コンテナの初期化でエラーが発生することに注意してください。この初期化のサイズはほぼ2000行です。



image2.png


また、内部のコードフラグメントは互いに類似しており、コレクションはここに入力されるだけなので、論理的です。したがって、大きくて類似した領域で何かをコピーする場合は特に注意してください。すぐに変更を加えてください。そうすると、目が疲れて問題が発生しなくなります。



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





V3022式 'request.Parameters.Count> 0'は常に真です。GDAXBrokerage.Utility.cs 63



このチェックはすでに上記で実行されているため、三元演算子条件は常にtrueです。これは冗長チェックであるか、上記の条件で演算子「&&」と「||」が混同されています。



これを回避するために、あなたが状態にあるとき、あなたがそれを入力する値を常に覚えておいてください。



可能な修正バージョン:



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





例6



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

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





V3008「MaxOrders」変数には2回連続して値が割り当てられます。おそらくこれは間違いです。チェック行:244、240。BacktestingSetupHandler.cs 244



ここで、MaxOrders変数には2回続けて値割り当てられます。つまり、条件付きのロジックは冗長です。



これを修正するには、2つのオプションがあります。then-elseブランチの割り当てを削除するか、条件の後の割り当てを削除します。ほとんどの場合、コードはテストでカバーされており、プログラムは正しく機能します。したがって、最後の割り当てのみを残します。可能な修正バージョン:



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

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





人間のよくある間違い



ここでは、コピー&ペースト、誤ってキーを押したなどのエラーについて検討します。一般的に、人間の不完全さの最も一般的な問題。私たちは機械ではないので、そのような状況は正常です。



それらの一般的な推奨事項:



  • 何かをコピーする場合は、貼り付けたらすぐにコピーに変更を加えます。
  • コードレビューを実施します。
  • エラーを探す特別なツールを使用してください。


状況1



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





V3001「&&」演算子の左側と右側に同一のサブ式「_medianMax.IsReady」があります。FisherTransform.cs 72



この例では、IsReadyフィールド は2つの条件に依存する必要がありますが、実際には1つに依存します。それはすべてタイプミスのせいです。ほとんどの場合、彼ら_medianMinの代わりに _medianMax書き ました修正されたバージョン:



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





状況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「SessionId」変数には2回連続して値が割り当てられます。おそらくこれは間違いです。チェック行:182、172。BacktestResultPacket.cs 182



クラスには、初期化する必要のある多くのフィールドがあります。コンストラクターには多くの行があります。すべてがマージされ、1つのフィールドが複数回初期化されます。この場合、不要な初期化が行われているか、他のフィールドの初期化を忘れている可能性があります。



興味がある場合は、この診断ルールによって検出された他のエラーを確認できます



状況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経験的分析。文字列リテラル内にタイプミスが存在する可能性があります。 「スコア」という言葉は疑わしいです。 InsightJsonConverterTests.cs209



大きくて怖いコードでごめんなさい。ここでは、異なるフィールドの値は同じです。これは、コピー&ペーストファミリの典型的な間違いです。コピーされ、思慮深く、変更を加えるのを忘れた-それは間違いです。



状況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「-」操作を2回以上連続して実行します。'--shares'式を調べることを検討してください。OpeningBreakoutAlgorithm.cs328単一の



「-」演算子が2回続けて適用されました。したがって、MarketOrderメソッドに渡される値 は変更されません。ここにいくつの単調な短所を残すべきかを言うのは非常に難しいです。おそらく、ここでは通常、プレフィックスデクリメント演算子「-」が必要ですが、スペースバーが誤ってヒットされました オプションが暗いため、可能な修正オプションの1つは次のとおりです。



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





状況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ザ・「_factory」変数は、自身に割り当てられています。ZipEntryNameSubscriptionDataSourceReader.cs 50



ポール _factoryは二度同じ値が割り当てられます。クラスには4つのフィールドしかないため、これはおそらく単なるタイプミスです。修正されたバージョン:



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





結論



間違いを犯す可能性のある場所はたくさんあります。一部は気づき、すぐに修正します。一部はコードレビュー用に修正されているため、一部を特別なツールに割り当てることをお勧めします。



また、このフォーマットが気に入ったら、それについて書いてください。私は別の同様のことをします。ありがとうございました!





この記事を英語を話す聴衆と共有したい場合は、翻訳リンクNikolayMironovを使用してください。 QuantConnectリーンコードのエラーについて話します



All Articles