Skip to content

Commit 5358354

Browse files
committed
Create testCases for earlyClose and lateOpen
1 parent 51e7cd1 commit 5358354

3 files changed

Lines changed: 142 additions & 42 deletions

File tree

Common/Securities/SecurityExchangeHours.cs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public HashSet<DateTime> BankHolidays
117117
/// </summary>
118118
public static SecurityExchangeHours AlwaysOpen(DateTimeZone timeZone)
119119
{
120-
var dayOfWeeks = Enum.GetValues(typeof(DayOfWeek)).OfType<DayOfWeek>();
120+
var dayOfWeeks = Enum.GetValues(typeof (DayOfWeek)).OfType<DayOfWeek>();
121121
return new SecurityExchangeHours(timeZone,
122122
Enumerable.Empty<DateTime>(),
123123
dayOfWeeks.Select(LocalMarketHours.OpenAllDay).ToDictionary(x => x.DayOfWeek),
@@ -187,13 +187,6 @@ public bool IsOpen(DateTime localDateTime, bool extendedMarketHours)
187187
[MethodImpl(MethodImplOptions.AggressiveInlining)]
188188
public bool IsOpen(DateTime startLocalDateTime, DateTime endLocalDateTime, bool extendedMarketHours)
189189
{
190-
var hasLateOpen = _lateOpens.TryGetValue(startLocalDateTime.Date, out var lateOpenTime);
191-
if (hasLateOpen && lateOpenTime > endLocalDateTime.TimeOfDay)
192-
{
193-
// if the late open is after the end time, we're closed
194-
return false;
195-
}
196-
197190
if (startLocalDateTime == endLocalDateTime)
198191
{
199192
// if we're testing an instantaneous moment, use the other function
@@ -289,7 +282,7 @@ public DateTime GetPreviousMarketOpen(DateTime localDateTime, bool extendedMarke
289282
for (int i = 0; i < 7; i++)
290283
{
291284
DateTime? potentialResult = null;
292-
foreach (var segment in marketHours.Segments.Reverse())
285+
foreach(var segment in marketHours.Segments.Reverse())
293286
{
294287
if ((time.Date + segment.Start <= localDateTime) &&
295288
(segment.State == MarketHoursState.Market || extendedMarketHours))
@@ -607,20 +600,18 @@ public LocalMarketHours GetMarketHours(DateTime localDateTime)
607600
// If the lateOpenTime is between a segment, change the start time with it
608601
// and add it before the segments previous to the lateOpenTime
609602
// Otherwise, just take the segments previous to the lateOpenTime
610-
bool lateOpenIsValid = false;
611603
List<MarketHoursSegment> segmentsLateOpen = null;
612604
if (hasLateOpen)
613605
{
614606
var index = 0;
615607
segmentsLateOpen = new List<MarketHoursSegment>();
616-
for (var i = 0; i < marketHoursSegments.Count; i++)
608+
for(var i = 0; i < marketHoursSegments.Count; i++)
617609
{
618610
var segment = marketHoursSegments[i];
619611
if (segment.Start <= lateOpenTime && lateOpenTime <= segment.End)
620612
{
621-
segmentsLateOpen.Add(new(segment.State, lateOpenTime, segment.End));
613+
segmentsLateOpen.Add(new (segment.State, lateOpenTime, segment.End));
622614
index = i + 1;
623-
lateOpenIsValid = true;
624615
break;
625616
}
626617
else if (lateOpenTime < segment.Start)
@@ -629,11 +620,9 @@ public LocalMarketHours GetMarketHours(DateTime localDateTime)
629620
break;
630621
}
631622
}
632-
if (lateOpenIsValid)
633-
{
634-
segmentsLateOpen.AddRange(marketHoursSegments.TakeLast(marketHoursSegments.Count - index));
635-
marketHoursSegments = segmentsLateOpen;
636-
}
623+
624+
segmentsLateOpen.AddRange(marketHoursSegments.TakeLast(marketHoursSegments.Count - index));
625+
marketHoursSegments = segmentsLateOpen;
637626
}
638627

639628
// Since it could be the case we have a late open after an early close (the market resumes after the early close), we need to take

Data/market-hours/market-hours-database.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6131,7 +6131,6 @@
61316131
"1/20/2020": "17:00:00",
61326132
"2/17/2020": "17:00:00",
61336133
"5/25/2020": "17:00:00",
6134-
"7/3/2020": "17:00:00",
61356134
"9/7/2020": "17:00:00",
61366135
"11/26/2020": "17:00:00",
61376136
"1/18/2021": "17:00:00",

Tests/Common/Securities/SecurityExchangeHoursTests.cs

Lines changed: 135 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -575,24 +575,6 @@ public void RegularMarketDurationIsFromMostCommonLocalMarketHours()
575575
Assert.AreEqual(TimeSpan.FromHours(5), exchangeHours.RegularMarketDuration);
576576
}
577577

578-
[Test]
579-
public void IsOpenDuringBarIsFalseWhenLateOpenIsGreaterThanMarketHourEndTime()
580-
{
581-
// Create exchange hours for future 6J contract, with late open at 5pm on 7/3/2020
582-
var exchangeHours = CreateFuture6JExchangeHours();
583-
var exchange = new SecurityExchange(exchangeHours);
584-
585-
// Define the bar start and end times for July 3, 2020.
586-
// Regular market hours: from 8:30 AM to 4:00 PM
587-
DateTime barStartTime = new DateTime(2020, 7, 3, 8, 30, 0);
588-
DateTime bartEndTime = new DateTime(2020, 7, 3, 16, 0, 0);
589-
var _isExtendedMarketHours = false;
590-
591-
// Check if the exchange is open during the bar
592-
var isOpen = exchange.IsOpenDuringBar(barStartTime, bartEndTime, _isExtendedMarketHours);
593-
Assert.IsFalse(isOpen);
594-
}
595-
596578
[Test]
597579
public void FillForwardDoesNotOccurOnLateOpenDates()
598580
{
@@ -610,12 +592,12 @@ public void FillForwardDoesNotOccurOnLateOpenDates()
610592
new TradeBar { Time = new DateTime(2020, 7, 6, 8, 30, 0), EndTime = new DateTime(2020, 7, 6, 16, 0, 0), Value = 1, Volume = 100},
611593
}.GetEnumerator();
612594

613-
var exchangeHours = CreateFuture6JExchangeHours();
595+
var closeDate = new DateTime(2020, 7, 3);
596+
var exchangeHours = CreateFuture6JExchangeHours(new DateTime(), closeDate);
614597
var exchange = new SecurityExchange(exchangeHours);
615598
using var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(fillForwardResolution), false, subscriptionEndTime, dataResolution, exchange.TimeZone, true);
616599

617600
// Date to check for late open
618-
var closeDate = new DateTime(2020, 7, 3);
619601
int dataCount = 0;
620602

621603
// Set to store unique dates
@@ -639,7 +621,134 @@ public void FillForwardDoesNotOccurOnLateOpenDates()
639621
Assert.AreEqual(dataCount, uniqueDates.Count);
640622
}
641623

642-
public static SecurityExchangeHours CreateFuture6JExchangeHours()
624+
[TestCaseSource(nameof(GetTestCases))]
625+
public void GetMarketHoursWorksCorrectly(DateTime earlyClose, DateTime lateOpen, LocalMarketHours expected)
626+
{
627+
var testDate = new DateTime(2020, 7, 3); // Friday
628+
var exchangeHours = CreateFuture6JExchangeHours(earlyClose, lateOpen);
629+
var actual = exchangeHours.GetMarketHours(testDate);
630+
631+
// Extracts the time segments for detailed comparison
632+
var actualSegments = actual.Segments;
633+
var expectedSegments = expected.Segments;
634+
635+
// Must have the same number of segments
636+
Assert.AreEqual(expectedSegments.Count, actualSegments.Count);
637+
638+
// 1. Market State (PreMarket/Market/PostMarket/Closed)
639+
// 2. Start Time (Validates late open adjustments)
640+
// 3. End Time (Validates early close adjustments)
641+
for (int i = 0; i < expectedSegments.Count; i++)
642+
{
643+
Assert.AreEqual(expectedSegments[i].State, actualSegments[i].State, $"Segment {i} state mismatch");
644+
Assert.AreEqual(expectedSegments[i].Start, actualSegments[i].Start, $"Segment {i} start time mismatch");
645+
Assert.AreEqual(expectedSegments[i].End, actualSegments[i].End, $"Segment {i} end time mismatch");
646+
}
647+
}
648+
649+
private static TestCaseData[] GetTestCases()
650+
{
651+
return new[]
652+
{
653+
// 1. Regular hours (no early close, no late open)
654+
new TestCaseData(
655+
new DateTime(), // No early close
656+
new DateTime(), // No late open
657+
new LocalMarketHours(DayOfWeek.Friday,
658+
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
659+
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(16, 0, 0)))
660+
),
661+
662+
// 2. Early close only scenarios
663+
// 2.1 Early close during regular market hours
664+
new TestCaseData(
665+
new DateTime(2020, 7, 3, 12, 0, 0), // Early close at noon
666+
new DateTime(),
667+
new LocalMarketHours(DayOfWeek.Friday,
668+
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
669+
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(12, 0, 0)))
670+
),
671+
// 2.2 Early close before market opens (should have no effect)
672+
new TestCaseData(
673+
new DateTime(2020, 7, 3, 7, 0, 0),
674+
new DateTime(),
675+
new LocalMarketHours(DayOfWeek.Friday,
676+
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(7, 0, 0)))
677+
),
678+
// 2.3 Early close after market closes (should have no effect)
679+
new TestCaseData(
680+
new DateTime(2020, 7, 3, 17, 0, 0),
681+
new DateTime(),
682+
new LocalMarketHours(DayOfWeek.Friday,
683+
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
684+
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(16, 0, 0)))
685+
),
686+
687+
// 3. Late open only scenarios
688+
// 3.1 Late open during regular market hours
689+
new TestCaseData(
690+
new DateTime(),
691+
new DateTime(2020, 7, 3, 10, 0, 0), // Late open at 10am
692+
new LocalMarketHours(DayOfWeek.Friday,
693+
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(10, 0, 0), new TimeSpan(16, 0, 0)))
694+
),
695+
// 3.2 Late open before market opens (should adjust pre-market)
696+
new TestCaseData(
697+
new DateTime(),
698+
new DateTime(2020, 7, 3, 7, 0, 0),
699+
new LocalMarketHours(DayOfWeek.Friday,
700+
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(7, 0, 0), new TimeSpan(8, 30, 0)),
701+
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(16, 0, 0)))
702+
),
703+
// 3.3 Late open after market closes (market should be closed)
704+
new TestCaseData(
705+
new DateTime(),
706+
new DateTime(2020, 7, 3, 17, 0, 0),
707+
LocalMarketHours.ClosedAllDay(DayOfWeek.Friday)
708+
),
709+
710+
// 4. Both early close and late open scenarios
711+
// 4.1 Early close before late open (market closes early and reopens)
712+
new TestCaseData(
713+
new DateTime(2020, 7, 3, 12, 0, 0), // Close at noon
714+
new DateTime(2020, 7, 3, 13, 0, 0), // Reopen at 1pm
715+
new LocalMarketHours(DayOfWeek.Friday,
716+
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
717+
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(12, 0, 0)),
718+
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(13, 0, 0), new TimeSpan(16, 0, 0)))
719+
),
720+
// 4.2 Early close after late open (only early close applies)
721+
new TestCaseData(
722+
new DateTime(2020, 7, 3, 15, 0, 0), // Close at 3pm
723+
new DateTime(2020, 7, 3, 14, 0, 0), // Late open at 2pm
724+
new LocalMarketHours(DayOfWeek.Friday,
725+
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(14, 0, 0), new TimeSpan(15, 0, 0)))
726+
),
727+
// 4.3 Both outside market hours (market should be closed)
728+
new TestCaseData(
729+
new DateTime(2020, 7, 3, 7, 0, 0), // Before open
730+
new DateTime(2020, 7, 3, 17, 0, 0), // After close
731+
LocalMarketHours.ClosedAllDay(DayOfWeek.Friday)
732+
),
733+
734+
// 5. Edge cases
735+
// 5.1 Early close exactly at market open
736+
new TestCaseData(
737+
new DateTime(2020, 7, 3, 8, 30, 0),
738+
new DateTime(),
739+
new LocalMarketHours(DayOfWeek.Friday,
740+
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)))
741+
),
742+
// 5.2 Late open exactly at market close
743+
new TestCaseData(
744+
new DateTime(),
745+
new DateTime(2020, 7, 3, 16, 0, 0),
746+
LocalMarketHours.ClosedAllDay(DayOfWeek.Friday)
747+
)
748+
};
749+
}
750+
751+
public static SecurityExchangeHours CreateFuture6JExchangeHours(DateTime earlyClose, DateTime lateOpen)
643752
{
644753
var sunday = new LocalMarketHours(
645754
DayOfWeek.Sunday,
@@ -682,11 +791,14 @@ public static SecurityExchangeHours CreateFuture6JExchangeHours()
682791

683792
var saturday = LocalMarketHours.ClosedAllDay(DayOfWeek.Saturday);
684793

685-
var earlyCloses = new Dictionary<DateTime, TimeSpan>();
794+
var earlyCloses = new Dictionary<DateTime, TimeSpan>
795+
{
796+
{ earlyClose.Date, earlyClose.TimeOfDay }
797+
};
686798

687799
var lateOpens = new Dictionary<DateTime, TimeSpan>
688800
{
689-
{ new DateTime(2020, 7, 3), new TimeSpan(17, 0, 0) }
801+
{ lateOpen.Date, lateOpen.TimeOfDay }
690802
};
691803

692804
var holidays = new List<DateTime>

0 commit comments

Comments
 (0)