SoftTraderBackend/DatabaseHandler/HistoricalRequestParser.cs

107 lines
3.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))
};
// Markets trade ~6.5 hours/day, 5 days/week — not 24/7 calendar time.
// Intraday fraction: (6.5h × 5 days) / (24h × 7 days) ≈ 0.194
// Daily/weekly fraction (weekdays only): 5 / 7 ≈ 0.714
// Monthly bars already map 1:1 to calendar months, no adjustment needed.
double marketFraction = barSize switch
{
HistoricalBarSize.OneMonth => 1.0,
HistoricalBarSize.OneDay or HistoricalBarSize.FiveDays => 5.0 / 7.0,
_ => (6.5 * 5.0) / (24.0 * 7.0)
};
return Math.Max(1, (int)Math.Floor(duration.TotalSeconds * marketFraction / secondsPerBar));
}
}
}