DEV Community

John Doe
John Doe

Posted on

Inside a Crypto Trading Bot: What Actually Happens Between Market Data and an Order

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:

price = exchange.get_price("BTC/USDT")
signal = strategy.calculate(price)

if signal == "BUY":
    exchange.buy(...)
Enter fullscreen mode Exit fullscreen mode

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:

Market Data
    ↓
Data Processing
    ↓
Strategy
    ↓
Signal
    ↓
Risk Management
    ↓
Execution
    ↓
Exchange
Enter fullscreen mode Exit fullscreen mode

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:

connect
receive data
process data
repeat
Enter fullscreen mode Exit fullscreen mode

A production-oriented system has to consider something closer to:

connect
    ↓
receive data
    ↓
process data
    ↓
connection fails
    ↓
detect failure
    ↓
reconnect
    ↓
restore state
    ↓
continue
Enter fullscreen mode Exit fullscreen mode

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:

market data
    ↓
strategy
    ↓
BUY
Enter fullscreen mode Exit fullscreen mode

That is much easier to work with than a strategy that immediately performs:

strategy
    ↓
authenticate with exchange
    ↓
construct request
    ↓
send order
Enter fullscreen mode Exit fullscreen mode

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:

BUY BTC/USDT
Enter fullscreen mode Exit fullscreen mode

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:

Strategy
    ↓
Signal
    ↓
Risk Check
    ↓
Order
Enter fullscreen mode Exit fullscreen mode

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:

This looks like an entry.
Enter fullscreen mode Exit fullscreen mode

The risk engine can then ask:

Is this position size allowed?
Is the account already exposed?
Are the configured limits satisfied?
Enter fullscreen mode Exit fullscreen mode

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:

                 Strategy
                    │
                    ▼
              Trading Signal
                    │
                    ▼
               Order Model
                    │
          ┌─────────┼─────────┐
          ▼         ▼         ▼
       Exchange   Exchange   Exchange
          A         B         C
Enter fullscreen mode Exit fullscreen mode

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:

Created
   ↓
Submitted
   ↓
Accepted
   ↓
Partially Filled
   ↓
Filled
Enter fullscreen mode Exit fullscreen mode

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 automated system makes a decision, you should be able to understand why.

If something unexpected happens, logs are often the only way to reconstruct the sequence of events.

A useful log trail might tell you:

market data received
→ strategy evaluated
→ signal generated
→ risk check performed
→ order submitted
→ exchange response received
Enter fullscreen mode Exit fullscreen mode

Without that information, debugging becomes guesswork.

This is especially important when the system is automated.

If a human clicks a button, there is usually a clear action associated with the event.

If a strategy makes a decision at 3:17 AM while nobody is watching, the software needs to leave enough information behind to explain itself.

Backtesting Changes the Data Source, Not the Strategy

Backtesting is another area where architecture matters.

Ideally, a strategy shouldn't need to know whether its input came from a live exchange or historical data.

Conceptually:

                   Strategy
                  /        \
                 /          \
        Historical Data    Live Data
              ↓                ↓
          Backtester        Exchange
Enter fullscreen mode Exit fullscreen mode

The strategy receives data.

The environment determines where that data came from and how execution is handled.

This makes experimentation considerably easier.

You can evaluate an idea against historical data without modifying the strategy just because the execution environment changed.

But there is an important limitation.

A backtest is not a reproduction of reality.

Historical results can differ from live execution because of fees, spread, slippage, liquidity, latency, partial fills, and changing market conditions.

There is also a statistical problem.

If you repeatedly adjust a strategy until it performs extremely well on the same historical dataset, you can end up fitting the strategy to the past.

That is not the same as discovering a robust strategy.

Machine Learning Adds Another Layer

Machine learning is attractive in trading because markets generate a large amount of data.

A model can be used for prediction, classification, pattern recognition, or adaptive strategies.

But introducing machine learning doesn't remove the rest of the architecture.

The model still needs:

  • input data;
  • preprocessing;
  • feature generation;
  • inference;
  • validation;
  • output handling;
  • monitoring.

And a model prediction still needs to become a decision inside the larger system.

For example:

Market Data
    ↓
Features
    ↓
Model
    ↓
Prediction
    ↓
Strategy Decision
    ↓
Risk Check
    ↓
Execution
Enter fullscreen mode Exit fullscreen mode

The machine-learning component is another part of the pipeline.

It isn't the pipeline itself.

Performance Needs Measurement

Trading software naturally leads to performance discussions.

Latency matters.

CPU usage matters.

Memory usage matters.

Concurrency matters.

But performance optimization without measurement can easily become wasted effort.

Suppose a particular calculation takes a meaningful amount of CPU time.

Optimizing that calculation may be worthwhile.

But if the application spends most of its time waiting for an external exchange API, making the calculation ten times faster may have almost no effect on the end-to-end latency.

That is why I prefer to start with profiling and measurements.

Find the actual bottleneck.

Then decide whether optimization is necessary.

Where C, C++ or Assembly Make Sense

Lower-level languages are interesting when a workload actually requires them.

C and C++ can provide more control over memory, execution and native integration.

Assembly can provide even more direct control over specific CPU operations.

But none of that automatically makes a trading system faster.

If the bottleneck is network latency, rewriting a piece of application logic in assembly won't make the exchange respond faster.

If the bottleneck is a CPU-heavy calculation performed millions of times, native optimization could make much more sense.

The important question isn't:

Can this be written in assembly?

It is:

Is this actually the part of the system that needs to be optimized?

That distinction prevents a lot of unnecessary complexity.

Why I Chose a Windows Application

Crypto trading software is often presented as a server or command-line application.

I wanted CryptoBot to be usable as a Windows application.

That changes the project considerably.

Now the software also has to deal with packaging, installation, configuration, executable distribution, logging locations, application startup and release management.

For a developer, running source code locally is easy.

For someone using a compiled application, the experience should be much simpler.

The user shouldn't have to understand the internal architecture just to launch the application.

That makes packaging and documentation part of the engineering work rather than something added at the very end.

Security Changes When the Software Can Trade

A program that can interact with a trading account needs to treat credentials seriously.

API keys should be protected.

Only required permissions should be enabled.

Withdrawal permissions should be disabled when they aren't necessary.

Credentials should never be committed to source control.

The same principle applies to wallet connections.

Users should understand what they're connecting and what they're authorizing.

This is another area where a trading application differs from a normal hobby script.

The software isn't just processing information.

It can potentially cause financial actions.

That raises the cost of mistakes.

The Most Difficult Part Isn't the Algorithm

After spending a lot of time working on this project, I think this is the part that is easiest to underestimate.

The algorithm is visible.

You can see the formula.

You can see the strategy.

You can see the signal.

The infrastructure around it is less visible.

Networking.

State.

Error handling.

Execution.

Risk.

Logging.

Recovery.

Configuration.

Testing.

Those pieces aren't as interesting in screenshots, but they're what determine whether the application behaves predictably.

A trading strategy can be mathematically simple.

Building software that executes it reliably is not.

What I Would Focus on Next

There are several areas of CryptoBot that I want to continue improving.

The first is making strategy development easier.

The second is improving the backtesting environment so that experiments can be performed more systematically.

Exchange connectivity and execution reliability are also areas that naturally require continuous work.

There is also plenty of room for performance improvements once actual bottlenecks become clear.

And machine learning remains an interesting area for experimentation, particularly when treated as one component of a larger system rather than as a replacement for the entire strategy architecture.

Final Thoughts

Building an automated crypto trading system changed my understanding of what a trading bot actually is.

It isn't just an algorithm.

It isn't just an exchange API.

It isn't just a dashboard.

It is a collection of systems that have to cooperate:

Market Data
     ↓
Strategy
     ↓
Risk Management
     ↓
Execution
     ↓
Exchange
Enter fullscreen mode Exit fullscreen mode

Around that core, there is another layer responsible for reliability, state, logging, configuration, testing and recovery.

That's where most of the interesting engineering happens.

The project started with the idea of automating trading strategies.

It became an exercise in designing software that can operate continuously while the environment around it keeps changing.

That's the part I find most interesting.

CryptoBot is available on GitHub:

https://github.com/pavloaser23/crypto-trading-bot

Top comments (0)