Handling Continuously Updating Market Data in Swift

I’ve been experimenting with processing continuously updating market data in Swift, mostly to understand how I should structure the networking and data layer for a small personal project.

The basic model is pretty simple:


struct MarketTick: Codable {
    let symbol: String
    let price: Double
    let timestamp: Int64
}

The part I’m less sure about is what happens when the data keeps arriving continuously.

My first approach was simply decoding each update and appending it to an array, but that obviously becomes less attractive as the amount of data grows.

I’ve been looking through different market-data implementations and API documentation, including BYDFi, to understand how continuously updated price data is normally structured.

In Swift, would it make more sense to keep a limited rolling buffer of recent values rather than retaining the entire stream?

Something roughly like:


var ticks: [MarketTick] = []
let maxTicks = 1000

func add(_ tick: MarketTick) {
    ticks.append(tick)

    if ticks.count > maxTicks {
        ticks.removeFirst(ticks.count - maxTicks)
    }
}

I’m also wondering whether an actor would be the better approach if updates are arriving asynchronously and another part of the application is reading the same data.

How would you normally structure this in modern Swift? Is a rolling in-memory buffer reasonable, or is there a more Swift-native pattern for this kind of continuously updating dataset?

For market data coalescing/conflation is in general the go to solution and just make the last available value available to subscribers per symbol. But YMMV depending on what your data source is.

For a real exchange colo MD connection this would look very differently, but for some over-the-internet streaming api just keeping the last received update in a dedicated task that processes the incoming data and sending a single "latest" update over a bounded (size 1, keep newest) asyncstream to a consumer will probably work just fine and then your consuming tasks can process data from that async stream.

This way you have no unbounded queues, which is the first thing to ensure.

1 Like