<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Doe</title>
    <description>The latest articles on DEV Community by John Doe (@pavloaser23).</description>
    <link>https://dev.to/pavloaser23</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4100182%2F705e4957-8d10-42bd-b624-02aa91b7c2f2.jpg</url>
      <title>DEV Community: John Doe</title>
      <link>https://dev.to/pavloaser23</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pavloaser23"/>
    <language>en</language>
    <item>
      <title>I Thought the Hardest Part of a Trading Bot Was the Strategy</title>
      <dc:creator>John Doe</dc:creator>
      <pubDate>Wed, 02 Sep 2026 11:02:39 +0000</pubDate>
      <link>https://dev.to/pavloaser23/i-thought-the-hardest-part-of-a-trading-bot-was-the-strategy-i2c</link>
      <guid>https://dev.to/pavloaser23/i-thought-the-hardest-part-of-a-trading-bot-was-the-strategy-i2c</guid>
      <description>&lt;p&gt;When I started building CryptoBot, I thought the hardest part would be the trading strategy.&lt;/p&gt;

&lt;p&gt;RSI, MACD, moving averages, momentum, backtesting...&lt;/p&gt;

&lt;p&gt;That was the fun part.&lt;/p&gt;

&lt;p&gt;But after spending more time on the project, I started running into a different kind of problem.&lt;/p&gt;

&lt;p&gt;The strategy could be completely fine.&lt;/p&gt;

&lt;p&gt;The software around it wasn't always fine.&lt;/p&gt;

&lt;p&gt;A bot can generate a perfectly reasonable &lt;code&gt;BUY&lt;/code&gt; signal and still make the wrong decision because the market data is stale, the exchange connection disappeared, or the application doesn't know whether an order was actually executed.&lt;/p&gt;

&lt;p&gt;That's when I started looking at the project differently.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Strategy → Signal → Order&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I started thinking about:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Market Data → Validation → Strategy → Risk Management → Execution → Exchange → Reconciliation → Recovery&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The strategy is only one part of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Happy Path Is Easy
&lt;/h2&gt;

&lt;p&gt;The basic flow of a trading bot is almost too simple:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┐
│ Market Data  │
└──────┬───────┘
       ↓
┌──────────────┐
│   Strategy   │
└──────┬───────┘
       ↓
┌──────────────┐
│ Risk Check   │
└──────┬───────┘
       ↓
┌──────────────┐
│    Order     │
└──────┬───────┘
       ↓
┌──────────────┐
│   Exchange   │
└──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Everything works.&lt;/p&gt;

&lt;p&gt;The data arrives.&lt;/p&gt;

&lt;p&gt;The strategy runs.&lt;/p&gt;

&lt;p&gt;The order is accepted.&lt;/p&gt;

&lt;p&gt;The exchange responds.&lt;/p&gt;

&lt;p&gt;Great.&lt;/p&gt;

&lt;p&gt;But that's the happy path.&lt;/p&gt;

&lt;p&gt;Real systems don't stay there for very long.&lt;/p&gt;

&lt;p&gt;Eventually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the WebSocket disconnects;&lt;/li&gt;
&lt;li&gt;market data becomes stale;&lt;/li&gt;
&lt;li&gt;an API request times out;&lt;/li&gt;
&lt;li&gt;an order is partially filled;&lt;/li&gt;
&lt;li&gt;the application crashes;&lt;/li&gt;
&lt;li&gt;an event is missed;&lt;/li&gt;
&lt;li&gt;local state doesn't match the exchange.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is where the interesting engineering starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A WebSocket Can Reconnect and Still Be Wrong
&lt;/h2&gt;

&lt;p&gt;CryptoBot can use WebSockets for real-time market data.&lt;/p&gt;

&lt;p&gt;Normally:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONNECT
   ↓
RECEIVE DATA
   ↓
PROCESS DATA
   ↓
CONTINUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then something happens:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONNECT
   ↓
RECEIVE DATA
   ↓
CONNECTION LOST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The obvious solution is to reconnect.&lt;/p&gt;

&lt;p&gt;But reconnecting isn't the same as recovering.&lt;/p&gt;

&lt;p&gt;Imagine the connection was down for ten seconds.&lt;/p&gt;

&lt;p&gt;During those ten seconds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the price may have changed;&lt;/li&gt;
&lt;li&gt;an order may have been filled;&lt;/li&gt;
&lt;li&gt;an order may have been cancelled;&lt;/li&gt;
&lt;li&gt;multiple events may have happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The WebSocket reconnects.&lt;/p&gt;

&lt;p&gt;But what did the bot miss?&lt;/p&gt;

&lt;p&gt;That's the important question.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connected
    ≠
Synchronized
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A safer recovery flow looks more like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connection Lost
      ↓
   Reconnect
      ↓
  Synchronize
      ↓
 Validate State
      ↓
Resume Trading
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The bot shouldn't necessarily start trading immediately after reconnecting.&lt;/p&gt;

&lt;p&gt;It first needs to know what happened while it was disconnected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stale Data Is More Dangerous Than a Crash
&lt;/h2&gt;

&lt;p&gt;A crash is easy to notice.&lt;/p&gt;

&lt;p&gt;The process is dead.&lt;/p&gt;

&lt;p&gt;Stale data is different.&lt;/p&gt;

&lt;p&gt;The application can be running perfectly while making decisions using old information.&lt;/p&gt;

&lt;p&gt;Imagine the last BTC update arrived 30 seconds ago.&lt;/p&gt;

&lt;p&gt;The bot still has:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;last_price = 105200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So the strategy runs.&lt;/p&gt;

&lt;p&gt;Maybe it sees an entry condition.&lt;/p&gt;

&lt;p&gt;Maybe it generates:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY BTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But the market data is 30 seconds old.&lt;/p&gt;

&lt;p&gt;The actual problem is not necessarily the strategy.&lt;/p&gt;

&lt;p&gt;The strategy was given bad input.&lt;/p&gt;

&lt;p&gt;A simple freshness check can prevent this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAX_DATA_AGE = 5

if time.time() - last_market_update &amp;gt; MAX_DATA_AGE:
    trading_enabled = False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Market Data
               ↓
        Is data fresh?
           /      \
         YES       NO
          ↓         ↓
      Strategy     STOP
          ↓
        Trade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Sometimes the best decision a trading bot can make is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't trust the data, so I'm not trading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not a failure.&lt;/p&gt;

&lt;p&gt;That's risk control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Worst Case: "Did My Order Happen?"
&lt;/h2&gt;

&lt;p&gt;This is probably the failure mode I find most interesting.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bot
 ↓
POST /order
 ↓
Exchange
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The request is sent.&lt;/p&gt;

&lt;p&gt;Then the network disappears.&lt;/p&gt;

&lt;p&gt;No response.&lt;/p&gt;

&lt;p&gt;Now the bot has a problem.&lt;/p&gt;

&lt;p&gt;Did the exchange receive the order?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;Did it accept it?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;Did it execute it?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;The bot doesn't know.&lt;/p&gt;

&lt;p&gt;And that's very different from:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A timeout really means:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The client didn't receive a response.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It doesn't necessarily mean:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The exchange didn't process the request.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is why blindly retrying can be dangerous.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create Order
     ↓
   Timeout
     ↓
    Retry
     ↓
Create Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If the first order actually succeeded, you may now have a duplicate.&lt;/p&gt;

&lt;p&gt;A better approach is to treat the result as unknown:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────┐
│  Submit Order   │
└────────┬────────┘
         ↓
      Timeout
         ↓
┌─────────────────┐
│     UNKNOWN     │
└────────┬────────┘
         ↓
   Query Exchange
         ↓
  Determine State
         ↓
 Update Local State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is one of those concepts that applies far beyond trading.&lt;/p&gt;

&lt;p&gt;Whenever you communicate with an external system, there can be a difference between:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"It failed."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"I don't know what happened."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those are not the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Order Is a State Machine
&lt;/h2&gt;

&lt;p&gt;It's also tempting to think of an order as a simple function call:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order = create_order(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But in reality, an order has a lifecycle.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATED
   ↓
SUBMITTED
   ↓
ACCEPTED
   ↓
PARTIALLY_FILLED
   ↓
FILLED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It can also go another way:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATED
   ↓
REJECTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ACCEPTED
   ↓
CANCELLED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or even:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ↓
UNKNOWN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Partial fills make things even more interesting.&lt;/p&gt;

&lt;p&gt;Suppose the bot wants to buy &lt;code&gt;1.0 BTC&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The exchange might report:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.2 BTC filled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.5 BTC filled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And eventually:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.0 BTC filled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If the bot misses one event, its local state can become incorrect.&lt;/p&gt;

&lt;p&gt;The exchange may know that &lt;code&gt;0.5 BTC&lt;/code&gt; has been filled.&lt;/p&gt;

&lt;p&gt;The bot may still think it's &lt;code&gt;0.2 BTC&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now the rest of the system is operating on the wrong state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local State Is Not Reality
&lt;/h2&gt;

&lt;p&gt;This is probably the most important thing I've learned while working on the project.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOCAL STATE
BTC Position = 0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But the exchange says:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXCHANGE STATE
BTC Position = 0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which one is correct?&lt;/p&gt;

&lt;p&gt;The exchange.&lt;/p&gt;

&lt;p&gt;The local state is only the application's current understanding of reality.&lt;/p&gt;

&lt;p&gt;It can become wrong because:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Missed Event
     ↓
Incorrect Local State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Network Failure
     ↓
Incomplete State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application Restart
     ↓
State Must Be Rebuilt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is why reconciliation matters.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────┐
│    Exchange   │
│  Actual State │
└───────┬───────┘
        ↓
┌───────────────┐
│ Reconciliation│
└───────┬───────┘
        ↓
┌───────────────┐
│  Local State  │
└───────┬───────┘
        ↓
┌───────────────┐
│    Strategy   │
└───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The goal isn't to eliminate every possible failure.&lt;/p&gt;

&lt;p&gt;The goal is to have a way to recover from them.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebSocket + REST
&lt;/h2&gt;

&lt;p&gt;This is where WebSocket and REST APIs complement each other.&lt;/p&gt;

&lt;p&gt;WebSocket is great for fast updates:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebSocket
    ↓
Real-Time Events
    ↓
Local State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;REST can provide a snapshot:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REST API
    ↓
Current State
    ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Together:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebSocket
    ↓
Fast Updates
    ↓
Something Goes Wrong
    ↓
REST Snapshot
    ↓
Reconciliation
    ↓
Correct State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This pattern is useful because events are fast, but snapshots are useful for recovery.&lt;/p&gt;

&lt;p&gt;If the application missed something, it needs another way to find out what the current state actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;connected = true&lt;/code&gt; Is Not Enough
&lt;/h2&gt;

&lt;p&gt;One of the simplest implementations would be:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if connected:
    trade()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But consider this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebSocket:       CONNECTED
Market Data:     STALE
Position:        UNKNOWN
Order State:     UNSYNCHRONIZED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Technically:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connected = True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But should the bot trade?&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;This is why application state matters.&lt;/p&gt;

&lt;p&gt;Instead of a single boolean, I prefer something closer to:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONNECTING
    ↓
  SYNCING
    ↓
   READY
    ↓
  RUNNING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And if something goes wrong:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUNNING
    ↓
RECOVERING
    ↓
  SYNCING
    ↓
   READY
    ↓
  RUNNING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There should also be a state where trading is explicitly blocked:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STATE MISMATCH
      ↓
STOP TRADING
      ↓
  RECONCILE
      ↓
   VALIDATE
      ↓
   RESUME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The goal isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep the bot running no matter what.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep the bot running when it is safe to do so.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Risk Management Should Be Separate
&lt;/h2&gt;

&lt;p&gt;The strategy might say:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY BTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That doesn't automatically mean the order should be sent.&lt;/p&gt;

&lt;p&gt;CryptoBot also includes risk-management concepts such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stop-loss;&lt;/li&gt;
&lt;li&gt;take-profit;&lt;/li&gt;
&lt;li&gt;trailing stops;&lt;/li&gt;
&lt;li&gt;position sizing;&lt;/li&gt;
&lt;li&gt;capital allocation;&lt;/li&gt;
&lt;li&gt;portfolio rebalancing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic idea is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy
   ↓
  BUY
   ↓
Risk Management
   ↓
 Allowed?
  /    \
YES     NO
 ↓       ↓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;ORDER   REJECT&lt;/p&gt;

&lt;p&gt;I like this separation because the strategy answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What do I want to do?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While risk management answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should I actually do it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are different questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backtesting Doesn't Test Reality
&lt;/h2&gt;

&lt;p&gt;CryptoBot also supports backtesting.&lt;/p&gt;

&lt;p&gt;That's useful for testing strategies against historical data:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Historical Data
       ↓
    Strategy
       ↓
Simulated Execution
       ↓
     Results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can analyze things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;profit and loss;&lt;/li&gt;
&lt;li&gt;win rate;&lt;/li&gt;
&lt;li&gt;drawdown;&lt;/li&gt;
&lt;li&gt;number of trades;&lt;/li&gt;
&lt;li&gt;trading costs;&lt;/li&gt;
&lt;li&gt;strategy performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there is something a backtest usually doesn't reproduce very well:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the ugly parts of reality.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A backtest doesn't randomly lose a WebSocket connection.&lt;/p&gt;

&lt;p&gt;It doesn't receive half of an execution event.&lt;/p&gt;

&lt;p&gt;It doesn't restart after an order was filled.&lt;/p&gt;

&lt;p&gt;It doesn't have to wonder whether an API request timed out after the exchange accepted it.&lt;/p&gt;

&lt;p&gt;So a strategy performing well in a backtest doesn't automatically mean the live trading system is reliable.&lt;/p&gt;

&lt;p&gt;These are two different engineering problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Changed How I Look at CryptoBot
&lt;/h2&gt;

&lt;p&gt;When I started CryptoBot, I was mostly interested in strategies.&lt;/p&gt;

&lt;p&gt;Now I'm just as interested in what happens around them.&lt;/p&gt;

&lt;p&gt;The project includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automated cryptocurrency trading;&lt;/li&gt;
&lt;li&gt;real-time market data;&lt;/li&gt;
&lt;li&gt;technical-analysis strategies;&lt;/li&gt;
&lt;li&gt;backtesting;&lt;/li&gt;
&lt;li&gt;risk management;&lt;/li&gt;
&lt;li&gt;automated execution;&lt;/li&gt;
&lt;li&gt;multi-exchange support;&lt;/li&gt;
&lt;li&gt;custom strategies;&lt;/li&gt;
&lt;li&gt;stop-loss and take-profit;&lt;/li&gt;
&lt;li&gt;trailing stops;&lt;/li&gt;
&lt;li&gt;portfolio rebalancing;&lt;/li&gt;
&lt;li&gt;performance analytics;&lt;/li&gt;
&lt;li&gt;experimental machine-learning approaches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the questions I find most interesting are often simpler:&lt;/p&gt;

&lt;p&gt;What happens when the connection disappears?&lt;/p&gt;

&lt;p&gt;What happens when the data is stale?&lt;/p&gt;

&lt;p&gt;What happens when the response disappears?&lt;/p&gt;

&lt;p&gt;What happens when the order actually succeeded?&lt;/p&gt;

&lt;p&gt;What happens when the application crashes?&lt;/p&gt;

&lt;p&gt;What happens when the local state is wrong?&lt;/p&gt;

&lt;p&gt;What happens when the bot simply doesn't know what happened?&lt;/p&gt;

&lt;p&gt;These aren't strategy questions.&lt;/p&gt;

&lt;p&gt;They're system questions.&lt;/p&gt;

&lt;p&gt;And I think they're what separates a trading script from a trading system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;A trading bot isn't just:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy → Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It's more like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data
     ↓
  Validation
     ↓
   Strategy
     ↓
Risk Management
     ↓
  Execution
     ↓
   Exchange
     ↓
Reconciliation
     ↓
   Recovery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The strategy decides what the bot wants to do.&lt;/p&gt;

&lt;p&gt;The rest of the system has to make sure the bot understands what is actually happening.&lt;/p&gt;

&lt;p&gt;And sometimes the safest decision isn't:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STOP
  ↓
RECOVER
  ↓
RECONCILE
  ↓
VALIDATE
  ↓
RESUME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's probably the biggest lesson I've learned while building CryptoBot.&lt;/p&gt;

&lt;p&gt;I'm not trying to build a bot that trades all the time.&lt;/p&gt;

&lt;p&gt;I'm trying to build one that knows &lt;strong&gt;when it shouldn't trade&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you're building a trading bot or another event-driven system, what's the failure mode you find hardest to handle?&lt;/p&gt;

&lt;p&gt;Stale data?&lt;/p&gt;

&lt;p&gt;Duplicate orders?&lt;/p&gt;

&lt;p&gt;Lost events?&lt;/p&gt;

&lt;p&gt;Exchange outages?&lt;/p&gt;

&lt;p&gt;State reconciliation?&lt;/p&gt;

&lt;p&gt;Or something completely different?&lt;/p&gt;

&lt;p&gt;I'd be interested to hear about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  CryptoBot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/pavloaser23/crypto-trading-bot" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CryptoBot is an ongoing project focused on cryptocurrency trading automation, algorithmic strategies, market analysis, backtesting, risk management, and experimentation with different approaches to automated trading.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; CryptoBot is provided for development, testing, research, and educational purposes. Cryptocurrency trading involves significant financial risk and can result in the loss of capital. Past backtesting results do not guarantee future performance or profits. The software does not guarantee profits.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>crypto</category>
      <category>trading</category>
      <category>softwareengineering</category>
      <category>python</category>
    </item>
    <item>
      <title>The Software Isn't Broken — Your Assumptions Are</title>
      <dc:creator>John Doe</dc:creator>
      <pubDate>Sun, 30 Aug 2026 14:32:54 +0000</pubDate>
      <link>https://dev.to/pavloaser23/the-software-isnt-broken-your-assumptions-are-32ff</link>
      <guid>https://dev.to/pavloaser23/the-software-isnt-broken-your-assumptions-are-32ff</guid>
      <description>&lt;p&gt;Most software doesn't fail because the developer forgot how to write code.&lt;/p&gt;

&lt;p&gt;It fails because the code was built around assumptions that eventually stopped being true.&lt;/p&gt;

&lt;p&gt;We assume the network will respond.&lt;/p&gt;

&lt;p&gt;We assume data will be fresh.&lt;/p&gt;

&lt;p&gt;We assume a process will keep running.&lt;/p&gt;

&lt;p&gt;We assume a database will always be available.&lt;/p&gt;

&lt;p&gt;We assume an external API will behave exactly as documented.&lt;/p&gt;

&lt;p&gt;We assume the state in our application represents the real state of the system.&lt;/p&gt;

&lt;p&gt;Most of these assumptions are reasonable.&lt;/p&gt;

&lt;p&gt;The problem is that software systems eventually encounter the situations where they aren't true.&lt;/p&gt;

&lt;p&gt;And that's where reliability starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Happy Path Creates False Confidence
&lt;/h2&gt;

&lt;p&gt;Consider a simple API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
API Request
    ↓
Response
    ↓
Process Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;You test it locally.&lt;/p&gt;

&lt;p&gt;You deploy it.&lt;/p&gt;

&lt;p&gt;Everything looks fine.&lt;/p&gt;

&lt;p&gt;Then one day:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
API Request
    ↓
Timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the application has a decision to make.&lt;/p&gt;

&lt;p&gt;Should it retry?&lt;/p&gt;

&lt;p&gt;Should it stop?&lt;/p&gt;

&lt;p&gt;Was the request processed?&lt;/p&gt;

&lt;p&gt;Is the local state still valid?&lt;/p&gt;

&lt;p&gt;A single line of code suddenly becomes a system-design problem.&lt;/p&gt;

&lt;p&gt;That's why the happy path is not enough.&lt;/p&gt;

&lt;p&gt;A reliable system needs to define what happens when the expected sequence is interrupted.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Timeout Doesn't Always Mean Failure
&lt;/h2&gt;

&lt;p&gt;One of the most dangerous assumptions in distributed software is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No response = operation failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That isn't necessarily true.&lt;/p&gt;

&lt;p&gt;Imagine an application sends a request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   |
   | request
   ↓
Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server processes it successfully.&lt;/p&gt;

&lt;p&gt;But before the response reaches the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server
   |
   | response
   X
Network failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TIMEOUT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the operation may already have happened.&lt;/p&gt;

&lt;p&gt;If the client blindly retries, it may perform the same operation twice.&lt;/p&gt;

&lt;p&gt;This is why reliable systems often need concepts such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;idempotency;&lt;/li&gt;
&lt;li&gt;request identifiers;&lt;/li&gt;
&lt;li&gt;state reconciliation;&lt;/li&gt;
&lt;li&gt;retries with limits;&lt;/li&gt;
&lt;li&gt;explicit unknown states.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important lesson is that &lt;strong&gt;communication failure and operation failure are not always the same thing&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stale Data Is Still Data
&lt;/h2&gt;

&lt;p&gt;Another dangerous assumption is that data is valid simply because it exists.&lt;/p&gt;

&lt;p&gt;Imagine a service receives information from another system.&lt;/p&gt;

&lt;p&gt;The last update arrived one minute ago.&lt;/p&gt;

&lt;p&gt;The application still has a perfectly valid object in memory.&lt;/p&gt;

&lt;p&gt;Nothing is technically broken.&lt;/p&gt;

&lt;p&gt;But the information may now be useless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Last Update
    ↓
Time Passes
    ↓
Local Data Remains
    ↓
Application Uses It
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why systems that depend on real-time information often need freshness checks.&lt;/p&gt;

&lt;p&gt;Instead of asking only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do I have data?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the application should sometimes ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How old is this data?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small difference can completely change system behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  State Is Harder Than Functions
&lt;/h2&gt;

&lt;p&gt;Developers naturally think in terms of functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fetch()
calculate()
save()
send()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But long-running systems are often easier to understand in terms of state.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STARTING
   ↓
CONNECTING
   ↓
READY
   ↓
RUNNING
   ↓
RECOVERING
   ↓
READY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now failures become transitions rather than unexpected events.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUNNING
   ↓
CONNECTION_LOST
   ↓
RECOVERING
   ↓
SYNCING
   ↓
RUNNING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way of thinking is useful for many kinds of software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;background workers;&lt;/li&gt;
&lt;li&gt;payment systems;&lt;/li&gt;
&lt;li&gt;monitoring services;&lt;/li&gt;
&lt;li&gt;distributed applications;&lt;/li&gt;
&lt;li&gt;desktop applications;&lt;/li&gt;
&lt;li&gt;data pipelines;&lt;/li&gt;
&lt;li&gt;automated trading systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact states change.&lt;/p&gt;

&lt;p&gt;The principle doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovery Is Different From Restarting
&lt;/h2&gt;

&lt;p&gt;A common approach to failure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Something went wrong
        ↓
Restart application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes that works.&lt;/p&gt;

&lt;p&gt;But restarting a process doesn't automatically restore the state that existed before the failure.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
Operation started
    ↓
Process crashes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the application starts again, it needs to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Did the operation finish?

Did it partially finish?

Is it still running?

Should it be retried?

Is the previous state trustworthy?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why recovery often requires synchronization with the external system.&lt;/p&gt;

&lt;p&gt;A useful pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Restart
   ↓
Load Local State
   ↓
Query External State
   ↓
Compare
   ↓
Reconcile
   ↓
Resume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application doesn't simply continue from where it thinks it stopped.&lt;/p&gt;

&lt;p&gt;It verifies reality first.&lt;/p&gt;

&lt;h2&gt;
  
  
  External Systems Will Eventually Behave Differently
&lt;/h2&gt;

&lt;p&gt;Even when an API is well designed, the surrounding environment can change.&lt;/p&gt;

&lt;p&gt;A response can be delayed.&lt;/p&gt;

&lt;p&gt;A field can be missing.&lt;/p&gt;

&lt;p&gt;A rate limit can be reached.&lt;/p&gt;

&lt;p&gt;Credentials can expire.&lt;/p&gt;

&lt;p&gt;A service can temporarily become unavailable.&lt;/p&gt;

&lt;p&gt;A dependency can return an unexpected error.&lt;/p&gt;

&lt;p&gt;This doesn't mean that every possible failure needs hundreds of lines of defensive code.&lt;/p&gt;

&lt;p&gt;It means important assumptions should be explicit.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if data is stale:
    stop processing

if dependency is unavailable:
    retry with backoff

if state is inconsistent:
    reconcile

if recovery fails repeatedly:
    stop and alert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact implementation depends on the application.&lt;/p&gt;

&lt;p&gt;The important part is that the behavior is intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not Everything Should Be Retried
&lt;/h2&gt;

&lt;p&gt;Retries are useful.&lt;/p&gt;

&lt;p&gt;They are also easy to misuse.&lt;/p&gt;

&lt;p&gt;A simple pattern looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error
 ↓
retry
 ↓
error
 ↓
retry
 ↓
error
 ↓
retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can turn a small problem into a much bigger one.&lt;/p&gt;

&lt;p&gt;For temporary network failures, a limited retry with backoff may be reasonable.&lt;/p&gt;

&lt;p&gt;For an inconsistent financial transaction, blindly retrying may be dangerous.&lt;/p&gt;

&lt;p&gt;For corrupted application state, retrying the same operation may accomplish nothing.&lt;/p&gt;

&lt;p&gt;A reliable system should first ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What kind of failure is this?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then choose the recovery strategy.&lt;/p&gt;

&lt;p&gt;Sometimes the correct answer is retry.&lt;/p&gt;

&lt;p&gt;Sometimes it is reconciliation.&lt;/p&gt;

&lt;p&gt;Sometimes it is simply stopping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability Changes Everything
&lt;/h2&gt;

&lt;p&gt;When something fails in production, the question isn't only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What exception happened?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A more useful question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What was the system doing when it happened?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good logs can describe a sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request started
Dependency connected
Data received
Validation passed
Operation started
Timeout detected
State changed: RUNNING → RECOVERING
Reconciliation started
State restored
System resumed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a story.&lt;/p&gt;

&lt;p&gt;Without that story, debugging becomes guesswork.&lt;/p&gt;

&lt;p&gt;For long-running software, logging and monitoring aren't decorations added after development.&lt;/p&gt;

&lt;p&gt;They are part of the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability Is About Controlled Failure
&lt;/h2&gt;

&lt;p&gt;Perfect software doesn't exist.&lt;/p&gt;

&lt;p&gt;Networks fail.&lt;/p&gt;

&lt;p&gt;Servers fail.&lt;/p&gt;

&lt;p&gt;Processes crash.&lt;/p&gt;

&lt;p&gt;Dependencies become unavailable.&lt;/p&gt;

&lt;p&gt;Data becomes stale.&lt;/p&gt;

&lt;p&gt;The goal isn't to prevent every failure.&lt;/p&gt;

&lt;p&gt;The goal is to make failure predictable.&lt;/p&gt;

&lt;p&gt;A reliable system should be able to answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What failed?

What state are we in?

Can we safely retry?

Do we need to reconcile?

Should we stop?

How do we know that recovery succeeded?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a much stronger design than simply adding more error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned From Building Real Systems
&lt;/h2&gt;

&lt;p&gt;Working on larger projects changed how I think about software.&lt;/p&gt;

&lt;p&gt;The interesting part is often not the main algorithm.&lt;/p&gt;

&lt;p&gt;It's everything surrounding it.&lt;/p&gt;

&lt;p&gt;State.&lt;/p&gt;

&lt;p&gt;Networking.&lt;/p&gt;

&lt;p&gt;Retries.&lt;/p&gt;

&lt;p&gt;Validation.&lt;/p&gt;

&lt;p&gt;Recovery.&lt;/p&gt;

&lt;p&gt;Logging.&lt;/p&gt;

&lt;p&gt;Concurrency.&lt;/p&gt;

&lt;p&gt;External dependencies.&lt;/p&gt;

&lt;p&gt;A small function can be perfectly correct while the overall system is still unreliable.&lt;/p&gt;

&lt;p&gt;The difficult engineering work begins when you ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens when my assumptions are wrong?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the question that turns a prototype into a real system.&lt;/p&gt;

&lt;p&gt;I've been exploring these ideas while building different parts of CryptoBot, where external APIs, real-time data, execution state, and recovery all have to work together.&lt;/p&gt;

&lt;p&gt;The project is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pavloaser23/crypto-trading-bot" rel="noopener noreferrer"&gt;https://github.com/pavloaser23/crypto-trading-bot&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The software usually isn't broken when the unexpected happens.&lt;/p&gt;

&lt;p&gt;The unexpected situation is often exactly what the software should have been designed to handle.&lt;/p&gt;

&lt;p&gt;The real problem is the assumption that everything will continue working exactly as it did during the first successful test.&lt;/p&gt;

&lt;p&gt;Good engineering starts when we stop designing only for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;everything works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and start designing for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;something will eventually go wrong
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because it will.&lt;/p&gt;

&lt;p&gt;The question is whether the system knows what to do next.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>programming</category>
      <category>architecture</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Hardest Part of a Trading Bot Isn't the Strategy</title>
      <dc:creator>John Doe</dc:creator>
      <pubDate>Sun, 30 Aug 2026 14:27:17 +0000</pubDate>
      <link>https://dev.to/pavloaser23/the-hardest-part-of-a-trading-bot-isnt-the-strategy-2h8j</link>
      <guid>https://dev.to/pavloaser23/the-hardest-part-of-a-trading-bot-isnt-the-strategy-2h8j</guid>
      <description>&lt;p&gt;A trading strategy is usually the most visible part of an automated trading system.&lt;/p&gt;

&lt;p&gt;You can see the indicators, the entry conditions, the signal, and eventually the order.&lt;/p&gt;

&lt;p&gt;But after working on CryptoBot, I've started to think that the strategy is actually one of the easier parts.&lt;/p&gt;

&lt;p&gt;The difficult part is everything that happens when reality doesn't behave as expected.&lt;/p&gt;

&lt;p&gt;What happens when the WebSocket disconnects?&lt;/p&gt;

&lt;p&gt;What happens when market data becomes stale?&lt;/p&gt;

&lt;p&gt;What happens when an order is submitted but the response is lost?&lt;/p&gt;

&lt;p&gt;What happens when the application restarts and its local state no longer matches the exchange?&lt;/p&gt;

&lt;p&gt;These aren't unusual edge cases.&lt;/p&gt;

&lt;p&gt;They are normal problems in a long-running distributed system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Happy Path Is Easy
&lt;/h2&gt;

&lt;p&gt;A simple trading bot can be represented like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data
    ↓
Strategy
    ↓
Signal
    ↓
Risk Check
    ↓
Order
    ↓
Exchange
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything works.&lt;/p&gt;

&lt;p&gt;The exchange responds, the network stays connected, market data arrives, and the order is accepted.&lt;/p&gt;

&lt;p&gt;For a prototype, this is enough.&lt;/p&gt;

&lt;p&gt;For software that should run continuously, it isn't.&lt;/p&gt;

&lt;p&gt;Eventually something will fail.&lt;/p&gt;

&lt;p&gt;The important question is not whether the system will fail.&lt;/p&gt;

&lt;p&gt;The important question is what it will do when it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconnecting Isn't Recovery
&lt;/h2&gt;

&lt;p&gt;Real-time market data is often delivered through WebSockets.&lt;/p&gt;

&lt;p&gt;A connection can disappear because of network problems, exchange maintenance, server-side disconnects, or client-side failures.&lt;/p&gt;

&lt;p&gt;A naive system does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connect
receive data
process data
repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A more realistic system needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connect
    ↓
receive data
    ↓
connection fails
    ↓
detect failure
    ↓
reconnect
    ↓
restore state
    ↓
validate data
    ↓
continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important difference is the recovery step.&lt;/p&gt;

&lt;p&gt;Suppose the connection was down for five seconds.&lt;/p&gt;

&lt;p&gt;The bot reconnects successfully.&lt;/p&gt;

&lt;p&gt;But what happened during those five seconds?&lt;/p&gt;

&lt;p&gt;It may have missed market updates or order events.&lt;/p&gt;

&lt;p&gt;Simply reconnecting doesn't restore the state that was lost.&lt;/p&gt;

&lt;p&gt;The application may need to synchronize with the exchange before trading again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stale Data Can Be Worse
&lt;/h2&gt;

&lt;p&gt;A disconnected connection is easy to detect.&lt;/p&gt;

&lt;p&gt;Stale data is harder.&lt;/p&gt;

&lt;p&gt;Imagine the application is still running, but the last market update arrived thirty seconds ago.&lt;/p&gt;

&lt;p&gt;The strategy still has a price.&lt;/p&gt;

&lt;p&gt;It's just an old price.&lt;/p&gt;

&lt;p&gt;That creates a dangerous situation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data Stops
      ↓
Local State Remains Unchanged
      ↓
Strategy Keeps Running
      ↓
Signal Generated
      ↓
Order Submitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A safer approach is to make data freshness part of the decision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data
    ↓
Freshness Check
    ↓
Fresh?
  /   \
Yes    No
 ↓      ↓
Trade   Stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the correct trading decision is simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't have reliable data, so I am not going to trade.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Hardest Case: An Unknown Order
&lt;/h2&gt;

&lt;p&gt;Consider a simple order request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application sends it.&lt;/p&gt;

&lt;p&gt;Then the network connection disappears.&lt;/p&gt;

&lt;p&gt;No response arrives.&lt;/p&gt;

&lt;p&gt;Did the exchange receive the order?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;Did it execute?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;The application cannot know from the timeout alone.&lt;/p&gt;

&lt;p&gt;This is why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timeout = failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is dangerous.&lt;/p&gt;

&lt;p&gt;The real state may be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UNKNOWN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blindly retrying can create a duplicate order if the first request actually succeeded.&lt;/p&gt;

&lt;p&gt;A safer flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Request
    ↓
Timeout
    ↓
UNKNOWN
    ↓
Query Exchange
    ↓
Determine Actual State
    ↓
Update Local State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the reasons an order should be treated as a state machine rather than just a function call.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Order Has a Lifecycle
&lt;/h2&gt;

&lt;p&gt;A simplified lifecycle can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created
   ↓
Submitted
   ↓
Accepted
   ↓
Partially Filled
   ↓
Filled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There can also be rejected, cancelled, expired, failed, or unknown states.&lt;/p&gt;

&lt;p&gt;Partial fills make this even more interesting.&lt;/p&gt;

&lt;p&gt;An order for &lt;code&gt;1.0 BTC&lt;/code&gt; might first be filled for &lt;code&gt;0.2 BTC&lt;/code&gt;, then &lt;code&gt;0.5 BTC&lt;/code&gt;, and finally &lt;code&gt;1.0 BTC&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the application disconnects between those events, its local state may become inaccurate.&lt;/p&gt;

&lt;p&gt;That's why reconciliation matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local State Is Not Reality
&lt;/h2&gt;

&lt;p&gt;The bot might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local:
BTC position = 0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while the exchange has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Exchange:
BTC position = 0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local state is wrong.&lt;/p&gt;

&lt;p&gt;The exchange is the system that actually holds the account and executes the trade.&lt;/p&gt;

&lt;p&gt;This means the application needs a way to verify and rebuild its state.&lt;/p&gt;

&lt;p&gt;A useful model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Exchange
    ↓
External State
    ↓
Reconciliation
    ↓
Local State
    ↓
Strategy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebSocket events provide fast updates.&lt;/p&gt;

&lt;p&gt;REST APIs can provide snapshots and help verify state.&lt;/p&gt;

&lt;p&gt;One gives responsiveness.&lt;/p&gt;

&lt;p&gt;The other provides a way to recover when events are missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Restarts Matter Too
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bot Running
    ↓
Order Submitted
    ↓
Order Filled
    ↓
Application Crashes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the application starts again, it needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What positions do I have?&lt;/li&gt;
&lt;li&gt;Which orders are still open?&lt;/li&gt;
&lt;li&gt;Which orders were filled?&lt;/li&gt;
&lt;li&gt;What happened while the bot was offline?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why startup recovery matters.&lt;/p&gt;

&lt;p&gt;Instead of immediately starting to trade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application Start
       ↓
Query Exchange
       ↓
Compare State
       ↓
Reconcile
       ↓
Validate
       ↓
Start Trading
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot should not assume that its previous local state is correct just because it was saved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovery Should Be a Real State
&lt;/h2&gt;

&lt;p&gt;A simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connected = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;isn't enough.&lt;/p&gt;

&lt;p&gt;The system can be connected while still having stale or inconsistent state.&lt;/p&gt;

&lt;p&gt;A better model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONNECTING
    ↓
SYNCING
    ↓
READY
    ↓
RUNNING
    ↓
RECOVERING
    ↓
SYNCING
    ↓
RUNNING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There should also be a state where the bot refuses to trade.&lt;/p&gt;

&lt;p&gt;For example, if the local position doesn't match the exchange position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mismatch Detected
      ↓
Stop Trading
      ↓
Reconcile
      ↓
Restore Consistency
      ↓
Resume Only If Safe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not every error should automatically result in another retry.&lt;/p&gt;

&lt;p&gt;Sometimes stopping is the correct behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging Is Part of Reliability
&lt;/h2&gt;

&lt;p&gt;When an automated system makes a decision, you should be able to understand why.&lt;/p&gt;

&lt;p&gt;A useful log trail might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market data received
    ↓
Strategy evaluated
    ↓
Signal generated
    ↓
Risk check passed
    ↓
Order submitted
    ↓
Exchange response received
    ↓
Order filled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During recovery, state transitions are equally important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebSocket disconnected
RUNNING → RECOVERING
Reconnect successful
State synchronized
Market data refreshed
RECOVERING → RUNNING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;When I started CryptoBot, I was mainly interested in automating trading strategies.&lt;/p&gt;

&lt;p&gt;Over time, the more interesting questions became different:&lt;/p&gt;

&lt;p&gt;What if the connection disappears?&lt;/p&gt;

&lt;p&gt;What if the response disappears?&lt;/p&gt;

&lt;p&gt;What if the data becomes stale?&lt;/p&gt;

&lt;p&gt;What if the application restarts?&lt;/p&gt;

&lt;p&gt;What if the local state is wrong?&lt;/p&gt;

&lt;p&gt;What if an order exists but the bot doesn't know about it?&lt;/p&gt;

&lt;p&gt;Those questions don't make the trading strategy smarter.&lt;/p&gt;

&lt;p&gt;They make the software more reliable.&lt;/p&gt;

&lt;p&gt;A real trading bot isn't just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy → Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data
     ↓
Strategy
     ↓
Risk Management
     ↓
Execution
     ↓
Exchange
     ↓
Reconciliation
     ↓
Recovery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hardest part isn't making the bot do something.&lt;/p&gt;

&lt;p&gt;It's making sure that when something goes wrong, the bot knows enough to stop, recover, reconcile, and continue safely.&lt;/p&gt;

&lt;p&gt;That's the part of CryptoBot I find most interesting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pavloaser23/crypto-trading-bot" rel="noopener noreferrer"&gt;https://github.com/pavloaser23/crypto-trading-bot&lt;/a&gt;&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>trading</category>
      <category>softwareengineering</category>
      <category>ai</category>
    </item>
    <item>
      <title>Inside a Crypto Trading Bot: What Actually Happens Between Market Data and an Order</title>
      <dc:creator>John Doe</dc:creator>
      <pubDate>Sat, 29 Aug 2026 12:24:36 +0000</pubDate>
      <link>https://dev.to/pavloaser23/inside-a-crypto-trading-bot-what-actually-happens-between-market-data-and-an-order-4ec4</link>
      <guid>https://dev.to/pavloaser23/inside-a-crypto-trading-bot-what-actually-happens-between-market-data-and-an-order-4ec4</guid>
      <description>&lt;p&gt;A cryptocurrency trading bot looks deceptively simple from the outside.&lt;/p&gt;

&lt;p&gt;There is a market, there is some trading logic, and eventually an order gets sent to an exchange.&lt;/p&gt;

&lt;p&gt;That description leaves out almost everything that makes the software interesting.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I've been working on CryptoBot around exactly this problem.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The project is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pavloaser23/crypto-trading-bot" rel="noopener noreferrer"&gt;https://github.com/pavloaser23/crypto-trading-bot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article is about the engineering side of that problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With the Data
&lt;/h2&gt;

&lt;p&gt;Every automated trading decision begins with information.&lt;/p&gt;

&lt;p&gt;Depending on the strategy, that might include price, volume, candles, order book information, account state, or other market data.&lt;/p&gt;

&lt;p&gt;The first architectural mistake is to let the strategy become responsible for acquiring all of it.&lt;/p&gt;

&lt;p&gt;It is convenient initially.&lt;/p&gt;

&lt;p&gt;You can write something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTC/USDT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BUY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buy&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There isn't anything inherently wrong with this for a prototype.&lt;/p&gt;

&lt;p&gt;The problem appears when the application grows.&lt;/p&gt;

&lt;p&gt;Now the strategy knows about the exchange.&lt;/p&gt;

&lt;p&gt;The exchange knows about the strategy.&lt;/p&gt;

&lt;p&gt;The trading logic knows about networking.&lt;/p&gt;

&lt;p&gt;Testing requires a live API.&lt;/p&gt;

&lt;p&gt;And eventually even a small change can affect several unrelated parts of the application.&lt;/p&gt;

&lt;p&gt;I prefer to think about the data flow as separate stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data
    ↓
Data Processing
    ↓
Strategy
    ↓
Signal
    ↓
Risk Management
    ↓
Execution
    ↓
Exchange
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact implementation can change, but keeping those responsibilities conceptually separate makes the system much easier to work on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Market Data Has Its Own Problems
&lt;/h2&gt;

&lt;p&gt;Market data sounds simple until you need it continuously.&lt;/p&gt;

&lt;p&gt;A program can request a price periodically through a REST API. That works for many basic use cases.&lt;/p&gt;

&lt;p&gt;A real-time application may instead use a WebSocket connection and continuously receive updates.&lt;/p&gt;

&lt;p&gt;That introduces another category of problems.&lt;/p&gt;

&lt;p&gt;Connections can disappear.&lt;/p&gt;

&lt;p&gt;Messages can arrive unexpectedly.&lt;/p&gt;

&lt;p&gt;The network can become unstable.&lt;/p&gt;

&lt;p&gt;The exchange can temporarily stop responding.&lt;/p&gt;

&lt;p&gt;Data can become stale.&lt;/p&gt;

&lt;p&gt;The application needs to know whether it is still receiving valid information.&lt;/p&gt;

&lt;p&gt;A simple implementation assumes this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connect
receive data
process data
repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production-oriented system has to consider something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connect
    ↓
receive data
    ↓
process data
    ↓
connection fails
    ↓
detect failure
    ↓
reconnect
    ↓
restore state
    ↓
continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between these two diagrams is where a lot of the engineering work lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why REST and WebSocket Are Different Problems
&lt;/h2&gt;

&lt;p&gt;REST APIs are request-oriented.&lt;/p&gt;

&lt;p&gt;The application asks for something and receives a response.&lt;/p&gt;

&lt;p&gt;That makes them convenient for operations such as retrieving account information, requesting historical information, or performing specific API operations.&lt;/p&gt;

&lt;p&gt;WebSockets are different.&lt;/p&gt;

&lt;p&gt;Instead of repeatedly asking for information, the application maintains a connection and receives events or updates.&lt;/p&gt;

&lt;p&gt;That makes streaming data useful for applications that need to react to changing market conditions.&lt;/p&gt;

&lt;p&gt;But persistent connections create their own responsibilities.&lt;/p&gt;

&lt;p&gt;The application has to understand connection state.&lt;/p&gt;

&lt;p&gt;It has to detect disconnects.&lt;/p&gt;

&lt;p&gt;It needs a reconnect strategy.&lt;/p&gt;

&lt;p&gt;It needs to decide what happens to data received around the time of a disconnect.&lt;/p&gt;

&lt;p&gt;It may also need to rebuild part of its local state after reconnecting.&lt;/p&gt;

&lt;p&gt;This is not really a "crypto problem."&lt;/p&gt;

&lt;p&gt;It is a distributed-systems problem that happens to exist inside a trading application.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Strategy Should Produce a Decision
&lt;/h2&gt;

&lt;p&gt;Once market data has been processed, the strategy can evaluate it.&lt;/p&gt;

&lt;p&gt;CryptoBot is designed around several common approaches, including technical analysis, trend following, scalping, arbitrage, and experiments involving machine learning.&lt;/p&gt;

&lt;p&gt;A technical strategy might use indicators such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Moving averages&lt;/li&gt;
&lt;li&gt;RSI&lt;/li&gt;
&lt;li&gt;MACD&lt;/li&gt;
&lt;li&gt;Bollinger Bands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important architectural point isn't which indicator is used.&lt;/p&gt;

&lt;p&gt;It's what the strategy produces.&lt;/p&gt;

&lt;p&gt;Ideally, it produces a signal or decision rather than directly manipulating the exchange.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;market data
    ↓
strategy
    ↓
BUY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much easier to work with than a strategy that immediately performs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strategy
    ↓
authenticate with exchange
    ↓
construct request
    ↓
send order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first design gives the rest of the system an opportunity to evaluate the decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Signal Is Not an Order
&lt;/h2&gt;

&lt;p&gt;This is one of the most important distinctions in an automated trading system.&lt;/p&gt;

&lt;p&gt;Suppose a strategy produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY BTC/USDT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is still no reason to assume that the system should immediately execute the trade.&lt;/p&gt;

&lt;p&gt;The application may have risk limits.&lt;/p&gt;

&lt;p&gt;There may already be an open position.&lt;/p&gt;

&lt;p&gt;The requested position size may be too large.&lt;/p&gt;

&lt;p&gt;The current configuration may prohibit the trade.&lt;/p&gt;

&lt;p&gt;There may be insufficient available capital.&lt;/p&gt;

&lt;p&gt;So the flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy
    ↓
Signal
    ↓
Risk Check
    ↓
Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes risk management a separate responsibility rather than something hidden inside individual strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk Management Is a System Component
&lt;/h2&gt;

&lt;p&gt;Automated trading makes explicit risk rules more important, not less.&lt;/p&gt;

&lt;p&gt;A human trader can decide not to take a trade.&lt;/p&gt;

&lt;p&gt;Software will generally continue following its rules until something stops it.&lt;/p&gt;

&lt;p&gt;CryptoBot includes controls such as stop-loss, take-profit, trailing stops, position sizing, and capital allocation.&lt;/p&gt;

&lt;p&gt;The goal isn't to eliminate risk.&lt;/p&gt;

&lt;p&gt;That isn't possible.&lt;/p&gt;

&lt;p&gt;The goal is to make the rules explicit and enforceable.&lt;/p&gt;

&lt;p&gt;For example, a strategy can say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This looks like an entry.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The risk engine can then ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is this position size allowed?
Is the account already exposed?
Are the configured limits satisfied?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after those checks should execution become possible.&lt;/p&gt;

&lt;p&gt;This separation also makes strategy development safer because strategy code doesn't have to contain every account-level restriction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exchange Integration Is a Separate Engineering Problem
&lt;/h2&gt;

&lt;p&gt;Supporting one exchange can be relatively straightforward.&lt;/p&gt;

&lt;p&gt;Supporting several exposes the architectural differences much more clearly.&lt;/p&gt;

&lt;p&gt;CryptoBot is designed around connections to centralized exchanges as well as Web3 wallet connections.&lt;/p&gt;

&lt;p&gt;Exchange integrations can differ in authentication, endpoints, order formats, supported operations, rate limits, and error responses.&lt;/p&gt;

&lt;p&gt;If those differences leak into the strategy layer, the strategy eventually becomes full of exchange-specific conditions.&lt;/p&gt;

&lt;p&gt;That is the kind of coupling that becomes expensive later.&lt;/p&gt;

&lt;p&gt;A cleaner model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Strategy
                    │
                    ▼
              Trading Signal
                    │
                    ▼
               Order Model
                    │
          ┌─────────┼─────────┐
          ▼         ▼         ▼
       Exchange   Exchange   Exchange
          A         B         C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy works with trading concepts.&lt;/p&gt;

&lt;p&gt;The integration handles the details of the particular exchange.&lt;/p&gt;

&lt;p&gt;This makes adding or changing integrations much less disruptive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Order Lifecycle Matters
&lt;/h2&gt;

&lt;p&gt;Another mistake is treating an order as a single function call.&lt;/p&gt;

&lt;p&gt;In a real system, an order has state.&lt;/p&gt;

&lt;p&gt;A simplified lifecycle might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created
   ↓
Submitted
   ↓
Accepted
   ↓
Partially Filled
   ↓
Filled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There can also be rejected, cancelled, expired, or failed states.&lt;/p&gt;

&lt;p&gt;The particularly difficult case is when the application doesn't know what happened.&lt;/p&gt;

&lt;p&gt;Imagine the application sends an order request and then the network connection disappears.&lt;/p&gt;

&lt;p&gt;No response arrives.&lt;/p&gt;

&lt;p&gt;That does not necessarily mean that the exchange didn't receive the order.&lt;/p&gt;

&lt;p&gt;The request could have reached the exchange successfully while the response was lost.&lt;/p&gt;

&lt;p&gt;This is why automated trading software has to think about state and reconciliation, not just function calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability Is More Important Than the Happy Path
&lt;/h2&gt;

&lt;p&gt;Most prototypes are designed around successful execution.&lt;/p&gt;

&lt;p&gt;The application connects.&lt;/p&gt;

&lt;p&gt;The API responds.&lt;/p&gt;

&lt;p&gt;The strategy produces a valid signal.&lt;/p&gt;

&lt;p&gt;The order succeeds.&lt;/p&gt;

&lt;p&gt;That's useful for proving that the idea works.&lt;/p&gt;

&lt;p&gt;It isn't enough for long-running automation.&lt;/p&gt;

&lt;p&gt;A useful system needs to consider failures such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exchange downtime;&lt;/li&gt;
&lt;li&gt;network interruptions;&lt;/li&gt;
&lt;li&gt;invalid configuration;&lt;/li&gt;
&lt;li&gt;expired credentials;&lt;/li&gt;
&lt;li&gt;API rate limits;&lt;/li&gt;
&lt;li&gt;malformed responses;&lt;/li&gt;
&lt;li&gt;unexpected order states;&lt;/li&gt;
&lt;li&gt;application restarts;&lt;/li&gt;
&lt;li&gt;missing market data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are particularly interesting when you're writing the first version.&lt;/p&gt;

&lt;p&gt;They become extremely interesting when the application has been running for hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging Is Part of the Architecture
&lt;/h2&gt;

&lt;p&gt;When an automated system makes a decision, you should be able to understand why.&lt;/p&gt;

&lt;p&gt;If something unexpected happens, logs are often the only way to reconstruct the sequence of events.&lt;/p&gt;

&lt;p&gt;A useful log trail might tell you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;market data received
→ strategy evaluated
→ signal generated
→ risk check performed
→ order submitted
→ exchange response received
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without that information, debugging becomes guesswork.&lt;/p&gt;

&lt;p&gt;This is especially important when the system is automated.&lt;/p&gt;

&lt;p&gt;If a human clicks a button, there is usually a clear action associated with the event.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backtesting Changes the Data Source, Not the Strategy
&lt;/h2&gt;

&lt;p&gt;Backtesting is another area where architecture matters.&lt;/p&gt;

&lt;p&gt;Ideally, a strategy shouldn't need to know whether its input came from a live exchange or historical data.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   Strategy
                  /        \
                 /          \
        Historical Data    Live Data
              ↓                ↓
          Backtester        Exchange
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy receives data.&lt;/p&gt;

&lt;p&gt;The environment determines where that data came from and how execution is handled.&lt;/p&gt;

&lt;p&gt;This makes experimentation considerably easier.&lt;/p&gt;

&lt;p&gt;You can evaluate an idea against historical data without modifying the strategy just because the execution environment changed.&lt;/p&gt;

&lt;p&gt;But there is an important limitation.&lt;/p&gt;

&lt;p&gt;A backtest is not a reproduction of reality.&lt;/p&gt;

&lt;p&gt;Historical results can differ from live execution because of fees, spread, slippage, liquidity, latency, partial fills, and changing market conditions.&lt;/p&gt;

&lt;p&gt;There is also a statistical problem.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That is not the same as discovering a robust strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Machine Learning Adds Another Layer
&lt;/h2&gt;

&lt;p&gt;Machine learning is attractive in trading because markets generate a large amount of data.&lt;/p&gt;

&lt;p&gt;A model can be used for prediction, classification, pattern recognition, or adaptive strategies.&lt;/p&gt;

&lt;p&gt;But introducing machine learning doesn't remove the rest of the architecture.&lt;/p&gt;

&lt;p&gt;The model still needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input data;&lt;/li&gt;
&lt;li&gt;preprocessing;&lt;/li&gt;
&lt;li&gt;feature generation;&lt;/li&gt;
&lt;li&gt;inference;&lt;/li&gt;
&lt;li&gt;validation;&lt;/li&gt;
&lt;li&gt;output handling;&lt;/li&gt;
&lt;li&gt;monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And a model prediction still needs to become a decision inside the larger system.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data
    ↓
Features
    ↓
Model
    ↓
Prediction
    ↓
Strategy Decision
    ↓
Risk Check
    ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The machine-learning component is another part of the pipeline.&lt;/p&gt;

&lt;p&gt;It isn't the pipeline itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Needs Measurement
&lt;/h2&gt;

&lt;p&gt;Trading software naturally leads to performance discussions.&lt;/p&gt;

&lt;p&gt;Latency matters.&lt;/p&gt;

&lt;p&gt;CPU usage matters.&lt;/p&gt;

&lt;p&gt;Memory usage matters.&lt;/p&gt;

&lt;p&gt;Concurrency matters.&lt;/p&gt;

&lt;p&gt;But performance optimization without measurement can easily become wasted effort.&lt;/p&gt;

&lt;p&gt;Suppose a particular calculation takes a meaningful amount of CPU time.&lt;/p&gt;

&lt;p&gt;Optimizing that calculation may be worthwhile.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That is why I prefer to start with profiling and measurements.&lt;/p&gt;

&lt;p&gt;Find the actual bottleneck.&lt;/p&gt;

&lt;p&gt;Then decide whether optimization is necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where C, C++ or Assembly Make Sense
&lt;/h2&gt;

&lt;p&gt;Lower-level languages are interesting when a workload actually requires them.&lt;/p&gt;

&lt;p&gt;C and C++ can provide more control over memory, execution and native integration.&lt;/p&gt;

&lt;p&gt;Assembly can provide even more direct control over specific CPU operations.&lt;/p&gt;

&lt;p&gt;But none of that automatically makes a trading system faster.&lt;/p&gt;

&lt;p&gt;If the bottleneck is network latency, rewriting a piece of application logic in assembly won't make the exchange respond faster.&lt;/p&gt;

&lt;p&gt;If the bottleneck is a CPU-heavy calculation performed millions of times, native optimization could make much more sense.&lt;/p&gt;

&lt;p&gt;The important question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can this be written in assembly?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this actually the part of the system that needs to be optimized?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction prevents a lot of unnecessary complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Chose a Windows Application
&lt;/h2&gt;

&lt;p&gt;Crypto trading software is often presented as a server or command-line application.&lt;/p&gt;

&lt;p&gt;I wanted CryptoBot to be usable as a Windows application.&lt;/p&gt;

&lt;p&gt;That changes the project considerably.&lt;/p&gt;

&lt;p&gt;Now the software also has to deal with packaging, installation, configuration, executable distribution, logging locations, application startup and release management.&lt;/p&gt;

&lt;p&gt;For a developer, running source code locally is easy.&lt;/p&gt;

&lt;p&gt;For someone using a compiled application, the experience should be much simpler.&lt;/p&gt;

&lt;p&gt;The user shouldn't have to understand the internal architecture just to launch the application.&lt;/p&gt;

&lt;p&gt;That makes packaging and documentation part of the engineering work rather than something added at the very end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Changes When the Software Can Trade
&lt;/h2&gt;

&lt;p&gt;A program that can interact with a trading account needs to treat credentials seriously.&lt;/p&gt;

&lt;p&gt;API keys should be protected.&lt;/p&gt;

&lt;p&gt;Only required permissions should be enabled.&lt;/p&gt;

&lt;p&gt;Withdrawal permissions should be disabled when they aren't necessary.&lt;/p&gt;

&lt;p&gt;Credentials should never be committed to source control.&lt;/p&gt;

&lt;p&gt;The same principle applies to wallet connections.&lt;/p&gt;

&lt;p&gt;Users should understand what they're connecting and what they're authorizing.&lt;/p&gt;

&lt;p&gt;This is another area where a trading application differs from a normal hobby script.&lt;/p&gt;

&lt;p&gt;The software isn't just processing information.&lt;/p&gt;

&lt;p&gt;It can potentially cause financial actions.&lt;/p&gt;

&lt;p&gt;That raises the cost of mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Difficult Part Isn't the Algorithm
&lt;/h2&gt;

&lt;p&gt;After spending a lot of time working on this project, I think this is the part that is easiest to underestimate.&lt;/p&gt;

&lt;p&gt;The algorithm is visible.&lt;/p&gt;

&lt;p&gt;You can see the formula.&lt;/p&gt;

&lt;p&gt;You can see the strategy.&lt;/p&gt;

&lt;p&gt;You can see the signal.&lt;/p&gt;

&lt;p&gt;The infrastructure around it is less visible.&lt;/p&gt;

&lt;p&gt;Networking.&lt;/p&gt;

&lt;p&gt;State.&lt;/p&gt;

&lt;p&gt;Error handling.&lt;/p&gt;

&lt;p&gt;Execution.&lt;/p&gt;

&lt;p&gt;Risk.&lt;/p&gt;

&lt;p&gt;Logging.&lt;/p&gt;

&lt;p&gt;Recovery.&lt;/p&gt;

&lt;p&gt;Configuration.&lt;/p&gt;

&lt;p&gt;Testing.&lt;/p&gt;

&lt;p&gt;Those pieces aren't as interesting in screenshots, but they're what determine whether the application behaves predictably.&lt;/p&gt;

&lt;p&gt;A trading strategy can be mathematically simple.&lt;/p&gt;

&lt;p&gt;Building software that executes it reliably is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Focus on Next
&lt;/h2&gt;

&lt;p&gt;There are several areas of CryptoBot that I want to continue improving.&lt;/p&gt;

&lt;p&gt;The first is making strategy development easier.&lt;/p&gt;

&lt;p&gt;The second is improving the backtesting environment so that experiments can be performed more systematically.&lt;/p&gt;

&lt;p&gt;Exchange connectivity and execution reliability are also areas that naturally require continuous work.&lt;/p&gt;

&lt;p&gt;There is also plenty of room for performance improvements once actual bottlenecks become clear.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building an automated crypto trading system changed my understanding of what a trading bot actually is.&lt;/p&gt;

&lt;p&gt;It isn't just an algorithm.&lt;/p&gt;

&lt;p&gt;It isn't just an exchange API.&lt;/p&gt;

&lt;p&gt;It isn't just a dashboard.&lt;/p&gt;

&lt;p&gt;It is a collection of systems that have to cooperate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data
     ↓
Strategy
     ↓
Risk Management
     ↓
Execution
     ↓
Exchange
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Around that core, there is another layer responsible for reliability, state, logging, configuration, testing and recovery.&lt;/p&gt;

&lt;p&gt;That's where most of the interesting engineering happens.&lt;/p&gt;

&lt;p&gt;The project started with the idea of automating trading strategies.&lt;/p&gt;

&lt;p&gt;It became an exercise in designing software that can operate continuously while the environment around it keeps changing.&lt;/p&gt;

&lt;p&gt;That's the part I find most interesting.&lt;/p&gt;

&lt;p&gt;CryptoBot is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pavloaser23/crypto-trading-bot" rel="noopener noreferrer"&gt;https://github.com/pavloaser23/crypto-trading-bot&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>trading</category>
      <category>crypto</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
