.NET SDK

Install HFTFeed.Client from NuGet, connect to the feed, subscribe to symbols and process ticks in a .NET 8 application.

HFTFeed.Client is the official .NET SDK for HFTFeed. It signs you in, runs the FIX 4.4 session for you (logon, heartbeats, reconnects) and hands you every price update as a .NET object. This page takes you from an empty project to a working feed. Every member, error text and limit is listed in the .NET SDK reference.

Before you start

You need:

  • An hftfeed.com account with an active plan. The SDK signs in with the same email and password you use on hftfeed.com. If you created your account with Google sign-in, set a password first (see Getting started). You can check your plan on the Subscriptions page and your connection details on the Connection page.
  • The .NET 8 SDK or later. The package contains builds for .NET 10 and .NET 8, so you can use it in .NET 8, 9 and 10 applications; .NET 9 applications use the .NET 8 build.
  • Outbound network access to fix-server.hftfeed.com on TCP port 9030 (or 9031), DNS to resolve that name, and HTTPS (port 443) to identitytoolkit.googleapis.com, which the SDK uses to sign you in (see What the SDK connects to if your firewall only allows the feed itself).
  • A free session. The feed accepts one live connection per account. Stop any other program that is connected with the same account before you start.

What the SDK connects to

The package opens two outbound connections and nothing else. It has no NuGet dependencies, no telemetry, and it never reads or writes a database.

DestinationPortWhenWhy
fix-server.hftfeed.com9030 or 9031Start()The FIX 4.4 market data session itself. Allow the name; the address behind it can change.
identitytoolkit.googleapis.com443 (HTTPS)Once per Start()Signs you in with your hftfeed.com email and password to read your Sender Comp ID, and to tell you immediately if the plan is inactive or expired.

The sign-in call is Google's standard Firebase sign-in endpoint, the same one the hftfeed.com website uses. Your password goes only there and to the feed server at logon; it is never sent anywhere else, and the SDK does not store it.

Skip the sign-in call. If your network allows only the feed, pass your Sender Comp ID (from the Connection page) to Start:

client.Start("C82");            // connects and logs on, no HTTPS call at all
await client.StartAsync("C82"); // asynchronous form

The only thing you lose is the early, explicit error when a plan has expired: the server refuses the logon instead.

What checks your credentials. The feed server verifies your email and password with Google's sign-in endpoint at logon and re-checks your plan every 60 seconds, so removing a plan ends a live session within a minute. The SDK's own decode of the sign-in response only decides which Sender Comp ID to present; the server never trusts it. Neither the SDK nor the feed server can read the hftfeed.com customer database: only the website does that, and it is what sets the plan your logon is checked against.

Install the package

The current version is 1.1.2. It has no dependencies on other NuGet packages. All versions are listed on nuget.org.

With the .NET CLI:

dotnet add package HFTFeed.Client --version 1.1.2

With the Package Manager Console in Visual Studio:

NuGet\Install-Package HFTFeed.Client -Version 1.1.2

Or add the reference to your project file yourself:

<ItemGroup>
  <PackageReference Include="HFTFeed.Client" Version="1.1.2" />
</ItemGroup>

All public types are in the HFTFeed.Client namespace.

Quick start

This console app connects, prints the symbol list, subscribes to EURUSD and XAUUSD, prints every tick and logs out cleanly when you press Ctrl+C.

Create a project and add the package. The project targets the .NET version of your SDK; any version from .NET 8 up works.

dotnet new console --name HftFeedQuickStart
cd HftFeedQuickStart
dotnet add package HFTFeed.Client --version 1.1.2

Replace the contents of Program.cs with:

using System.Globalization;
using HFTFeed.Client;

// Your hftfeed.com login, read from environment variables so it never ends up in source control.
var email = Environment.GetEnvironmentVariable("HFTFEED_EMAIL");
var password = Environment.GetEnvironmentVariable("HFTFEED_PASSWORD");
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
{
    Console.Error.WriteLine("Set HFTFEED_EMAIL and HFTFEED_PASSWORD first.");
    return 1;
}

using var client = new HFTFeedClient(email, password);

// Symbol key -> symbol. Filled before subscribing, then only read by the tick handler.
var symbolsByKey = new Dictionary<string, HFTFeedSymbol>();

client.AutoReconnect = true;
client.OnConnectionStatus = status => Console.WriteLine($"Connection: {status}");
client.OnSubscribe = notice => Console.WriteLine(notice);
client.OnError = error => Console.Error.WriteLine($"Error: {error.Message}");
client.OnNewTick = tick =>
{
    // Runs on the client's receive thread: keep it short.
    // Console output is fine for a demo; see "Process ticks off the receive thread" for production code.
    if (!symbolsByKey.TryGetValue(tick.Symbol, out var symbol)) return;
    var format = "F" + (int)symbol.Digits;
    Console.WriteLine(
        $"{tick.EntryTimeUtc:HH:mm:ss.fff} {symbol.SymbolName,-8} " +
        $"{tick.Bid.ToString(format, CultureInfo.InvariantCulture)} / " +
        $"{tick.Ask.ToString(format, CultureInfo.InvariantCulture)}");
};

try
{
    // Signs in, connects, logs on and loads the symbol list.
    await client.StartAsync();
}
catch (HFTFeedLogoutException ex) when (ex.IsTooManySessions)
{
    Console.Error.WriteLine("This account is already connected from another program.");
    return 1;
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Could not connect: {ex.Message}");
    return 1;
}

var symbols = client.GetSymbols();
Console.WriteLine($"{symbols.Length} symbols:");
foreach (var s in symbols)
{
    Console.WriteLine($"  {s.SymbolKey,-6} {s.SymbolName,-10} {s.Digits} digits");
    symbolsByKey[s.SymbolKey] = s;
}

// Choose symbols by name, subscribe by key.
foreach (var name in new[] { "EURUSD", "XAUUSD" })
{
    var symbol = symbols.FirstOrDefault(s => s.SymbolName == name);
    if (symbol is null)
        Console.WriteLine($"{name} is not in the symbol list.");
    else
        client.Subscribe(symbol.SymbolKey);
}

// Wait for Ctrl+C.
var stopRequested = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
Console.CancelKeyPress += (_, e) =>
{
    e.Cancel = true; // keep the process alive so the client can log out
    stopRequested.TrySetResult();
};
Console.WriteLine("Streaming. Press Ctrl+C to stop.");
await stopRequested.Task;

await client.StopAsync(); // sends a FIX Logout and closes the connection
Console.WriteLine($"Stopped. {client.TPM} ticks received in the last minute.");
return 0;

Set your login and run it. On macOS or Linux:

export HFTFEED_EMAIL="you@example.com"
export HFTFEED_PASSWORD="your-password"
dotnet run

On Windows (PowerShell):

$env:HFTFEED_EMAIL = "you@example.com"
$env:HFTFEED_PASSWORD = "your-password"
dotnet run

You should see Connection: CONNECTED, the symbol list, two Subscribed: notices and then one line per price update. Ticks only flow while the market for a symbol is open.

How the quick start works

Create a client

var client = new HFTFeedClient(email, password);                            // fix-server.hftfeed.com, port 9030
var viaAlternatePort = new HFTFeedClient(email, password, useAlternatePort: true); // same server, port 9031

Creating a client does not connect anything. Use port 9031 only when your network blocks 9030: both ports serve the same feed with the same login, and the one-session rule applies across both. A third constructor, HFTFeedClient(email, password, host, port), takes an explicit endpoint; you only need it if HFTFeed support gives you one.

Keep your password out of source code, as the quick start does. The feed connection is plain TCP without encryption and your password travels in the FIX Logon message, so use a password you don't use anywhere else.

Connect

Start() and its asynchronous twin StartAsync() do four things in order and return once the last one is done:

  1. Sign in over HTTPS with your email and password, and read your SenderCompID and plan status (up to 20 seconds).
  2. Open a TCP connection to the server (up to 10 seconds).
  3. Send the FIX Logon and wait for the server to accept it (up to 10 seconds).
  4. Request the symbol list and wait for it (up to 10 seconds).

After that, ConnectionStatus is CONNECTED and OnConnectionStatus has fired. If any step fails, the method throws; see Handle errors. You can pass a CancellationToken to StartAsync to give up earlier:

using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await client.StartAsync(timeout.Token); // throws OperationCanceledException when the token fires

A Start() that throws has already closed its connection and does not start automatic reconnects, so you can call it again. Leave time between attempts: every call opens a new connection, and the server accepts only a few per minute from one IP address (see Stay within the connection limit).

Read the symbol list

GetSymbols() returns the list the server sent during Start(). Each HFTFeedSymbol has:

PropertyTypeMeaning
SymbolKeystringThe server's numeric id for the symbol, as text (for example "1013"). Pass it to Subscribe. Ticks carry it in Md.Symbol.
SymbolNamestringThe instrument name, for example EURUSD. Show it to people.
DigitsdoubleNumber of decimals in the symbol's prices. Cast it to int when you format prices.

Look symbols up by name once, after Start(), and keep the keys:

var eurusd = client.GetSymbols().FirstOrDefault(s => s.SymbolName == "EURUSD")
    ?? throw new InvalidOperationException("EURUSD is not available.");
var format = "F" + (int)eurusd.Digits; // "F5" for a 5-decimal symbol

The list is empty until the first successful Start() and is reloaded after every automatic reconnect. Treat the returned array as read-only.

Subscribe to symbols

client.Subscribe(eurusd.SymbolKey);       // start streaming one symbol
client.Unsubscribe(eurusd.SymbolKey);     // stop streaming it
client.SubscribeAll();                    // every symbol in the list, in one request
client.UnsubscribeAll();                  // cancel everything, in one request
client.RequestSnapshot(eurusd.SymbolKey); // one tick with the latest price, without subscribing

Things to know:

  • Pass the key, not the name. Subscribe("EURUSD") is refused by the client with the notice Wrong symbol key: EURUSD.
  • The methods don't wait for the server. They send the request and return. Local results arrive on OnSubscribe and OnUnsubscribe (for example Subscribed: 1013 or Already subscribed: 1013). The server does not confirm a subscription; ticks simply start to arrive. If the server refuses one, OnError receives an error such as MarketDataRequest rejected: Too many subscriptions.
  • Call them after Start() has succeeded. Otherwise nothing is sent and OnError receives Not connected: call Start() before subscribing.
  • One session can hold up to 64 subscriptions. SubscribeAll() asks for every symbol in the list; if the list is longer than that, the server refuses the rest.
  • The server accepts at most 50 messages per second from a session. Each Subscribe call sends one message, so pace long lists:
foreach (var name in new[] { "EURUSD", "GBPUSD", "USDJPY", "XAUUSD" })
{
    var symbol = client.GetSymbols().FirstOrDefault(s => s.SymbolName == name);
    if (symbol is null) continue;
    client.Subscribe(symbol.SymbolKey);
    await Task.Delay(TimeSpan.FromMilliseconds(50)); // at most 20 requests per second
}
  • A refused subscription still counts as subscribed in the client. To try again, call Unsubscribe(key) and then Subscribe(key).

Handle ticks

Every price update arrives on OnNewTick as an Md object:

PropertyTypeMeaning
SymbolstringThe symbol key, for example "1013", not the name. Map it with your lookup from GetSymbols().
Bid, AskdoubleBest bid and best ask. The server sends them with the symbol's Digits decimals.
BidSize, AskSizedoubleSize available at the bid and at the ask, as reported by the upstream source. 0 when the source does not provide sizes.
EntryTimeUtcDateTime (UTC)Time of the price, to the millisecond: the upstream source's timestamp when it provides one, otherwise the time HFTFeed's server received the price. DateTime.MinValue when the message carries no time.

Each tick is a complete top-of-book snapshot for one symbol: both sides, current values. By default every callback gets a new Md object that you may keep. If you turn on ReuseTickObject, the same object is reused for every tick, so copy the values you need instead (see the next sections).

If you compare EntryTimeUtc with DateTime.UtcNow, the result includes the difference between your clock and the source's clock. Keep your machine's clock synchronized (NTP or PTP) before you draw conclusions from it.

Stop

await client.StopAsync(); // or client.Stop()
client.Dispose();         // Stop() plus cleanup; this is what `using` calls

Stop() sends a FIX Logout, closes the connection, sets ConnectionStatus to DISCONNECTED, stops automatic reconnects and clears the client's list of subscriptions. It is safe to call more than once, and before Start(). Always stop the client before your process exits: the server allows one live session per account, and a session that never logged out keeps that slot until the server notices the connection is gone.

You can start a stopped client again (a disposed one can't be). It starts without subscriptions, so subscribe again once Start() has returned:

await client.StopAsync();
// ... later ...
await client.StartAsync(cancellationToken);
client.Subscribe(eurusd.SymbolKey); // SubscribedSymbolsCount was reset to 0 by Stop()

Keep the connection alive

client.AutoReconnect = true;
client.OnConnectionStatus = status =>
{
    if (status == ConnectionStatus.CONNECTED)
        Console.WriteLine("Feed connected.");
    else
        Console.WriteLine("Feed disconnected.");
};

AutoReconnect is off by default. When it is on and the connection drops unexpectedly (a network failure, a server restart, a heartbeat timeout), the client:

  1. waits a random delay before each attempt, 1 to 2 seconds before the first and doubling up to between 15 and 30 seconds, and keeps trying. When another attempt would go over the connection limit, it also waits until one is allowed;
  2. connects and logs on again with the same account, reloads the symbol list and raises OnConnectionStatus(CONNECTED);
  3. subscribes again to everything you had subscribed, in one request, and raises OnSubscribe with Resubscribed: <n> symbols.

Every failed attempt is reported on OnError. The client does not reconnect after Stop() or Dispose() (both also end an attempt or a wait that is in progress), or when the server ended the session with a Logout (for example an expired plan, too many sessions or slow consumer): those need your attention, and reconnecting would only be refused again. Automatic reconnects also never start from a Start() that failed; Start() throws instead. You can change AutoReconnect at any time.

The client also keeps a quiet session open: it sends a heartbeat whenever it has sent nothing for HeartbeatSeconds (30 by default) and answers the server's test requests. The server closes a session it hasn't heard from for two heartbeat intervals. If you change HeartbeatSeconds, do it before Start() and keep it between 5 and 60.

The client notices a drop when the connection closes; it does not time out a silent connection by itself. If your application must react within seconds to a stalled feed, watch client.TPS or the time of your last tick during market hours.

Stay within the connection limit

The server accepts at most 6 new connections per minute from one IP address. It closes any connection over that limit at once, without an answer, and gives the address a strike. One strike expires every 10 minutes; 3 strikes block the address for 5 minutes, 6 for an hour and 9 for a day. Every program and machine that shares your public IP address shares this limit.

The SDK keeps you below it: it opens at most 5 connections to a host in any 60 seconds, counted across all HFTFeedClient objects in your process. Every connection the server accepts counts, whether it comes from Start() or from an automatic reconnect and even if the logon then fails, and so does a connection that is still being opened. Attempts that never reach the server (connection refused, host unreachable, connect timeout) don't count, so while the server restarts the client keeps reconnecting on its normal schedule.

  • Automatic reconnects wait until another connection is allowed. Stop() and Dispose() end the wait at once.
  • Start() and StartAsync() never wait. When 5 connections are already in the window, they throw HFTFeedThrottledException without connecting. Its message names the limit and the number of seconds to wait, and its RetryAfter property holds the same wait:
try
{
    await client.StartAsync(cancellationToken);
}
catch (HFTFeedThrottledException ex)
{
    Console.Error.WriteLine(ex.Message);
    await Task.Delay(ex.RetryAfter, cancellationToken);
    await client.StartAsync(cancellationToken);
}

The SDK counts per process and per host name as you pass it to the constructor. Two programs on the same machine each count their own 5 attempts, so run one feed program per machine, or start them at different times.

If the server closes the connection while Start() is logging on, Start() throws an HFTFeedException whose message says so. That usually means your IP address went over the limit or is blocked. Wait a few minutes before you try again.

Process ticks off the receive thread

The client reads from the network on one background thread and calls OnNewTick on that thread, one tick at a time, in the order the server sent them. While your handler runs, the client reads nothing. If your handlers are slow, data backs up: the server first merges queued updates so you only get the latest price per symbol, and if your session's queue stays full for 10 seconds it logs you out with slow consumer.

So keep OnNewTick short and never block in it: no file or network I/O, no database calls, no Thread.Sleep, no waiting on locks that slow code holds. Copy the values you need and hand them to your own worker, for example through a channel, which is built into .NET:

using System.Threading.Channels;
using HFTFeed.Client;

public readonly record struct Quote(string SymbolKey, double Bid, double Ask, DateTime EntryTimeUtc);

public sealed class QuotePump
{
    private readonly Channel<Quote> _quotes = Channel.CreateBounded<Quote>(
        new BoundedChannelOptions(100_000)
        {
            SingleReader = true,
            FullMode = BoundedChannelFullMode.DropOldest, // never block the receive thread
        });

    public void Attach(HFTFeedClient client)
    {
        client.ReuseTickObject = true; // one Md instance for every tick: copy it, never keep it
        client.OnNewTick = tick =>
            _quotes.Writer.TryWrite(new Quote(tick.Symbol, tick.Bid, tick.Ask, tick.EntryTimeUtc));
    }

    public async Task RunAsync(CancellationToken cancellationToken)
    {
        await foreach (var quote in _quotes.Reader.ReadAllAsync(cancellationToken))
        {
            // Your strategy, storage or UI update goes here.
            Console.WriteLine($"{quote.SymbolKey} {quote.Bid} / {quote.Ask}");
        }
    }
}

Attach the pump before you start the client:

var pump = new QuotePump();
pump.Attach(client);
var worker = pump.RunAsync(cancellationToken);
await client.StartAsync(cancellationToken);

DropOldest discards the oldest quotes when your worker falls behind, which keeps the newest prices; choose the policy that suits your application, but don't let the receive thread wait.

The other callbacks run on other threads:

  • OnSubscribe and OnUnsubscribe run on the thread that called Subscribe, Unsubscribe and the other subscription methods, before the method returns. Resubscribed notices come from the reconnect thread.
  • OnConnectionStatus and OnError can run on the receive thread, on the client's timer or reconnect threads, or on your own thread.

Your handlers can therefore run at the same time on different threads, so make them thread-safe. An exception thrown in a handler is caught and passed to OnError, and the client keeps running. Don't call Start(), Stop() or Dispose() from inside a handler; signal your own code instead. In desktop apps (WPF, WinForms, MAUI), use StartAsync() so the UI thread isn't blocked while connecting, and marshal updates to the UI thread yourself.

Handle errors

Start() and StartAsync() throw when they can't connect:

ExceptionCause
HFTFeedAuthExceptionSign-in failed: wrong email or password, no active plan (No active HFTFeed subscription. …), an expired plan (HFTFeed subscription expired. …), an account without a SenderCompID yet, or no HTTPS access to the sign-in service.
HFTFeedLogoutExceptionThe server refused the logon. Text holds its reason; IsTooManySessions is true when the account is already connected.
HFTFeedTimeoutExceptionThe connection, the logon or the symbol list took longer than 10 seconds.
HFTFeedThrottledExceptionYour process already opened 5 connections to the server in the last 60 seconds. Nothing was sent. Wait RetryAfter; see Stay within the connection limit.
HFTFeedExceptionThe server closed the connection during the logon without saying why, usually because your IP address made too many connection attempts or is blocked.
SocketExceptionThe server could not be reached, for example because a firewall refused the connection.
OperationCanceledExceptionYour cancellation token fired, or the HTTPS sign-in took longer than 20 seconds.

HFTFeedAuthException, HFTFeedLogoutException, HFTFeedThrottledException and HFTFeedTimeoutException all derive from HFTFeedException. Once connected, the client never throws from its background threads: every problem goes to OnError. A Logout from the server arrives there as an HFTFeedLogoutException, followed by OnConnectionStatus(DISCONNECTED). The reference lists every reason the server can send and what to do about it.

If another program still holds your session, the server releases it when it notices that connection is gone: at once if that program exited, or after two heartbeat intervals (60 seconds by default) if its machine or network went away. A short, bounded retry covers that case:

for (var attempt = 1; ; attempt++)
{
    try
    {
        await client.StartAsync(cancellationToken);
        break;
    }
    catch (HFTFeedLogoutException ex) when (ex.IsTooManySessions && attempt < 6)
    {
        await Task.Delay(TimeSpan.FromSeconds(15), cancellationToken);
    }
}

Don't retry automatically after Invalid credentials or HFTFeedAuthException. Repeated failed logons temporarily block your account and your IP address, and every retry counts toward the connection limit.

Production checklist

  • Load your login from configuration or a secret store, and never log it. The outgoing Logon message that OnFixMsg shows contains your password.
  • Use one HFTFeedClient per account, and dispose it when your application shuts down.
  • Set OnError and log everything it reports.
  • Turn on AutoReconnect, and handle the server logouts it deliberately does not retry.
  • If your code retries Start(), catch HFTFeedThrottledException and wait RetryAfter. Don't create a new client for every attempt, and run one feed program per IP address where you can.
  • Keep OnNewTick short and hand the data to your own worker.
  • Set ReuseTickObject = true once your handler copies what it needs.
  • Leave OnFixMsg unset except while you debug.
  • Leave BusyPollMicroseconds at 0 unless the machine has a CPU core to spare (see Performance and latency).
  • Build your symbol lookups once after Start(), not on every tick.
  • Stay within 64 subscriptions and 50 messages per second.
  • Monitor ConnectionStatus, TPS and TPM.

The reference has more on latency and a troubleshooting table.

Upgrading from 1.0.x

  1. Update the package with dotnet add package HFTFeed.Client --version 1.1.2, or change Version in your PackageReference to 1.1.2. The package now contains a .NET 10 build next to the .NET 8 one; NuGet picks the right one for your project.
  2. Rebuild. By default, the client connects to fix-server.hftfeed.com on port 9030.
  3. Since 1.1.0 the package no longer depends on NetCoreServer, FirebaseAuthentication.net or System.IdentityModel.Tokens.Jwt. If your own code uses any of them, add a direct package reference.

Almost all code compiles unchanged: every 1.0.x member of HFTFeedClient, HFTFeedSymbol, Md and ConnectionStatus keeps its name, signature and meaning. Two things were removed:

  • The MessageBuilder class, which built raw FIX strings. Use the HFTFeedClient methods, or build messages yourself as described in the FIX protocol reference.
  • Inheritance from HFTFeedClient: the class is now sealed. Wrap it in your own class instead.

What behaves differently:

  • Start() returns as soon as the symbol list has arrived; 1.0.x spent about three seconds in internal waits.
  • The client answers the server's test requests, so a quiet session is no longer dropped.
  • TPS and TPM count the ticks of the last second and the last minute at the moment you read them.
  • Exceptions thrown in your handlers are caught and passed to OnError.
  • The client opens at most 5 connections per minute; see Stay within the connection limit. Code that calls Start() in a loop must handle HFTFeedThrottledException.
  • Stop() clears the subscription list. Subscribe again after the next Start().

Removed in 1.2.0: HFTFeedSymbol.ActiveSource and Md.SourceId. Both reported the upstream venue behind a price, which is not part of the product; the server no longer sends the underlying tags. Delete any use of them — the rest of the API is unchanged, and an older client keeps working, it simply reports an empty source.

New in 1.1.0: StartAsync, StopAsync, Dispose (the client is now IDisposable), OnError, AutoReconnect, HeartbeatSeconds, ReuseTickObject, RequestSnapshot, Connected and IsConnected, Host, Port and ClientId, the useAlternatePort constructor, the Md properties BidSize, AskSize and EntryTimeUtc, and the exception types HFTFeedException, HFTFeedAuthException, HFTFeedLogoutException and HFTFeedTimeoutException. New in 1.1.1: BusyPollMicroseconds, ReceiveBufferSize and HFTFeedThrottledException. New in 1.1.2: HFTFeedClient.DefaultHost is the name fix-server.hftfeed.com instead of an address.

Upgrading from 1.1.0 or 1.1.1

Update the package to 1.1.2 as shown in Install the package. Your code compiles unchanged.

From 1.1.1 the only change is the default host: HFTFeedClient.DefaultHost is the name fix-server.hftfeed.com instead of a fixed address, so the feed can move without another SDK release. Make sure the machine can resolve that name, and allow the name in your firewall rather than an address. If your code passes an explicit host, pass the name.

Coming from 1.1.0, that change applies too, and so does everything below:

  • Connections are limited to 5 per host in any 60 seconds, across all clients in your process, so that reconnects can't get your IP address blocked. Attempts the server never received (refused, unreachable, timed out) don't count. Automatic reconnects now wait 1 to 2 seconds before the first attempt and wait for the limit when needed. Start() throws HFTFeedThrottledException instead of connecting when no attempt is left. See Stay within the connection limit.
  • A stopped client can be started again. Stop() clears the subscription list, so Subscribe() after the next Start() sends a request and ticks arrive. You no longer need a new client for every session.
  • A logon the server cuts off fails at once. When the server closes the connection during the logon, Start() throws an HFTFeedException that says so, instead of an HFTFeedTimeoutException after 10 seconds. A failed Start() closes its connection and doesn't start automatic reconnects.
  • Out-of-range tick times no longer drop the connection. A tick whose date and time lie beyond DateTime.MaxValue gets EntryTimeUtc = DateTime.MinValue.
  • Faster receiving. Ticks are decoded in a single pass, and on Linux and macOS the receive thread waits for data without allocating memory.
  • Two opt-in settings: BusyPollMicroseconds and ReceiveBufferSize. Both are off by default; see Performance and latency.