DEV Community

Cover image for Your Crypto Bot Isn't Slow but it's Lying to You
turboline-ai
turboline-ai

Posted on

Your Crypto Bot Isn't Slow but it's Lying to You

There's a specific kind of bug that doesn't show up in your logs. No stack trace, no exception, no obvious failure. Your bot runs, places trades, and loses money in a way that looks almost rational. The logic checks out. The math is right. But the data it was working with was already 800 milliseconds old when the decision was made.

This is the actual failure mode that kills most algorithmic trading bots in production. Not bad strategy. Stale data.

The Hidden Cost of Asking

REST polling feels safe because it's familiar. You write a loop, set an interval, fire a request, handle the response. The request-response cycle is something every developer understands intuitively. You're in control. You decide when to ask.

That control is exactly the problem.

When you poll a price endpoint every 500ms, you're not getting the current price. You're getting the price as it was at some point during the server's processing window, delivered after network round-trip time, parsed after deserialization. By the time your bot reads that number, the market has already moved. In a liquid market with real volatility, 500ms is not a slight delay. It is a meaningful chunk of history.

And here's the part that makes it worse: your bot doesn't know it's working with stale data. It treats that number as current. It makes a confident decision based on something that is no longer true. Polling doesn't just underperform in latency-sensitive scenarios. It actively misleads the systems that depend on it.

That said, REST has a real place in any trading system. Portfolio snapshots, audit logs, low-frequency alerts triggered once every few minutes. These are all workloads where polling is perfectly appropriate. The mistake isn't using REST. It's using it where the data has a shelf life measured in milliseconds.

Switching From Asking to Listening

The architectural shift to WebSockets sounds like a performance upgrade. In practice, it's a different model of control.

With polling, your code drives the data flow. With WebSockets, the exchange does. You open a persistent connection, subscribe to a channel, and messages arrive when they happen. This sounds like a small distinction, but it changes how you design nearly everything downstream.

Your bot's internal state now has to handle out-of-order messages. You need to think about what happens when a message arrives during a write operation. Your reconnection logic becomes critical infrastructure rather than an afterthought. Polling abstracts all of this away by nature. Each request is stateless. Each response is self-contained. You can crash and restart and nothing is lost except the polling interval.

WebSocket bots don't have that luxury. A dropped connection means missed messages. If you're tracking an order book and you miss an update, your local state is now wrong in a way that isn't obvious. You might not even know the gap exists.

Here's a minimal example of how a WebSocket price feed subscription might look in practice:

import websockets
import asyncio
import json

async def stream_price(symbol: str):
    uri = "wss://stream.exchange.com/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            "method": "subscribe",
            "params": [f"{symbol}@trade"]
        }))
        async for message in ws:
            data = json.loads(message)
            price = float(data["p"])
            timestamp = data["T"]
            process_tick(price, timestamp)

asyncio.run(stream_price("BTCUSDT"))
Enter fullscreen mode Exit fullscreen mode

This is the happy path. What the snippet doesn't show is the reconnection handler, the message sequence validator, the heartbeat monitor, and the exponential backoff logic. All of that is real work, and it's where most WebSocket implementations fall apart in production.

Where Real Implementations Break

Connection drops happen. Exchanges impose rate limits on subscriptions. Some platforms silently stop sending messages after a certain idle period without closing the socket. You need to detect that and respond to it.

Missed messages are trickier. Unlike a failed HTTP request, you won't necessarily get an error when a WebSocket message is lost. Your sequence tracking has to catch the gap, and your recovery logic has to decide whether to re-sync state, skip the gap, or halt and alert.

This is the reliability layer that separates a demo from a production bot. It's also the layer that most developers underestimate until they've watched a bot trade on stale state for six hours because a connection dropped silently at 3am.

Managing that persistent connection infrastructure is a significant engineering surface area. Turboline exists specifically to handle this layer, so you can subscribe to real-time exchange streams without rebuilding the connection management, delivery guarantees, and failure handling from scratch.

The Takeaway

The gap between a polling bot and a WebSocket bot isn't primarily about speed. It's about whether your bot is operating on the market as it is or as it was. Latency tolerance depends on your strategy, but most strategies assume the data they're working with is at least approximately current. Polling in a fast market doesn't give you that. It gives you a confident bot making decisions with a map of terrain that no longer exists.

The reliability work that comes with WebSockets is real and should be treated as a first-class engineering problem, not plumbing you figure out later. Build that layer carefully or use infrastructure that already has it. Your trade logic is only as good as the data flowing into it.

Top comments (0)