SoftTraderBackend/DatabaseHandler/HistoricalRequestParser.cs
2026-03-20 10:42:48 -04:00

96 lines
2.7 KiB
C#

using Backend.MarketDataRequest;
using System.Globalization;
namespace Backend.DatabaseHandler
{
public static class HistoricalRequestParser
{
public static HistoricalRequestWindow BuildWindow(
string endDateTimeIsoUtc,
string duration,
HistoricalBarSize barSize)
{
DateTime endUtc = ParseRequestEndDateTimeUtc(endDateTimeIsoUtc);
TimeSpan durationSpan = ParseDuration(duration);
DateTime startUtc = endUtc - durationSpan;
int expectedBarsMin = EstimateExpectedBars(durationSpan, barSize);
return new HistoricalRequestWindow
{
StartUtc = startUtc,
EndUtc = endUtc,
ExpectedBarCountMin = expectedBarsMin
};
}
public static DateTime ParseRequestEndDateTimeUtc(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
throw new FormatException("endDateTime is empty.");
raw = raw.Trim();
Console.WriteLine(raw);
if (!DateTimeOffset.TryParseExact(
raw,
new[]
{
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.FFF'Z'",
"O"
},
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
out var dto))
{
throw new FormatException(
$"endDateTime must be ISO 8601 UTC, e.g. 2026-03-17T20:00:00Z. Received: '{raw}'");
}
return dto.UtcDateTime;
}
private static TimeSpan ParseDuration(string duration)
{
if (string.IsNullOrWhiteSpace(duration))
throw new ArgumentException("Duration is empty.");
string normalized = duration.Trim().Replace("+", " ");
var parts = normalized.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
throw new ArgumentException($"Invalid duration format: '{duration}'");
int value = int.Parse(parts[0], CultureInfo.InvariantCulture);
string unit = parts[1].ToUpperInvariant();
return unit switch
{
"S" => TimeSpan.FromSeconds(value),
"D" => TimeSpan.FromDays(value),
"W" => TimeSpan.FromDays(value * 7),
"M" => TimeSpan.FromDays(value * 30),
"Y" => TimeSpan.FromDays(value * 365),
_ => throw new ArgumentException($"Unsupported duration unit: '{unit}'")
};
}
private static int EstimateExpectedBars(TimeSpan duration, HistoricalBarSize barSize)
{
double secondsPerBar = barSize switch
{
HistoricalBarSize.OneMin => 60,
HistoricalBarSize.FiveMins => 300,
HistoricalBarSize.FifteenMins => 900,
HistoricalBarSize.OneHour => 3600,
HistoricalBarSize.OneDay => 86400,
HistoricalBarSize.FiveDays => 432000,
HistoricalBarSize.OneMonth => 2592000,
_ => throw new ArgumentOutOfRangeException(nameof(barSize))
};
return Math.Max(1, (int)Math.Floor(duration.TotalSeconds / secondsPerBar));
}
}
}