Skip to content

Keep stream processing bounded

A price consumer that falls behind acts on old information. Measure queue age and processing time before changing buffer sizes or adding worker tasks.

Choose a retention policy

Data Useful policy
Current prices for a dashboard Keep the latest record per instrument
A rolling indicator window Keep a bounded history with an explicit sampling rule
Transactions used to reconstruct account state Persist IDs and reconcile gaps through history

Increasing a buffer may delay the point at which it fills while making its oldest records less useful. OANDA's pricing stream is sampled; see streaming semantics before treating it as a tick archive.

Retain a bounded price history

The following helper collects a finite number of price records. It filters heartbeats, retains at most 100 prices, and closes the stream on early exit:

from collections import deque
from contextlib import aclosing

from fivetwenty import AsyncClient
from fivetwenty.models import ClientPrice


async def collect_prices(client: AsyncClient, count: int = 10) -> list[ClientPrice]:
    if count < 1:
        message = "count must be positive"
        raise ValueError(message)
    history: deque[ClientPrice] = deque(maxlen=100)
    stream = client.pricing.get_pricing_stream(client.account_id, ["EUR_USD"])
    received = 0
    async with aclosing(stream):
        async for record in stream:
            if isinstance(record, ClientPrice):
                history.append(record)
                received += 1
                if received >= count:
                    break
    return list(history)

A record limit is not a wall-clock deadline: a quiet or closed market may produce heartbeats without prices. Wrap the helper with asyncio.wait_for when the task also needs a time limit.

Keep slow work off the read path

Avoid blocking file writes, synchronous HTTP calls and expensive calculations in the stream loop. Use a bounded work queue and a fixed number of consumers. Decide whether a full queue blocks, replaces an older price or stops processing; do not silently drop transaction records.

The synchronous pricing iterator already drops the oldest queued records when its 1,024-record queue fills. Its behavior suits current-price consumers, not lossless capture. The async interfaces give your application control over processing but do not eliminate network buffers or prevent data loss during a disconnect.

Measure what matters

Track receipt-to-processing delay, queue length, discarded records, stream restarts and time since the last record. Distinguish missing prices from missing heartbeats. A low average callback duration can hide occasional long stalls, so record tail latencies as well.