Skip to content

Commit 73684e8

Browse files
authored
Keep time of day on open interest consolidator (#8667)
* Avoid time round down for open interest hour and daily consolidation * Consolidate OI data on date and hour change * Cleanup * Add more test cases
1 parent 1e84ee5 commit 73684e8

File tree

2 files changed

+206
-1
lines changed

2 files changed

+206
-1
lines changed

Common/Data/Consolidators/OpenInterestConsolidator.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ namespace QuantConnect.Data.Consolidators
2424
/// </summary>
2525
public class OpenInterestConsolidator : PeriodCountConsolidatorBase<Tick, OpenInterest>
2626
{
27+
private bool _hourOrDailyConsolidation;
28+
// Keep track of the last input to detect hour or date change
29+
private Tick _lastInput;
30+
2731
/// <summary>
2832
/// Create a new OpenInterestConsolidator for the desired resolution
2933
/// </summary>
@@ -41,6 +45,7 @@ public static OpenInterestConsolidator FromResolution(Resolution resolution)
4145
public OpenInterestConsolidator(TimeSpan period)
4246
: base(period)
4347
{
48+
_hourOrDailyConsolidation = period >= Time.OneHour;
4449
}
4550

4651
/// <summary>
@@ -104,7 +109,7 @@ protected override void AggregateBar(ref OpenInterest workingBar, Tick data)
104109
workingBar = new OpenInterest
105110
{
106111
Symbol = data.Symbol,
107-
Time = GetRoundedBarTime(data),
112+
Time = _hourOrDailyConsolidation ? data.EndTime : GetRoundedBarTime(data),
108113
Value = data.Value
109114
};
110115

@@ -113,7 +118,37 @@ protected override void AggregateBar(ref OpenInterest workingBar, Tick data)
113118
{
114119
//Update the working bar
115120
workingBar.Value = data.Value;
121+
122+
// If we are consolidating hourly or daily, we need to update the time of the working bar
123+
// for the end time to match the last data point time
124+
if (_hourOrDailyConsolidation)
125+
{
126+
workingBar.Time = data.EndTime;
127+
}
116128
}
117129
}
130+
131+
/// <summary>
132+
/// Updates this consolidator with the specified data. This method is
133+
/// responsible for raising the DataConsolidated event.
134+
/// It will check for date or hour change and force consolidation if needed.
135+
/// </summary>
136+
/// <param name="data">The new data for the consolidator</param>
137+
public override void Update(Tick data)
138+
{
139+
if (_lastInput != null &&
140+
_hourOrDailyConsolidation &&
141+
// Detect hour or date change
142+
((Period == Time.OneHour && data.EndTime.Hour != _lastInput.EndTime.Hour) ||
143+
(Period == Time.OneDay && data.EndTime.Date != _lastInput.EndTime.Date)))
144+
{
145+
// Date or hour change, force consolidation, no need to wait for the whole period to pass.
146+
// Force consolidation by scanning at a time after the end of the period
147+
Scan(_lastInput.EndTime.Add(Period.Value + Time.OneSecond));
148+
}
149+
150+
base.Update(data);
151+
_lastInput = data;
152+
}
118153
}
119154
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3+
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using NUnit.Framework;
19+
using QuantConnect.Data;
20+
using QuantConnect.Data.Consolidators;
21+
using QuantConnect.Data.Market;
22+
using QuantConnect.Logging;
23+
24+
namespace QuantConnect.Tests.Common.Data
25+
{
26+
[TestFixture]
27+
public class OpenInterestConsolidatorTests : BaseConsolidatorTests
28+
{
29+
[TestCaseSource(nameof(HourAndDailyTestValues))]
30+
public void HourAndDailyConsolidationKeepsTimeOfDay(TimeSpan period, List<(OpenInterest, bool)> data)
31+
{
32+
using var consolidator = new OpenInterestConsolidator(period);
33+
34+
var consolidatedOpenInterest = (OpenInterest)null;
35+
consolidator.DataConsolidated += (sender, consolidated) =>
36+
{
37+
Log.Debug($"{consolidated.EndTime} - {consolidated}");
38+
consolidatedOpenInterest = consolidated;
39+
};
40+
41+
var prevData = (OpenInterest)null;
42+
foreach (var (openInterest, shouldConsolidate) in data)
43+
{
44+
consolidator.Update(openInterest);
45+
46+
if (shouldConsolidate)
47+
{
48+
Assert.IsNotNull(consolidatedOpenInterest);
49+
Assert.AreEqual(prevData.Symbol, consolidatedOpenInterest.Symbol);
50+
Assert.AreEqual(prevData.Value, consolidatedOpenInterest.Value);
51+
Assert.AreEqual(prevData.EndTime, consolidatedOpenInterest.EndTime);
52+
consolidatedOpenInterest = null;
53+
}
54+
else
55+
{
56+
Assert.IsNull(consolidatedOpenInterest);
57+
}
58+
59+
prevData = openInterest;
60+
}
61+
}
62+
63+
protected override IDataConsolidator CreateConsolidator()
64+
{
65+
return new OpenInterestConsolidator(TimeSpan.FromDays(1));
66+
}
67+
68+
protected override IEnumerable<IBaseData> GetTestValues()
69+
{
70+
var time = new DateTime(2015, 04, 13, 8, 31, 0);
71+
return new List<OpenInterest>()
72+
{
73+
new OpenInterest(){ Time = time, Symbol = Symbols.SPY, Value = 10 },
74+
new OpenInterest(){ Time = time.AddMinutes(1), Symbol = Symbols.SPY, Value = 12 },
75+
new OpenInterest(){ Time = time.AddMinutes(2), Symbol = Symbols.SPY, Value = 10 },
76+
new OpenInterest(){ Time = time.AddMinutes(3), Symbol = Symbols.SPY, Value = 5 },
77+
new OpenInterest(){ Time = time.AddMinutes(4), Symbol = Symbols.SPY, Value = 15 },
78+
new OpenInterest(){ Time = time.AddMinutes(5), Symbol = Symbols.SPY, Value = 20 },
79+
new OpenInterest(){ Time = time.AddMinutes(6), Symbol = Symbols.SPY, Value = 18 },
80+
new OpenInterest(){ Time = time.AddMinutes(7), Symbol = Symbols.SPY, Value = 12 },
81+
new OpenInterest(){ Time = time.AddMinutes(8), Symbol = Symbols.SPY, Value = 25 },
82+
new OpenInterest(){ Time = time.AddMinutes(9), Symbol = Symbols.SPY, Value = 30 },
83+
new OpenInterest(){ Time = time.AddMinutes(10), Symbol = Symbols.SPY, Value = 26 },
84+
};
85+
}
86+
87+
private static IEnumerable<TestCaseData> HourAndDailyTestValues()
88+
{
89+
var symbol = Symbols.SPY_C_192_Feb19_2016;
90+
var time = new DateTime(2015, 04, 13, 6, 30, 0);
91+
var period = Time.OneDay;
92+
93+
yield return new TestCaseData(
94+
period,
95+
new List<(OpenInterest, bool)>()
96+
{
97+
(new OpenInterest(time, symbol, 10), false),
98+
(new OpenInterest(time.AddDays(1), symbol, 11), true),
99+
(new OpenInterest(time.AddDays(2), symbol, 12), true),
100+
(new OpenInterest(time.AddDays(3), symbol, 13), true),
101+
(new OpenInterest(time.AddDays(4), symbol, 14), true),
102+
(new OpenInterest(time.AddDays(5), symbol, 15), true),
103+
});
104+
105+
yield return new TestCaseData(
106+
period,
107+
new List<(OpenInterest, bool)>()
108+
{
109+
(new OpenInterest(time, symbol, 10), false),
110+
(new OpenInterest(time.AddDays(1), symbol, 11), true),
111+
// Same date, should not consolidate
112+
(new OpenInterest(time.AddDays(1).AddMinutes(1), symbol, 12), false),
113+
// Same date, should not consolidate
114+
(new OpenInterest(time.AddDays(1).AddMinutes(2), symbol, 13), false),
115+
// Same date, should not consolidate
116+
(new OpenInterest(time.AddDays(1).AddMinutes(3), symbol, 14), false),
117+
// Not the full period passed but different date, should consolidate
118+
(new OpenInterest(time.AddDays(2).AddHours(-1), symbol, 15), true),
119+
(new OpenInterest(time.AddDays(3).AddHours(-2), symbol, 16), true),
120+
(new OpenInterest(time.AddDays(4).AddHours(-3), symbol, 17), true),
121+
(new OpenInterest(time.AddDays(5).AddHours(-4), symbol, 18), true),
122+
});
123+
124+
period = Time.OneHour;
125+
126+
yield return new TestCaseData(
127+
period,
128+
new List<(OpenInterest, bool)>()
129+
{
130+
(new OpenInterest(time, symbol, 10), false),
131+
(new OpenInterest(time.AddHours(1), symbol, 11), true),
132+
(new OpenInterest(time.AddHours(2), symbol, 12), true),
133+
(new OpenInterest(time.AddHours(3), symbol, 13), true),
134+
(new OpenInterest(time.AddHours(4), symbol, 14), true),
135+
(new OpenInterest(time.AddHours(5), symbol, 15), true),
136+
});
137+
138+
yield return new TestCaseData(
139+
period,
140+
new List<(OpenInterest, bool)>()
141+
{
142+
(new OpenInterest(time.AddHours(0.5).AddMinutes(10), symbol, 10), false),
143+
(new OpenInterest(time.AddHours(2.5).AddMinutes(20), symbol, 11), true),
144+
(new OpenInterest(time.AddHours(4.5).AddMinutes(30), symbol, 12), true),
145+
(new OpenInterest(time.AddHours(6.5).AddMinutes(40), symbol, 13), true),
146+
(new OpenInterest(time.AddHours(8.5), symbol, 14), true),
147+
(new OpenInterest(time.AddHours(10.5).AddMinutes(50), symbol, 15), true),
148+
});
149+
150+
yield return new TestCaseData(
151+
period,
152+
new List<(OpenInterest, bool)>()
153+
{
154+
(new OpenInterest(time, symbol, 10), false),
155+
(new OpenInterest(time.AddHours(1), symbol, 11), true),
156+
// Same date, should not consolidate
157+
(new OpenInterest(time.AddHours(1).AddMinutes(5), symbol, 12), false),
158+
// Same date, should not consolidate
159+
(new OpenInterest(time.AddHours(1).AddMinutes(10), symbol, 13), false),
160+
// Same date, should not consolidate
161+
(new OpenInterest(time.AddHours(1).AddMinutes(15), symbol, 14), false),
162+
// Not the full period passed but different date, should consolidate
163+
(new OpenInterest(time.AddHours(2).AddMinutes(-5), symbol, 15), true),
164+
(new OpenInterest(time.AddHours(3).AddMinutes(-10), symbol, 16), true),
165+
(new OpenInterest(time.AddHours(4).AddMinutes(-15), symbol, 17), true),
166+
(new OpenInterest(time.AddHours(5).AddMinutes(-20), symbol, 18), true),
167+
});
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)