A cryptocurrency trading bot looks deceptively simple from the outside.
There is a market, there is some trading logic, and eventually an order gets sent to an exchange.
That description leaves out almost everything that makes the software interesting.
A real automated trading system has to continuously receive information, turn that information into something a strategy can understand, make a decision, check whether that decision is allowed, execute it through an external API, keep track of what happened, and recover when one of those pieces stops behaving normally.
I've been working on CryptoBot around exactly this problem.
The project started as an attempt to automate trading strategies and gradually grew into a much larger software engineering exercise.
The more functionality I added, the more obvious it became that the trading strategy itself was only one small part of the system.
The project is available on GitHub: https://github.com/pavloaser23/crypto-trading-bot This article is about the engineering side of that problem.
Start With the Data Every automated trading decision begins with information.
Depending on the strategy, that might include price, volume, candles, order book information, account state, or other market data.
The first architectural mistake is to let the strategy become responsible for acquiring all of it.
It is convenient initially.
You can write something like: There isn't anything inherently wrong with this for a prototype.
The problem appears when the application grows.
Now the strategy knows about the exchange.
The exchange knows about the strategy.
The trading logic knows about networking.
Testing requires a live API.
And eventually even a small change can affect several unrelated parts of the application.
I prefer to think about the data flow as separate stages: The exact implementation can change, but keeping those responsibilities conceptually separate makes the system much easier to work on.
Market Data Has Its Own Problems Market data sounds simple until you need it continuously.
A program can request a price periodically through a REST API.
That works for many basic use cases.
A real-time application may instead use a WebSocket connection and continuously receive updates.
That introduces another category of problems.
Connections can disappear.
Messages can arrive unexpectedly.
The network can become unstable.
The exchange can temporarily stop responding.
Data can become stale.
The application needs to know whether it is still receiving valid information.
A simple implementation assumes this: A production-oriented system has to consider something closer to: The difference between these two diagrams is where a lot of the engineering work lives.
Why REST and WebSocket Are Different Problems REST APIs are request-oriented.
The application asks for something and receives a response.
That makes them convenient for operations such as retrieving account information, requesting historical information, or performing specific API operations.
WebSockets are different.
Instead of repeatedly asking for information, the application maintains a connection and receives events or updates.
That makes streaming data useful for applications that need to react to changing market conditions.
But persistent connections create their own responsibilities.
The application has to understand connection state.
It has to detect disconnects.
It needs a reconnect strategy.
It needs to decide what happens to data received around the time of a disconnect.
It may also need to rebuild part of its local state after reconnecting.
This is not really a "crypto problem." It is a distributed-systems problem that happens to exist inside a trading application.
A Strategy Should Produce a Decision Once market data has been processed, the strategy can evaluate it.
CryptoBot is designed around several common approaches, including technical analysis, trend following, scalping, arbitrage, and experiments involving machine learning.
A technical strategy might use indicators such as: Moving averages RSI MACD Bollinger Bands The important architectural point isn't which indicator is used.
It's what the strategy produces.
Ideally, it produces a signal or decision rather than directly manipulating the exchange.
For example: That is much easier to work with than a strategy that immediately performs: The first design gives the rest of the system an opportunity to evaluate the decision.
A Signal Is Not an Order This is one of the most important distinctions in an automated trading system.
Suppose a strategy produces: There is still no reason to assume that the system should immediately execute the trade.
The application may have risk limits.
There may already be an open position.
The requested position size may be too large.
The current configuration may prohibit the trade.
There may be insufficient available capital.
So the flow becomes: This makes risk management a separate responsibility rather than something hidden inside individual strategies.
Risk Management Is a System Component Automated trading makes explicit risk rules more important, not less.
A human trader can decide not to take a trade.
Software will generally continue following its rules until something stops it.
CryptoBot includes controls such as stop-loss, take-profit, trailing stops, position sizing, and capital allocation.
The goal isn't to eliminate risk.
That isn't possible.
The goal is to make the rules explicit and enforceable.
For example, a strategy can say: The risk engine can then ask: Only after those checks should execution become possible.
This separation also makes strategy development safer because strategy code doesn't have to contain every account-level restriction.
Exchange Integration Is a Separate Engineering Problem Supporting one exchange can be relatively straightforward.
Supporting several exposes the architectural differences much more clearly.
CryptoBot is designed around connections to centralized exchanges as well as Web3 wallet connections.
Exchange integrations can differ in authentication, endpoints, order formats, supported operations, rate limits, and error responses.
If those differences leak into the strategy layer, the strategy eventually becomes full of exchange-specific conditions.
That is the kind of coupling that becomes expensive later.
A cleaner model is: The strategy works with trading concepts.
The integration handles the details of the particular exchange.
This makes adding or changing integrations much less disruptive.
The Order Lifecycle Matters Another mistake is treating an order as a single function call.
In a real system, an order has state.
A simplified lifecycle might look like: There can also be rejected, cancelled, expired, or failed states.
The particularly difficult case is when the application doesn't know what happened.
Imagine the application sends an order request and then the network connection disappears.
No response arrives.
That does not necessarily mean that the exchange didn't receive the order.
The request could have reached the exchange successfully while the response was lost.
This is why automated trading software has to think about state and reconciliation, not just function calls.
Reliability Is More Important Than the Happy Path Most prototypes are designed around successful execution.
The application connects.
The API responds.
The strategy produces a valid signal.
The order succeeds.
That's useful for proving that the idea works.
It isn't enough for long-running automation.
A useful system needs to consider failures such as: exchange downtime; network interruptions; invalid configuration; expired credentials; API rate limits; malformed responses; unexpected order states; application restarts; missing market data.
None of these are particularly interesting when you're writing the first version.
They become extremely interesting when the application has been running for hours.
Logging Is Part of the Architecture When an automate