@@ -575,57 +575,11 @@ public void RegularMarketDurationIsFromMostCommonLocalMarketHours()
575575 Assert . AreEqual ( TimeSpan . FromHours ( 5 ) , exchangeHours . RegularMarketDuration ) ;
576576 }
577577
578- [ Test ]
579- public void FillForwardDoesNotOccurOnLateOpenDates ( )
580- {
581- // Set resolution for data and fill forward to one day
582- var dataResolution = Time . OneDay ;
583- var fillForwardResolution = Time . OneDay ;
584-
585- // Define the initial time and subscription end time
586- var time = new DateTime ( 2020 , 6 , 28 , 8 , 30 , 0 ) ;
587- var subscriptionEndTime = time . AddDays ( 30 ) ;
588-
589- var enumerator = new List < BaseData >
590- {
591- new TradeBar { Time = new DateTime ( 2020 , 6 , 28 , 8 , 30 , 0 ) , EndTime = new DateTime ( 2020 , 6 , 28 , 16 , 0 , 0 ) , Value = 1 , Volume = 100 } ,
592- new TradeBar { Time = new DateTime ( 2020 , 7 , 6 , 8 , 30 , 0 ) , EndTime = new DateTime ( 2020 , 7 , 6 , 16 , 0 , 0 ) , Value = 1 , Volume = 100 } ,
593- } . GetEnumerator ( ) ;
594-
595- var closeDate = new DateTime ( 2020 , 7 , 3 ) ;
596- var exchangeHours = CreateFuture6JExchangeHours ( new DateTime ( ) , closeDate ) ;
597- var exchange = new SecurityExchange ( exchangeHours ) ;
598- using var fillForwardEnumerator = new FillForwardEnumerator ( enumerator , exchange , Ref . Create ( fillForwardResolution ) , false , subscriptionEndTime , dataResolution , exchange . TimeZone , true ) ;
599-
600- // Date to check for late open
601- int dataCount = 0 ;
602-
603- // Set to store unique dates
604- SortedSet < DateTime > uniqueDates = new SortedSet < DateTime > ( ) ;
605-
606- // Iterate through the enumerator
607- while ( fillForwardEnumerator . MoveNext ( ) )
608- {
609- var currentValue = fillForwardEnumerator . Current ;
610-
611- // Add unique end time to the sorted set and increment data count
612- uniqueDates . Add ( currentValue . EndTime ) ;
613- dataCount ++ ;
614-
615- // Ensure that no fill forward occurs on the late open date (5 PM)
616- Assert . AreNotEqual ( closeDate . Date , currentValue . EndTime ) ;
617- Assert . IsFalse ( fillForwardEnumerator . Current . EndTime > subscriptionEndTime ) ;
618- }
619-
620- // Ensure there are no duplicate dates in the result
621- Assert . AreEqual ( dataCount , uniqueDates . Count ) ;
622- }
623-
624578 [ TestCaseSource ( nameof ( GetTestCases ) ) ]
625579 public void GetMarketHoursWorksCorrectly ( DateTime earlyClose , DateTime lateOpen , LocalMarketHours expected )
626580 {
627581 var testDate = new DateTime ( 2020 , 7 , 3 ) ; // Friday
628- var exchangeHours = CreateFuture6JExchangeHours ( earlyClose , lateOpen ) ;
582+ var exchangeHours = CreateCustomFutureExchangeHours ( earlyClose , lateOpen ) ;
629583 var actual = exchangeHours . GetMarketHours ( testDate ) ;
630584
631585 // Extracts the time segments for detailed comparison
@@ -668,49 +622,47 @@ private static TestCaseData[] GetTestCases()
668622 new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 0 , 0 , 0 ) , new TimeSpan ( 8 , 30 , 0 ) ) ,
669623 new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 8 , 30 , 0 ) , new TimeSpan ( 12 , 0 , 0 ) ) )
670624 ) ,
671- // 2.2 Early close before market opens (should have no effect )
625+ // 2.2 Early close before market opens (should remove market segment )
672626 new TestCaseData (
673- new DateTime ( 2020 , 7 , 3 , 7 , 0 , 0 ) ,
627+ new DateTime ( 2020 , 7 , 3 , 7 , 0 , 0 ) , // Early close before open
674628 new DateTime ( ) ,
675629 new LocalMarketHours ( DayOfWeek . Friday ,
676630 new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 0 , 0 , 0 ) , new TimeSpan ( 7 , 0 , 0 ) ) )
677631 ) ,
678632 // 2.3 Early close after market closes (should have no effect)
679633 new TestCaseData (
680- new DateTime ( 2020 , 7 , 3 , 17 , 0 , 0 ) ,
634+ new DateTime ( 2020 , 7 , 3 , 17 , 0 , 0 ) , // Early close after regular close
681635 new DateTime ( ) ,
682636 new LocalMarketHours ( DayOfWeek . Friday ,
683637 new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 0 , 0 , 0 ) , new TimeSpan ( 8 , 30 , 0 ) ) ,
684638 new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 8 , 30 , 0 ) , new TimeSpan ( 16 , 0 , 0 ) ) )
685639 ) ,
686640
687641 // 3. Late open only scenarios
688- // 3.1 Late open during regular market hours
642+ // 3.1 Late open during regular market hours (should adjust market open)
689643 new TestCaseData (
690644 new DateTime ( ) ,
691645 new DateTime ( 2020 , 7 , 3 , 10 , 0 , 0 ) , // Late open at 10am
692646 new LocalMarketHours ( DayOfWeek . Friday ,
693647 new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 10 , 0 , 0 ) , new TimeSpan ( 16 , 0 , 0 ) ) )
694648 ) ,
695- // 3.2 Late open before market opens (should adjust pre-market )
649+ // 3.2 Late open before market opens (should delay premarket start )
696650 new TestCaseData (
697651 new DateTime ( ) ,
698- new DateTime ( 2020 , 7 , 3 , 7 , 0 , 0 ) ,
652+ new DateTime ( 2020 , 7 , 3 , 7 , 0 , 0 ) , // Late open before market
699653 new LocalMarketHours ( DayOfWeek . Friday ,
700654 new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 7 , 0 , 0 ) , new TimeSpan ( 8 , 30 , 0 ) ) ,
701655 new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 8 , 30 , 0 ) , new TimeSpan ( 16 , 0 , 0 ) ) )
702656 ) ,
703- // 3.3 Late open after market closes
657+ // 3.3 Late open after market close (market should be closed all day)
704658 new TestCaseData (
705659 new DateTime ( ) ,
706660 new DateTime ( 2020 , 7 , 3 , 17 , 0 , 0 ) , // Late open at 17
707- new LocalMarketHours ( DayOfWeek . Friday ,
708- new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 0 , 0 , 0 ) , new TimeSpan ( 8 , 30 , 0 ) ) ,
709- new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 8 , 30 , 0 ) , new TimeSpan ( 16 , 0 , 0 ) ) )
661+ LocalMarketHours . ClosedAllDay ( DayOfWeek . Friday )
710662 ) ,
711663
712664 // 4. Both early close and late open scenarios
713- // 4.1 Early close before late open (market closes early and reopens)
665+ // 4.1 Open <= Earlyclose <= Close and EarlyClose < LateOpen (market closes then reopens)
714666 new TestCaseData (
715667 new DateTime ( 2020 , 7 , 3 , 12 , 0 , 0 ) , // Close at noon
716668 new DateTime ( 2020 , 7 , 3 , 13 , 0 , 0 ) , // Reopen at 1pm
@@ -719,42 +671,75 @@ private static TestCaseData[] GetTestCases()
719671 new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 8 , 30 , 0 ) , new TimeSpan ( 12 , 0 , 0 ) ) ,
720672 new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 13 , 0 , 0 ) , new TimeSpan ( 16 , 0 , 0 ) ) )
721673 ) ,
722- // 4.2 Early close after late open (only early close applies )
674+ // 4.2 Open <= Earlyclose <= Close and EarlyClose > LateOpen (only one market segment should exist )
723675 new TestCaseData (
724676 new DateTime ( 2020 , 7 , 3 , 15 , 0 , 0 ) , // Close at 3pm
725677 new DateTime ( 2020 , 7 , 3 , 14 , 0 , 0 ) , // Late open at 2pm
726678 new LocalMarketHours ( DayOfWeek . Friday ,
727679 new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 14 , 0 , 0 ) , new TimeSpan ( 15 , 0 , 0 ) ) )
728680 ) ,
729- // 4.3 Both outside market hours
681+ // 4.3 Open <= Earlyclose <= Close and LateOpen > Close ( market closed all day)
730682 new TestCaseData (
731- new DateTime ( 2020 , 7 , 3 , 7 , 0 , 0 ) , // Before open
732- new DateTime ( 2020 , 7 , 3 , 17 , 0 , 0 ) , // After close
683+ new DateTime ( 2020 , 7 , 3 , 13 , 0 , 0 ) ,
684+ new DateTime ( 2020 , 7 , 3 , 17 , 0 , 0 ) ,
685+ LocalMarketHours . ClosedAllDay ( DayOfWeek . Friday )
686+ ) ,
687+ // 4.4 Earlyclose <= Open and LateOpen > Close (market closed all day)
688+ new TestCaseData (
689+ new DateTime ( 2020 , 7 , 3 , 7 , 0 , 0 ) ,
690+ new DateTime ( 2020 , 7 , 3 , 17 , 0 , 0 ) ,
691+ LocalMarketHours . ClosedAllDay ( DayOfWeek . Friday )
692+ ) ,
693+ // 4.5 Earlyclose <= Open and EarlyClose < LateOpen <= Close
694+ new TestCaseData (
695+ new DateTime ( 2020 , 7 , 3 , 7 , 0 , 0 ) ,
696+ new DateTime ( 2020 , 7 , 3 , 14 , 0 , 0 ) ,
733697 new LocalMarketHours ( DayOfWeek . Friday ,
734698 new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 0 , 0 , 0 ) , new TimeSpan ( 7 , 0 , 0 ) ) ,
735- new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 0 , 0 , 0 ) , new TimeSpan ( 8 , 30 , 0 ) ) ,
736- new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 8 , 30 , 0 ) , new TimeSpan ( 16 , 0 , 0 ) ) )
699+ new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 14 , 0 , 0 ) , new TimeSpan ( 16 , 0 , 0 ) ) )
700+ ) ,
701+ // 4.6 LateOpen < Earlyclose <= Open
702+ new TestCaseData (
703+ new DateTime ( 2020 , 7 , 3 , 7 , 0 , 0 ) ,
704+ new DateTime ( 2020 , 7 , 3 , 6 , 0 , 0 ) ,
705+ new LocalMarketHours ( DayOfWeek . Friday ,
706+ new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 6 , 0 , 0 ) , new TimeSpan ( 7 , 0 , 0 ) ) )
737707 ) ,
738708
739709 // 5. Edge cases
740- // 5.1 Early close exactly at market open
710+ // 5.1 Early close exactly at market open (no market segment)
741711 new TestCaseData (
742712 new DateTime ( 2020 , 7 , 3 , 8 , 30 , 0 ) ,
743713 new DateTime ( ) ,
744714 new LocalMarketHours ( DayOfWeek . Friday ,
745715 new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 0 , 0 , 0 ) , new TimeSpan ( 8 , 30 , 0 ) ) )
746716 ) ,
747- // 5.2 Late open exactly at market close
717+ // 5.2 Late open exactly at market close (market segment has zero duration)
748718 new TestCaseData (
749719 new DateTime ( ) ,
750720 new DateTime ( 2020 , 7 , 3 , 16 , 0 , 0 ) ,
751721 new LocalMarketHours ( DayOfWeek . Friday ,
752722 new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 16 , 0 , 0 ) , new TimeSpan ( 16 , 0 , 0 ) ) )
723+ ) ,
724+ // 5.3 Early close and late open at the same time (split into two segments with zero-duration overlap)
725+ new TestCaseData (
726+ new DateTime ( 2020 , 7 , 3 , 13 , 0 , 0 ) ,
727+ new DateTime ( 2020 , 7 , 3 , 13 , 0 , 0 ) ,
728+ new LocalMarketHours ( DayOfWeek . Friday ,
729+ new MarketHoursSegment ( MarketHoursState . PreMarket , new TimeSpan ( 0 , 0 , 0 ) , new TimeSpan ( 8 , 30 , 0 ) ) ,
730+ new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 8 , 30 , 0 ) , new TimeSpan ( 13 , 0 , 0 ) ) ,
731+ new MarketHoursSegment ( MarketHoursState . Market , new TimeSpan ( 13 , 0 , 0 ) , new TimeSpan ( 13 , 0 , 0 ) ) )
732+ ) ,
733+ // 5.4 EarlyOpen > Close and LateOpen > Close (market closed all day)
734+ new TestCaseData (
735+ new DateTime ( 2020 , 7 , 3 , 17 , 0 , 0 ) ,
736+ new DateTime ( 2020 , 7 , 3 , 17 , 0 , 0 ) ,
737+ LocalMarketHours . ClosedAllDay ( DayOfWeek . Friday )
753738 )
754739 } ;
755740 }
756741
757- public static SecurityExchangeHours CreateFuture6JExchangeHours ( DateTime earlyClose , DateTime lateOpen )
742+ private static SecurityExchangeHours CreateCustomFutureExchangeHours ( DateTime earlyClose , DateTime lateOpen )
758743 {
759744 var sunday = new LocalMarketHours (
760745 DayOfWeek . Sunday ,
0 commit comments