Skip to content

Commit 9641b73

Browse files
authored
Refactor: Level One Service (#16)
* refactor: usual order book to Best Bid And Offer (which we get from server) * refactor: Best Bid and Offer service * refactor: use LevelOneService from Lean * feat: use new LevelOneServiceManager for DQH * refactor: using path of LevelOneOrderBook
1 parent b673f1e commit 9641b73

File tree

3 files changed

+163
-143
lines changed

3 files changed

+163
-143
lines changed

QuantConnect.ThetaData.Tests/ThetaDataAdditionalTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
*/
1515

1616
using System;
17+
using Newtonsoft.Json;
1718
using NUnit.Framework;
1819
using System.Collections.Generic;
20+
using QuantConnect.Lean.DataSource.ThetaData.Models.Enums;
21+
using QuantConnect.Lean.DataSource.ThetaData.Models.WebSocket;
1922

2023
namespace QuantConnect.Lean.DataSource.ThetaData.Tests;
2124

@@ -84,4 +87,56 @@ public void GenerateDateRangesWithInterval_ShouldHandleSameStartEndDate()
8487

8588
Assert.AreEqual(1, ranges.Count, "There should be no date ranges generated.");
8689
}
90+
91+
[Test]
92+
public void DeserializeWebSocketQuoteResponse()
93+
{
94+
var webSocketQuoteResponse = @"{
95+
""header"": {
96+
""type"": ""QUOTE"",
97+
""status"": ""CONNECTED""
98+
},
99+
""contract"": {
100+
""security_type"": ""STOCK"",
101+
""root"": ""NVDA""
102+
},
103+
""quote"": {
104+
""ms_of_day"": 43032783,
105+
""bid_size"": 516,
106+
""bid_exchange"": 29,
107+
""bid"": 145.58,
108+
""bid_condition"": 0,
109+
""ask_size"": 1527,
110+
""ask_exchange"": 29,
111+
""ask"": 145.59,
112+
""ask_condition"": 0,
113+
""date"": 20250616
114+
}
115+
}";
116+
117+
var webSocketResponse = JsonConvert.DeserializeObject<WebSocketResponse>(webSocketQuoteResponse);
118+
119+
Assert.IsNotNull(webSocketResponse);
120+
Assert.IsNotNull(webSocketResponse.Header.Type);
121+
Assert.AreEqual(WebSocketHeaderType.Quote, webSocketResponse.Header.Type);
122+
123+
Assert.IsNotNull(webSocketResponse.Contract);
124+
Assert.AreEqual(ContractSecurityType.Equity, webSocketResponse.Contract?.SecurityType);
125+
Assert.AreEqual("NVDA", webSocketResponse.Contract?.Root);
126+
127+
Assert.IsNotNull(webSocketResponse.Quote);
128+
var quote = webSocketResponse.Quote.Value;
129+
Assert.AreEqual(43032783, quote.TimeMilliseconds);
130+
Assert.AreEqual(516m, quote.BidSize);
131+
Assert.AreEqual(29m, quote.BidExchange);
132+
Assert.AreEqual(145.58m, quote.BidPrice);
133+
Assert.AreEqual(0, quote.BidCondition);
134+
Assert.AreEqual(1527m, quote.AskSize);
135+
Assert.AreEqual(29m, quote.AskExchange);
136+
Assert.AreEqual(145.59m, quote.AskPrice);
137+
Assert.AreEqual(0, quote.AskCondition);
138+
Assert.AreEqual(new DateTime(2025, 06, 16), quote.Date);
139+
140+
Assert.IsNull(webSocketResponse.Trade);
141+
}
87142
}

QuantConnect.ThetaData.Tests/ThetaDataProviderTests.cs

Lines changed: 93 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using System.Text;
1718
using System.Linq;
1819
using NUnit.Framework;
1920
using System.Threading;
@@ -54,16 +55,61 @@ public void TearDown()
5455
}
5556
}
5657

57-
[TestCase("AAPL", SecurityType.Equity, Resolution.Second, 0, "2024/08/16")]
58-
[TestCase("AAPL", SecurityType.Option, Resolution.Second, 215, "2024/08/16")]
59-
[TestCase("VIX", SecurityType.Index, Resolution.Second, 0, "2024/08/16")]
60-
public void CanSubscribeAndUnsubscribeOnSecondResolution(string ticker, SecurityType securityType, Resolution resolution, decimal strikePrice, DateTime expiryDate = default)
58+
private static IEnumerable<TestCaseData> TestParameters
6159
{
62-
var configs = GetSubscriptionDataConfigs(ticker, securityType, resolution, strikePrice, expiryDate);
60+
get
61+
{
62+
var AAPL = Symbols.AAPL;
63+
yield return new TestCaseData(new Symbol[] { AAPL }, Resolution.Second);
6364

64-
Assert.That(configs, Is.Not.Empty);
65+
var DJI_Index = Symbol.Create("DJI", SecurityType.Index, Market.USA);
66+
yield return new TestCaseData(new Symbol[] { DJI_Index }, Resolution.Second);
67+
68+
var NVDA = Symbol.Create("NVDA", SecurityType.Equity, Market.USA);
69+
var DJT = Symbol.Create("DJT", SecurityType.Equity, Market.USA);
70+
var TSLA = Symbol.Create("TSLA", SecurityType.Equity, Market.USA);
71+
yield return new TestCaseData(new Symbol[] { AAPL, DJI_Index, NVDA, DJT, TSLA }, Resolution.Second);
72+
73+
var AAPL_Option = Symbol.CreateOption(AAPL, AAPL.ID.Market, SecurityType.Option.DefaultOptionStyle(), OptionRight.Call, 220m, new DateTime(2025, 06, 20));
74+
var AAPL_Option2 = Symbol.CreateOption(AAPL, AAPL.ID.Market, SecurityType.Option.DefaultOptionStyle(), OptionRight.Call, 217.5m, new DateTime(2026, 06, 20));
75+
var AAPL_Option3 = Symbol.CreateOption(AAPL, AAPL.ID.Market, SecurityType.Option.DefaultOptionStyle(), OptionRight.Put, 220m, new DateTime(2026, 06, 20));
76+
var AAPL_Option4 = Symbol.CreateOption(AAPL, AAPL.ID.Market, SecurityType.Option.DefaultOptionStyle(), OptionRight.Put, 217.5m, new DateTime(2026, 06, 20));
77+
yield return new TestCaseData(new[] { AAPL_Option, AAPL_Option2, AAPL_Option3, AAPL_Option4 }, Resolution.Second);
78+
79+
var nok = Symbol.Create("NOK", SecurityType.Equity, Market.USA);
80+
var nok_option = Symbol.CreateOption(nok, nok.ID.Market, SecurityType.Option.DefaultOptionStyle(), OptionRight.Call, 7.5m, new DateTime(2026, 06, 20));
81+
82+
yield return new TestCaseData(new[] { nok_option }, Resolution.Second);
83+
}
84+
}
85+
86+
[Test, TestCaseSource(nameof(TestParameters))]
87+
public void CanSubscribeAndUnsubscribeOnSecondResolution(Symbol[] symbols, Resolution resolution)
88+
{
89+
90+
var configs = new List<SubscriptionDataConfig>();
91+
92+
var dataFromEnumerator = new Dictionary<Symbol, Dictionary<Type, int>>();
6593

66-
var dataFromEnumerator = new Dictionary<Type, int>() { { typeof(TradeBar), 0 }, { typeof(QuoteBar), 0 } };
94+
foreach (var symbol in symbols)
95+
{
96+
dataFromEnumerator[symbol] = new Dictionary<Type, int>();
97+
foreach (var config in GetSubscriptionDataConfigs(symbol, resolution))
98+
{
99+
configs.Add(config);
100+
101+
var tickType = config.TickType switch
102+
{
103+
TickType.Quote => typeof(QuoteBar),
104+
TickType.Trade => typeof(TradeBar),
105+
_ => throw new NotImplementedException()
106+
};
107+
108+
dataFromEnumerator[symbol][tickType] = 0;
109+
}
110+
}
111+
112+
Assert.That(configs, Is.Not.Empty);
67113

68114
Action<BaseData> callback = (dataPoint) =>
69115
{
@@ -74,13 +120,18 @@ public void CanSubscribeAndUnsubscribeOnSecondResolution(string ticker, Security
74120

75121
switch (dataPoint)
76122
{
77-
case TradeBar _:
78-
dataFromEnumerator[typeof(TradeBar)] += 1;
123+
case TradeBar tb:
124+
dataFromEnumerator[tb.Symbol][typeof(TradeBar)] += 1;
79125
break;
80-
case QuoteBar _:
81-
dataFromEnumerator[typeof(QuoteBar)] += 1;
126+
case QuoteBar qb:
127+
Assert.GreaterOrEqual(qb.Ask.Open, qb.Bid.Open, $"QuoteBar validation failed for {qb.Symbol}: Ask.Open ({qb.Ask.Open}) <= Bid.Open ({qb.Bid.Open}). Full data: {DisplayBaseData(qb)}");
128+
Assert.GreaterOrEqual(qb.Ask.High, qb.Bid.High, $"QuoteBar validation failed for {qb.Symbol}: Ask.High ({qb.Ask.High}) <= Bid.High ({qb.Bid.High}). Full data: {DisplayBaseData(qb)}");
129+
Assert.GreaterOrEqual(qb.Ask.Low, qb.Bid.Low, $"QuoteBar validation failed for {qb.Symbol}: Ask.Low ({qb.Ask.Low}) <= Bid.Low ({qb.Bid.Low}). Full data: {DisplayBaseData(qb)}");
130+
Assert.GreaterOrEqual(qb.Ask.Close, qb.Bid.Close, $"QuoteBar validation failed for {qb.Symbol}: Ask.Close ({qb.Ask.Close}) <= Bid.Close ({qb.Bid.Close}). Full data: {DisplayBaseData(qb)}");
131+
dataFromEnumerator[qb.Symbol][typeof(QuoteBar)] += 1;
82132
break;
83-
};
133+
}
134+
;
84135
};
85136

86137
foreach (var config in configs)
@@ -92,7 +143,7 @@ public void CanSubscribeAndUnsubscribeOnSecondResolution(string ticker, Security
92143
}), _cancellationTokenSource.Token, callback: callback);
93144
}
94145

95-
Thread.Sleep(TimeSpan.FromSeconds(25));
146+
Thread.Sleep(TimeSpan.FromSeconds(60));
96147

97148
Log.Trace("Unsubscribing symbols");
98149
foreach (var config in configs)
@@ -104,20 +155,40 @@ public void CanSubscribeAndUnsubscribeOnSecondResolution(string ticker, Security
104155

105156
_cancellationTokenSource.Cancel();
106157

107-
Log.Trace($"{nameof(ThetaDataProviderTests)}.{nameof(CanSubscribeAndUnsubscribeOnSecondResolution)}: ***** Summary *****");
108-
Log.Trace($"Input parameters: ticker:{ticker} | securityType:{securityType} | resolution:{resolution}");
158+
var str = new StringBuilder();
109159

110-
foreach (var data in dataFromEnumerator)
160+
str.AppendLine($"{nameof(ThetaDataProviderTests)}.{nameof(CanSubscribeAndUnsubscribeOnSecondResolution)}: ***** Summary *****");
161+
162+
foreach (var symbol in symbols)
111163
{
112-
Log.Trace($"[{data.Key}] = {data.Value}");
164+
str.AppendLine($"Input parameters: ticker:{symbol} | securityType:{symbol.SecurityType} | resolution:{resolution}");
165+
166+
foreach (var tickType in dataFromEnumerator[symbol])
167+
{
168+
str.AppendLine($"[{tickType.Key}] = {tickType.Value}");
169+
170+
if (symbol.SecurityType != SecurityType.Index)
171+
{
172+
Assert.Greater(tickType.Value, 0);
173+
}
174+
// The ThetaData returns TradeBar seldom. Perhaps should find more relevant ticker.
175+
Assert.GreaterOrEqual(tickType.Value, 0);
176+
}
177+
str.AppendLine(new string('-', 30));
113178
}
114179

115-
if (securityType != SecurityType.Index)
180+
Log.Trace(str.ToString());
181+
}
182+
183+
private static string DisplayBaseData(BaseData item)
184+
{
185+
switch (item)
116186
{
117-
Assert.Greater(dataFromEnumerator[typeof(QuoteBar)], 0);
187+
case TradeBar tradeBar:
188+
return $"Data Type: {item.DataType} | " + tradeBar.ToString() + $" Time: {tradeBar.Time}, EndTime: {tradeBar.EndTime}";
189+
default:
190+
return $"DEFAULT: Data Type: {item.DataType} | Time: {item.Time} | End Time: {item.EndTime} | Symbol: {item.Symbol} | Price: {item.Price} | IsFillForward: {item.IsFillForward}";
118191
}
119-
// The ThetaData returns TradeBar seldom. Perhaps should find more relevant ticker.
120-
Assert.GreaterOrEqual(dataFromEnumerator[typeof(TradeBar)], 0);
121192
}
122193

123194
[TestCase("AAPL", SecurityType.Equity)]
@@ -152,7 +223,7 @@ public void MultipleSubscriptionOnOptionContractsTickResolution(string ticker, S
152223
case TickType.Quote:
153224
incomingSymbolDataByTickType[(tick.Symbol, tick.TickType)] += 1;
154225
break;
155-
};
226+
}
156227
}
157228
};
158229

0 commit comments

Comments
 (0)