<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Building a Crypto Trading Engine: Market Data, Strategies, Risk Management and Order Execution]]></title><description><![CDATA[Building a Crypto Trading Engine: Market Data, Strategies, Risk Management and Order Execution]]></description><link>https://johnduegit.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Building a Crypto Trading Engine: Market Data, Strategies, Risk Management and Order Execution</title><link>https://johnduegit.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 09 Sep 2026 14:40:23 GMT</lastBuildDate><atom:link href="https://johnduegit.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The Hardest Part of Software Isn't the Algorithm]]></title><description><![CDATA[When we build a new system, we usually focus on the part that makes it interesting.
For a recommendation system, it's the recommendation logic. For an API, it's the business logic. For a data platform]]></description><link>https://johnduegit.hashnode.dev/the-hardest-part-of-software-isn-t-the-algorithm</link><guid isPermaLink="true">https://johnduegit.hashnode.dev/the-hardest-part-of-software-isn-t-the-algorithm</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[distributed systems]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[John Doe]]></dc:creator><pubDate>Wed, 02 Sep 2026 11:46:51 GMT</pubDate><content:encoded><![CDATA[<p>When we build a new system, we usually focus on the part that makes it interesting.</p>
<p>For a recommendation system, it's the recommendation logic. For an API, it's the business logic. For a data platform, it's the processing pipeline. For a trading system, it might be the strategy.</p>
<p>That's usually where the fun is.</p>
<p>We think about algorithms, performance, architecture, and new features. We build the happy path first, make it work, and then move on.</p>
<p>But after spending more time building software that depends on external systems, I've started to see the problem differently.</p>
<p>The hardest part often isn't making the system work.</p>
<p>It's making sure the system behaves correctly when reality doesn't.</p>
<p>A network connection disappears. An API request times out. A message arrives twice. Another message never arrives. Data becomes stale. A process crashes halfway through an operation. The application restarts and discovers that its local state doesn't match what actually happened.</p>
<p>These aren't unusual edge cases.</p>
<p>They're normal.</p>
<p>And this is where a simple application starts turning into a real system.</p>
<p>I've been exploring many of these problems while building <a href="https://github.com/pavloaser23/crypto-trading-bot">CryptoBot</a>, but the ideas aren't specific to trading. The same problems appear in payment systems, distributed applications, background workers, event-driven services, microservices, and almost anything that communicates with the outside world.</p>
<h2>The Happy Path Is Easy</h2>
<p>The first version of almost any system can look surprisingly simple:</p>
<pre><code class="language-plaintext">Input
  ↓
Application
  ↓
External Service
  ↓
Response
</code></pre>
<p>The request arrives.</p>
<p>The application processes it.</p>
<p>The external service responds.</p>
<p>Everything continues.</p>
<p>That's the happy path.</p>
<p>And the happy path is usually not where the difficult engineering starts.</p>
<p>The interesting problems appear when one of our assumptions stops being true.</p>
<p>What happens if the external service doesn't respond?</p>
<p>What happens if the request reached the server, but the response was lost?</p>
<p>What happens if the application crashes immediately after the operation succeeds?</p>
<p>What happens if the same message is delivered twice?</p>
<p>What happens if the application believes something happened, but the external system says otherwise?</p>
<p>At that point, <code>success</code> and <code>failure</code> are no longer enough.</p>
<p>Sometimes the correct state is:</p>
<pre><code class="language-plaintext">UNKNOWN
</code></pre>
<p>And that single state changes how you have to design the system.</p>
<h2>A Timeout Doesn't Necessarily Mean Failure</h2>
<p>Imagine an application sends a request to an external service.</p>
<p>The request leaves the application, but the connection disappears before the response arrives.</p>
<p>Eventually, the client reports:</p>
<pre><code class="language-plaintext">TIMEOUT
</code></pre>
<p>What actually happened?</p>
<p>Maybe the request never reached the server.</p>
<p>Maybe the server is still processing it.</p>
<p>Maybe the server completed the operation.</p>
<p>Maybe the operation completed successfully, but the response was lost.</p>
<p>From the application's point of view, these situations can look almost identical.</p>
<p>That's why this assumption can be dangerous:</p>
<pre><code class="language-plaintext">TIMEOUT = FAILED
</code></pre>
<p>A timeout really means something closer to:</p>
<pre><code class="language-plaintext">"I didn't receive the response I expected."
</code></pre>
<p>That's a very different statement.</p>
<p>The operation might still have happened.</p>
<p>This becomes especially important when retries are involved.</p>
<p>Imagine the application does this:</p>
<pre><code class="language-plaintext">Send Request
    ↓
  Timeout
    ↓
  Retry
    ↓
Send Request Again
</code></pre>
<p>If the first request actually succeeded, the second request could create a duplicate operation.</p>
<p>This is why reliable systems often need things like idempotency keys, unique request identifiers, retry limits, exponential backoff, and explicit operation states.</p>
<p>The important lesson isn't that retries are bad.</p>
<p>Retries are extremely useful.</p>
<p>The lesson is that you need to understand what happens when you retry.</p>
<h2>Your Local State Is Not Reality</h2>
<p>Another problem appears when an application maintains its own representation of an external system.</p>
<p>Imagine the application stores:</p>
<pre><code class="language-plaintext">status = "completed"
</code></pre>
<p>It might have received that information from an API response, an event, a database, or another service.</p>
<p>But the application doesn't directly know reality.</p>
<p>It knows what it currently believes reality to be.</p>
<p>Usually that's good enough.</p>
<p>Until it isn't.</p>
<p>An event might be missed. A process might crash before saving an update. Messages might arrive out of order. Another service might change the state. A network connection might disappear at exactly the wrong moment.</p>
<p>Eventually you can have:</p>
<pre><code class="language-plaintext">Local State
     ≠
Actual State
</code></pre>
<p>This is one of the fundamental problems of distributed software.</p>
<p>Your application can be internally consistent while still being wrong about the outside world.</p>
<h2>This Is Why Reconciliation Matters</h2>
<p>Reconciliation is simply the process of comparing what your application believes with what the external source of truth says.</p>
<p>Conceptually:</p>
<pre><code class="language-plaintext">Local State
     ↓
   Compare
     ↓
External State
     ↓
  Mismatch?
   /     \
 No      Yes
 ↓        ↓
</code></pre>
<p>Continue Reconcile</p>
<p>This pattern is much more common than it might seem.</p>
<p>A payment system can reconcile transactions.</p>
<p>An inventory system can reconcile stock.</p>
<p>A cloud platform can reconcile infrastructure.</p>
<p>A trading system can reconcile orders and positions.</p>
<p>An event-driven application can compare its local state with a current snapshot.</p>
<p>The details are different, but the underlying problem is the same.</p>
<p>The application has an opinion about the current state.</p>
<p>Sometimes it needs to verify that opinion.</p>
<p>That's why I don't think reconciliation should be treated as a weird edge case.</p>
<p>For many systems, it's part of normal operation.</p>
<h2>Events Are Fast. Snapshots Are a Safety Net.</h2>
<p>Event-driven systems are powerful because applications can react to changes as they happen.</p>
<p>For example:</p>
<pre><code class="language-plaintext">Created
  ↓
Updated
  ↓
Completed
</code></pre>
<p>This is great when every event arrives correctly.</p>
<p>But imagine the application receives <code>Created</code> and <code>Updated</code>, then loses its connection before <code>Completed</code>.</p>
<p>Now the local state is incomplete.</p>
<p>The application doesn't necessarily know that it missed something.</p>
<p>This is where snapshots become useful.</p>
<p>Events are great for keeping local state updated quickly.</p>
<p>Snapshots are useful for verifying or rebuilding that state.</p>
<p>A recovery flow might look like:</p>
<pre><code class="language-plaintext">Events
  ↓
Local State
  ↓
Something Goes Wrong
  ↓
Fetch Current State
  ↓
Reconcile
  ↓
Correct Local State
</code></pre>
<p>This gives the system two mechanisms:</p>
<p>Fast updates during normal operation.</p>
<p>A way to recover when normal operation breaks.</p>
<p>That combination is much more robust than assuming every event will always arrive.</p>
<h2>A Connected System Can Still Be Broken</h2>
<p>One of the easiest mistakes is treating connectivity as a complete health check.</p>
<p>For example:</p>
<pre><code class="language-plaintext">connected = true
</code></pre>
<p>Therefore:</p>
<pre><code class="language-plaintext">system = healthy
</code></pre>
<p>Not necessarily.</p>
<p>The application can be connected while its data is stale.</p>
<p>It can be connected while an operation is still unknown.</p>
<p>It can be connected while its local state doesn't match the external system.</p>
<p>Connectivity tells you that a connection exists.</p>
<p>It doesn't tell you that the application is ready to make decisions.</p>
<p>That's why explicit application states can be much more useful.</p>
<p>For example:</p>
<pre><code class="language-plaintext">STARTING
   ↓
CONNECTING
   ↓
SYNCING
   ↓
READY
   ↓
RUNNING
</code></pre>
<p>And if something goes wrong:</p>
<pre><code class="language-plaintext">RUNNING
   ↓
DEGRADED
   ↓
RECOVERING
   ↓
SYNCING
   ↓
READY
   ↓
RUNNING
</code></pre>
<p>Now the application has a vocabulary for what is happening.</p>
<p>It's not simply "connected" or "disconnected."</p>
<p>It can be connected but not synchronized.</p>
<p>It can be running but degraded.</p>
<p>It can be recovering.</p>
<p>And importantly, it can know when it should not continue normal processing.</p>
<h2>Sometimes the Correct Action Is to Stop</h2>
<p>There is a natural tendency in automation to keep everything running.</p>
<p>Something fails?</p>
<p>Retry it.</p>
<p>The connection disappears?</p>
<p>Reconnect.</p>
<p>The request times out?</p>
<p>Send it again.</p>
<p>Sometimes that's correct.</p>
<p>Sometimes it's exactly the wrong thing to do.</p>
<p>Imagine the application discovers that its local state doesn't match the external system.</p>
<p>Continuing to process new operations might make the situation worse because every new decision is based on information the application no longer trusts.</p>
<p>A safer approach can be:</p>
<pre><code class="language-plaintext">Mismatch Detected
      ↓
Stop Normal Processing
      ↓
Verify External State
      ↓
Reconcile
      ↓
Validate
      ↓
Resume
</code></pre>
<p>This is an important distinction.</p>
<p>The goal of reliability isn't:</p>
<pre><code class="language-plaintext">"Keep the application running no matter what."
</code></pre>
<p>It's:</p>
<pre><code class="language-plaintext">"Keep the application running when it is safe to do so."
</code></pre>
<p>Sometimes stopping is a successful recovery decision.</p>
<h2>State Machines Are More Useful Than They Look</h2>
<p>A lot of application logic starts with boolean flags.</p>
<p>You might have:</p>
<pre><code class="language-plaintext">connected = true
processing = true
healthy = true
retrying = false
</code></pre>
<p>At first, this is simple.</p>
<p>As the application grows, however, more flags appear.</p>
<p>Eventually you can end up with combinations that don't make much sense:</p>
<pre><code class="language-plaintext">connected = true
healthy = true
synchronized = false
processing = true
</code></pre>
<p>Should the system really be processing?</p>
<p>Explicit states make these situations easier to reason about.</p>
<p>For example:</p>
<pre><code class="language-plaintext">CREATED
   ↓
PROCESSING
   ↓
COMPLETED
</code></pre>
<p>Or:</p>
<pre><code class="language-plaintext">CREATED
   ↓
PROCESSING
   ↓
FAILED
   ↓
RECOVERING
   ↓
PROCESSING
</code></pre>
<p>Now the system has explicit transitions.</p>
<p>Instead of asking:</p>
<pre><code class="language-plaintext">"Which combination of flags is currently true?"
</code></pre>
<p>You can ask:</p>
<pre><code class="language-plaintext">"What state are we in, and what transitions are allowed from here?"
</code></pre>
<p>That's a much easier question to reason about.</p>
<h2>Idempotency Changes How You Think About Failures</h2>
<p>One of the most useful concepts for reliable systems is idempotency.</p>
<p>The idea is simple: performing the same operation multiple times should not accidentally create multiple side effects.</p>
<p>Imagine an application sends request <code>#123</code>.</p>
<p>The server processes it successfully.</p>
<p>But the response never reaches the client.</p>
<p>The client doesn't know what happened, so it tries again.</p>
<p>Without idempotency:</p>
<pre><code class="language-plaintext">Request #123
     ↓
Resource Created

Request #123 Again
     ↓
Another Resource Created
</code></pre>
<p>With idempotency:</p>
<pre><code class="language-plaintext">Request #123
     ↓
Resource Created

Request #123 Again
     ↓
Existing Result Returned
</code></pre>
<p>This doesn't solve every distributed-systems problem.</p>
<p>But it makes uncertain network behavior much easier to handle.</p>
<p>And uncertain network behavior is something we can't completely eliminate.</p>
<h2>Recovery Should Be Designed From the Beginning</h2>
<p>A common development process looks like this:</p>
<pre><code class="language-plaintext">Build Feature
     ↓
Make It Work
     ↓
Add Error Handling
     ↓
Add Retries
     ↓
Deploy
</code></pre>
<p>The problem is that recovery often requires information that wasn't designed into the system in the first place.</p>
<p>If you don't store enough state, recovery becomes difficult.</p>
<p>If operations don't have unique identifiers, safe retries become harder.</p>
<p>If important transitions aren't observable, debugging becomes guesswork.</p>
<p>If there is no reliable source of truth, reconciliation may be impossible.</p>
<p>That's why I've started thinking about recovery at the same time as the normal workflow.</p>
<p>When designing a feature, ask two questions:</p>
<pre><code class="language-plaintext">What happens when everything works?

How do we recover when it doesn't?
</code></pre>
<p>The second question often reveals more about the architecture than the first.</p>
<h2>Observability Is Part of the System</h2>
<p>Logs are often treated as something you add when debugging becomes painful.</p>
<p>I think they're more important than that.</p>
<p>When an application behaves unexpectedly, you need to understand not only what happened, but what the application believed was happening and why it made a particular decision.</p>
<p>For example:</p>
<pre><code class="language-plaintext">RUNNING → RECOVERING
Connection restored
Synchronization started
State mismatch detected
State reconciled
RECOVERING → RUNNING
</code></pre>
<p>Now there is a story.</p>
<p>Without that context, you might only know that:</p>
<pre><code class="language-plaintext">"The application stopped working."
</code></pre>
<p>Good observability should help answer three questions:</p>
<p><strong>What happened?</strong></p>
<p><strong>What did the system believe happened?</strong></p>
<p><strong>Why did it make that decision?</strong></p>
<p>This is especially important in asynchronous systems where there isn't always one place containing the complete history.</p>
<h2>The Algorithm Is Only One Layer</h2>
<p>This is probably the biggest change in how I think about software.</p>
<p>We often describe applications like this:</p>
<pre><code class="language-plaintext">Input
  ↓
Algorithm
  ↓
Output
</code></pre>
<p>That's useful for explaining the core logic.</p>
<p>But it doesn't describe a production system.</p>
<p>A real application is usually closer to:</p>
<pre><code class="language-plaintext">Input
  ↓
Validation
  ↓
Business Logic
  ↓
State
  ↓
External Systems
  ↓
Failure Handling
  ↓
Recovery
  ↓
Observability
</code></pre>
<p>The algorithm can be completely correct while the system still behaves incorrectly.</p>
<p>Not because the algorithm is bad.</p>
<p>Because the assumptions around it were wrong.</p>
<p>The input was stale.</p>
<p>The response was lost.</p>
<p>The state was outdated.</p>
<p>An operation was duplicated.</p>
<p>An event never arrived.</p>
<p>The process restarted at the wrong moment.</p>
<p>These aren't algorithm problems.</p>
<p>They're system problems.</p>
<h2>What Building CryptoBot Taught Me</h2>
<p>I've been exploring many of these ideas while building <a href="https://github.com/pavloaser23/crypto-trading-bot">CryptoBot</a>.</p>
<p>The project started around automated cryptocurrency trading, with market data, technical strategies, risk management, execution, backtesting, and exchange integrations.</p>
<p>But the more interesting engineering questions quickly moved beyond the strategy itself.</p>
<p>What happens when market data becomes stale?</p>
<p>What happens when an exchange connection disappears?</p>
<p>What happens when an order request times out?</p>
<p>What happens if the order actually succeeded?</p>
<p>What happens after a process restart?</p>
<p>What happens when local state doesn't match the external system?</p>
<p>These questions are useful even if you're not building trading software.</p>
<p>They are the same questions that eventually appear in almost any system that has to communicate with external services and operate without constant human supervision.</p>
<p>If you're interested in the project, you can find the source code here:</p>
<p><a href="https://github.com/pavloaser23/crypto-trading-bot">https://github.com/pavloaser23/crypto-trading-bot</a></p>
<h2>The Real Engineering Challenge</h2>
<p>I don't think reliable software means preventing every possible failure.</p>
<p>That's impossible.</p>
<p>Networks will fail. Servers will restart. Dependencies will become unavailable. Messages will be delayed. External systems will behave unexpectedly.</p>
<p>The goal is to make those failures understandable and recoverable.</p>
<p>A reliable system should be able to recognize that something went wrong, determine what state it is in, verify what actually happened, recover its state, and only then continue.</p>
<p>Sometimes that means retrying.</p>
<p>Sometimes it means reconciling.</p>
<p>Sometimes it means rebuilding state.</p>
<p>And sometimes the safest decision is simply:</p>
<pre><code class="language-plaintext">STOP
  ↓
RECOVER
  ↓
RECONCILE
  ↓
VALIDATE
  ↓
RESUME
</code></pre>
<p>That's the part of software engineering I find increasingly interesting.</p>
<p>Not just building systems that work.</p>
<p>Building systems that know when they don't.</p>
<p>And, more importantly, know what to do next.</p>
<h2>Final Thought</h2>
<p>The older I get in software engineering, the less impressed I am by systems that only work when everything goes perfectly.</p>
<p>The interesting systems are the ones that remain understandable when things go wrong.</p>
<p>A good algorithm matters. A clean architecture matters. Performance matters.</p>
<p>But so do the boring questions.</p>
<p>What if the response disappears?</p>
<p>What if the data is stale?</p>
<p>What if the same message arrives twice?</p>
<p>What if a message never arrives?</p>
<p>What if the process crashes halfway through an operation?</p>
<p>What if the local state is wrong?</p>
<p>What if we simply don't know what happened?</p>
<p>Those questions usually don't appear in the first version of a feature.</p>
<p>Eventually, they become part of the feature itself.</p>
<p>Maybe that's the real difference between software that <strong>works</strong> and software that can actually <strong>be trusted</strong>.</p>
<p>If you're building distributed systems, APIs, background workers, event-driven applications, or anything that depends on external services, I'd be interested to hear what failure case taught you the most.</p>
<p>For me, it was learning that <strong>"nothing crashed" doesn't necessarily mean "everything is okay."</strong></p>
<hr />
<p><strong>Repository:</strong> <a href="https://github.com/pavloaser23/crypto-trading-bot">https://github.com/pavloaser23/crypto-trading-bot</a></p>
<p><em>Disclaimer: 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 trading results. The software does not guarantee profits. You are responsible for your own trading decisions, exchange accounts, API credentials, and financial risk.</em></p>
]]></content:encoded></item><item><title><![CDATA[Building a Crypto Trading Engine: Market Data, Strategies, Risk Management and Order Execution]]></title><description><![CDATA[Building a cryptocurrency trading bot is one of those projects that looks straightforward until you try to make the software run continuously.
At first, the architecture can be almost trivial:
Price →]]></description><link>https://johnduegit.hashnode.dev/building-a-crypto-trading-engine-market-data-strategies-risk-management-and-order-execution</link><guid isPermaLink="true">https://johnduegit.hashnode.dev/building-a-crypto-trading-engine-market-data-strategies-risk-management-and-order-execution</guid><category><![CDATA[Cryptocurrency]]></category><category><![CDATA[crypto]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[John Doe]]></dc:creator><pubDate>Sun, 30 Aug 2026 14:52:11 GMT</pubDate><content:encoded><![CDATA[<p>Building a cryptocurrency trading bot is one of those projects that looks straightforward until you try to make the software run continuously.</p>
<p>At first, the architecture can be almost trivial:</p>
<pre><code class="language-text">Price → Strategy → Buy/Sell
</code></pre>
<p>That is enough to demonstrate an idea.</p>
<p>It isn't enough to build a reliable trading application.</p>
<p>Once the project needs real-time market data, multiple exchanges, automated execution, backtesting, risk controls, persistent state, logging and a usable Windows application, the problem changes completely.</p>
<p>You are no longer just writing a trading strategy.</p>
<p>You are building a small distributed system that happens to interact with financial markets.</p>
<p>I've been working on CryptoBot with that problem in mind. The project is a Windows-based automated cryptocurrency trading application built around strategy execution, market data, risk management and exchange connectivity.</p>
<p>Repository:</p>
<p><a href="https://github.com/pavloaser23/crypto-trading-bot">https://github.com/pavloaser23/crypto-trading-bot</a></p>
<p>This article focuses on how I think about the architecture behind an automated trading system and some of the engineering problems that appear once the initial prototype starts becoming a real application.</p>
<h2>The Core Pipeline</h2>
<p>The simplest useful model for the system is:</p>
<pre><code class="language-text">                    Market Data
                         │
                         ▼
                  Data Processing
                         │
                         ▼
                     Strategy
                         │
                         ▼
                       Signal
                         │
                         ▼
                  Risk Management
                         │
                         ▼
                     Execution
                         │
                         ▼
                      Exchange
</code></pre>
<p>There are more components around this pipeline, but this is the part that matters most.</p>
<p>The key idea is that every stage has a different responsibility.</p>
<p>Market data provides information.</p>
<p>The strategy interprets that information.</p>
<p>Risk management decides whether the resulting action is acceptable.</p>
<p>Execution translates an approved decision into an exchange operation.</p>
<p>Keeping these responsibilities separate is much more valuable than trying to make the application "smart" in one giant module.</p>
<h2>Market Data Comes First</h2>
<p>A trading strategy is only as useful as the information it receives.</p>
<p>For different strategies, that information might include:</p>
<ul>
<li><p>current price;</p>
</li>
<li><p>OHLCV candles;</p>
</li>
<li><p>volume;</p>
</li>
<li><p>order book data;</p>
</li>
<li><p>account balances;</p>
</li>
<li><p>open positions;</p>
</li>
<li><p>historical market data.</p>
</li>
</ul>
<p>There are two common ways an application can obtain this information from an exchange.</p>
<p>The first is request-based access through REST APIs.</p>
<p>The second is streaming data through WebSockets.</p>
<p>REST is convenient when the application needs to ask for something specific.</p>
<p>For example:</p>
<pre><code class="language-text">Get account balance
Get current order
Get historical candles
Submit order
</code></pre>
<p>WebSockets are more appropriate when the application needs a continuous stream of changing information.</p>
<p>The architecture then looks more like:</p>
<pre><code class="language-text">Exchange
   │
   │ WebSocket
   ▼
Market Data Handler
   │
   ▼
Internal Market State
</code></pre>
<p>That sounds simple until the connection breaks.</p>
<h2>The Real Problem With WebSockets</h2>
<p>A prototype often assumes that the connection remains alive.</p>
<p>Real networks don't work that way.</p>
<p>The connection can disappear.</p>
<p>The exchange can stop responding.</p>
<p>A message can be malformed.</p>
<p>Data can stop arriving without an obvious exception.</p>
<p>The application can reconnect but have incomplete local state.</p>
<p>This means a streaming component has to deal with more than receiving messages.</p>
<p>It needs to understand connection lifecycle.</p>
<p>A simplified lifecycle looks like:</p>
<pre><code class="language-text">CONNECT
   ↓
RECEIVE
   ↓
PROCESS
   ↓
DISCONNECT
   ↓
RECONNECT
   ↓
RESTORE STATE
   ↓
CONTINUE
</code></pre>
<p>The important part is the last two steps.</p>
<p>Reconnecting isn't necessarily enough.</p>
<p>The application also needs to know what happened while it was disconnected.</p>
<p>That is one of the first places where a trading application starts looking like a distributed system rather than a simple script.</p>
<h2>Don't Let the Strategy Know About the Exchange</h2>
<p>One of the architectural decisions that becomes important very quickly is keeping strategy logic independent from exchange-specific implementation.</p>
<p>Imagine a strategy that contains code like this:</p>
<pre><code class="language-python">if exchange == "binance":
    ...
elif exchange == "bybit":
    ...
elif exchange == "kraken":
    ...
</code></pre>
<p>It might work initially.</p>
<p>After several strategies and several exchanges, it becomes a maintenance problem.</p>
<p>Instead, the strategy should work with concepts such as:</p>
<pre><code class="language-text">price
volume
position
signal
order
</code></pre>
<p>The exchange adapter should deal with:</p>
<pre><code class="language-text">authentication
endpoints
request formats
exchange-specific parameters
responses
errors
</code></pre>
<p>Conceptually:</p>
<pre><code class="language-text">                 Trading Strategy
                        │
                        ▼
                     Signal
                        │
                        ▼
                   Order Model
                        │
             ┌──────────┼──────────┐
             ▼          ▼          ▼
         Exchange A  Exchange B  Exchange C
</code></pre>
<p>This separation makes the rest of the application considerably easier to evolve.</p>
<h2>A Signal Is Not an Execution Command</h2>
<p>This distinction is easy to miss.</p>
<p>Suppose a strategy determines that the current conditions match its entry rules.</p>
<p>It produces:</p>
<pre><code class="language-text">BUY BTC/USDT
</code></pre>
<p>That is a signal.</p>
<p>It should not necessarily mean:</p>
<pre><code class="language-text">SEND ORDER NOW
</code></pre>
<p>There can be another layer between them.</p>
<p>For example:</p>
<pre><code class="language-text">Strategy
   ↓
BUY signal
   ↓
Position check
   ↓
Risk check
   ↓
Position sizing
   ↓
Order creation
   ↓
Execution
</code></pre>
<p>This gives the application a place to enforce rules independently of the strategy.</p>
<p>That matters because strategies answer one question:</p>
<blockquote>
<p>What would I like to do?</p>
</blockquote>
<p>Risk management answers another:</p>
<blockquote>
<p>Am I allowed to do it?</p>
</blockquote>
<p>Those are not the same question.</p>
<h2>Strategy Architecture</h2>
<p>A strategy should ideally be replaceable without requiring changes throughout the application.</p>
<p>CryptoBot includes several categories of strategies, including technical analysis, trend following, scalping, arbitrage and machine-learning-based experimentation.</p>
<p>Technical analysis strategies can use familiar indicators such as:</p>
<ul>
<li><p>Moving Average;</p>
</li>
<li><p>RSI;</p>
</li>
<li><p>MACD;</p>
</li>
<li><p>Bollinger Bands.</p>
</li>
</ul>
<p>The important architectural property is that the strategy can consume market information and produce a consistent result.</p>
<p>For example:</p>
<pre><code class="language-text">Input
  ↓
Market State
  ↓
Strategy
  ↓
Signal
</code></pre>
<p>The rest of the system doesn't need to know whether that signal came from RSI, a moving-average crossover or a machine-learning model.</p>
<p>That is what makes a modular strategy system useful.</p>
<h2>Why Backtesting Should Use the Same Strategy Logic</h2>
<p>One of the things worth avoiding is maintaining completely separate implementations for live trading and backtesting.</p>
<p>If live trading uses one version of the strategy and the backtester uses another, the results become much harder to interpret.</p>
<p>A better model is:</p>
<pre><code class="language-text">                  Strategy
                 /        \
                /          \
       Historical Data    Live Data
              ↓                ↓
         Backtesting        Trading
</code></pre>
<p>The environment changes.</p>
<p>The strategy doesn't have to.</p>
<p>That makes it possible to test the same decision logic against historical data before exposing it to live execution.</p>
<p>Of course, a backtest still has limitations.</p>
<p>Historical execution isn't identical to live execution.</p>
<p>Real trading introduces:</p>
<ul>
<li><p>fees;</p>
</li>
<li><p>spread;</p>
</li>
<li><p>slippage;</p>
</li>
<li><p>liquidity constraints;</p>
</li>
<li><p>latency;</p>
</li>
<li><p>partial fills;</p>
</li>
<li><p>changing market conditions.</p>
</li>
</ul>
<p>A good backtest can provide useful evidence.</p>
<p>It cannot turn historical performance into a guarantee about future results.</p>
<h2>Risk Management Needs to Sit Between Strategy and Execution</h2>
<p>Automated systems make risk controls especially important because software doesn't get bored or hesitate.</p>
<p>If a condition remains true, the program can continue acting on it.</p>
<p>That is why risk management should be explicit.</p>
<p>Common controls include:</p>
<pre><code class="language-text">Stop-loss
Take-profit
Trailing stop
Position sizing
Capital allocation
</code></pre>
<p>A useful mental model is:</p>
<pre><code class="language-text">                 Strategy
                    │
                    ▼
                  Signal
                    │
                    ▼
              Risk Engine
               /       \
              /         \
          REJECT       APPROVE
                         │
                         ▼
                      Order
</code></pre>
<p>A rejected signal is not necessarily a strategy failure.</p>
<p>The strategy may be correct about the market and the risk engine may still decide that the trade should not happen.</p>
<p>That separation is important.</p>
<h2>Position Sizing Is More Important Than It Looks</h2>
<p>A trading signal answers what direction a strategy wants.</p>
<p>It doesn't necessarily answer how much capital should be used.</p>
<p>Position sizing turns a decision into a quantity.</p>
<p>That means it belongs closer to the risk layer than the strategy itself.</p>
<p>For example, two strategies could generate the same signal:</p>
<pre><code class="language-text">BUY BTC
</code></pre>
<p>But the risk engine could calculate different position sizes depending on:</p>
<ul>
<li><p>available balance;</p>
</li>
<li><p>configured risk;</p>
</li>
<li><p>existing exposure;</p>
</li>
<li><p>stop distance;</p>
</li>
<li><p>portfolio allocation.</p>
</li>
</ul>
<p>This keeps account-level risk rules consistent across different strategies.</p>
<h2>Order Execution Is Its Own Layer</h2>
<p>Once a signal passes risk checks, the system still needs to create and submit an order.</p>
<p>This is where exchange-specific details become important.</p>
<p>Different APIs can have different:</p>
<ul>
<li><p>authentication mechanisms;</p>
</li>
<li><p>order parameters;</p>
</li>
<li><p>symbol formats;</p>
</li>
<li><p>response structures;</p>
</li>
<li><p>error codes;</p>
</li>
<li><p>rate limits;</p>
</li>
<li><p>supported order types.</p>
</li>
</ul>
<p>The execution layer translates the application's internal order representation into whatever the exchange expects.</p>
<p>Conceptually:</p>
<pre><code class="language-text">Internal Order
      ↓
Execution Adapter
      ↓
Exchange Request
      ↓
Exchange Response
      ↓
Internal Order State
</code></pre>
<p>This gives the rest of the application a stable interface even when the external API changes.</p>
<h2>The Order Lifecycle</h2>
<p>An order shouldn't be treated as a simple function call.</p>
<p>The application needs to understand that an order can exist in multiple states.</p>
<p>A simplified lifecycle might look like:</p>
<pre><code class="language-text">Created
   ↓
Submitted
   ↓
Accepted
   ↓
Partially Filled
   ↓
Filled
</code></pre>
<p>There are also other possible outcomes:</p>
<pre><code class="language-text">Rejected
Cancelled
Expired
Failed
Unknown
</code></pre>
<p>The <code>Unknown</code> state is particularly interesting.</p>
<p>Imagine:</p>
<ol>
<li><p>The application submits an order.</p>
</li>
<li><p>The exchange receives it.</p>
</li>
<li><p>The network connection breaks.</p>
</li>
<li><p>The application doesn't receive the response.</p>
</li>
</ol>
<p>The application cannot safely conclude that the order failed.</p>
<p>It may have succeeded.</p>
<p>This is one of those problems that doesn't appear in a basic trading-bot tutorial but becomes extremely important in real systems.</p>
<h2>State Has to Survive Problems</h2>
<p>An automated trading application needs to maintain some understanding of its current state.</p>
<p>For example:</p>
<pre><code class="language-text">Current balance
Open positions
Active orders
Selected strategy
Risk configuration
Connection state
</code></pre>
<p>If the application restarts, it shouldn't blindly assume that everything on the exchange disappeared with the process.</p>
<p>External state exists independently of the application.</p>
<p>This is why synchronization and reconciliation become important.</p>
<p>The application needs a way to compare what it thinks happened with what actually exists on the exchange.</p>
<h2>Logging Is Part of the Trading System</h2>
<p>Logging is sometimes treated as a debugging feature.</p>
<p>For automated trading, I think it's more than that.</p>
<p>Suppose the application opens a position and you later want to understand why.</p>
<p>You want to be able to reconstruct something like:</p>
<pre><code class="language-text">Market data received
↓
Strategy evaluated
↓
Signal generated
↓
Risk check passed
↓
Order created
↓
Order submitted
↓
Exchange response received
↓
Position updated
</code></pre>
<p>Without those events, investigating unexpected behaviour becomes difficult.</p>
<p>The dashboard tells you what is happening now.</p>
<p>Logs tell you what happened before.</p>
<p>Both are useful, but for different reasons.</p>
<h2>Machine Learning Doesn't Replace the Trading Architecture</h2>
<p>Machine learning is an interesting part of the project because it introduces another source of decisions.</p>
<p>A traditional strategy might be based on explicit rules.</p>
<p>A machine-learning system might instead produce a prediction.</p>
<p>For example:</p>
<pre><code class="language-text">Market Data
   ↓
Features
   ↓
Model
   ↓
Prediction
   ↓
Trading Decision
</code></pre>
<p>The prediction still has to pass through the same risk and execution layers.</p>
<p>That means:</p>
<pre><code class="language-text">Model
   ↓
Signal
   ↓
Risk Management
   ↓
Execution
</code></pre>
<p>This is important because a model should not have unrestricted access to the exchange simply because it produces a prediction.</p>
<p>Keeping the model separated from execution also makes experimentation easier.</p>
<p>You can change the model without rewriting the exchange layer.</p>
<h2>Where Performance Actually Matters</h2>
<p>Trading software naturally leads to discussions about performance.</p>
<p>Latency can matter.</p>
<p>CPU usage can matter.</p>
<p>Memory usage can matter.</p>
<p>But performance optimization should begin with measurement.</p>
<p>Suppose a calculation takes 5 milliseconds.</p>
<p>You could spend a lot of time rewriting it in C++ or assembly.</p>
<p>But if the application is waiting 100 milliseconds for an external operation, optimizing that 5-millisecond calculation may not produce a meaningful improvement.</p>
<p>The first question should always be:</p>
<blockquote>
<p>Where is the bottleneck?</p>
</blockquote>
<p>Profiling is much more useful than guessing.</p>
<h2>Python, C++, C and Assembly</h2>
<p>There is no reason to treat programming languages as mutually exclusive.</p>
<p>Python is useful for rapid development, data processing, experimentation and machine-learning workflows.</p>
<p>C and C++ provide more control when native performance or lower-level integration becomes important.</p>
<p>Assembly gives even more direct control over processor instructions.</p>
<p>But lower-level code is not automatically better.</p>
<p>The correct place for it depends on the workload.</p>
<p>If a calculation is genuinely CPU-bound, optimizing it can make sense.</p>
<p>If the application is dominated by network latency, assembly probably isn't going to solve the important problem.</p>
<p>That is why I prefer to optimize measured bottlenecks rather than optimize based on assumptions.</p>
<h2>Windows Distribution Is Part of the Engineering</h2>
<p>CryptoBot is intended to be used as a Windows application.</p>
<p>That means the project has concerns beyond source code.</p>
<p>The application needs to be packaged.</p>
<p>The executable needs a sensible version.</p>
<p>Configuration needs to be understandable.</p>
<p>Logs need to be accessible.</p>
<p>The release needs to be downloadable.</p>
<p>The application needs to start reliably.</p>
<p>These details aren't glamorous, but they are part of turning a development project into software that another person can actually use.</p>
<p>There is a significant difference between:</p>
<pre><code class="language-text">git clone
install dependencies
configure environment
run source code
</code></pre>
<p>and:</p>
<pre><code class="language-text">download
install
launch
</code></pre>
<p>The second experience requires additional engineering.</p>
<h2>Security Is a Separate Concern</h2>
<p>Trading software deals with credentials and potentially valuable accounts.</p>
<p>Exchange API keys should be treated as sensitive credentials.</p>
<p>A good baseline is to grant only the permissions that are actually required.</p>
<p>If withdrawals aren't required, withdrawal permissions should be disabled.</p>
<p>Credentials should never be committed into source control.</p>
<p>Wallet connections introduce another layer of responsibility.</p>
<p>A user should verify what the application is requesting before approving a connection or transaction.</p>
<p>Automation doesn't reduce the importance of security.</p>
<p>It increases it.</p>
<h2>What Makes an Automated Trading System Difficult?</h2>
<p>After working on the project, I'd describe the problem differently than I did when I started.</p>
<p>The hard part isn't calculating an indicator.</p>
<p>The hard part isn't sending a single API request.</p>
<p>The hard part is getting all the pieces to behave correctly together.</p>
<p>You need:</p>
<pre><code class="language-text">Reliable data
     +
Correct strategy logic
     +
Risk controls
     +
Reliable execution
     +
State management
     +
Error handling
     +
Observability
</code></pre>
<p>And those components have to keep working when the environment isn't behaving perfectly.</p>
<p>That's the real engineering problem.</p>
<h2>A Useful Architecture Is One That Lets You Change Things</h2>
<p>I don't think a good architecture is necessarily the one with the most abstractions.</p>
<p>It is the one that makes future changes less painful.</p>
<p>If adding a new strategy requires rewriting the exchange layer, something is too tightly coupled.</p>
<p>If supporting another exchange requires changing every strategy, the boundaries aren't clear enough.</p>
<p>If backtesting requires a second copy of the trading logic, the abstraction probably needs another look.</p>
<p>If changing the user interface requires touching execution code, the components aren't sufficiently separated.</p>
<p>The goal isn't architectural complexity.</p>
<p>The goal is controlled complexity.</p>
<h2>What I'm Still Working On</h2>
<p>CryptoBot is not something I consider "finished."</p>
<p>There are several areas where I want to keep improving the project.</p>
<p>Strategy development is one.</p>
<p>A trading system becomes much more useful when experimenting with a new idea doesn't require modifying unrelated parts of the application.</p>
<p>Backtesting is another.</p>
<p>The closer the testing environment can represent real execution conditions, the more useful the results become.</p>
<p>Exchange connectivity and execution reliability are also areas that naturally require ongoing work.</p>
<p>And there is still plenty to explore around machine learning and lower-level performance optimization.</p>
<h2>Final Thoughts</h2>
<p>The most useful lesson I've taken from building CryptoBot is that a trading bot isn't really a trading strategy.</p>
<p>The strategy is only one part.</p>
<p>The larger system looks more like:</p>
<pre><code class="language-text">                    ┌──────────────┐
                    │ Market Data  │
                    └──────┬───────┘
                           ↓
                    ┌──────────────┐
                    │   Strategy   │
                    └──────┬───────┘
                           ↓
                    ┌──────────────┐
                    │ Risk Engine  │
                    └──────┬───────┘
                           ↓
                    ┌──────────────┐
                    │  Execution   │
                    └──────┬───────┘
                           ↓
                    ┌──────────────┐
                    │   Exchange   │
                    └──────────────┘
</code></pre>
<p>Around that pipeline you need networking, state management, logging, configuration, testing and recovery.</p>
<p>That's where most of the engineering work lives.</p>
<p>The interesting part isn't making a program capable of placing one trade.</p>
<p>It's making a system that can continuously process information, make decisions, enforce its own constraints and communicate with external services without assuming that everything will always go according to plan.</p>
<p>That's the problem I'm interested in solving with CryptoBot.</p>
<p>The project is available here:</p>
<p><a href="https://github.com/pavloaser23/crypto-trading-bot">https://github.com/pavloaser23/crypto-trading-bot</a></p>
]]></content:encoded></item></channel></rss>