Why Real-Time Market Data Matters
Traders lose an average of 0.3% per minute when they receive price updates even a second late, according to a 2023 industry analysis. In a landscape where milliseconds dictate profit, a robust pipeline that pushes stock quotes instantly to browsers is no longer a nice‑to‑have; it is a competitive imperative.
Architectural Overview
The solution hinges on four Azure‑native components: Event Hubs as the ingestion backbone, a .NET 8 background service that reads the feed, SignalR for WebSocket delivery, and Redis for low‑latency caching of the latest ticker snapshot. Each piece is loosely coupled, which lets you scale the producer, hub, or cache independently.
Setting Up Azure Event Hubs
Start with a Standard tier Event Hub namespace. A single partition can sustain ~1 000 msg/s, but a live market feed often exceeds 5 000 msg/s, so allocate at least three partitions. Use the Azure portal or Azure CLI:
az eventhubs namespace create --name StockNamespace --resource-group MyRG --location eastus --sku Standard
az eventhubs eventhub create --name StockHub --namespace-name StockNamespace --partition-count 3Record the connection string; you will need it in the .NET configuration.
Implementing the .NET 8 Producer
The producer pulls raw quotes from a third‑party TCP socket, transforms them into a JSON payload, and pushes them to Event Hubs. Use the new EventHubProducerClient introduced in .NET 8 for async batch sending.
using Azure.Messaging.EventHubs;
using Azure.Messaging.EventHubs.Producer;
using System.Text;
using System.Text.Json;
public class StockPublisher : BackgroundService
{
private readonly EventHubProducerClient _producer;
public StockPublisher(IConfiguration config)
{
var connection = config["EventHubs:ConnectionString"];
var hubName = config["EventHubs:HubName"];
_producer = new EventHubProducerClient(connection, hubName);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var quote = await ReceiveFromTcpAsync(); // implement per vendor spec
var eventData = new EventData(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(quote)));
await _producer.SendAsync(new[] { eventData }, stoppingToken);
await Task.Delay(10, stoppingToken); // throttle to ~100 msg/s per socket
}
}
}
Register the service in Program.cs with builder.Services.AddHostedService<StockPublisher>();.
Streaming to Clients with SignalR
SignalR abstracts WebSocket handling and automatically falls back to Server‑Sent Events when needed. Create a hub that subscribes to Event Hubs and forwards each message to connected browsers.
using Microsoft.AspNetCore.SignalR;
using Azure.Messaging.EventHubs.Consumer;
public class MarketHub : Hub
{
private readonly EventHubConsumerClient _consumer;
public MarketHub(IConfiguration config)
{
var connection = config["EventHubs:ConnectionString"];
var hubName = config["EventHubs:HubName"];
_consumer = new EventHubConsumerClient(EventHubConsumerClient.DefaultConsumerGroupName, connection, hubName);
}
public override async Task OnConnectedAsync()
{
_ = Task.Run(async () =>
{
await foreach (var partitionEvent in _consumer.ReadEventsAsync())
{
var payload = Encoding.UTF8.GetString(partitionEvent.Data.Body.ToArray());
await Clients.All.SendAsync("ReceiveQuote", payload);
}
});
await base.OnConnectedAsync();
}
}
In Program.cs map the hub: app.MapHub<MarketHub>("/marketHub");. On the client side, a simple JavaScript snippet connects and updates a table in real time.
Caching Snapshots in Redis
While SignalR pushes every tick, most dashboards also need the latest price without waiting for the next push. Store the most recent quote per ticker in a Redis hash. The .NET Redis client (StackExchange.Redis) makes this trivial.
using StackExchange.Redis;
public class QuoteCache
{
private readonly IDatabase _db;
public QuoteCache(IConnectionMultiplexer mux)
{
_db = mux.GetDatabase();
}
public async Task UpdateAsync(string symbol, string json)
{
await _db.HashSetAsync("latestQuotes", symbol, json);
}
public async Task GetAsync(string symbol)
{
return await _db.HashGetAsync("latestQuotes", symbol);
}
}
Inject IConnectionMultiplexer via builder.Services.AddSingleton(ConnectionMultiplexer.Connect(...)); and call UpdateAsync from the Event Hub consumer loop before broadcasting.
Putting It All Together
1. Azure Event Hubs ingests raw quotes.
2. The .NET 8 background service normalizes and pushes events.
3. The SignalR hub consumes events, updates Redis, and pushes to browsers.
4. Front‑end JavaScript subscribes to ReceiveQuote and falls back to a quick Redis lookup for the current snapshot.
Deploy the API to Azure App Service (Linux) with the dotnet publish command, and spin up an Azure Cache for Redis instance in the same region to minimize latency (typical round‑trip < 2 ms).
Performance Tips and Monitoring
Enable Azure Monitor for Event Hubs to watch incoming throughput; set an alert at 80 % of partition capacity. Use ILogger in the producer and hub to capture serialization latency – aim for < 5 ms per message. In Redis, enable maxmemory-policy allkeys‑lrul to automatically evict the oldest quotes when memory hits 80 % of the allocated quota.
Conclusion
By combining .NET 8’s asynchronous pipelines, Azure Event Hubs’ high‑throughput ingestion, SignalR’s real‑time push model, and Redis’s sub‑millisecond caching, you can deliver a stock market feed that stays within the sub‑second window demanded by modern traders. The modular design also future‑proofs the architecture: swap the producer for a Kafka source, add authentication to the hub, or scale Redis horizontally without rewriting core logic.
Sources
- Microsoft Docs – Azure Event Hubs
- Microsoft Docs – ASP.NET Core SignalR
- StackExchange.Redis Documentation
Author: Mahmut Sarıkaya — sarikayadev.com