Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ public void Should_Receive_Summary_When_ReqWatchMarketByPrice()
Assert.IsTrue(eventRaised.WaitOne());
}

[Test, MaxTime(TimeoutMs)]
public void Should_Receive_Summary_When_ReqWatchMarketByPrice_With_MaxPriceLevels()
{
// Arrange
var eventRaised = new ManualResetEvent(false);
_level2Client.PriceLevelSummary += message =>
{
eventRaised.Set();
};

// Act
_level2Client.ReqWatchMarketByPrice(Symbol, 10);

// Assert
Assert.IsTrue(eventRaised.WaitOne());
}

[Test, MaxTime(TimeoutMs)]
[Description("Ignore the test if market closed")]
public void Should_Receive_Update_When_ReqWatchMarketByPrice()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using IQFeed.CSharpApiClient.Streaming.Common.Messages;
using IQFeed.CSharpApiClient.Tests.Common;
using NUnit.Framework;

namespace IQFeed.CSharpApiClient.Tests.Streaming.Common.Messages
{
public class SymbolHasNoDepthAvailableMessageTests
{
[Test, TestCaseSource(typeof(CultureNameTestCase), nameof(CultureNameTestCase.CultureNames))]
public void Should_Parse_SymbolHasNoDepthAvailableMessage_Culture_Independent(string cultureName)
{
// Arrange
TestHelper.SetThreadCulture(cultureName);
var message = "q,MSFT";

// Act
var symbolHasNoDepthAvailableMessageParsed = SymbolHasNoDepthAvailableMessage.Parse(message);
var symbolHasNoDepthAvailableMessage = new SymbolHasNoDepthAvailableMessage("MSFT");

// Assert
Assert.AreEqual(symbolHasNoDepthAvailableMessageParsed, symbolHasNoDepthAvailableMessage);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,26 @@ public void Should_Receive_SymbolNotFound()
Assert.AreEqual(symbolNotFoundMessage, expectedMessage);
}

[Test]
public void Should_Receive_SymbolHasNoDepthAvailable()
{
// Arrange
var message = TestHelper.GetMessageBytes("q,MSFT\r\n");
var expectedMessage = new SymbolHasNoDepthAvailableMessage("MSFT");

SymbolHasNoDepthAvailableMessage symbolHasNoDepthAvailableMessage = null;
_level2MessageHandler.SymbolHasNoDepthAvailable += msg =>
{
symbolHasNoDepthAvailableMessage = msg;
};

// Act
_level2MessageHandler.ProcessMessages(message, message.Length);

// Assert
Assert.AreEqual(symbolHasNoDepthAvailableMessage, expectedMessage);
}

[Test]
public void Should_Receive_Error()
{
Expand Down Expand Up @@ -315,7 +335,7 @@ public void Should_Receive_PriceLevelSummaryMessages()
var message = TestHelper.GetMessageBytes("7,@ESM19,B,2938.25,65,11,2,20:31:04.876740,2019-04-23,\r\n");
TimeSpan.TryParseExact("20:31:04.876740", PriceLevelUpdateSummaryMessage.UpdateMessageTimeFormat, CultureInfo.InvariantCulture, TimeSpanStyles.None, out var time);
DateTime.TryParseExact("2019-04-23", PriceLevelUpdateSummaryMessage.UpdateMessageDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date);
var expectedMessage = new PriceLevelUpdateSummaryMessage(Level2MessageType.PriceLevelSummary, "@ESM19", Level2Side.Buy, 2938.25, 65, 10, 2, time, date);
var expectedMessage = new PriceLevelUpdateSummaryMessage(Level2MessageType.PriceLevelSummary, "@ESM19", Level2Side.Buy, 2938.25, 65, 11, 2, time, date);

PriceLevelUpdateSummaryMessage receivedMessage = null;
_level2MessageHandler.PriceLevelSummary += msg =>
Expand All @@ -337,7 +357,7 @@ public void Should_Receive_PriceLevelUpdateMessages()
var message = TestHelper.GetMessageBytes("8,@ESM19,B,2938.25,65,11,2,20:31:04.876740,2019-04-23,\r\n");
TimeSpan.TryParseExact("20:31:04.876740", PriceLevelUpdateSummaryMessage.UpdateMessageTimeFormat, CultureInfo.InvariantCulture, TimeSpanStyles.None, out var time);
DateTime.TryParseExact("2019-04-23", PriceLevelUpdateSummaryMessage.UpdateMessageDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date);
var expectedMessage = new PriceLevelUpdateSummaryMessage(Level2MessageType.PriceLevelUpdate, "@ESM19", Level2Side.Buy, 2938.25, 65, 10, 2, time, date);
var expectedMessage = new PriceLevelUpdateSummaryMessage(Level2MessageType.PriceLevelUpdate, "@ESM19", Level2Side.Buy, 2938.25, 65, 11, 2, time, date);

PriceLevelUpdateSummaryMessage receivedMessage = null;
_level2MessageHandler.PriceLevelUpdate += msg =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ public void Should_Parse_PriceLevelUpdateSummaryMessage_Culture_Independent(stri
TimeSpan.TryParseExact("20:31:04.876740", PriceLevelUpdateSummaryMessage.UpdateMessageTimeFormat, CultureInfo.InvariantCulture, TimeSpanStyles.None, out var time);
DateTime.TryParseExact("2019-04-23", PriceLevelUpdateSummaryMessage.UpdateMessageDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date);
var priceLevelUpdateMessage = new PriceLevelUpdateSummaryMessage(Level2MessageType.PriceLevelUpdate, "@ESM19", Level2Side.Sell, 2938.25, 65, 10, 2, time, date);
var priceLevelSummaryMessage = new PriceLevelUpdateSummaryMessage(Level2MessageType.PriceLevelSummary, "@ESM19", Level2Side.Buy, 2938.25, 65, 10, 2, time, date);
var priceLevelSummaryMessage = new PriceLevelUpdateSummaryMessage(Level2MessageType.PriceLevelSummary, "@ESM19", Level2Side.Buy, 2938.25, 65, 11, 2, time, date);

// Assert
Assert.AreEqual(priceLevelUpdateMessageParsed, priceLevelUpdateMessage);
Assert.AreEqual(priceLevelSummaryMessageParsed, priceLevelSummaryMessage);
}

[Test, TestCaseSource(typeof(CultureNameTestCase), nameof(CultureNameTestCase.CultureNames))]
public void Should_Parse_PriceLevelDeleteMessage_Culture_Independent(string cultureName)
public void Should_Parse_PriceLevelDeleteMessage_Ask_Culture_Independent(string cultureName)
{
// Arrange
TestHelper.SetThreadCulture(cultureName);
Expand All @@ -104,5 +104,22 @@ public void Should_Parse_PriceLevelDeleteMessage_Culture_Independent(string cult
Assert.AreEqual(priceLevelDeleteMessageParsed, priceLevelDeleteMessage);
}

[Test, TestCaseSource(typeof(CultureNameTestCase), nameof(CultureNameTestCase.CultureNames))]
public void Should_Parse_PriceLevelDeleteMessage_Bid_Culture_Independent(string cultureName)
{
// Arrange
TestHelper.SetThreadCulture(cultureName);
var priceLevelDeleteMessageString = "9,@ESM19,B,2938.25,20:31:04.876740,2019-04-23,";

// Act
var priceLevelDeleteMessageParsed = PriceLevelDeleteMessage.Parse(priceLevelDeleteMessageString);
TimeSpan.TryParseExact("20:31:04.876740", PriceLevelDeleteMessage.UpdateMessageTimeFormat, CultureInfo.InvariantCulture, TimeSpanStyles.None, out var time);
DateTime.TryParseExact("2019-04-23", PriceLevelDeleteMessage.UpdateMessageDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date);
var priceLevelDeleteMessage = new PriceLevelDeleteMessage(Level2MessageType.PriceLevelDelete, "@ESM19", Level2Side.Buy, 2938.25, time, date);

// Assert
Assert.AreEqual(priceLevelDeleteMessageParsed, priceLevelDeleteMessage);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using IQFeed.CSharpApiClient.Extensions;

namespace IQFeed.CSharpApiClient.Streaming.Common.Messages
{
public class SymbolHasNoDepthAvailableMessage
{
public SymbolHasNoDepthAvailableMessage(string symbol)
{
Symbol = symbol;
}

public string Symbol { get; private set; }

public static SymbolHasNoDepthAvailableMessage Parse(string message)
{
var values = message.SplitFeedMessage();
return new SymbolHasNoDepthAvailableMessage(values[1]);
}

public override bool Equals(object obj)
{
return obj is SymbolHasNoDepthAvailableMessage message &&
Symbol == message.Symbol;
}

public override int GetHashCode()
{
unchecked
{
return -1758840423 + Symbol.GetHashCode();
}
}

public override string ToString()
{
return $"{nameof(Symbol)}: {Symbol}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace IQFeed.CSharpApiClient.Streaming.Level2
public interface ILevel2Client : IClient, ILevel2Event, ILevel2Snapshot
{
void ReqWatch(string symbol);
void ReqWatchMarketByPrice(string symbol);
void ReqWatchMarketByPrice(string symbol, int? maxPriceLevels = null);
void ReqWatchMarketByOrder(string symbol);
void ReqMarketMakerNameById(string mmid);
void ReqUnwatch(string symbol);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public interface ILevel2Event
event Action<UpdateSummaryMessage> Summary;
event Action<UpdateSummaryMessage> Update;
event Action<SymbolNotFoundMessage> SymbolNotFound;
event Action<SymbolHasNoDepthAvailableMessage> SymbolHasNoDepthAvailable;
event Action<MarketMakerNameMessage> Query;
event Action<ErrorMessage> Error;
event Action<TimestampMessage> Timestamp;
Expand Down
9 changes: 7 additions & 2 deletions src/IQFeed.CSharpApiClient/Streaming/Level2/Level2Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public event Action<SymbolNotFoundMessage> SymbolNotFound
add => _level2MessageHandler.SymbolNotFound += value;
remove => _level2MessageHandler.SymbolNotFound -= value;
}
public event Action<SymbolHasNoDepthAvailableMessage> SymbolHasNoDepthAvailable
{
add => _level2MessageHandler.SymbolHasNoDepthAvailable += value;
remove => _level2MessageHandler.SymbolHasNoDepthAvailable -= value;
}
public event Action<ErrorMessage> Error
{
add => _level2MessageHandler.Error += value;
Expand Down Expand Up @@ -141,14 +146,14 @@ public void ReqWatch(string symbol)
_socketClient.Send(request);
}

public void ReqWatchMarketByPrice(string symbol)
public void ReqWatchMarketByPrice(string symbol, int? maxPriceLevels = null)
{
if (GetProtocolVersionAsNumber() <= 6.1M)
{
throw new Exception($"ReqWatchMarketByPrice is only supported in protocols 6.2 and above.");
}

var request = _level2RequestFormatter.ReqWatchMarketByPrice(symbol);
var request = _level2RequestFormatter.ReqWatchMarketByPrice(symbol, maxPriceLevels);
_socketClient.Send(request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class Level2MessageHandler : ILevel2MessageHandler
public event Action<UpdateSummaryMessage> Summary;
public event Action<UpdateSummaryMessage> Update;
public event Action<SymbolNotFoundMessage> SymbolNotFound;
public event Action<SymbolHasNoDepthAvailableMessage> SymbolHasNoDepthAvailable;
public event Action<MarketMakerNameMessage> Query;
public event Action<ErrorMessage> Error;
public event Action<TimestampMessage> Timestamp;
Expand Down Expand Up @@ -77,6 +78,10 @@ public void ProcessMessages(byte[] messageBytes, int count)
case 'n': // Symbol not found message
ProcessSymbolNotFoundMessage(message);
break;
case 'q': // No depth available message. (protocol 6.2 and above)
// This message is received when a symbol is valid but there is currently no depth available.
ProcessSymbolHasNoDepthAvailableMessage(message);
break;
case 'E': // An error message
ProcessErrorMessage(message);
break;
Expand Down Expand Up @@ -172,6 +177,12 @@ private void ProcessSymbolNotFoundMessage(string msg)
SymbolNotFound?.Invoke(symbolNotFoundMessage);
}

private void ProcessSymbolHasNoDepthAvailableMessage(string msg)
{
var symbolHasNoDepthAvailableMessage = SymbolHasNoDepthAvailableMessage.Parse(msg);
SymbolHasNoDepthAvailable?.Invoke(symbolHasNoDepthAvailableMessage);
}

private void ProcessErrorMessage(string msg)
{
var errorMessage = ErrorMessage.Parse(msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ public string ReqWatch(string symbol)
return $"w{symbol.ToUpper()}{IQFeedDefault.ProtocolTerminatingCharacters}";
}

public string ReqWatchMarketByPrice(string symbol)
public string ReqWatchMarketByPrice(string symbol, int? maxPriceLevels = null)
{
return $"WPL,{symbol.ToUpper()}{IQFeedDefault.ProtocolTerminatingCharacters}";
return maxPriceLevels.HasValue
? $"WPL,{symbol.ToUpper()},{maxPriceLevels.Value}{IQFeedDefault.ProtocolTerminatingCharacters}"
: $"WPL,{symbol.ToUpper()}{IQFeedDefault.ProtocolTerminatingCharacters}";
}

public string ReqWatchMarketByOrder(string symbol)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public PriceLevelDeleteMessage(
{
MessageType = messageType;
Symbol = symbol;
Side = Side;
Side = side;
Price = price;
Time = time;
Date = date;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public PriceLevelUpdateSummaryMessage(
Side = side;
Price = price;
Size = size;
orderCount = orderCount;
OrderCount = orderCount;
Precision = precision;
Time = time;
Date = date;
Expand Down