An unofficial .NET wrapper for Millistream's low-latency, high-throughput and high-availability C/C++ streaming API that can be used to subscribe to streaming real-time or delayed financial data.
Package | Build Status | NuGet |
---|---|---|
Millistream.Streaming | ||
Millistream.Streaming.DataTypes |
Millistream.NET is distributed via NuGet. The native and wrapped API can be downloaded from Millistream's official website. Binaries are available for Linux, macOS and Windows. They come in both 32- and 64-bit versions and should work on both little- and big-endian systems. Please refer to the official documentation for more information about the wrapped API itself. The NuGet package does not include any native assemblies. You will have to download and install these separately.
On Windows you download and run an .exe that will install the native libmdf
core library along pre-built binaries of zlib and OpenSSL (libmdf
links against these). You can do this silently from a command prompt using Powershell:
powershell (new-object System.Net.WebClient).DownloadFile('https://packages.millistream.com/Windows/libmdf-1.0.28.exe', 'libmdf-1.0.28.exe')
.\libmdf-1.0.28.exe /S
On macOS you download and install a .pkg
file, for example in a Bash shell:
curl -O https://packages.millistream.com/macOS/libmdf-1.0.26.pkg
sudo installer -pkg libmdf-1.0.26.pkg -target /
On Linux, the native API and the dependent libraries are available through your distribution repository. Below is an example of how to install everything needed using the apt-get
command-line tool on Ubuntu:
sudo wget "https://packages.millistream.com/apt/sources.list.d/`lsb_release -cs`.list" -O /etc/apt/sources.list.d/millistream.list
wget -O- "https://packages.millistream.com/D2FCCE35.gpg" | gpg --dearmor | sudo tee /usr/share/keyrings/millistream-archive-keyring.gpg > /dev/null
sudo apt update
sudo apt-get install libmdf
Instructions on how to install the API on other supported distributions can be found on the here. You may also want to take a look at the YAML build pipeline in this repository. It installs the native binaries and runs integration tests against them on macOS, Ubuntu and Windows using the cloud-hosted runners in GitHub Actions.
Once you have installed the native libmdf
library on your computer, you can then install Millistream.NET into your project using NuGet:
PM> Install-Package Millistream.Streaming
Below is a basic example of how to use the .NET API to connect to a server and subscribe to some streaming data:
using System;
using System.Text;
using Millistream.Streaming;
using MarketDataFeed = Millistream.Streaming.MarketDataFeed<object, object>;
//1. Initialize the managed API and message handles.
using MarketDataFeed mdf = new();
using Message message = new();
//2. Register a connection status callback (optional).
mdf.StatusCallback = (data, status, host, ip) =>
Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - " +
$"Connection Status: {status}");
//3. Connect.
mdf.Connect("sandbox.millistream.com:9100");
//4. Send a MDF_M_LOGON message to log on.
message.Add(0, MessageReferences.MDF_M_LOGON);
message.AddString(Fields.MDF_F_USERNAME, "sandbox");
message.AddString(Fields.MDF_F_PASSWORD, "sandbox");
mdf.Send(message);
message.Reset();
//5. Consume and wait for the server to send a MDF_M_LOGONGREETING message.
if (!Consume(mdf, MessageReferences.MDF_M_LOGONGREETING))
{
Console.WriteLine("Failed to connect to the API.");
return;
}
Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - Logged in");
//6. Register a data callback (optional).
mdf.DataCallback = (data, handle) =>
{
while (handle.GetNextMessage(out ushort mref, out ulong insref))
{
Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - " +
"Received a message with the following fields:");
while (handle.GetNextField(out uint tag, out ReadOnlySpan<byte> value))
{
#if NETCOREAPP
Console.WriteLine($"Field: {tag}, Value: {Encoding.UTF8.GetString(value)}");
#else
Console.WriteLine($"Field: {tag}, Value: {Encoding.UTF8.GetString(value.ToArray())}");
#endif
}
}
};
//7. Request some data.
message.Add(0, MessageReferences.MDF_M_REQUEST);
message.AddList(Fields.MDF_F_REQUESTCLASS, // <- What kind of data to request.
RequestClasses.MDF_RC_BASICDATA + " " + // <- Basic data
RequestClasses.MDF_RC_QUOTE); // < ...and quotes in this case.
message.AddNumeric(
Fields.MDF_F_REQUESTTYPE, // <- The type of request.
RequestTypes.MDF_RT_FULL // <- Full (image+streaming) in this case.
);
message.AddList(
Fields.MDF_F_INSREFLIST, // <- What instrument identifier(s) the request is for.
"772"); // <- 772 is the unique identifier for Ericsson B.
mdf.Send(message);
message.Reset();
// 8. Consume until a key is pressed.
// NOTE: If you don't register a data callback by setting the DataCallback property,
// you should call the GetNextMessage and GetNextField methods after calling Consume
// below.
while (!Console.KeyAvailable)
{
if (mdf.Consume(1) == -1)
break;
}
// 9. Log off by sending an MDF_M_LOGOFF message (optional).
mdf.DataCallback = null; // Unregister the data callback before logging out.
message.Add(0, MessageReferences.MDF_M_LOGOFF);
mdf.Send(message);
if (Consume(mdf, MessageReferences.MDF_M_LOGOFF))
Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - Logged out");
// 10. Disconnect.
mdf.Disconnect();
// 11. Explictly or implictly dispose the managed handles.
static bool Consume(MarketDataFeed mdf, ushort messageReference)
{
DateTime time = DateTime.UtcNow;
do
{
int ret = mdf.Consume(1);
switch (ret)
{
case 1:
while (mdf.GetNextMessage(out ushort mref, out ulong _))
if (mref == messageReference)
return true;
break;
case -1:
return false;
}
} while (DateTime.UtcNow.Subtract(time).TotalSeconds < 10);
return false;
}
There is a separate Millistream.Streaming.DataTypes NuGet package that contains managed implementations of all the data types (String
, Time
, Date
, InsRef
, List
, Tabular
, Number
, Bool
, UInt
and BitField
) that are used in the native streaming API. All data types are implemented as immutable value types and declared as readonly structs.
This package targets .NET Standard 2.1. The reason for this is that it internally uses some of the built-in .NET types' TryParse
overloads that are only available in the .NET Core 3.x runtime.
Millistream.Streaming
targets .NET Standard 1.4 and .NET Framework 4.5 and has no dependency on Millistream.Streaming.DataTypes
.
Below is an example of how to parse the value of a ReadOnlySpan<byte>
received from the GetNextField
method of the MarketDataFeed
handle to a Number
:
Number? number;
if (handle.GetNextField(out Field field, out ReadOnlySpan<byte> value)
&& Number.TryParse(value, out Number parsedNumber)
{
number = parsedNumber;
}
Each data type contains Parse
and TryParse
methods that accept either a ReadOnlySpan<char>
or a ReadOnlySpan<byte>
. Just like with the built-in types, Parse
throws an ArgumentException
if the conversion fails and TryParse
returns false
.