@@ -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