Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ToStringOut() for series #1150

Draft
wants to merge 14 commits into
base: v3
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/_common/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,27 @@ public enum MaType
WMA
}

/// <summary>
/// String output format type.
/// </summary>
public enum OutType
{
/// <summary>
/// Fixed width format.
/// </summary>
FixedWidth,

/// <summary>
/// Comma-separated values format.
/// </summary>
CSV,

/// <summary>
/// JSON format.
/// </summary>
JSON
}

/// <summary>
/// Period size, usually referring to the time period represented in a quote candle.
/// </summary>
Expand Down
96 changes: 96 additions & 0 deletions src/_common/Generics/StringOut.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Text;
using System.Text.Json;

namespace Skender.Stock.Indicators;

/// <summary>
/// Provides extension methods for converting ISeries lists to formatted strings.
/// </summary>
public static class StringOut
{
/// <summary>
/// Converts an IEnumerable of ISeries to a formatted string.
/// </summary>
/// <typeparam name="T">The type of the elements in the list.</typeparam>
/// <param name="list">The list of elements to convert.</param>
/// <param name="outType">The output format type.</param>
/// <param name="limitQty">The maximum number of elements to include in the output.</param>
/// <param name="startIndex">The starting index of the elements to include in the output.</param>
/// <param name="endIndex">The ending index of the elements to include in the output.</param>
/// <returns>A formatted string representing the list of elements.</returns>
public static string ToStringOut<T>(this IEnumerable<T> list, OutType outType = OutType.FixedWidth, int? limitQty = null, int? startIndex = null, int? endIndex = null) where T : ISeries
{
if (list == null || !list.Any())
{
return string.Empty;
}

var limitedList = list;

Check failure on line 28 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 28 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 28 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 28 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

if (limitQty.HasValue)
{
limitedList = limitedList.Take(limitQty.Value);
}

if (startIndex.HasValue && endIndex.HasValue)
{
limitedList = limitedList.Skip(startIndex.Value).Take(endIndex.Value - startIndex.Value + 1);
}

switch (outType)
{
case OutType.CSV:
return ToCsv(limitedList);
case OutType.JSON:
return ToJson(limitedList);
case OutType.FixedWidth:
default:
return ToFixedWidth(limitedList);
}
}

private static string ToCsv<T>(IEnumerable<T> list) where T : ISeries
{
var sb = new StringBuilder();

Check failure on line 54 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 54 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 54 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 54 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)
var properties = typeof(T).GetProperties();

Check failure on line 55 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 55 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 55 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 55 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

sb.AppendLine(string.Join(",", properties.Select(p => p.Name)));

foreach (var item in list)

Check failure on line 59 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 59 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 59 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 59 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)
{
sb.AppendLine(string.Join(",", properties.Select(p => p.GetValue(item))));
}

return sb.ToString();
}

private static string ToJson<T>(IEnumerable<T> list) where T : ISeries
{
return JsonSerializer.Serialize(list);
}

private static string ToFixedWidth<T>(IEnumerable<T> list) where T : ISeries
{
var sb = new StringBuilder();

Check failure on line 74 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 74 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 74 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 74 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)
var properties = typeof(T).GetProperties();

Check failure on line 75 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 75 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 75 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 75 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

var headers = properties.Select(p => p.Name).ToArray();

Check failure on line 77 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 77 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 77 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 77 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)
var values = list.Select(item => properties.Select(p => p.GetValue(item)?.ToString() ?? string.Empty).ToArray()).ToArray();

Check failure on line 78 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 78 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 78 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 78 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

var columnWidths = new int[headers.Length];

Check failure on line 80 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 80 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 80 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 80 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

for (int i = 0; i < headers.Length; i++)
{
columnWidths[i] = Math.Max(headers[i].Length, values.Max(row => row[i].Length));
}

sb.AppendLine(string.Join(" ", headers.Select((header, index) => header.PadRight(columnWidths[index]))));

foreach (var row in values)

Check failure on line 89 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / analyze

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 89 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / integration tests

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 89 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (ubuntu-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)

Check failure on line 89 in src/_common/Generics/StringOut.cs

View workflow job for this annotation

GitHub Actions / unit tests (macos-latest, 9.x)

Use explicit type instead of 'var' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008)
{
sb.AppendLine(string.Join(" ", row.Select((value, index) => value.PadRight(columnWidths[index]))));
}

return sb.ToString();
}
}
256 changes: 256 additions & 0 deletions tests/indicators/_common/Results/Result.Utilities.ToStringOut.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
namespace Tests.Common;

[TestClass]
public class ResultsToString : TestBase
{
[TestMethod]
public void ToStringFixedWidth()
{
string output = Quotes.ToMacd().ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Macd");
output.Should().Contain("Histogram");
output.Should().Contain("Signal");

string[] lines = output.Split('\n');
lines[0].Trim().Should().Be("Timestamp Macd Histogram Signal ");
lines[1].Trim().Should().Be("2017-01-03 0.0000 0.0000 0.0000 ");
}

[TestMethod]
public void ToStringCSV()
{
string output = Quotes.ToMacd().ToStringOut(OutType.CSV);
Console.WriteLine(output);

output.Should().Contain("Timestamp,Macd,Histogram,Signal");

string[] lines = output.Split('\n');
lines[0].Trim().Should().Be("Timestamp,Macd,Histogram,Signal");
lines[1].Trim().Should().Be("2017-01-03,0.0000,0.0000,0.0000");
}

[TestMethod]
public void ToStringJson()
{
string output = Quotes.ToMacd().ToStringOut(OutType.JSON);
Console.WriteLine(output);

output.Should().StartWith("[");
output.Should().EndWith("]");
}

[TestMethod]
public void ToStringWithLimitQty()
{
string output = Quotes.ToMacd().ToStringOut(OutType.FixedWidth, 4);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Macd");
output.Should().Contain("Histogram");
output.Should().Contain("Signal");

string[] lines = output.Split('\n');
lines.Length.Should().Be(5); // 1 header + 4 data rows
}

[TestMethod]
public void ToStringWithStartIndexAndEndIndex()
{
string output = Quotes.ToMacd().ToStringOut(OutType.FixedWidth, null, 2, 5);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Macd");
output.Should().Contain("Histogram");
output.Should().Contain("Signal");

string[] lines = output.Split('\n');
lines.Length.Should().Be(5); // 1 header + 4 data rows
}

[TestMethod]
public void ToStringOutOrderDateFirst()
{
string output = Quotes.ToMacd().ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

string[] lines = output.Split('\n');
string headerLine = lines[0];
string firstDataLine = lines[1];

headerLine.Should().StartWith("Timestamp");
firstDataLine.Should().StartWith("2017-01-03");
}

[TestMethod]
public void ToStringOutProperUseOfOutType()
{
string outputFixedWidth = Quotes.ToMacd().ToStringOut(OutType.FixedWidth);
string outputCSV = Quotes.ToMacd().ToStringOut(OutType.CSV);
string outputJSON = Quotes.ToMacd().ToStringOut(OutType.JSON);

outputFixedWidth.Should().Contain("Timestamp");
outputCSV.Should().Contain("Timestamp,Macd,Histogram,Signal");
outputJSON.Should().StartWith("[");
outputJSON.Should().EndWith("]");
}

[TestMethod]
public void ToStringOutDateFormatting()
{
string output = Quotes.ToMacd().ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

string[] lines = output.Split('\n');
string firstDataLine = lines[1];

firstDataLine.Should().StartWith("2017-01-03");
}

[TestMethod]
public void ToStringOutPerformance()
{
var watch = System.Diagnostics.Stopwatch.StartNew();
string output = Quotes.ToMacd().ToStringOut(OutType.FixedWidth);
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;

Console.WriteLine($"Elapsed time: {elapsedMs} ms");
elapsedMs.Should().BeLessThan(500); // Ensure performance is within acceptable limits
}

[TestMethod]
public void ToStringOutDifferentBaseListTypes()
{
string output = Quotes.ToCandle().ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Open");
output.Should().Contain("High");
output.Should().Contain("Low");
output.Should().Contain("Close");
output.Should().Contain("Volume");
output.Should().Contain("Size");
output.Should().Contain("Body");
output.Should().Contain("UpperWick");
output.Should().Contain("LowerWick");

string[] lines = output.Split('\n');
lines[0].Trim().Should().Be("Timestamp Open High Low Close Volume Size Body UpperWick LowerWick ");
lines[1].Trim().Should().Be("2017-01-03 212.71 213.35 211.52 212.57 96708880 1.83 0.14 0.64 0.18 ");
}

[TestMethod]
public void ToStringOutWithMultipleIndicators()
{
string output = Quotes.ToMacd().ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Macd");
output.Should().Contain("Histogram");
output.Should().Contain("Signal");

output = Quotes.ToAdx().ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Pdi");
output.Should().Contain("Mdi");
output.Should().Contain("Adx");

string[] lines = output.Split('\n');
lines[0].Trim().Should().Be("Timestamp Pdi Mdi Adx ");
lines[1].Trim().Should().Be("2017-01-03 0.0000 0.0000 0.0000 ");
}

[TestMethod]
public void ToStringOutWithUniqueHeadersAndValues()
{
string output = Quotes.ToMacd().ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Macd");
output.Should().Contain("Histogram");
output.Should().Contain("Signal");

output = Quotes.ToAdx().ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Pdi");
output.Should().Contain("Mdi");
output.Should().Contain("Adx");

string[] lines = output.Split('\n');
lines[0].Trim().Should().Be("Timestamp Pdi Mdi Adx ");
lines[1].Trim().Should().Be("2017-01-03 0.0000 0.0000 0.0000 ");
}

[TestMethod]
public void ToStringOutWithListQuote()
{
string output = Quotes.ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Open");
output.Should().Contain("High");
output.Should().Contain("Low");
output.Should().Contain("Close");
output.Should().Contain("Volume");

string[] lines = output.Split('\n');
lines[0].Trim().Should().Be("Timestamp Open High Low Close Volume ");
lines[1].Trim().Should().Be("2017-01-03 212.71 213.35 211.52 212.57 96708880 ");
}

[TestMethod]
public void ToStringOutWithIntradayQuotes()
{
var intradayQuotes = new List<Quote>
{
new Quote(new DateTime(2023, 1, 1, 9, 30, 0), 100, 105, 95, 102, 1000),
new Quote(new DateTime(2023, 1, 1, 9, 31, 0), 102, 106, 96, 103, 1100),
new Quote(new DateTime(2023, 1, 1, 9, 32, 0), 103, 107, 97, 104, 1200),
new Quote(new DateTime(2023, 1, 1, 9, 33, 0), 104, 108, 98, 105, 1300),
new Quote(new DateTime(2023, 1, 1, 9, 34, 0), 105, 109, 99, 106, 1400)
};

string output = intradayQuotes.ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

output.Should().Contain("Timestamp");
output.Should().Contain("Open");
output.Should().Contain("High");
output.Should().Contain("Low");
output.Should().Contain("Close");
output.Should().Contain("Volume");

string[] lines = output.Split('\n');
lines[0].Trim().Should().Be("Timestamp Open High Low Close Volume ");
lines[1].Trim().Should().Be("2023-01-01 09:30 100.00 105.00 95.00 102.00 1000 ");
}

[TestMethod]
public void ToStringOutWith20Rows()
{
var quotes = new List<Quote>();
for (int i = 0; i < 20; i++)
{
quotes.Add(new Quote(new DateTime(2023, 1, 1, 9, 30, 0).AddMinutes(i), 100 + i, 105 + i, 95 + i, 102 + i, 1000 + i));
}

string output = quotes.ToStringOut(OutType.FixedWidth);
Console.WriteLine(output);

string[] lines = output.Split('\n');
lines.Length.Should().Be(21); // 1 header + 20 data rows
}
}
Loading