Skip to content

Commit 4627168

Browse files
authored
Fix invalid time on Session working bar (#9300)
* Use consolidated bar as reference for new working bar initialization * Update ValidateAndScan method and unit tests * Fix issues with syntax check * Solve review comments * Minor fix * Minor fix
1 parent 36453d8 commit 4627168

4 files changed

Lines changed: 124 additions & 14 deletions

File tree

Common/Data/Consolidators/SessionConsolidator.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using QuantConnect.Securities;
2020
using QuantConnect.Data.Market;
2121
using QuantConnect.Data.Consolidators;
22+
using QuantConnect.Util;
2223

2324
namespace Common.Data.Consolidators
2425
{
@@ -64,13 +65,6 @@ public SessionConsolidator(SecurityExchangeHours exchangeHours, TickType sourceT
6465
/// <param name="data">The new data</param>
6566
protected override void AggregateBar(ref SessionBar workingBar, BaseData data)
6667
{
67-
if (!_initialized)
68-
{
69-
workingBar.Time = data.Time.Date;
70-
workingBar.Period = TimeSpan.FromDays(1);
71-
_initialized = true;
72-
}
73-
7468
// Handle open interest
7569
if (data.DataType == MarketDataType.Tick && data is Tick oiTick && oiTick.TickType == TickType.OpenInterest)
7670
{
@@ -88,6 +82,20 @@ protected override void AggregateBar(ref SessionBar workingBar, BaseData data)
8882
workingBar.Update(data, Consolidated);
8983
}
9084

85+
/// <summary>
86+
/// Updates the session with new market data and initializes the consolidator if needed
87+
/// </summary>
88+
/// <param name="data">The new data to update the session with</param>
89+
public override void Update(BaseData data)
90+
{
91+
if (!_initialized)
92+
{
93+
_workingBar.Time = data.Time.Date;
94+
_initialized = true;
95+
}
96+
base.Update(data);
97+
}
98+
9199
/// <summary>
92100
/// Validates the current local time and triggers Scan() if a new day is detected.
93101
/// </summary>
@@ -98,7 +106,8 @@ public void ValidateAndScan(DateTime currentLocalTime)
98106
{
99107
return;
100108
}
101-
if (currentLocalTime.Date != WorkingInstance?.Time.Date)
109+
110+
if (currentLocalTime.Date != WorkingInstance.Time.Date)
102111
{
103112
Scan(currentLocalTime);
104113
}
@@ -133,9 +142,14 @@ public override void Reset()
133142

134143
private void InitializeWorkingBar()
135144
{
145+
var time = DateTime.MaxValue;
146+
if (Consolidated != null)
147+
{
148+
time = _exchangeHours.GetNextTradingDay(Consolidated.Time).Date;
149+
}
136150
_workingBar = new SessionBar(_sourceTickType)
137151
{
138-
Time = DateTime.MaxValue,
152+
Time = time,
139153
Symbol = _symbol
140154
};
141155
_initialized = false;

Common/Data/Market/Session.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public Session(TickType tickType, SecurityExchangeHours exchangeHours, Symbol sy
101101
}
102102

103103
/// <summary>
104-
/// Updates the session with new market data and initializes the consolidator if needed
104+
/// Updates the session with new market data
105105
/// </summary>
106106
/// <param name="data">The new data to update the session with</param>
107107
public void Update(BaseData data)
@@ -117,7 +117,7 @@ private void OnConsolidated(object sender, IBaseData consolidated)
117117
}
118118

119119
/// <summary>
120-
/// Scans this consolidator to see if it should emit a bar due to time passing
120+
/// Scans the consolidator to see if it should emit a bar due to time passing
121121
/// </summary>
122122
public void Scan(DateTime currentLocalTime)
123123
{

Common/Data/Market/SessionBar.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,23 @@ public override DateTime EndTime
7171
/// <summary>
7272
/// The period of this session bar
7373
/// </summary>
74-
public override TimeSpan Period { get; set; } = QuantConnect.Time.OneDay;
74+
public override TimeSpan Period { get; set; }
7575

7676
/// <summary>
7777
/// Initializes a new instance of SessionBar with default values
7878
/// </summary>
79-
public SessionBar() { }
79+
public SessionBar()
80+
{
81+
Period = QuantConnect.Time.OneDay;
82+
}
8083

8184
/// <summary>
8285
/// Initializes a new instance of SessionBar with a specific tick type
8386
/// </summary>
8487
public SessionBar(TickType sourceTickType)
8588
{
8689
_sourceTickType = sourceTickType;
90+
Period = QuantConnect.Time.OneDay;
8791
}
8892

8993
/// <summary>

Tests/Indicators/SessionTests.cs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using NUnit.Framework;
1818
using QuantConnect.Securities;
1919
using QuantConnect.Data.Market;
20+
using System.Collections.Generic;
2021

2122
namespace QuantConnect.Tests.Indicators
2223
{
@@ -90,12 +91,103 @@ public void EndTimeDoesNotOverflowWhenAccessedBeforeFirstUpdate()
9091
Assert.AreEqual(1000, session.Volume);
9192
}
9293

93-
private Session GetSession(TickType tickType, int initialSize)
94+
private static IEnumerable<TestCaseData> ConsolidationTestCases()
95+
{
96+
// Hour resolution during regular market hours
97+
yield return new TestCaseData(new DateTime(2025, 8, 25, 10, 0, 0), Resolution.Hour);
98+
99+
// Daily resolution and bar emitted at midnight
100+
yield return new TestCaseData(new DateTime(2025, 8, 25, 0, 0, 0), Resolution.Daily);
101+
}
102+
103+
[TestCaseSource(nameof(ConsolidationTestCases))]
104+
public void ConsolidatesDaily(DateTime baseDate, Resolution resolution)
105+
{
106+
var symbol = Symbols.SPY;
107+
var session = GetSession(TickType.Trade, 4);
108+
var days = new[]
109+
{
110+
new { Expected = new TradeBar(baseDate.Date, symbol, 100, 101, 99, 100, 6000, Time.OneDay) },
111+
new { Expected = new TradeBar(baseDate.Date.AddDays(1), symbol, 100, 101, 99, 100, 6000, Time.OneDay) },
112+
new { Expected = new TradeBar(baseDate.Date.AddDays(2), symbol, 100, 101, 99, 100, 6000, Time.OneDay) },
113+
};
114+
115+
Assert.AreEqual(1, session.Samples);
116+
117+
for (int i = 0; i < days.Length; i++)
118+
{
119+
var startDate = baseDate.AddDays(i);
120+
var endDate = startDate.Date.AddDays(1);
121+
122+
if (resolution == Resolution.Hour)
123+
{
124+
for (int j = 0; j < 6; j++)
125+
{
126+
session.Update(new TradeBar(startDate.AddHours(j), symbol, 100, 101, 99, 100, 1000, Time.OneHour));
127+
}
128+
}
129+
else
130+
{
131+
session.Update(new TradeBar(startDate, symbol, 100, 101, 99, 100, 6000, Time.OneDay));
132+
}
133+
134+
session.Scan(endDate);
135+
Assert.AreEqual(i + 2, session.Samples);
136+
Assert.IsTrue(BarsAreEqual(days[i].Expected, session[1]));
137+
}
138+
}
139+
140+
[TestCaseSource(nameof(NextSessionTradingDayCases))]
141+
public void CreatesNewSessionBarWithCorrectNextTradingDay(DateTime startDate, DateTime expectedDate)
142+
{
143+
var symbol = Symbols.SPY;
144+
var session = GetSession(TickType.Trade, 3);
145+
var endDate = startDate.AddHours(14);
146+
147+
for (int i = 0; i < 6; i++)
148+
{
149+
session.Update(new TradeBar(startDate.AddHours(i), symbol, 100, 101, 99, 100, 1000, TimeSpan.FromHours(1)));
150+
}
151+
152+
session.Scan(endDate);
153+
154+
var sessionBar = session[0];
155+
Assert.AreNotEqual(DateTime.MaxValue, sessionBar.Time);
156+
Assert.AreEqual(expectedDate, sessionBar.Time);
157+
Assert.AreEqual(expectedDate.AddDays(1), sessionBar.EndTime);
158+
Assert.AreEqual(0, sessionBar.Open);
159+
Assert.AreEqual(0, sessionBar.High);
160+
Assert.AreEqual(0, sessionBar.Low);
161+
Assert.AreEqual(0, sessionBar.Close);
162+
Assert.AreEqual(0, sessionBar.Volume);
163+
}
164+
165+
private static IEnumerable<TestCaseData> NextSessionTradingDayCases()
166+
{
167+
// Regular weekday: next trading day is simply the next calendar day
168+
yield return new TestCaseData(new DateTime(2025, 8, 25, 10, 0, 0), new DateTime(2025, 8, 26));
169+
170+
// Friday before Labor Day weekend -> next trading day is Tuesday (Sep 2, 2025)
171+
yield return new TestCaseData(new DateTime(2025, 8, 29, 10, 0, 0), new DateTime(2025, 9, 2));
172+
}
173+
174+
private static Session GetSession(TickType tickType, int initialSize)
94175
{
95176
var symbol = Symbols.SPY;
96177
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
97178
var exchangeHours = marketHoursDatabase.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
98179
return new Session(tickType, exchangeHours, symbol, initialSize);
99180
}
181+
182+
private static bool BarsAreEqual(TradeBar bar1, TradeBar bar2)
183+
{
184+
return bar1.Time == bar2.Time &&
185+
bar1.EndTime == bar2.EndTime &&
186+
bar1.Open == bar2.Open &&
187+
bar1.High == bar2.High &&
188+
bar1.Low == bar2.Low &&
189+
bar1.Close == bar2.Close &&
190+
bar1.Volume == bar2.Volume;
191+
}
100192
}
101193
}

0 commit comments

Comments
 (0)