
Este artículo analiza los errores en un proyecto de código abierto que se encontraron utilizando el analizador estático. Aquí hay algunas cosas simples que pueden ayudarlo a evitarlas. Por ejemplo, usando construcciones sintácticas del lenguaje desde C # 8.0. Espero que sea interesante. Feliz lectura.
QuantConnect Lean es un motor de operaciones algorítmicas de código abierto creado para facilitar la investigación de estrategias, las pruebas retrospectivas y las operaciones en vivo. Compatible con Windows, Linux y macOS. Se integra con los principales proveedores de datos y empresas de corretaje para implementar rápidamente estrategias de negociación algorítmica.
La comprobación se realizó con el analizador estático PVS-Studio . PVS-Studio es una herramienta para identificar errores y posibles vulnerabilidades en el código fuente de programas escritos en C, C ++, C # y Java en Windows, Linux y macOS.
Los accidentes no son accidentales
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;
}
....
}
La expresión V3022 '_random.Next (0, 1) == 0' siempre es verdadera. RandomValueGenerator.cs 142
El punto era que se seleccionaba uno u otro valor con un 50% de probabilidad. Sin embargo, en este caso, el método Next siempre devolverá 0.
Esto se debe a que el rango no incluye el segundo argumento. Es decir, el valor que puede devolver el método estará en el rango [0,1). Arreglemos esto:
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;
}
....
}
Pasar parámetros de tipo de referencia
Ejemplo
/// <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 El parámetro 'matriz' siempre se reescribe en el cuerpo del método antes de usarse. SecurityPortfolioManager.cs 192
El método toma una colección e inmediatamente sobrescribe su valor. De acuerdo en que esto parece bastante sospechoso. Así que intentemos comprender qué debería hacer este método.
A partir del comentario y el nombre del método, queda claro que alguna otra matriz debe copiarse en la matriz pasada. Sin embargo, esto no sucederá y el valor de la matriz fuera del método actual permanecerá sin cambios.
Esto sucede porque el argumento de la matrizse pasará al método por valor, no por referencia. Así, después de realizar la operación de asignación, la referencia al nuevo objeto será almacenada por la variable de matriz , disponible dentro del método. El valor del argumento pasado al método permanecerá sin cambios. Para solucionar este problema, debe pasar el argumento del tipo de referencia por referencia:
public void CopyTo(KeyValuePair<Symbol, SecurityHolding>[] out array, // <=
int index)
{
array = new KeyValuePair<Symbol, SecurityHolding>[Securities.Count];
....
}
Dado que ciertamente estamos escribiendo una nueva matriz en el método, usamos el modificador out en lugar de ref . Esto significa inmediatamente que a la variable se le asignará un valor dentro.
Por cierto, este caso repone la colección que recopila mi colega Andrey Karpov y sobre la que puede aprender del artículo " Comienzo de la recopilación de errores en las funciones de copia ".
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 ID El objeto desechable 'cripta' no se elimina antes de que el método regrese. Extensions.cs 510
Para comprender el significado de este diagnóstico, primero recordemos un poco la teoría. Con su permiso, tomaré información de la documentación para este diagnóstico:
"El recolector de basura libera automáticamente la memoria asociada con el objeto controlado si ya no se usa y no hay referencias visibles al mismo. Sin embargo, es imposible predecir cuándo ocurrirá exactamente la recolección. recolección de basura (a menos que lo invoque manualmente). Además, el recolector de basura no tiene conocimiento de recursos no administrados como identificadores, ventanas o archivos abiertos y flujos. Dispose se usa típicamente para liberar tales recursos no administrados ".
Es decir, hemos creado una variable de cripta de tipo SHA256Managed , que implementa la interfaz IDisposable . Como resultado, cuando salgamos del método, los recursos potencialmente adquiridos no se liberarán.
Para evitar esto, recomiendo usar using . El método Dispose se llamará automáticamente cuando se alcance la llave de cierre asociada con la instrucción using . Se parece a esto:
public static string ToSHA256(this string data)
{
using (var crypt = new SHA256Managed())
{
var hash = new StringBuilder();
....
}
}
Y si no le gustan las llaves, entonces en C # 8.0 puede escribir así:
public static string ToSHA256(this string data)
{
using var crypt = new SHA256Managed();
var hash = new StringBuilder();
....
}
La diferencia con la opción anterior es que se llama al método Dispose cuando se alcanza la llave de cierre del método . Este es el final de la región donde se declara la cripta .
Numeros reales
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 El literal '10 .25 'del tipo' double 'se compara con un valor del tipo' int '. OpeningBreakoutAlgorithm.cs 426
Parece extraño que en la condición el valor de una variable de tipo int se compare con un literal de tipo double . Parece extraño y alguna otra variable claramente se pregunta por sí misma. Y, de hecho, si miramos qué campos con un nombre similar tiene TimeOfDay , encontraremos:
public double TotalHours { get; }
Lo más probable es que el código se vea así:
public bool ShouldPlot
{
get
{
....
if (Time.TimeOfDay.TotalHours < 10.25) return true;
....
}
}
También recuerde que no puede comparar por igualdad directa ("==", "! =") Números de coma flotante. Y no te olvides del casting de tipos .
Declaración de cambio
Consejo 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 La declaración de cambio no cubre todos los valores de la enumeración 'TradingDayType': EconomicEvent. TradingCalendar.cs 79 El
tipo de la variable tipo es TradingDayType , y este es un enum :
public enum TradingDayType
{
BusinessDay,
PublicHoliday,
Weekend,
OptionExpiration,
FutureExpiration,
FutureRoll,
SymbolDelisting,
EquityDividends,
EconomicEvent
}
Si cuenta, verá que hay 9 elementos en la enumeración, y solo se analizan 8 elementos en el switch, situación que podría surgir debido a la expansión del código. Para evitar esto, siempre recomiendo usar default explícitamente :
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 habrá notado, la declaración de devolución después del cambio se ha movido a la sección predeterminada . En este caso, la lógica del programa no ha cambiado, pero aún así le aconsejo que escriba de esta manera.
La razón de esto es la extensibilidad del código. En el caso del original, puede agregar con seguridad algo de lógica antes de devolver falso , sin sospechar que este es el valor predeterminado de la instrucción switch . Ahora todo es claro y obvio.
Sin embargo, si cree que, en su caso, solo una parte de los elementos de enumeración siempre debe procesarse, puede lanzar una excepción:
default:
throw new CustomExeption("Invalid enumeration element");
Personalmente, me enganché con este azúcar sintáctico de 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
};
};
Consejo 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 Dos o más ramas de casos realizan las mismas acciones. SecurityCacheTests.cs 510
Dos casos diferentes devuelven el mismo valor. De esta forma, parece muy sospechoso. Inmediatamente hay la sensación de que ha copiado, pegado y olvidado cambiar. Por lo tanto, recomiendo que si se debe realizar la misma lógica para diferentes valores, combine el caso de esta manera:
public string[] GetPropertiesBy(SecuritySeedData type)
{
switch (type)
{
case SecuritySeedData.None:
return new string[0];
case SecuritySeedData.OpenInterest:
case SecuritySeedData.OpenInterestTick:
return new[] { "OpenInterest" };
....
}
}
Esto indica claramente lo que queremos, además de eliminar la línea de texto adicional. :)
Si declaración
Ejemplo 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 Expresión 'symbol.SecurityType! = SecurityType.Equity || resolution! = Resolution.Daily || resolution! = Resolution.Hour 'siempre es cierto. LiveTradingDataFeedTests.cs 1431
Esta condición es siempre verdadera. De hecho, para que no se cumpla la condición, la variable de resolución debe tener el valor Resolution.Daily y Resolution.Hour al mismo tiempo. Posible versión corregida:
[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);
}
....
}
Algunas pautas para la declaración if . Cuando hay una condición que consta completamente de operadores "||", luego de escribir, verifique si la misma variable no está verificada por desigualdad con algo varias veces seguidas.
La situación es similar con el operador "&&". Si se comprueba varias veces la igualdad de una variable con algo, lo más probable es que sea un error lógico.
Además, si escribe una condición compuesta y contiene "&&" y "||", no dude en poner paréntesis. Esto puede ayudarlo a ver el error o evitarlo.
Ejemplo 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 función ' Subcadena ' podría recibir el valor '-1' mientras se espera un valor no negativo. Inspeccione el primer argumento. StringExtensions.cs 311
El analizador dice que -1 se puede pasar al primer argumento del método Substring . Esto arrojará una excepción de tipo System.ArgumentOutOfRangeException . Veamos por qué puede resultar tal valor. En este ejemplo, no nos interesan las dos primeras condiciones, por lo que se omitirán en el razonamiento.
El parámetro startIndex es de tipo intpor lo tanto, sus valores están en el rango [-2147483648, 2147483647]. Por lo tanto, para evitar desbordar los límites de la matriz, el desarrollador escribió la siguiente condición:
if (startIndex < -1)
{
startIndex = 0;
}
Es decir, se asumió que si llegaba un valor negativo, simplemente lo cambiamos a 0. Pero en lugar de "<=" escribimos "<", y ahora el límite inferior del rango de la variable startIndex (desde el punto de vista del analizador) es -1.
Sugiero usar una construcción como esta en situaciones como esta:
if (variable < value)
{
variable = value;
}
Esta combinación es mucho más fácil de leer, ya que hay un valor menos involucrado. Por lo tanto, propongo solucionar el problema de esta manera:
public static string SafeSubstring(....)
{
....
if (startIndex < 0)
{
startIndex = 0;
}
return value.Substring(startIndex,
Math.Min(length, value.Length - startIndex));
}
Puede decir que en el ejemplo inicial podríamos simplemente cambiar el signo en la condición:
if (startIndex <= -1)
{
startIndex = 0;
}
El error también desaparece. Sin embargo, la lógica se verá así:
if (variable <= value - 1)
{
variable = value;
}
De acuerdo en que parece abrumado.
Ejemplo 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 Posible desreferencia nula. Considere la posibilidad de inspeccionar 'purchasePowerModel'. BasicTemplateFuturesAlgorithm.cs 107
V3019 Posiblemente, una variable incorrecta se compara con nula después de la conversión de tipo usando la palabra clave 'as'. Verifique las variables 'shoppingPowerModel', 'futureMarginModel'. BasicTemplateFuturesAlgorithm.cs 105
Una pieza muy curiosa. El analizador genera dos advertencias a la vez. Y de hecho, contienen el problema y su causa. Primero, veamos qué sucede si se cumple la condición. Dado que comprarPowerModel en el interior será estrictamente nulo , se producirá una desreferenciación:
$"Found: {buyingPowerModel.GetType().Name}. "
La razón es que una variable está mezclada en la condición, que se compara con nula . En lugar de comprarPowerModel, futureMarginModel debe escribirse explícitamente . Versión corregida:
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)}");
}
....
}
Sin embargo, sigue existiendo un problema con la eliminación de referencias comprandoModelo de energía dentro de una condición. Después de todo, futureMarginModel será nulo no solo cuando no es un FutureMarginModel , sino también cuando purchasePowerModel es nulo . Por tanto, sugiero esta opción:
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)}");
}
....
}
Personalmente, últimamente me ha encantado escribir este tipo de construcciones utilizando is . Entonces el código se vuelve menos y es más difícil cometer un error. Este ejemplo es completamente similar al ejemplo anterior:
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
if (!(buyingPowerModel is FutureMarginModel futureMarginModel))
{
....
}
....
}
Además, en C # 9.0 agregaremos la capacidad de escribir la palabra clave not :
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
if (buyingPowerModel is not FutureMarginModel futureMarginModel)
{
....
}
....
}
Ejemplo 4
public static readonly Dictionary<....>
FuturesExpiryDictionary = new Dictionary<....>()
{
....
if (twoMonthsPriorToContractMonth.Month == 2)
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
else
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
....
}
V3004 La instrucción 'then' es equivalente a la instrucción 'else'. FuturesExpiryFunctions.cs 1561
En diferentes condiciones, se ejecuta la misma lógica. Dado que uno de los argumentos es un literal numérico, es posible que deba pasarse un valor diferente. Por ejemplo:
public static readonly Dictionary<....>
FuturesExpiryDictionary = new Dictionary<....>()
{
....
if (twoMonthsPriorToContractMonth.Month == 2)
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 2);
}
else
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
....
}
Pero esto no es más que una suposición. Aquí me gustaría llamar su atención sobre el hecho de que se produce un error en la inicialización del contenedor. El tamaño de esta inicialización es de casi 2000 líneas:

Además, los fragmentos de código en el interior son similares entre sí, lo cual es lógico, porque la colección simplemente se completa aquí. Por lo tanto, tenga especial cuidado al copiar algo en áreas grandes y similares. Haga cambios de inmediato, porque entonces sus ojos se cansarán y no verá el problema.
Ejemplo 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 Expresión 'request.Parameters.Count> 0' siempre es verdadera. GDAXBrokerage.Utility.cs 63 La
condición en el operador ternario siempre es verdadera porque esta verificación ya se realizó anteriormente. Ahora bien, esto es una comprobación redundante o los operadores "&&" y "||" están mezclados en la condición anterior.
Para evitar esto, cuando esté en una condición, siempre tenga en cuenta los valores que ingresará.
Posible versión corregida:
public AuthenticationToken GetAuthenticationToken(IRestRequest request)
{
....
if (request.Method == Method.GET && request.Parameters.Count > 0)
{
var parameters = string.Join(....);
url = $"{request.Resource}?{parameters}";
}
}
Ejemplo 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 la variable 'MaxOrders' se le asignan valores dos veces seguidas. Quizás esto sea un error. Líneas de verificación: 244, 240. BacktestingSetupHandler.cs 244
Aquí, a la variable MaxOrders se le asigna un valor dos veces seguidas. Es decir, la lógica con condiciones es redundante.
Para solucionar esto, tenemos 2 opciones. Eliminamos las asignaciones en las ramas entonces-else, o la asignación después de la condición. Lo más probable es que el código esté cubierto por pruebas y el programa funcione correctamente. Por tanto, solo dejaremos la última asignación. Posible versión corregida:
public bool Setup(SetupHandlerParameters parameters)
{
....
if (job.UserPlan != UserPlan.Free)
{
MaximumRuntime += MaximumRuntime;
}
MaxOrders = job.Controls.BacktestingMaxOrders;
....
}
Errores comunes para los humanos
Aquí consideraremos errores como copiar y pegar, teclas presionadas accidentalmente, etc. En general, los problemas más comunes de imperfección humana. No somos máquinas, por lo que este tipo de situaciones son normales.
Recomendaciones generales para ellos:
- si copia algo, haga cambios en la copia tan pronto como lo pegue;
- realizar una revisión del código;
- utilice herramientas especiales que buscarán errores por usted.
Situación 1
public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private readonly Minimum _medianMin;
private readonly Maximum _medianMax;
public override bool IsReady => _medianMax.IsReady && _medianMax.IsReady;
}
V3001 Hay subexpresiones idénticas '_medianMax.IsReady' a la izquierda y a la derecha del operador '&&'. FisherTransform.cs 72
En este ejemplo, el campo IsReady debe depender de dos condiciones, pero de hecho depende de una. Todo es culpa de un error tipográfico. Lo más probable es que hayan escrito _medianMax en lugar de _medianMin . Versión corregida:
public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private readonly Minimum _medianMin;
private readonly Maximum _medianMax;
public override bool IsReady => _medianMin.IsReady && _medianMax.IsReady;
}
Situación 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 la variable 'SessionId' se le asignan valores dos veces seguidas. Quizás esto sea un error. Verifique las líneas: 182, 172. BacktestResultPacket.cs 182
La clase tiene muchos campos que deben inicializarse, muchas líneas en el constructor. Todo se fusiona y un campo se inicializa varias veces. En este caso, puede haber una inicialización innecesaria o se olvidaron de inicializar algún otro campo.
Si está interesado, puede consultar otros errores encontrados por esta regla de diagnóstico.
Situación 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álisis empírico. Es posible que haya un error tipográfico dentro de la cadena literal. La palabra "puntuación" es sospechosa. InsightJsonConverterTests.cs 209
Lo siento por el código grande y aterrador. Los diferentes campos tienen los mismos valores aquí. Este es un error clásico de la familia de copiar y pegar. Copiado, reflexivo, olvidado hacer cambios, ese es el error.
Situación 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 La operación '-' se ejecuta 2 o más veces seguidas. Considere inspeccionar la expresión '- -shares'. OpeningBreakoutAlgorithm.cs 328 Operador unario
"-" aplicado dos veces seguidas. Por lo tanto, el valor pasado al método MarketOrder permanecerá sin cambios. Es muy difícil decir cuántos contras unarios deberían quedar aquí. Quizás el operador de decremento de prefijo "-" generalmente se necesita aquí, pero la tecla de espacio se presionó accidentalmente . Las opciones son oscuras, por lo que una de las posibles opciones corregidas:
private void ScanForEntrance()
{
....
if (ShouldEnterLong)
{
MarketTicket = MarketOrder(symbol, shares);
....
}
else if (ShouldEnterShort)
{
MarketTicket = MarketOrder(symbol, -shares);
....
StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice);
....
}
....
}
Situación 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' se asigna a sí misma. ZipEntryNameSubscriptionDataSourceReader.cs 50
Paul _factory dos veces se le asigna el mismo valor. Solo hay cuatro campos en la clase, por lo que lo más probable es que sea un error tipográfico. Versión corregida:
public ZipEntryNameSubscriptionDataSourceReader(....)
{
_config = config;
_date = date;
_isLiveMode = isLiveMode;
_factory = config.GetBaseDataInstance();
}
Conclusión
Hay muchos lugares donde puede cometer errores. Algunas las notamos y las corregimos de inmediato. Parte de ella se corrige para la revisión del código, y recomiendo asignar parte a herramientas especiales.
Además, si te gustó este formato, escribe sobre él. Haré otra cosa similar. ¡Gracias!

Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace de traducción: Nikolay Mironov. Hablando de errores en el código Lean de QuantConnect .