Building a Trading Robot (Part 2): Market Data Pipelines and Signal Generation
We walked through the minimal architecture: quotes module → prompt building → model call → result storage → web UI and notifications. We also mentioned an optional context layer — news that can be added to the prompt as short summaries.
In part two there will be less “in general” and more practice: which data you actually need at the start, where exchange APIs bite, what to store, how not to smash into limits, and why without caching, logging, and guardrails your advisor will fail exactly when you rely on it.
What market data to start with
At the beginning you have three main classes of market data you can fetch from an exchange via API. The most common and easiest is OHLCV candles: Open/High/Low/Close plus volume per interval. This is a “compressed” view of price that’s convenient to store, cache, and pass into a model as a stable context slice (for example, the last N candles of a chosen timeframe1).
If you want to see what happens inside a candle, you can add the trade tape (trades) — a stream of executions: price, size, time (sometimes side). This layer adds more granularity about activity and impulses and is usually used as a “finer” source than candles.
The third option is the order book: bid/ask levels and sizes at each level. These are market microstructure and liquidity data: not “what happened”, but “what is currently queued”. People add it as an extra context layer when they want to account for depth, bid/ask imbalance, and reactions around levels.
For now we’ll stick to the simplest option — OHLCV candles. In your own implementation you can add trades and the order book, but the goal of this article is educational: build a clear skeleton and avoid drowning in details.
A unified data format and history
As soon as you start pulling quotes from more than one source, an unpleasant reality shows up: different sources return the same data in different formats. Somewhere a candle is an array of numbers, somewhere it’s an object with fields, somewhere timestamps are milliseconds, somewhere seconds — and field names and ordering can differ even across “similar” APIs.
If you don’t introduce a unified internal format, the system quickly turns into a pile of special cases: every new source brings its own parsers, exceptions, and “one-off” handling. That’s why the quotes module almost always needs a common internal representation — the minimal set required for downstream analysis. At our current level it’s typically: instrument, timeframe1, timestamp2, Open/High/Low/Close, and Volume.
It’s also worth calling out historical data. “The current market state” is nice, but without history you can’t expand analysis, test hypotheses, or even explain to a user why a signal looks the way it does. So even in a simple implementation it’s worth storing candle history (or being able to load it fast) and recording which exact inputs formed a particular signal.
Rate limits: why they matter
Almost every exchange has limits on how frequently you can call the API — rate limits. The reason is simple: if everyone could pull quotes endlessly, the API would fall over even without any DDoS.
For an advisor this is doubly important. Requests are not continuous — they run on a timer — and if you pick a wrong cadence or multiply it by the number of instruments, you’ll hit the limit very fast. The outcome is usually the same: 429 errors / key bans, gaps in data, and silence exactly when the market moves the most.
The pragmatic approach is to budget requests up front (cadence × instruments × data types), cache results, avoid fetching the same thing twice, and handle limits properly (backoff / retry after a pause instead of spamming requests).
Prompts and models: same API, different rules
With data fetching covered, we can move to the next level: prompts and working with models.
At first glance it looks very similar to pulling quotes: again an API, again rate limits, again you need to think about retries, timeouts, and caching. The only difference is that instead of “give me candles” your request becomes “here is the data, here is context — return a signal and an explanation”.
Then the differences begin. For an exchange the answer is a data structure; for a model it’s an interpretation. That’s why the prompt becomes a contract, not just “text”, and you should treat it like a data format: version it, validate it, constrain it, and log it.
A small feature that makes integration much easier: you can predefine the response structure in the prompt. For example, you can ask the model to return strictly valid JSON with fields like signal, confidence, and reasons. It’s not “magic”, but it disciplines the output format and lets your application parse and validate responses automatically instead of scraping free-form text.
It’s also worth mentioning MCP (Model Context Protocol): an approach/protocol that helps standardize how models receive context and how external data sources and tools are connected. Even if today you call a specific provider API directly, keeping an MCP-like layer in mind is useful — it forces better boundaries and makes future expansion easier.
And yes — in the long run the “analyst” does not have to be an external service. If you later have resources and motivation, this module can be replaced with your own model (or a fine-tuned open-source one) without rewriting the rest of the system — as long as interfaces were separated correctly from the start.
Storage: simpler than it looks
At this point we already have at least two “buckets” of data: market candles and analysis results. Looking ahead, you’ll add news history (and summaries), plus storing prompts/model responses for debugging and reruns. In other words, there’s enough data, and it appears at different stages of the pipeline.
So it makes sense to think early about unified storage. The nature of this data often means you don’t need a full SQL schema: in many cases an embedded database and a simple key-value approach is enough.
A KV store (key-value) is a model where data is stored as “key → value” pairs: by key you quickly fetch the object you need (for example, candles for an instrument and timeframe1 for a period, or an analysis result for a specific request).
Interface: the minimum without which the advisor is useless
And finally — the interface. We won’t dive into UI/UX right now, but the basic point from part one still stands: you need at least a web UI, and as a nice addition — Telegram notifications.
Practically, a user needs only a few things. First, to see current signals and alerts (and quickly understand what’s happening). Second, to request analysis on demand — pull fresh data and get a signal in one click. And third, to configure the system: choose instruments and data sources, manage API keys, and — importantly — edit prompts and analysis parameters without touching code.
Core: “the ring” that binds everything
Tolkien had “one ring to rule them all… and in the darkness bind them”. An advisor architecture has its own ring too — without the drama: the core that keeps modules together.
The core is not responsible for analysis itself. It owns lifecycle: start/stop, dependency initialization, a scheduler, request routing (on demand and on schedule), and clean shutdown. This is where you decide what runs when, where data is written, and how components talk to each other without turning into a knot.
If you want to add new quote sources, swap LLM providers, plug in news, and avoid rewriting everything each time, the core must be simple but strict: modules behind interfaces, dependencies explicit, configuration centralized, and lifecycle predictable.
This is a good place to stop and be honest: the advisor “skeleton” is already visible. Data comes in, prompts are built, the model responds, results are stored and shown — and it all stands on a core that manages lifecycle. In the next article we’ll go one level deeper into implementation details: what a unified interface for quote sources looks like, how to design caching and retries, where to store history, how to validate model outputs (including JSON), and what to do so the system doesn’t break because of limits, time, and “rare” failures.
For additional technical explanations and common implementation questions, see the related Trading Automation FAQ section dedicated to this topic.
Footnotes
- A timeframe is the duration of one candle (the aggregation interval): for example 1m, 5m, 1h, 1d. It defines how the raw price stream is “compressed” into OHLCV.
- A timestamp is the time marker (usually UTC) a candle/trade is attached to. A classic mistake is mixing seconds and milliseconds (or local time and UTC), which shifts data and breaks analysis.