diff --git a/src/GlobalUsings.cs b/src/GlobalUsings.cs new file mode 100644 index 000000000..6abfda8ed --- /dev/null +++ b/src/GlobalUsings.cs @@ -0,0 +1 @@ +global using System.Text.Json.Serialization; diff --git a/src/_common/Enums.cs b/src/_common/Enums.cs index 16bc408ac..be6b267ff 100644 --- a/src/_common/Enums.cs +++ b/src/_common/Enums.cs @@ -206,6 +206,27 @@ public enum MaType WMA } +/// +/// String output format type. +/// +public enum OutType +{ + /// + /// Fixed width format. + /// + FixedWidth, + + /// + /// Comma-separated values format. + /// + CSV, + + /// + /// JSON format. + /// + JSON +} + /// /// Period size, usually referring to the time period represented in a quote candle. /// diff --git a/src/_common/Generics/StringOut.List.cs b/src/_common/Generics/StringOut.List.cs new file mode 100644 index 000000000..8009d9112 --- /dev/null +++ b/src/_common/Generics/StringOut.List.cs @@ -0,0 +1,384 @@ +using System.Reflection; +using System.Text; + + +namespace Skender.Stock.Indicators; + +/// +/// Provides extension methods for converting ISeries lists to formatted strings. +/// +public static partial class StringOut +{ + private static readonly string[] IndexHeaderName = ["i"]; + + /// + /// Default formats for numeric and date properties. + /// 'Key' value is either property type or property name. + /// 'Value' is the format string for the property's ToString(). + /// + /// The last matching key is used, so when users provide + /// an 'args' dictionary, it will override these defaults. + /// + /// + private static readonly Dictionary defaultArgs = new() + { + { "Decimal" , "auto" }, + { "Double" , "N6" }, + { "Single" , "N6" }, + { "Int16" , "N0" }, + { "Int32" , "N0" }, + { "Int64" , "N0" }, + { "DateOnly", "yyyy-MM-dd" }, + { "DateTime", "yyyy-MM-dd HH:mm:ss" }, + { "DateTimeOffset", "yyyy-MM-dd HH:mm:ss" }, + { "Timestamp", "auto" } + }; + + /// + /// Converts a list of ISeries to a fixed-width formatted string and writes it to the console. + /// + /// The type of elements in the list, which must implement ISeries. + /// The list of ISeries elements to convert. + /// The fixed-width formatted string representation of the list. + public static string ToConsole( + this IReadOnlyList source) + where T : ISeries + { + string? output = source.ToStringOut(); + Console.WriteLine(output); + return output ?? string.Empty; + } + + /// + /// Converts a list of ISeries to a fixed-width formatted string. + /// + /// The type of elements in the list, which must implement ISeries. + /// The list of ISeries elements to convert. + /// Optional overrides for `ToString()` formatter. Key values can be type or property name. + /// A fixed-width formatted string representation of the list. + /// + /// Examples: + /// + /// Dictionary<string, string> args = new() + /// { + /// { "Decimal", "N2" }, + /// { "DateTime", "MM/dd/yyyy" }, + /// { "MyPropertyName", "C" } + /// }; + /// + /// + public static string ToStringOut( + this IEnumerable source, IDictionary? args = null) + where T : ISeries => source.ToList().ToStringOut(0, int.MaxValue, args); + + /// + /// Converts a list of ISeries to a fixed-width formatted string. + /// + /// The type of elements in the list, which must implement ISeries. + /// The list of ISeries elements to convert. + /// The maximum number of elements to include in the output. + /// Optional overrides for `ToString()` formatter. Key values can be type or property name. + /// A fixed-width formatted string representation of the list. + /// + /// Examples: + /// + /// Dictionary<string, string> args = new() + /// { + /// { "Decimal", "N2" }, + /// { "DateTime", "MM/dd/yyyy" }, + /// { "MyPropertyName", "C" } + /// }; + /// + /// + public static string ToStringOut( + this IEnumerable source, int limitQty, IDictionary? args = null) + where T : ISeries => source.ToList().ToStringOut(0, limitQty - 1, args); + + /// + /// Converts a list of ISeries to a fixed-width formatted string. + /// + /// The type of elements in the list, which must implement ISeries. + /// The list of ISeries elements to convert. + /// The starting index of the elements to include in the output. + /// The ending index of the elements to include in the output. + /// Optional overrides for `ToString()` formatter. Key values can be type or property name. + /// A fixed-width formatted string representation of the list. + /// + /// Examples: + /// + /// Dictionary<string, string> args = new() + /// { + /// { "Decimal", "N2" }, + /// { "DateTime", "MM/dd/yyyy" }, + /// { "MyPropertyName", "C" } + /// }; + /// + /// + public static string ToStringOut( + this IEnumerable source, int startIndex, int endIndex, IDictionary? args = null) + where T : ISeries => source.ToList().ToStringOut(startIndex, endIndex, args); + + /// + /// Converts a list of ISeries to a fixed-width formatted string. + /// + /// The type of elements in the list, which must implement ISeries. + /// The list of ISeries elements to convert. + /// Optional overrides for `ToString()` formatter. Key values can be type or property name. + /// A fixed-width formatted string representation of the list. + /// + /// Examples: + /// + /// Dictionary<string, string> args = new() + /// { + /// { "Decimal", "N2" }, + /// { "DateTime", "MM/dd/yyyy" }, + /// { "MyPropertyName", "C" } + /// }; + /// + /// + public static string ToStringOut( + this IReadOnlyList source, + IDictionary? args = null) + where T : ISeries => source.ToStringOut(0, int.MaxValue, args); + + /// + /// Converts a list of ISeries to a fixed-width formatted string. + /// + /// The type of elements in the list, which must implement ISeries. + /// The list of ISeries elements to convert. + /// The maximum number of elements to include in the output. + /// Optional overrides for `ToString()` formatter. Key values can be type or property name. + /// A fixed-width formatted string representation of the list. + /// + /// Examples: + /// + /// Dictionary<string, string> args = new() + /// { + /// { "Decimal", "N2" }, + /// { "DateTime", "MM/dd/yyyy" }, + /// { "MyPropertyName", "C" } + /// }; + /// + /// + public static string ToStringOut( + this IReadOnlyList source, + int limitQty, + IDictionary? args = null) + where T : ISeries => source.ToStringOut(0, limitQty - 1, args); + + /// + /// Converts a list of ISeries to a fixed-width formatted string. + /// + /// The type of elements in the list, which must implement ISeries. + /// The list of ISeries elements to convert. + /// The starting index of the elements to include in the output. + /// The ending index of the elements to include in the output. + /// Optional overrides for `ToString()` formatter. Key values can be type or property name. + /// A fixed-width formatted string representation of the list. + /// + /// Examples: + /// + /// Dictionary<string, string> args = new() + /// { + /// { "Decimal", "N2" }, + /// { "DateTime", "MM/dd/yyyy" }, + /// { "MyPropertyName", "C" } + /// }; + /// + /// + public static string ToStringOut( + this IReadOnlyList source, + int startIndex, + int endIndex, + IDictionary? args = null) + where T : ISeries + { + ArgumentNullException.ThrowIfNull(source); + + int endIndexReal = Math.Min(endIndex, source.Count - 1); + T[] sourceSubset = source.Skip(startIndex).Take(endIndexReal - startIndex + 1).ToArray(); + + Dictionary formatArgs = defaultArgs + .Concat(args ?? Enumerable.Empty>()) + .GroupBy(kvp => kvp.Key.ToUpperInvariant()) + .ToDictionary(g => g.Key, g => g.Last().Value); + + // Get properties of the object + PropertyInfo[] properties = GetStringOutProperties(typeof(T)); + + // Define header values + string[] headers = IndexHeaderName + .Concat(properties.Select(p => p.Name)) + .ToArray(); + + int columnCount = headers.Length; + + // Set formatting for each column + string[] formats = new string[columnCount]; + bool[] alignLeft = new bool[columnCount]; + int[] columnWidth = headers.Select(header => header.Length).ToArray(); + + formats[0] = "N0"; // index is always an integer + alignLeft[0] = false; // index is always right-aligned + + for (int i = 1; i < columnCount; i++) + { + PropertyInfo property = properties[i - 1]; + + // try by property type + formats[i] = formatArgs.TryGetValue( + ColloquialTypeName(property.PropertyType).ToUpperInvariant(), + out string? typeFormat) + ? typeFormat + : string.Empty; + + // try by property name (overrides type) + formats[i] = formatArgs.TryGetValue( + property.Name.ToUpperInvariant(), + out string? nameFormat) + ? nameFormat + : formats[i]; + + // handle auto-detect + if (formats[i] == "auto") + { + formats[i] = AutoFormat(property, sourceSubset); + } + + // set alignment + alignLeft[i] = !property.PropertyType.IsNumeric(); + } + + // Compile formatted values + string[][] dataRows = sourceSubset.Select((item, index) => { + string[] row = new string[columnCount]; + + row[0] = (index + startIndex).ToString(formats[0], culture); + + for (int i = 1; i < columnCount; i++) + { + object? value = properties[i - 1].GetValue(item); + row[i] = value is IFormattable formattable + ? formattable.ToString(formats[i], culture) ?? string.Empty + : value?.ToString() ?? string.Empty; + + columnWidth[i] = Math.Max(columnWidth[i], row[i].Length); + } + + return row; + }).ToArray(); + + columnWidth[0] = dataRows.Max(row => row[0].Length); + + // Compile formatted string + StringBuilder sb = new(); + + // Create header line with proper alignment + sb.AppendLine(string.Join(" ", + headers.Select((header, index) => alignLeft[index] + ? header.PadRight(columnWidth[index]) + : header.PadLeft(columnWidth[index]) + ))); + + // Create separator + sb.AppendLine(new string('-', columnWidth.Sum(w => w + 2) - 2)); + + // Create data lines with proper alignment + foreach (string[] row in dataRows) + { + sb.AppendLine(string.Join(" ", + row.Select((value, index) => alignLeft[index] + ? value.PadRight(columnWidth[index]) + : value.PadLeft(columnWidth[index]) + ))); + } + + return sb.ToString(); // includes a trailing newline + } + + /// + /// Determines the appropriate date precision or decimal places + /// based on the first 1,000 actual values. + /// + /// The type of elements in the list, which must implement ISeries. + /// The array of PropertyInfo objects representing the properties of the type. + /// The list of ISeries elements to analyze. + /// Format to be used in ToString() + private static string AutoFormat( + PropertyInfo property, + IReadOnlyList list) + where T : ISeries + { + Type propertyType = property.PropertyType; + + // auto-detect date format from precision + if (propertyType == typeof(DateOnly)) + { + return "yyyy-MM-dd"; + } + + else if (propertyType == typeof(DateTime) || propertyType == typeof(DateTimeOffset)) + { + List dateValues = list + .Take(1000) + .Select(item => ((DateTime)property.GetValue(item)!).ToString("o", culture)) + .ToList(); + + bool sameHour = dateValues.Select(d => d.Substring(11, 2)).Distinct().Count() == 1; + bool sameMinute = dateValues.Select(d => d.Substring(14, 2)).Distinct().Count() == 1; + bool sameSecond = dateValues.Select(d => d.Substring(17, 2)).Distinct().Count() == 1; + + return sameHour && sameMinute && sameSecond + ? "yyyy-MM-dd" + : sameSecond + ? "yyyy-MM-dd HH:mm" + : "yyyy-MM-dd HH:mm:ss"; + } + + // auto-detect decimal places + else if (propertyType == typeof(decimal)) + { + int decimalPlaces = list + .Take(1000) + .Select(item => ((decimal)property.GetValue(item)!).GetDecimalPlaces()) + .Max(); + + return $"N{decimalPlaces}"; + } + else + { + return string.Empty; + } + } + + /// + /// Returns the colloquial type name for a given type. + /// + /// The type to get the colloquial name for. + /// The colloquial type name. + public static string ColloquialTypeName(Type type) + { + if (type == null) + { + return string.Empty; + } + + // Handle nullable types + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + type = Nullable.GetUnderlyingType(type) ?? type; // Extract the underlying type + } + + // Return the type's C# alias if it exists, or the type's name otherwise + if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime)) + { + // Return the type's C# alias if it exists, or the type's name otherwise + return type.Name; + } + else + { + // Return the type's C# alias if it exists, or the type's name otherwise + return type.Name; + } + } +} diff --git a/src/_common/Generics/StringOut.Type.cs b/src/_common/Generics/StringOut.Type.cs new file mode 100644 index 000000000..a56c5247d --- /dev/null +++ b/src/_common/Generics/StringOut.Type.cs @@ -0,0 +1,237 @@ +using System.Globalization; +using System.Reflection; +using System.Text; +using System.Xml.Linq; + +namespace Skender.Stock.Indicators; + +/// +/// Provides extension methods for converting ISeries instances to formatted strings. +/// +public static partial class StringOut +{ + private static readonly CultureInfo culture = CultureInfo.InvariantCulture; + + /// + /// Writes the string representation of an ISeries instance to the console. + /// + /// The type of the ISeries instance. + /// The ISeries instance to write to the console. + /// The string representation of the ISeries instance. + public static string ToConsole(this T obj) where T : ISeries + { + string? output = obj.ToStringOut(); + Console.WriteLine(output); + return output ?? string.Empty; + } + + /// + /// Converts an ISeries instance to a formatted string. + /// + /// The type of the ISeries instance. + /// The ISeries instance to convert. + /// A formatted string representation of the ISeries instance. + public static string ToStringOut(this T obj) where T : ISeries + { + ArgumentNullException.ThrowIfNull(obj); + StringBuilder sb = new(); + + // Header names + string[] headers = ["Property", "Type", "Value", "Description"]; + + // Get properties of the object + PropertyInfo[] properties = GetStringOutProperties(typeof(T)); + + // Lists to hold column data + List names = new(properties.Length); + List types = new(properties.Length); + List values = new(properties.Length); + List descriptions = new(properties.Length); + + // Get descriptions from XML documentation + Dictionary descriptionDict + = GetPropertyDescriptionsFromXml(typeof(T)); + + // Populate the lists + foreach (PropertyInfo prop in properties) + { + string name = prop.Name; + string type = prop.PropertyType.Name; + object? value = prop.GetValue(obj); + + // add values to lists + names.Add(name); + types.Add(type); + + switch (value) + { + case DateTime dateTimeValue: + values.Add(dateTimeValue.Kind == DateTimeKind.Utc + ? dateTimeValue.ToString("u", culture) + : dateTimeValue.ToString("s", culture)); + break; + + case DateOnly dateOnlyValue: + values.Add(dateOnlyValue.ToString("yyyy-MM-dd", culture)); + break; + + case DateTimeOffset dateTimeOffsetValue: + values.Add(dateTimeOffsetValue.ToString("o", culture)); + break; + + case string stringValue: + // limit string size + if (stringValue.Length > 35) + { + stringValue = string.Concat(stringValue.AsSpan(0, 32), "..."); + } + + values.Add(stringValue); + break; + + default: + values.Add(value?.ToString() ?? string.Empty); + break; + } + + // get/add description from XML documentation + descriptionDict.TryGetValue(name, out string? description); + + description = description == null + ? string.Empty + : description.Length > 50 + ? string.Concat(description.AsSpan(0, 47), "...") + : description; + + descriptions.Add(description); + } + + // Calculate the maximum width for each column + int widthOfName = MaxWidth(headers[0], names); + int widthOfType = MaxWidth(headers[1], types); + int widthOfValue = MaxWidth(headers[2], values); + int widthOfDesc = MaxWidth(headers[3], descriptions); + + // Ensure at least 2 spaces between columns + string format = $"{{0,-{widthOfName}}} {{1,-{widthOfType}}} {{2,{widthOfValue}}} {{3}}"; + + // Build the header + sb.AppendLine(string.Format(culture, format, headers[0], headers[1], headers[2], headers[3])); + + // Build the separator line + int totalWidth = widthOfName + widthOfType + widthOfValue + Math.Min(widthOfDesc, 30) + 6; // +6 for spaces + sb.AppendLine(new string('-', totalWidth)); + + // Build each row + for (int i = 0; i < names.Count; i++) + { + string row = string.Format(culture, format, names[i], types[i], values[i], descriptions[i]); + sb.AppendLine(row); + } + + return sb.ToString().TrimEnd(); + } + + /// + /// Calculates the maximum width of a column based on the header and values. + /// + /// The header of the column. + /// The list of values in the column. + /// The maximum width of the column. + private static int MaxWidth(string header, List values) + { + int maxValue = values.Count != 0 ? values.Max(v => v.Length) : 0; + return Math.Max(header.Length, maxValue); + } + + /// + /// Retrieves property descriptions from the XML documentation file. + /// + /// The type whose property descriptions are to be retrieved. + /// A dictionary containing property names and their descriptions. + private static Dictionary GetPropertyDescriptionsFromXml(Type type) + { + Dictionary descriptions = []; + + // Get the assembly of the type + Assembly assembly = type.Assembly; + string? assemblyLocation = assembly.Location; + + // Assume the XML documentation file is in the same directory as the assembly + string xmlFilePath = Path.ChangeExtension(assemblyLocation, ".xml"); + + if (!File.Exists(xmlFilePath)) + { + // XML documentation file not found + return descriptions; + } + + // Load the XML documentation file + XDocument xdoc = XDocument.Load(xmlFilePath); + + // Build the prefix for property members + string memberPrefix = "P:" + type.FullName + "."; + + // Query all member elements + foreach (XElement memberElement in xdoc.Descendants("member")) + { + string? nameAttribute = memberElement.Attribute("name")?.Value; + + if (nameAttribute != null && nameAttribute.StartsWith(memberPrefix, StringComparison.Ordinal)) + { + string propName = nameAttribute[memberPrefix.Length..]; + + // Get the summary element + XElement? summaryElement = memberElement.Element("summary"); + descriptions[propName] = summaryElement?.ParseXmlElement() ?? string.Empty; + } + } + + return descriptions; + } + + /// + /// Ensures that the text content of an XML documentation properly + /// converts HTML refs like and ."/> + /// + /// to be cleaned. + /// The cleaned text content of the XML documentation. + private static string ParseXmlElement(this XElement? summaryElement) + { + if (summaryElement == null) + { + return string.Empty; + } + + // Handle elements + foreach (XNode node in summaryElement.DescendantNodes().ToList()) + { + if (node is XElement element && element.Name.LocalName == "see") + { + foreach (XAttribute attribute in element.Attributes().ToList()) + { + string word = attribute.Value.Split('.').Last(); + element.ReplaceWith($"'{new XText(word)}'"); + } + } + } + + // Return summary text without line breaks + return summaryElement.Value + .Replace("\n", " ", StringComparison.Ordinal) + .Replace("\r", " ", StringComparison.Ordinal) + .Trim(); + } + + /// + /// Retrieves the public instance properties of a type that are not marked with + /// or . + /// + /// The type whose properties are to be retrieved. + /// An array of objects representing the properties of the type. + private static PropertyInfo[] GetStringOutProperties(Type type) + => type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.GetCustomAttribute() == null + && p.GetCustomAttribute() == null) + .ToArray(); +} diff --git a/src/_common/Math/Numerical.cs b/src/_common/Math/Numerical.cs index f55dcf473..bbcde9944 100644 --- a/src/_common/Math/Numerical.cs +++ b/src/_common/Math/Numerical.cs @@ -1,5 +1,6 @@ namespace Skender.Stock.Indicators; +#pragma warning disable IDE0066 // Convert switch statement to expression #pragma warning disable IDE0072 // Missing cases in switch statement /// @@ -153,4 +154,40 @@ internal static int GetDecimalPlaces(this decimal n) return decimalPlaces; } + + /// + /// Determines if a type is a numeric non-date type. + /// + /// The data + /// True if numeric type. + internal static bool IsNumeric(this Type type) + { + + if (type == typeof(DateTime) || + type == typeof(DateTimeOffset) || + type == typeof(DateOnly)) + { + return false; + } + + Type realType = Nullable.GetUnderlyingType(type) ?? type; + + switch (Type.GetTypeCode(realType)) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + default: + return false; + } + } } diff --git a/src/_common/ObsoleteV3.cs b/src/_common/ObsoleteV3.cs index 93c7dd115..d94116f21 100644 --- a/src/_common/ObsoleteV3.cs +++ b/src/_common/ObsoleteV3.cs @@ -108,5 +108,7 @@ public interface IReusableResult : IReusable; public sealed class BasicData : IReusable { public DateTime Timestamp { get; set; } + + [JsonIgnore] public double Value { get; set; } } diff --git a/src/_common/Quotes/Quote.Models.cs b/src/_common/Quotes/Quote.Models.cs index 94b084aaf..ee5718a55 100644 --- a/src/_common/Quotes/Quote.Models.cs +++ b/src/_common/Quotes/Quote.Models.cs @@ -54,7 +54,7 @@ public interface IQuote : IReusable /// Built-in Quote type, representing an OHLCV aggregate price period. /// /// -/// Close date/time of the aggregate period +/// Close date/time of the aggregate /// /// /// Aggregate bar's first tick price @@ -84,6 +84,7 @@ decimal Volume ) : IQuote { /// + [JsonIgnore] public double Value => (double)Close; /// @@ -117,5 +118,6 @@ double Volume ) : IReusable { /// + [JsonIgnore] public double Value => Close; } diff --git a/src/_common/Reusable/IReusable.cs b/src/_common/Reusable/IReusable.cs index fdd04b2a7..4b36421e3 100644 --- a/src/_common/Reusable/IReusable.cs +++ b/src/_common/Reusable/IReusable.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace Skender.Stock.Indicators; /// @@ -9,5 +11,6 @@ public interface IReusable : ISeries /// /// Value that is passed to chained indicators. /// + [JsonIgnore] double Value { get; } } diff --git a/src/a-d/Adl/Adl.Models.cs b/src/a-d/Adl/Adl.Models.cs index c10375293..6f3a3b3f0 100644 --- a/src/a-d/Adl/Adl.Models.cs +++ b/src/a-d/Adl/Adl.Models.cs @@ -17,5 +17,6 @@ public record AdlResult ) : IReusable { /// + [JsonIgnore] public double Value => Adl; } diff --git a/src/a-d/Adx/Adx.Models.cs b/src/a-d/Adx/Adx.Models.cs index 9f65ac570..a29ca40dd 100644 --- a/src/a-d/Adx/Adx.Models.cs +++ b/src/a-d/Adx/Adx.Models.cs @@ -21,5 +21,6 @@ public record AdxResult ) : IReusable { /// + [JsonIgnore] public double Value => Adx.Null2NaN(); } diff --git a/src/a-d/Alma/Alma.Models.cs b/src/a-d/Alma/Alma.Models.cs index 29db95aec..611b72ea5 100644 --- a/src/a-d/Alma/Alma.Models.cs +++ b/src/a-d/Alma/Alma.Models.cs @@ -14,5 +14,6 @@ public record AlmaResult ) : IReusable { /// + [JsonIgnore] public double Value => Alma.Null2NaN(); } diff --git a/src/a-d/Aroon/Aroon.Models.cs b/src/a-d/Aroon/Aroon.Models.cs index fb80f80fb..a99fc9fbf 100644 --- a/src/a-d/Aroon/Aroon.Models.cs +++ b/src/a-d/Aroon/Aroon.Models.cs @@ -17,5 +17,6 @@ public record AroonResult ) : IReusable { /// + [JsonIgnore] public double Value => Oscillator.Null2NaN(); } diff --git a/src/a-d/Atr/Atr.Models.cs b/src/a-d/Atr/Atr.Models.cs index 7a0da44d0..e7281222f 100644 --- a/src/a-d/Atr/Atr.Models.cs +++ b/src/a-d/Atr/Atr.Models.cs @@ -17,5 +17,6 @@ public record AtrResult ) : IReusable { /// + [JsonIgnore] public double Value => Atrp.Null2NaN(); } diff --git a/src/a-d/Awesome/Awesome.Models.cs b/src/a-d/Awesome/Awesome.Models.cs index 2c9d37276..28dbb9ab6 100644 --- a/src/a-d/Awesome/Awesome.Models.cs +++ b/src/a-d/Awesome/Awesome.Models.cs @@ -15,5 +15,6 @@ public record AwesomeResult ) : IReusable { /// + [JsonIgnore] public double Value => Oscillator.Null2NaN(); } diff --git a/src/a-d/Beta/Beta.Models.cs b/src/a-d/Beta/Beta.Models.cs index 907d60a52..59f681ef1 100644 --- a/src/a-d/Beta/Beta.Models.cs +++ b/src/a-d/Beta/Beta.Models.cs @@ -24,6 +24,7 @@ public record BetaResult( ) : IReusable { /// + [JsonIgnore] public double Value => Beta.Null2NaN(); } diff --git a/src/a-d/BollingerBands/BollingerBands.Models.cs b/src/a-d/BollingerBands/BollingerBands.Models.cs index 63f93597a..4682f93cf 100644 --- a/src/a-d/BollingerBands/BollingerBands.Models.cs +++ b/src/a-d/BollingerBands/BollingerBands.Models.cs @@ -23,5 +23,6 @@ public record BollingerBandsResult ) : IReusable { /// + [JsonIgnore] public double Value => PercentB.Null2NaN(); } diff --git a/src/a-d/Bop/Bop.Models.cs b/src/a-d/Bop/Bop.Models.cs index e6eada31b..488fcdcc1 100644 --- a/src/a-d/Bop/Bop.Models.cs +++ b/src/a-d/Bop/Bop.Models.cs @@ -13,5 +13,6 @@ public record BopResult ) : IReusable { /// + [JsonIgnore] public double Value => Bop.Null2NaN(); } diff --git a/src/a-d/Cci/Cci.Models.cs b/src/a-d/Cci/Cci.Models.cs index a29070b1b..2ede77358 100644 --- a/src/a-d/Cci/Cci.Models.cs +++ b/src/a-d/Cci/Cci.Models.cs @@ -13,5 +13,6 @@ public record CciResult ) : IReusable { /// + [JsonIgnore] public double Value => Cci.Null2NaN(); } diff --git a/src/a-d/ChaikinOsc/ChaikinOsc.Models.cs b/src/a-d/ChaikinOsc/ChaikinOsc.Models.cs index 31e593dd7..a382addfa 100644 --- a/src/a-d/ChaikinOsc/ChaikinOsc.Models.cs +++ b/src/a-d/ChaikinOsc/ChaikinOsc.Models.cs @@ -19,5 +19,6 @@ public record ChaikinOscResult ) : IReusable { /// + [JsonIgnore] public double Value => Oscillator.Null2NaN(); } diff --git a/src/a-d/Chandelier/Chandelier.Models.cs b/src/a-d/Chandelier/Chandelier.Models.cs index ce2ae1688..2e13972ca 100644 --- a/src/a-d/Chandelier/Chandelier.Models.cs +++ b/src/a-d/Chandelier/Chandelier.Models.cs @@ -13,6 +13,7 @@ public record ChandelierResult ) : IReusable { /// + [JsonIgnore] public double Value => ChandelierExit.Null2NaN(); } diff --git a/src/a-d/Chop/Chop.Models.cs b/src/a-d/Chop/Chop.Models.cs index 4884ce187..82e44194d 100644 --- a/src/a-d/Chop/Chop.Models.cs +++ b/src/a-d/Chop/Chop.Models.cs @@ -13,5 +13,6 @@ public record ChopResult ) : IReusable { /// + [JsonIgnore] public double Value => Chop.Null2NaN(); } diff --git a/src/a-d/Cmf/Cmf.Models.cs b/src/a-d/Cmf/Cmf.Models.cs index 152044d23..c903b7158 100644 --- a/src/a-d/Cmf/Cmf.Models.cs +++ b/src/a-d/Cmf/Cmf.Models.cs @@ -17,5 +17,6 @@ public record CmfResult ) : IReusable { /// + [JsonIgnore] public double Value => Cmf.Null2NaN(); } diff --git a/src/a-d/Cmo/Cmo.Models.cs b/src/a-d/Cmo/Cmo.Models.cs index 10cdd33bd..f768d600e 100644 --- a/src/a-d/Cmo/Cmo.Models.cs +++ b/src/a-d/Cmo/Cmo.Models.cs @@ -13,5 +13,6 @@ public record CmoResult ) : IReusable { /// + [JsonIgnore] public double Value => Cmo.Null2NaN(); } diff --git a/src/a-d/ConnorsRsi/ConnorsRsi.Models.cs b/src/a-d/ConnorsRsi/ConnorsRsi.Models.cs index 6a4915acb..8f79aa69a 100644 --- a/src/a-d/ConnorsRsi/ConnorsRsi.Models.cs +++ b/src/a-d/ConnorsRsi/ConnorsRsi.Models.cs @@ -21,5 +21,6 @@ public record ConnorsRsiResult ) : IReusable { /// + [JsonIgnore] public double Value => ConnorsRsi.Null2NaN(); } diff --git a/src/a-d/Correlation/Correlation.Models.cs b/src/a-d/Correlation/Correlation.Models.cs index 3b9612bb8..2b292cd1f 100644 --- a/src/a-d/Correlation/Correlation.Models.cs +++ b/src/a-d/Correlation/Correlation.Models.cs @@ -21,5 +21,6 @@ public record CorrResult ) : IReusable { /// + [JsonIgnore] public double Value => Correlation.Null2NaN(); } diff --git a/src/a-d/Dema/Dema.Models.cs b/src/a-d/Dema/Dema.Models.cs index 84a85236a..a6cef1200 100644 --- a/src/a-d/Dema/Dema.Models.cs +++ b/src/a-d/Dema/Dema.Models.cs @@ -13,5 +13,6 @@ public record DemaResult ) : IReusable { /// + [JsonIgnore] public double Value => Dema.Null2NaN(); } diff --git a/src/a-d/Dpo/Dpo.Models.cs b/src/a-d/Dpo/Dpo.Models.cs index ee68569aa..f32890f49 100644 --- a/src/a-d/Dpo/Dpo.Models.cs +++ b/src/a-d/Dpo/Dpo.Models.cs @@ -15,5 +15,6 @@ public record DpoResult ) : IReusable { /// + [JsonIgnore] public double Value => Dpo.Null2NaN(); } diff --git a/src/a-d/Dynamic/Dynamic.Models.cs b/src/a-d/Dynamic/Dynamic.Models.cs index 62ad74196..1419316fd 100644 --- a/src/a-d/Dynamic/Dynamic.Models.cs +++ b/src/a-d/Dynamic/Dynamic.Models.cs @@ -13,5 +13,6 @@ public record DynamicResult ) : IReusable { /// + [JsonIgnore] public double Value => Dynamic.Null2NaN(); } diff --git a/src/e-k/ElderRay/ElderRay.Models.cs b/src/e-k/ElderRay/ElderRay.Models.cs index 6c1cf774f..e3a939e20 100644 --- a/src/e-k/ElderRay/ElderRay.Models.cs +++ b/src/e-k/ElderRay/ElderRay.Models.cs @@ -17,5 +17,6 @@ public record ElderRayResult ) : IReusable { /// + [JsonIgnore] public double Value => (BullPower + BearPower).Null2NaN(); } diff --git a/src/e-k/Ema/Ema.Models.cs b/src/e-k/Ema/Ema.Models.cs index 96f2f4029..288fcc772 100644 --- a/src/e-k/Ema/Ema.Models.cs +++ b/src/e-k/Ema/Ema.Models.cs @@ -13,5 +13,6 @@ public record EmaResult ) : IReusable { /// + [JsonIgnore] public double Value => Ema.Null2NaN(); } diff --git a/src/e-k/Epma/Epma.Models.cs b/src/e-k/Epma/Epma.Models.cs index b344aaeba..2ad873f6e 100644 --- a/src/e-k/Epma/Epma.Models.cs +++ b/src/e-k/Epma/Epma.Models.cs @@ -13,5 +13,6 @@ public record EpmaResult ) : IReusable { /// + [JsonIgnore] public double Value => Epma.Null2NaN(); } diff --git a/src/e-k/FisherTransform/FisherTransform.Models.cs b/src/e-k/FisherTransform/FisherTransform.Models.cs index 406506ebc..159833872 100644 --- a/src/e-k/FisherTransform/FisherTransform.Models.cs +++ b/src/e-k/FisherTransform/FisherTransform.Models.cs @@ -15,5 +15,6 @@ public record FisherTransformResult ) : IReusable { /// + [JsonIgnore] public double Value => Fisher.Null2NaN(); } diff --git a/src/e-k/ForceIndex/ForceIndex.Models.cs b/src/e-k/ForceIndex/ForceIndex.Models.cs index b26364cca..4ce3bd9fc 100644 --- a/src/e-k/ForceIndex/ForceIndex.Models.cs +++ b/src/e-k/ForceIndex/ForceIndex.Models.cs @@ -13,5 +13,6 @@ public record ForceIndexResult ) : IReusable { /// + [JsonIgnore] public double Value => ForceIndex.Null2NaN(); } diff --git a/src/e-k/Hma/Hma.Models.cs b/src/e-k/Hma/Hma.Models.cs index 700672d3c..b57486380 100644 --- a/src/e-k/Hma/Hma.Models.cs +++ b/src/e-k/Hma/Hma.Models.cs @@ -13,5 +13,6 @@ public record HmaResult ) : IReusable { /// + [JsonIgnore] public double Value => Hma.Null2NaN(); } diff --git a/src/e-k/HtTrendline/HtTrendline.Models.cs b/src/e-k/HtTrendline/HtTrendline.Models.cs index 0da6f2bc6..91a72888e 100644 --- a/src/e-k/HtTrendline/HtTrendline.Models.cs +++ b/src/e-k/HtTrendline/HtTrendline.Models.cs @@ -17,5 +17,6 @@ public record HtlResult ) : IReusable { /// + [JsonIgnore] public double Value => Trendline.Null2NaN(); } diff --git a/src/e-k/Hurst/Hurst.Models.cs b/src/e-k/Hurst/Hurst.Models.cs index d74b766f8..634340b4f 100644 --- a/src/e-k/Hurst/Hurst.Models.cs +++ b/src/e-k/Hurst/Hurst.Models.cs @@ -13,5 +13,6 @@ public record HurstResult ) : IReusable { /// + [JsonIgnore] public double Value => HurstExponent.Null2NaN(); } diff --git a/src/e-k/Kama/Kama.Models.cs b/src/e-k/Kama/Kama.Models.cs index ffba9b5f7..f9f1fbfe4 100644 --- a/src/e-k/Kama/Kama.Models.cs +++ b/src/e-k/Kama/Kama.Models.cs @@ -15,5 +15,6 @@ public record KamaResult ) : IReusable { /// + [JsonIgnore] public double Value => Kama.Null2NaN(); } diff --git a/src/e-k/Kvo/Kvo.Models.cs b/src/e-k/Kvo/Kvo.Models.cs index d4556f6f1..939252fa6 100644 --- a/src/e-k/Kvo/Kvo.Models.cs +++ b/src/e-k/Kvo/Kvo.Models.cs @@ -15,5 +15,6 @@ public record KvoResult ) : IReusable { /// + [JsonIgnore] public double Value => Oscillator.Null2NaN(); } diff --git a/src/m-r/Macd/Macd.Models.cs b/src/m-r/Macd/Macd.Models.cs index e0cf88d8d..860942479 100644 --- a/src/m-r/Macd/Macd.Models.cs +++ b/src/m-r/Macd/Macd.Models.cs @@ -24,5 +24,6 @@ public record MacdResult ) : IReusable { /// + [JsonIgnore] public double Value => Macd.Null2NaN(); } diff --git a/src/m-r/Mama/Mama.Models.cs b/src/m-r/Mama/Mama.Models.cs index 80a26e85f..53fe55d90 100644 --- a/src/m-r/Mama/Mama.Models.cs +++ b/src/m-r/Mama/Mama.Models.cs @@ -15,5 +15,6 @@ public record MamaResult ) : IReusable { /// + [JsonIgnore] public double Value => Mama.Null2NaN(); } diff --git a/src/m-r/Mfi/Mfi.Models.cs b/src/m-r/Mfi/Mfi.Models.cs index 6091a0850..18fe070a2 100644 --- a/src/m-r/Mfi/Mfi.Models.cs +++ b/src/m-r/Mfi/Mfi.Models.cs @@ -13,5 +13,6 @@ public record MfiResult ) : IReusable { /// + [JsonIgnore] public double Value => Mfi.Null2NaN(); } diff --git a/src/m-r/Obv/Obv.Models.cs b/src/m-r/Obv/Obv.Models.cs index 337574945..fa6c4c984 100644 --- a/src/m-r/Obv/Obv.Models.cs +++ b/src/m-r/Obv/Obv.Models.cs @@ -13,5 +13,6 @@ double Obv ) : IReusable { /// + [JsonIgnore] public double Value => Obv; } diff --git a/src/m-r/ParabolicSar/ParabolicSar.Models.cs b/src/m-r/ParabolicSar/ParabolicSar.Models.cs index 63407cdae..a330d8102 100644 --- a/src/m-r/ParabolicSar/ParabolicSar.Models.cs +++ b/src/m-r/ParabolicSar/ParabolicSar.Models.cs @@ -15,5 +15,6 @@ public record ParabolicSarResult ) : IReusable { /// + [JsonIgnore] public double Value => Sar.Null2NaN(); } diff --git a/src/m-r/Pmo/Pmo.Models.cs b/src/m-r/Pmo/Pmo.Models.cs index e5f248011..e93eb03d4 100644 --- a/src/m-r/Pmo/Pmo.Models.cs +++ b/src/m-r/Pmo/Pmo.Models.cs @@ -15,5 +15,6 @@ public record PmoResult ) : IReusable { /// + [JsonIgnore] public double Value => Pmo.Null2NaN(); } diff --git a/src/m-r/Prs/Prs.Models.cs b/src/m-r/Prs/Prs.Models.cs index a6aaa1fcf..fd7af538c 100644 --- a/src/m-r/Prs/Prs.Models.cs +++ b/src/m-r/Prs/Prs.Models.cs @@ -15,5 +15,6 @@ public record PrsResult ) : IReusable { /// + [JsonIgnore] public double Value => Prs.Null2NaN(); } diff --git a/src/m-r/Pvo/Pvo.Models.cs b/src/m-r/Pvo/Pvo.Models.cs index ed3bf7f3e..230bc9fcc 100644 --- a/src/m-r/Pvo/Pvo.Models.cs +++ b/src/m-r/Pvo/Pvo.Models.cs @@ -17,5 +17,6 @@ public record PvoResult ) : IReusable { /// + [JsonIgnore] public double Value => Pvo.Null2NaN(); } diff --git a/src/m-r/Roc/Roc.Models.cs b/src/m-r/Roc/Roc.Models.cs index ed913bc2b..882427dfb 100644 --- a/src/m-r/Roc/Roc.Models.cs +++ b/src/m-r/Roc/Roc.Models.cs @@ -15,5 +15,6 @@ public record RocResult ) : IReusable { /// + [JsonIgnore] public double Value => Roc.Null2NaN(); } diff --git a/src/m-r/RocWb/RocWb.Models.cs b/src/m-r/RocWb/RocWb.Models.cs index 6b61b169d..318aaae9b 100644 --- a/src/m-r/RocWb/RocWb.Models.cs +++ b/src/m-r/RocWb/RocWb.Models.cs @@ -19,5 +19,6 @@ public record RocWbResult ) : IReusable { /// + [JsonIgnore] public double Value => Roc.Null2NaN(); } diff --git a/src/m-r/Rsi/Rsi.Models.cs b/src/m-r/Rsi/Rsi.Models.cs index c3147245c..a9e3a5c9b 100644 --- a/src/m-r/Rsi/Rsi.Models.cs +++ b/src/m-r/Rsi/Rsi.Models.cs @@ -13,5 +13,6 @@ public record RsiResult ) : IReusable { /// + [JsonIgnore] public double Value => Rsi.Null2NaN(); } diff --git a/src/s-z/Slope/Slope.Models.cs b/src/s-z/Slope/Slope.Models.cs index 6f220837b..54b93da9e 100644 --- a/src/s-z/Slope/Slope.Models.cs +++ b/src/s-z/Slope/Slope.Models.cs @@ -21,5 +21,6 @@ public record SlopeResult ) : IReusable { /// + [JsonIgnore] public double Value => Slope.Null2NaN(); } diff --git a/src/s-z/Sma/Sma.Models.cs b/src/s-z/Sma/Sma.Models.cs index 43ecc2306..f760eb364 100644 --- a/src/s-z/Sma/Sma.Models.cs +++ b/src/s-z/Sma/Sma.Models.cs @@ -12,5 +12,6 @@ public record SmaResult( ) : IReusable { /// + [JsonIgnore] public double Value => Sma.Null2NaN(); } diff --git a/src/s-z/Smi/Smi.Models.cs b/src/s-z/Smi/Smi.Models.cs index d39772499..9ad9b6788 100644 --- a/src/s-z/Smi/Smi.Models.cs +++ b/src/s-z/Smi/Smi.Models.cs @@ -15,5 +15,6 @@ public record SmiResult ) : IReusable { /// + [JsonIgnore] public double Value => Smi.Null2NaN(); } diff --git a/src/s-z/Smma/Smma.Models.cs b/src/s-z/Smma/Smma.Models.cs index 1e6e558a5..16a7a68c5 100644 --- a/src/s-z/Smma/Smma.Models.cs +++ b/src/s-z/Smma/Smma.Models.cs @@ -13,5 +13,6 @@ public record SmmaResult ) : IReusable { /// + [JsonIgnore] public double Value => Smma.Null2NaN(); } diff --git a/src/s-z/Stc/Stc.Models.cs b/src/s-z/Stc/Stc.Models.cs index a330e29e2..75efc7b62 100644 --- a/src/s-z/Stc/Stc.Models.cs +++ b/src/s-z/Stc/Stc.Models.cs @@ -13,5 +13,6 @@ public record StcResult ) : IReusable { /// + [JsonIgnore] public double Value => Stc.Null2NaN(); } diff --git a/src/s-z/StdDev/StdDev.Models.cs b/src/s-z/StdDev/StdDev.Models.cs index 0354b1e41..450e8fedd 100644 --- a/src/s-z/StdDev/StdDev.Models.cs +++ b/src/s-z/StdDev/StdDev.Models.cs @@ -17,5 +17,6 @@ public record StdDevResult ) : IReusable { /// + [JsonIgnore] public double Value => StdDev.Null2NaN(); } diff --git a/src/s-z/Stoch/Stoch.Models.cs b/src/s-z/Stoch/Stoch.Models.cs index d3578a20b..a08bcc317 100644 --- a/src/s-z/Stoch/Stoch.Models.cs +++ b/src/s-z/Stoch/Stoch.Models.cs @@ -17,6 +17,7 @@ public record StochResult ) : IReusable { /// + [JsonIgnore] public double Value => Oscillator.Null2NaN(); // aliases diff --git a/src/s-z/StochRsi/StochRsi.Models.cs b/src/s-z/StochRsi/StochRsi.Models.cs index 56b14e965..7c86bfeda 100644 --- a/src/s-z/StochRsi/StochRsi.Models.cs +++ b/src/s-z/StochRsi/StochRsi.Models.cs @@ -15,5 +15,6 @@ public record StochRsiResult ) : IReusable { /// + [JsonIgnore] public double Value => StochRsi.Null2NaN(); } diff --git a/src/s-z/T3/T3.Models.cs b/src/s-z/T3/T3.Models.cs index 7859b91a9..3e4b13b86 100644 --- a/src/s-z/T3/T3.Models.cs +++ b/src/s-z/T3/T3.Models.cs @@ -13,5 +13,6 @@ public record T3Result ) : IReusable { /// + [JsonIgnore] public double Value => T3.Null2NaN(); } diff --git a/src/s-z/Tema/Tema.Models.cs b/src/s-z/Tema/Tema.Models.cs index d51821acb..348bcc3cf 100644 --- a/src/s-z/Tema/Tema.Models.cs +++ b/src/s-z/Tema/Tema.Models.cs @@ -13,5 +13,6 @@ public record TemaResult ) : IReusable { /// + [JsonIgnore] public double Value => Tema.Null2NaN(); } diff --git a/src/s-z/Tr/Tr.Models.cs b/src/s-z/Tr/Tr.Models.cs index 849450e72..41fd02c15 100644 --- a/src/s-z/Tr/Tr.Models.cs +++ b/src/s-z/Tr/Tr.Models.cs @@ -12,5 +12,6 @@ public record TrResult( ) : IReusable { /// + [JsonIgnore] public double Value => Tr.Null2NaN(); } diff --git a/src/s-z/Trix/Trix.Models.cs b/src/s-z/Trix/Trix.Models.cs index 1fae80466..0558baef6 100644 --- a/src/s-z/Trix/Trix.Models.cs +++ b/src/s-z/Trix/Trix.Models.cs @@ -15,5 +15,6 @@ public record TrixResult ) : IReusable { /// + [JsonIgnore] public double Value => Trix.Null2NaN(); } diff --git a/src/s-z/Tsi/Tsi.Models.cs b/src/s-z/Tsi/Tsi.Models.cs index be8e360b6..5b3600371 100644 --- a/src/s-z/Tsi/Tsi.Models.cs +++ b/src/s-z/Tsi/Tsi.Models.cs @@ -15,5 +15,6 @@ public record TsiResult ) : IReusable { /// + [JsonIgnore] public double Value => Tsi.Null2NaN(); } diff --git a/src/s-z/UlcerIndex/UlcerIndex.Models.cs b/src/s-z/UlcerIndex/UlcerIndex.Models.cs index 9f117918a..d4df2d384 100644 --- a/src/s-z/UlcerIndex/UlcerIndex.Models.cs +++ b/src/s-z/UlcerIndex/UlcerIndex.Models.cs @@ -13,6 +13,7 @@ public record UlcerIndexResult ) : IReusable { /// + [JsonIgnore] public double Value => UlcerIndex.Null2NaN(); /// diff --git a/src/s-z/Ultimate/Ultimate.Models.cs b/src/s-z/Ultimate/Ultimate.Models.cs index eedfc23bb..30d3dc340 100644 --- a/src/s-z/Ultimate/Ultimate.Models.cs +++ b/src/s-z/Ultimate/Ultimate.Models.cs @@ -13,5 +13,6 @@ public record UltimateResult ) : IReusable { /// + [JsonIgnore] public double Value => Ultimate.Null2NaN(); } diff --git a/src/s-z/VolatilityStop/VolatilityStop.Models.cs b/src/s-z/VolatilityStop/VolatilityStop.Models.cs index 36128a4d5..d1efc95e3 100644 --- a/src/s-z/VolatilityStop/VolatilityStop.Models.cs +++ b/src/s-z/VolatilityStop/VolatilityStop.Models.cs @@ -22,5 +22,6 @@ public record VolatilityStopResult ) : IReusable { /// + [JsonIgnore] public double Value => Sar.Null2NaN(); } diff --git a/src/s-z/Vwap/Vwap.Models.cs b/src/s-z/Vwap/Vwap.Models.cs index 4f304aa68..9db2e5bf3 100644 --- a/src/s-z/Vwap/Vwap.Models.cs +++ b/src/s-z/Vwap/Vwap.Models.cs @@ -13,5 +13,6 @@ public record VwapResult ) : IReusable { /// + [JsonIgnore] public double Value => Vwap.Null2NaN(); } diff --git a/src/s-z/Vwma/Vwma.Models.cs b/src/s-z/Vwma/Vwma.Models.cs index cde0a5cfc..71eac28da 100644 --- a/src/s-z/Vwma/Vwma.Models.cs +++ b/src/s-z/Vwma/Vwma.Models.cs @@ -13,5 +13,6 @@ public record VwmaResult ) : IReusable { /// + [JsonIgnore] public double Value => Vwma.Null2NaN(); } diff --git a/src/s-z/WilliamsR/WilliamsR.Models.cs b/src/s-z/WilliamsR/WilliamsR.Models.cs index 5c3c85a4e..e28f0c02a 100644 --- a/src/s-z/WilliamsR/WilliamsR.Models.cs +++ b/src/s-z/WilliamsR/WilliamsR.Models.cs @@ -13,5 +13,6 @@ public record WilliamsResult ) : IReusable { /// + [JsonIgnore] public double Value => WilliamsR.Null2NaN(); } diff --git a/src/s-z/Wma/Wma.Models.cs b/src/s-z/Wma/Wma.Models.cs index a5033851b..0082997bc 100644 --- a/src/s-z/Wma/Wma.Models.cs +++ b/src/s-z/Wma/Wma.Models.cs @@ -13,5 +13,6 @@ public record WmaResult ) : IReusable { /// + [JsonIgnore] public double Value => Wma.Null2NaN(); } diff --git a/src/s-z/ZigZag/ZigZag.Models.cs b/src/s-z/ZigZag/ZigZag.Models.cs index 8eee805b5..1a1b348ce 100644 --- a/src/s-z/ZigZag/ZigZag.Models.cs +++ b/src/s-z/ZigZag/ZigZag.Models.cs @@ -19,6 +19,7 @@ public record ZigZagResult ) : IReusable { /// + [JsonIgnore] public double Value => ZigZag.Null2NaN(); } diff --git a/tests/indicators/TestBase.cs b/tests/indicators/TestBase.cs index bb0d9c501..c2f573fcd 100644 --- a/tests/indicators/TestBase.cs +++ b/tests/indicators/TestBase.cs @@ -15,6 +15,7 @@ namespace Test.Data; internal static readonly CultureInfo invariantCulture = CultureInfo.InvariantCulture; internal static readonly IReadOnlyList Quotes = Data.GetDefault(); + internal static readonly IReadOnlyList Intraday = Data.GetIntraday(); internal static readonly IReadOnlyList OtherQuotes = Data.GetCompare(); internal static readonly IReadOnlyList BadQuotes = Data.GetBad(); internal static readonly IReadOnlyList BigQuotes = Data.GetTooBig(); diff --git a/tests/indicators/Tests.Indicators.csproj b/tests/indicators/Tests.Indicators.csproj index d7dbc7271..e0cd73900 100644 --- a/tests/indicators/Tests.Indicators.csproj +++ b/tests/indicators/Tests.Indicators.csproj @@ -8,7 +8,9 @@ disable true - + true + $(NoWarn);NU1507;CS1591 + true latest AllEnabledByDefault diff --git a/tests/indicators/_common/Generics/StringOut.Tests.cs b/tests/indicators/_common/Generics/StringOut.Tests.cs new file mode 100644 index 000000000..61364cd19 --- /dev/null +++ b/tests/indicators/_common/Generics/StringOut.Tests.cs @@ -0,0 +1,430 @@ +using System.Globalization; +using Test.Utilities; + +namespace Tests.Common; + +[TestClass] +public class StringOutputs : TestBase +{ + [TestMethod] + public void ToConsoleQuoteType() + { + DateTime timestamp = DateTime.TryParse( + "2017-02-03", CultureInfo.InvariantCulture, out DateTime d) ? d : default; + + Quote quote = new(timestamp, 216.1579m, 216.875m, 215.84m, 216.67m, 98765432832); + + string sut = quote.ToConsole(); + string val = quote.ToStringOut(); + + sut.Should().Be(val); + } + + [TestMethod] + public void ToConsoleQuoteList() + { + string sut = Quotes.ToConsole(); + string val = Quotes.ToStringOut(); + int length = sut.Split(Environment.NewLine).Length; + + sut.Should().Be(val); + length.Should().Be(505); // 2 headers + 502 data rows + 1 eof line + } + + [TestMethod] + public void ToStringOutQuoteType() + { + DateTime timestamp = DateTime.TryParse( + "2017-02-03", CultureInfo.InvariantCulture, out DateTime d) ? d : default; + + Quote quote = new(timestamp, 216.1m, 216.875m, 215.84m, 216.67m, 85273832); + + string sut = StringOut.ToStringOut(quote); + Console.WriteLine(sut); + + // note description has max of 30 "-" characters + string expected = """ + Property Type Value Description + ------------------------------------------------------------------------ + Timestamp DateTime 2017-02-03T00:00:00 Close date/time of the aggregate + Open Decimal 216.1 Aggregate bar's first tick price + High Decimal 216.875 Aggregate bar's highest tick price + Low Decimal 215.84 Aggregate bar's lowest tick price + Close Decimal 216.67 Aggregate bar's last tick price + Volume Decimal 85273832 Aggregate bar's tick volume + """.WithDefaultLineEndings(); + + sut.Should().Be(expected); + } + + [TestMethod] + public void ToStringOutMostTypes() + { + AllTypes allTypes = new(); + string sut = StringOut.ToStringOut(allTypes); + Console.WriteLine(sut); + + string expected = """ + Property Type Value Description + ----------------------------------------------------------------------------------------------------------- + Timestamp DateTime 2023-01-01 14:30:00Z A 'DateTime' type with time (UTC) + DateTimeProperty DateTime 2023-01-01T09:30:00 A 'DateTime' type with time + DateProperty DateOnly 2023-01-01 A 'DateOnly' type without time. + DateTimeOffsetProperty DateTimeOffset 2023-01-01T09:30:00.0000000-05:00 A 'DateTimeOffset' type with time and offset. + TimeSpanProperty TimeSpan 01:02:03 A 'TimeSpan' type + ByteProperty Byte 255 A 'Byte' type + ShortProperty Int16 32767 A 'Int16' short integer type + IntProperty Int32 -2147483648 A 'Int32' integer type + LongProperty Int64 9223372036854775803 'get' the 'Int64' long integer type + FloatProperty Single -125.25143 A get of 'Single' floating point type + DoubleProperty Double 5.251426433759354 A 'Double' floating point type + DecimalProperty Decimal 7922815.2514264337593543950335 A 'Decimal' type + CharProperty Char A A 'Char' type + BoolProperty Boolean True A 'Boolean' type + NoXmlProperty Boolean False + StringProperty String The lazy dog jumped over the sly... A 'String' type + """.WithDefaultLineEndings(); + + sut.Should().Be(expected); + } + + [TestMethod] + public void ToFixedWidthQuoteStandard() + { + /* based on what we know about the test data precision */ + + string output = Quotes.ToStringOut(limitQty: 12); + Console.WriteLine(output); + + string expected = """ + i Timestamp Open High Low Close Volume + ---------------------------------------------------------- + 0 2017-01-03 212.61 213.35 211.52 212.80 96,708,880 + 1 2017-01-04 213.16 214.22 213.15 214.06 83,348,752 + 2 2017-01-05 213.77 214.06 213.02 213.89 82,961,968 + 3 2017-01-06 214.02 215.17 213.42 214.66 75,744,152 + 4 2017-01-09 214.38 214.53 213.91 213.95 49,684,316 + 5 2017-01-10 213.97 214.89 213.52 213.95 67,500,792 + 6 2017-01-11 213.86 214.55 213.13 214.55 79,014,928 + 7 2017-01-12 213.99 214.22 212.53 214.02 76,329,760 + 8 2017-01-13 214.21 214.84 214.17 214.51 66,385,084 + 9 2017-01-17 213.81 214.25 213.33 213.75 64,821,664 + 10 2017-01-18 214.02 214.27 213.42 214.22 57,997,156 + 11 2017-01-19 214.31 214.46 212.96 213.43 70,503,512 + + """.WithDefaultLineEndings(); + + output.Should().Be(expected); + } + + [TestMethod] + public void ToFixedWidthQuoteWithArgs() + { + Dictionary args = new() + { + { "decimal", "N4" }, + { "Close", "N3" }, + { "Volume", "N0" } + }; + + string output = Quotes.Take(12).ToStringOut(args); + Console.WriteLine(output); + + string expected = """ + i Timestamp Open High Low Close Volume + ----------------------------------------------------------------- + 0 2017-01-03 212.6100 213.3500 211.5200 212.800 96,708,880 + 1 2017-01-04 213.1600 214.2200 213.1500 214.060 83,348,752 + 2 2017-01-05 213.7700 214.0600 213.0200 213.890 82,961,968 + 3 2017-01-06 214.0200 215.1700 213.4200 214.660 75,744,152 + 4 2017-01-09 214.3800 214.5300 213.9100 213.950 49,684,316 + 5 2017-01-10 213.9700 214.8900 213.5200 213.950 67,500,792 + 6 2017-01-11 213.8600 214.5500 213.1300 214.550 79,014,928 + 7 2017-01-12 213.9900 214.2200 212.5300 214.020 76,329,760 + 8 2017-01-13 214.2100 214.8400 214.1700 214.510 66,385,084 + 9 2017-01-17 213.8100 214.2500 213.3300 213.750 64,821,664 + 10 2017-01-18 214.0200 214.2700 213.4200 214.220 57,997,156 + 11 2017-01-19 214.3100 214.4600 212.9600 213.430 70,503,512 + + """.WithDefaultLineEndings(); + + output.Should().Be(expected); + } + + [TestMethod] + public void ToFixedWidthQuoteIntraday() + { + string output = Intraday.ToStringOut(limitQty: 12); + Console.WriteLine(output); + + string expected = """ + i Timestamp Open High Low Close Volume + ---------------------------------------------------------------- + 0 2020-12-15 09:30 367.400 367.620 367.360 367.46 407,870 + 1 2020-12-15 09:31 367.480 367.480 367.190 367.19 173,406 + 2 2020-12-15 09:32 367.190 367.400 367.020 367.35 149,240 + 3 2020-12-15 09:33 367.345 367.640 367.345 367.59 197,941 + 4 2020-12-15 09:34 367.590 367.610 367.320 367.43 147,919 + 5 2020-12-15 09:35 367.430 367.650 367.260 367.34 170,552 + 6 2020-12-15 09:36 367.350 367.560 367.150 367.53 200,528 + 7 2020-12-15 09:37 367.535 367.720 367.340 367.47 117,417 + 8 2020-12-15 09:38 367.480 367.480 367.190 367.42 127,936 + 9 2020-12-15 09:39 367.440 367.600 367.300 367.57 150,339 + 10 2020-12-15 09:40 367.580 367.775 367.560 367.61 136,414 + 11 2020-12-15 09:41 367.610 367.640 367.450 367.60 98,185 + + """.WithDefaultLineEndings(); + + output.Should().Be(expected); + } + + [TestMethod] + public void ToFixedWidthQuoteMinutes() + { + List quotes = []; + for (int i = 0; i < 20; i++) + { + DateTime timestamp = new DateTime(2023, 1, 1, 9, 30, 0).AddMinutes(i); + quotes.Add(new Quote(timestamp, 100 + i, 105 + i, 95 + i, 102 + i, 1000 + i)); + } + + string expected = """ + i Timestamp Open High Low Close Volume + ---------------------------------------------------- + 0 2023-01-01 09:30 100 105 95 102 1,000 + 1 2023-01-01 09:31 101 106 96 103 1,001 + 2 2023-01-01 09:32 102 107 97 104 1,002 + 3 2023-01-01 09:33 103 108 98 105 1,003 + 4 2023-01-01 09:34 104 109 99 106 1,004 + 5 2023-01-01 09:35 105 110 100 107 1,005 + 6 2023-01-01 09:36 106 111 101 108 1,006 + 7 2023-01-01 09:37 107 112 102 109 1,007 + 8 2023-01-01 09:38 108 113 103 110 1,008 + 9 2023-01-01 09:39 109 114 104 111 1,009 + 10 2023-01-01 09:40 110 115 105 112 1,010 + 11 2023-01-01 09:41 111 116 106 113 1,011 + 12 2023-01-01 09:42 112 117 107 114 1,012 + 13 2023-01-01 09:43 113 118 108 115 1,013 + 14 2023-01-01 09:44 114 119 109 116 1,014 + 15 2023-01-01 09:45 115 120 110 117 1,015 + 16 2023-01-01 09:46 116 121 111 118 1,016 + 17 2023-01-01 09:47 117 122 112 119 1,017 + 18 2023-01-01 09:48 118 123 113 120 1,018 + 19 2023-01-01 09:49 119 124 114 121 1,019 + + """.WithDefaultLineEndings(); + + string output = quotes.ToStringOut(); + Console.WriteLine(output); + + string[] lines = output.Split(Environment.NewLine); + lines.Length.Should().Be(23); // 2 headers + 20 data rows + 1 eof line + + output.Should().Be(expected); + } + + [TestMethod] + public void ToFixedWidthQuoteSeconds() + { + List quotes = []; + for (int i = 0; i < 20; i++) + { + DateTime timestamp = new DateTime(2023, 1, 1, 9, 30, 0).AddSeconds(i); + quotes.Add(new Quote(timestamp, 100 + i, 105 + i, 95 + i, 102 + i, 1000 + i)); + } + + string expected = """ + i Timestamp Open High Low Close Volume + ------------------------------------------------------- + 0 2023-01-01 09:30:00 100 105 95 102 1,000 + 1 2023-01-01 09:30:01 101 106 96 103 1,001 + 2 2023-01-01 09:30:02 102 107 97 104 1,002 + 3 2023-01-01 09:30:03 103 108 98 105 1,003 + 4 2023-01-01 09:30:04 104 109 99 106 1,004 + 5 2023-01-01 09:30:05 105 110 100 107 1,005 + 6 2023-01-01 09:30:06 106 111 101 108 1,006 + 7 2023-01-01 09:30:07 107 112 102 109 1,007 + 8 2023-01-01 09:30:08 108 113 103 110 1,008 + 9 2023-01-01 09:30:09 109 114 104 111 1,009 + 10 2023-01-01 09:30:10 110 115 105 112 1,010 + 11 2023-01-01 09:30:11 111 116 106 113 1,011 + 12 2023-01-01 09:30:12 112 117 107 114 1,012 + 13 2023-01-01 09:30:13 113 118 108 115 1,013 + 14 2023-01-01 09:30:14 114 119 109 116 1,014 + 15 2023-01-01 09:30:15 115 120 110 117 1,015 + 16 2023-01-01 09:30:16 116 121 111 118 1,016 + 17 2023-01-01 09:30:17 117 122 112 119 1,017 + 18 2023-01-01 09:30:18 118 123 113 120 1,018 + 19 2023-01-01 09:30:19 119 124 114 121 1,019 + + """.WithDefaultLineEndings(); + + string output = quotes.ToStringOut(); + Console.WriteLine(output); + + string[] lines = output.Split(Environment.NewLine); + lines.Length.Should().Be(23); // 2 headers + 20 data rows + 1 eof line + + output.Should().Be(expected); + } + + [TestMethod] + public void ToFixedWidthResultEma() + { + IReadOnlyList ema = Quotes.ToEma(14); + string output = ema.ToStringOut(startIndex: ema.Count - 21, endIndex: ema.Count - 1); + Console.WriteLine(output); + + // TODO: fix after adding index range + + string expected = """ + i Timestamp Ema + --------------------------- + 481 2018-11-29 264.114847 + 482 2018-11-30 264.760868 + 483 2018-12-03 265.795419 + 484 2018-12-04 265.514696 + 485 2018-12-06 265.218070 + 486 2018-12-07 264.144994 + 487 2018-12-10 263.280328 + 488 2018-12-11 262.538951 + 489 2018-12-12 262.068424 + 490 2018-12-13 261.649968 + 491 2018-12-14 260.649972 + 492 2018-12-17 259.117976 + 493 2018-12-18 257.754246 + 494 2018-12-19 256.075013 + 495 2018-12-20 254.087678 + 496 2018-12-21 251.706654 + 497 2018-12-24 248.811100 + 498 2018-12-26 247.850954 + 499 2018-12-27 247.265493 + 500 2018-12-28 246.716761 + 501 2018-12-31 246.525193 + + """.WithDefaultLineEndings(); + + output.Should().Be(expected); + } + + [TestMethod] + public void ToFixedWidthResultHtTrendline() + { + string output = Quotes.ToHtTrendline().ToStringOut(startIndex: 90, endIndex: 110); + Console.WriteLine(output); + + // TODO: fix after adding index range + + string expected = """ + i Timestamp DcPeriods Trendline SmoothPrice + --------------------------------------------------- + 90 2017-05-12 18 225.587904 226.912000 + 91 2017-05-15 19 225.755992 227.174500 + 92 2017-05-16 19 225.969113 227.481500 + 93 2017-05-17 19 226.155297 226.608000 + 94 2017-05-18 20 226.224826 225.659000 + 95 2017-05-19 21 226.246929 225.548000 + 96 2017-05-22 22 226.251725 226.017000 + 97 2017-05-23 22 226.340184 226.802000 + 98 2017-05-24 22 226.487975 227.505000 + 99 2017-05-25 22 226.646455 228.305000 + 100 2017-05-26 23 226.790405 228.846000 + 101 2017-05-30 24 226.905861 229.084500 + 102 2017-05-31 25 226.999587 229.089000 + 103 2017-06-01 26 227.098513 229.479000 + 104 2017-06-02 26 227.227763 230.233000 + 105 2017-06-05 25 227.413835 230.913000 + 106 2017-06-06 24 227.634324 231.168500 + 107 2017-06-07 23 227.889454 231.138500 + 108 2017-06-08 22 228.143057 231.170500 + 109 2017-06-09 21 228.386085 231.095000 + 110 2017-06-12 21 228.603337 230.852000 + + """.WithDefaultLineEndings(); + + output.Should().Be(expected); + } +} + +/// +/// A test class implementing containing properties of various data types. +/// +public class AllTypes : ISeries +{ + /// + /// A type with time (UTC) + /// + public DateTime Timestamp { get; } = new DateTime(2023, 1, 1, 14, 30, 0, DateTimeKind.Utc); + + /// + /// A type with time + /// + public DateTime DateTimeProperty { get; } = new DateTime(2023, 1, 1, 9, 30, 0); + + /// + /// A type without time. + /// + public DateOnly DateProperty { get; } = new DateOnly(2023, 1, 1); + + /// + /// A type with time and offset. + /// + public DateTimeOffset DateTimeOffsetProperty { get; } = new DateTimeOffset(2023, 1, 1, 9, 30, 0, TimeSpan.FromHours(-5)); + + /// + /// A type + /// + public TimeSpan TimeSpanProperty { get; } = new TimeSpan(1, 2, 3); + + /// + /// A type + /// + public byte ByteProperty { get; } = 255; + + /// + /// A short integer type + /// + public short ShortProperty { get; } = 32767; + + /// + /// A integer type + /// + public int IntProperty { get; } = -2147483648; + + /// + /// the long integer type + /// + public long LongProperty { get; } = 9223372036854775803L; + + /// + /// A get of floating point type + /// + public float FloatProperty { get; } = -125.25143f; + + /// + /// A floating point type + /// + public double DoubleProperty { get; } = 5.251426433759354d; + + /// + /// A type + /// + public decimal DecimalProperty { get; } = 7922815.2514264337593543950335m; + + /// + /// A type + /// + public char CharProperty { get; } = 'A'; + + /// + /// A type + /// + public bool BoolProperty { get; } = true; + + public bool NoXmlProperty { get; } // false + + /// + /// A type + /// + public string StringProperty { get; } = "The lazy dog jumped over the sly brown fox."; +} diff --git a/tests/indicators/_testdata/TestData.Getter.cs b/tests/indicators/_testdata/TestData.Getter.cs index 69d763678..5883c90a2 100644 --- a/tests/indicators/_testdata/TestData.Getter.cs +++ b/tests/indicators/_testdata/TestData.Getter.cs @@ -14,8 +14,14 @@ internal static IReadOnlyList GetDefault(int days = 502) .ToSortedList(); // RANDOM: gaussian brownaian motion - internal static IReadOnlyList GetRandom(int days = 502) - => new RandomGbm(bars: days); + internal static IReadOnlyList GetRandom( + int bars = 502, + PeriodSize periodSize = PeriodSize.OneMinute, + bool includeWeekends = true) + => new RandomGbm( + bars: bars, + periodSize: periodSize, + includeWeekends: includeWeekends); // sorted by filename diff --git a/tests/indicators/_testdata/TestData.Random.cs b/tests/indicators/_testdata/TestData.Random.cs index 7a8d1cb5f..6cf6bd92c 100644 --- a/tests/indicators/_testdata/TestData.Random.cs +++ b/tests/indicators/_testdata/TestData.Random.cs @@ -3,44 +3,85 @@ namespace Test.Data; /// /// Geometric Brownian Motion (GMB) is a random simulator of market movement. /// GBM can be used for testing indicators, validation and Monte Carlo simulations of strategies. -/// -/// -/// Sample usage: -/// -/// RandomGbm data = new(); // generates 1 year (252) list of bars -/// RandomGbm data = new(Bars: 1000); // generates 1,000 bars -/// RandomGbm data = new(Bars: 252, Volatility: 0.05, Drift: 0.0005, Seed: 100.0) -/// -/// Parameters: -/// -/// Bars: number of bars (quotes) requested -/// Volatility: how dymamic/volatile the series should be; default is 1 -/// Drift: incremental drift due to annual interest rate; default is 5% -/// Seed: starting value of the random series; should not be 0. -/// - +/// internal class RandomGbm : List { private readonly double _volatility; private readonly double _drift; private double _seed; + private static readonly Random _random = new((int)DateTime.UtcNow.Ticks); + /// + /// Initializes a new instance of the class. + /// + /// Sample usage: + /// + /// RandomGbm data = new(); // generates 1 year (252) list of bars + /// RandomGbm data = new(Bars: 1000); // generates 1,000 bars + /// RandomGbm data = new(Bars: 252, Volatility: 0.05, Drift: 0.0005, Seed: 100.0) + /// + /// + /// Number of bars (quotes) requested. + /// How dynamic/volatile the series should be; default is 1. + /// Incremental drift due to annual interest rate; default is 5%. + /// Starting value of the random series; should not be 0. + /// The period size for the quotes. + /// Whether to include weekends in the generated data. + /// Thrown when an invalid argument is provided. public RandomGbm( int bars = 250, double volatility = 1.0, double drift = 0.01, - double seed = 1000.0) + double seed = 1000.0, + PeriodSize periodSize = PeriodSize.OneMinute, + bool includeWeekends = true) { + // validation + if (bars <= 0) + { + throw new ArgumentException("Number of bars must be greater than zero.", nameof(bars)); + } + + if (volatility <= 0) + { + throw new ArgumentException("Volatility must be greater than zero.", nameof(volatility)); + } + + if (seed <= 0) + { + throw new ArgumentException("Seed must be greater than zero.", nameof(seed)); + } + + TimeSpan frequency = periodSize.ToTimeSpan(); + + if (!includeWeekends && (frequency < TimeSpan.FromHours(1) || frequency >= TimeSpan.FromDays(7))) + { + throw new ArgumentException("Weekends can only be excluded for period sizes between OneHour and OneWeek.", nameof(includeWeekends)); + } + _seed = seed; _volatility = volatility * 0.01; _drift = drift * 0.001; - for (int i = 0; i < bars; i++) + + DateTime date = DateTime.Today.Add(frequency * -bars); + int generatedBars = 0; + + while (generatedBars < bars) { - DateTime date = DateTime.Today.AddMinutes(i - bars); - Add(date); + if (includeWeekends || frequency < TimeSpan.FromHours(1) || frequency >= TimeSpan.FromDays(7) || (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday)) + { + Add(date); + generatedBars++; + } + + date = date.Add(frequency); } } + /// + /// Adds a new quote to the list. + /// + /// The timestamp of the quote. public void Add(DateTime timestamp) { double open = Price(_seed, _volatility * _volatility, _drift); @@ -54,7 +95,7 @@ public void Add(DateTime timestamp) double low = Price(_seed, _volatility * 0.5, 0); low = low > ocMin ? (2 * ocMin) - low : low; - double volume = Price(_seed * 10, _volatility * 2, drift: 0); + double volume = Price(_seed * 1000, _volatility * 2, drift: 0); Quote quote = new( Timestamp: timestamp, @@ -68,11 +109,17 @@ public void Add(DateTime timestamp) _seed = close; } + /// + /// Generates a random price based on the seed, volatility, and drift. + /// + /// The seed value. + /// The volatility value. + /// The drift value. + /// A random price. private static double Price(double seed, double volatility, double drift) { - Random rnd = new((int)DateTime.UtcNow.Ticks); - double u1 = 1.0 - rnd.NextDouble(); - double u2 = 1.0 - rnd.NextDouble(); + double u1 = 1.0 - _random.NextDouble(); + double u2 = 1.0 - _random.NextDouble(); double z = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); return seed * Math.Exp(drift - (volatility * volatility * 0.5) + (volatility * z)); } diff --git a/tests/indicators/_testdata/TestData.Utilities.cs b/tests/indicators/_testdata/TestData.Utilities.cs new file mode 100644 index 000000000..39b3638f4 --- /dev/null +++ b/tests/indicators/_testdata/TestData.Utilities.cs @@ -0,0 +1,9 @@ +namespace Test.Utilities; + +internal static class StringUtilities +{ + internal static string WithDefaultLineEndings(this string input) + => input + .Replace("\r\n", "\n", StringComparison.Ordinal) + .Replace("\n", Environment.NewLine, StringComparison.Ordinal); +} diff --git a/tests/performance/Perf.Utility.cs b/tests/performance/Perf.Utility.cs index eca434507..e1b2b030a 100644 --- a/tests/performance/Perf.Utility.cs +++ b/tests/performance/Perf.Utility.cs @@ -5,27 +5,34 @@ namespace Performance; [ShortRunJob] public class Utility { - private static readonly IReadOnlyList q = Data.GetDefault(); - private static readonly IReadOnlyList i = Data.GetIntraday(); + private static readonly IReadOnlyList quotes = Data.GetDefault(); + private static readonly IReadOnlyList intraday = Data.GetIntraday(); + private static readonly Quote quote = quotes[0]; [Benchmark] - public object ToSortedList() => q.ToSortedList(); + public object ToSortedList() => quotes.ToSortedList(); [Benchmark] - public object ToListQuoteD() => q.ToQuoteDList(); + public object ToListQuoteD() => quotes.ToQuoteDList(); [Benchmark] - public object ToReusableClose() => q.ToReusable(CandlePart.Close); + public object ToReusableClose() => quotes.ToReusable(CandlePart.Close); [Benchmark] - public object ToReusableOhlc4() => q.ToReusable(CandlePart.OHLC4); + public object ToReusableOhlc4() => quotes.ToReusable(CandlePart.OHLC4); [Benchmark] - public object ToCandleResults() => q.ToCandles(); + public object ToCandleResults() => quotes.ToCandles(); [Benchmark] - public object Validate() => q.Validate(); + public object ToStringOutType() => quote.ToStringOut(); [Benchmark] - public object Aggregate() => i.Aggregate(PeriodSize.FifteenMinutes); + public object ToStringOutList() => quotes.ToStringOut(); + + [Benchmark] + public object Validate() => quotes.Validate(); + + [Benchmark] + public object Aggregate() => intraday.Aggregate(PeriodSize.FifteenMinutes); }