A trading strategy is usually the most visible part of an automated trading system.
You can see the indicators, the entry conditions, the signal, and eventually the order.
But after working on CryptoBot, I've started to think that the strategy is actually one of the easier parts.
The difficult part is everything that happens when reality doesn't behave as expected.
What happens when the WebSocket disconnects?
What happens when market data becomes stale?
What happens when an order is submitted but the response is lost?
What happens when the application restarts and its local state no longer matches the exchange?
These aren't unusual edge cases.
They are normal problems in a long-running distributed system.
The Happy Path Is Easy
A simple trading bot can be represented like this:
Market Data
↓
Strategy
↓
Signal
↓
Risk Check
↓
Order
↓
Exchange
Everything works.
The exchange responds, the network stays connected, market data arrives, and the order is accepted.
For a prototype, this is enough.
For software that should run continuously, it isn't.
Eventually something will fail.
The important question is not whether the system will fail.
The important question is what it will do when it does.
Reconnecting Isn't Recovery
Real-time market data is often delivered through WebSockets.
A connection can disappear because of network problems, exchange maintenance, server-side disconnects, or client-side failures.
A naive system does:
connect
receive data
process data
repeat
A more realistic system needs:
connect
↓
receive data
↓
connection fails
↓
detect failure
↓
reconnect
↓
restore state
↓
validate data
↓
continue
The important difference is the recovery step.
Suppose the connection was down for five seconds.
The bot reconnects successfully.
But what happened during those five seconds?
It may have missed market updates or order events.
Simply reconnecting doesn't restore the state that was lost.
The application may need to synchronize with the exchange before trading again.
Stale Data Can Be Worse
A disconnected connection is easy to detect.
Stale data is harder.
Imagine the application is still running, but the last market update arrived thirty seconds ago.
The strategy still has a price.
It's just an old price.
That creates a dangerous situation:
Market Data Stops
↓
Local State Remains Unchanged
↓
Strategy Keeps Running
↓
Signal Generated
↓
Order Submitted
A safer approach is to make data freshness part of the decision.
Market Data
↓
Freshness Check
↓
Fresh?
/ \
Yes No
↓ ↓
Trade Stop
Sometimes the correct trading decision is simply:
I don't have reliable data, so I am not going to trade.
The Hardest Case: An Unknown Order
Consider a simple order request:
POST /order
The application sends it.
Then the network connection disappears.
No response arrives.
Did the exchange receive the order?
Maybe.
Did it execute?
Maybe.
The application cannot know from the timeout alone.
This is why:
timeout = failure
is dangerous.
The real state may be:
UNKNOWN
Blindly retrying can create a duplicate order if the first request actually succeeded.
A safer flow is:
Order Request
↓
Timeout
↓
UNKNOWN
↓
Query Exchange
↓
Determine Actual State
↓
Update Local State
This is one of the reasons an order should be treated as a state machine rather than just a function call.
An Order Has a Lifecycle
A simplified lifecycle can look like:
Created
↓
Submitted
↓
Accepted
↓
Partially Filled
↓
Filled
There can also be rejected, cancelled, expired, failed, or unknown states.
Partial fills make this even more interesting.
An order for 1.0 BTC might first be filled for 0.2 BTC, then 0.5 BTC, and finally 1.0 BTC.
If the application disconnects between those events, its local state may become inaccurate.
That's why reconciliation matters.
Local State Is Not Reality
The bot might have:
Local:
BTC position = 0.0
while the exchange has:
Exchange:
BTC position = 0.1
The local state is wrong.
The exchange is the system that actually holds the account and executes the trade.
This means the application needs a way to verify and rebuild its state.
A useful model is:
Exchange
↓
External State
↓
Reconciliation
↓
Local State
↓
Strategy
WebSocket events provide fast updates.
REST APIs can provide snapshots and help verify state.
One gives responsiveness.
The other provides a way to recover when events are missed.
Application Restarts Matter Too
Imagine:
Bot Running
↓
Order Submitted
↓
Order Filled
↓
Application Crashes
When the application starts again, it needs to know:
- What positions do I have?
- Which orders are still open?
- Which orders were filled?
- What happened while the bot was offline?
This is why startup recovery matters.
Instead of immediately starting to trade:
Application Start
↓
Query Exchange
↓
Compare State
↓
Reconcile
↓
Validate
↓
Start Trading
The bot should not assume that its previous local state is correct just because it was saved.
Recovery Should Be a Real State
A simple:
connected = true
isn't enough.
The system can be connected while still having stale or inconsistent state.
A better model is:
CONNECTING
↓
SYNCING
↓
READY
↓
RUNNING
↓
RECOVERING
↓
SYNCING
↓
RUNNING
There should also be a state where the bot refuses to trade.
For example, if the local position doesn't match the exchange position:
Mismatch Detected
↓
Stop Trading
↓
Reconcile
↓
Restore Consistency
↓
Resume Only If Safe
Not every error should automatically result in another retry.
Sometimes stopping is the correct behavior.
Logging Is Part of Reliability
When an automated system makes a decision, you should be able to understand why.
A useful log trail might look like:
Market data received
↓
Strategy evaluated
↓
Signal generated
↓
Risk check passed
↓
Order submitted
↓
Exchange response received
↓
Order filled
During recovery, state transitions are equally important:
WebSocket disconnected
RUNNING → RECOVERING
Reconnect successful
State synchronized
Market data refreshed
RECOVERING → RUNNING
If something happens at 3 AM while nobody is watching, the logs should make it possible to reconstruct what the bot believed and what it did.
Final Thoughts
When I started CryptoBot, I was mainly interested in automating trading strategies.
Over time, the more interesting questions became different:
What if the connection disappears?
What if the response disappears?
What if the data becomes stale?
What if the application restarts?
What if the local state is wrong?
What if an order exists but the bot doesn't know about it?
Those questions don't make the trading strategy smarter.
They make the software more reliable.
A real trading bot isn't just:
Strategy → Order
It is closer to:
Market Data
↓
Strategy
↓
Risk Management
↓
Execution
↓
Exchange
↓
Reconciliation
↓
Recovery
The hardest part isn't making the bot do something.
It's making sure that when something goes wrong, the bot knows enough to stop, recover, reconcile, and continue safely.
That's the part of CryptoBot I find most interesting.
Top comments (0)