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