<?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: Vuk Stanic</title>
    <description>The latest articles on DEV Community by Vuk Stanic (@stanco23).</description>
    <link>https://dev.to/stanco23</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3899407%2F74a16cc2-d8e9-4576-85ab-ddb54d8689de.jpeg</url>
      <title>DEV Community: Vuk Stanic</title>
      <link>https://dev.to/stanco23</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stanco23"/>
    <language>en</language>
    <item>
      <title>The Bug That Cost Me Three Weeks: Why Your SL/TP Logic Is Probably Wrong</title>
      <dc:creator>Vuk Stanic</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:36:41 +0000</pubDate>
      <link>https://dev.to/stanco23/the-bug-that-cost-me-three-weeks-why-your-sltp-logic-is-probably-wrong-30eb</link>
      <guid>https://dev.to/stanco23/the-bug-that-cost-me-three-weeks-why-your-sltp-logic-is-probably-wrong-30eb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the story of a production bug I fixed, turned into a book. It's also why most algorithmic traders fail.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every algorithmic trader thinks they understand stop-loss and take-profit (SL/TP). Most are wrong. Not subtly wrong — catastrophically wrong in ways that don't show up in backtesting but destroy live systems.&lt;/p&gt;

&lt;p&gt;This is the opening chapter of &lt;a href="https://whop.com/joined/tse-publishing/products/the-circuit-breaker-problem/" rel="noopener noreferrer"&gt;my second book&lt;/a&gt;, and it's the reason I wrote the whole series.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Naive Implementation
&lt;/h2&gt;

&lt;p&gt;Here's what the original code looked like in our production system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;entry_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sl_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tp_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sl_triggered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tp_triggered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;check_sl_tp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;)&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;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.sl_price&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.sl_triggered&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.sl_triggered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;close_position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&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;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.tp_price&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.tp_triggered&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.tp_triggered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;close_position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks reasonable, right? Check price against SL/TP levels, set a flag, close if triggered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It failed in production. Here's why.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Mode 1: Flag-Based Checking Doesn't Track What Actually Happened
&lt;/h2&gt;

&lt;p&gt;The problem with &lt;code&gt;sl_triggered: bool&lt;/code&gt; is that it tells you &lt;em&gt;that something happened&lt;/em&gt;, but not &lt;em&gt;what actually happened&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Consider this sequence in a fast-moving market:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T=0:    Price at $105.00, position long with SL at $100.00
T=1:    Price drops to $99.95 (below SL!)
T=2:    Your check runs, sets sl_triggered = true
T=3:    Your system submits close order at $99.95
T=4:    Price bounces back to $100.50
T=5:    Exchange confirms: fill at $99.95
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your code set &lt;code&gt;sl_triggered = true&lt;/code&gt; when price crossed $100.00. The exchange filled you at $99.95. Your flag doesn't tell you what fill price you actually got.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More critically:&lt;/strong&gt; In step T=4, before the exchange confirmed the fill, your code thought "SL triggered, position closed." But the position wasn't actually closed yet — it was in flight.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;state vs event confusion&lt;/strong&gt;. Your flag tracks an event (trigger), not a state (position actually closed).&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Mode 2: Re-Entry on the Next Tick
&lt;/h2&gt;

&lt;p&gt;Here's where it gets really bad. After the SL triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T=10:   Price moves back up to $102.00
T=11:   Your strategy sees "price is $102, no open position"
T=12:   Strategy decides to re-enter long
T=13:   New position opened at $102.00
T=14:   Price drops again to $99.90
T=15:   Another SL triggered, another loss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your system has no memory that the previous close was an SL close. It just sees "no position, price looks good, buy."&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;re-entry problem&lt;/strong&gt; — and it's more expensive than the original loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Correct Mental Model
&lt;/h2&gt;

&lt;p&gt;SL/TP should be &lt;strong&gt;state-based&lt;/strong&gt;, not &lt;strong&gt;event-based&lt;/strong&gt;. Instead of "did we trigger?" think "what should we do given the current state and price?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;PositionState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Position is active, checks are running&lt;/span&gt;
    &lt;span class="n"&gt;ClosePending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Close order submitted, waiting for fill&lt;/span&gt;
    &lt;span class="n"&gt;Closed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// Position fully exited, no more checks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;entry_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sl_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tp_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PositionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Track the close order, not just the trigger&lt;/span&gt;
    &lt;span class="n"&gt;close_order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;close_trigger_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;opened_at_ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;close_submitted_at_ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;closed_at_ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key difference:&lt;/strong&gt; The &lt;code&gt;close_order_id&lt;/code&gt; field tracks &lt;em&gt;the actual close order&lt;/em&gt;, not just a trigger flag. If you have a close order ID, the position is in &lt;code&gt;ClosePending&lt;/code&gt; state. If it's filled, the state transitions to &lt;code&gt;Closed&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SLTPAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TriggerSL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TriggerTP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;check_sl_tp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SLTPAction&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;position&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nn"&gt;PositionState&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;;&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;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.sl_price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TriggerSL&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.tp_price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TriggerTP&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Nothing&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;on_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Nothing&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(()),&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TriggerSL&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TriggerTP&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;submit_close_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;PositionState&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ClosePending&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.close_order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.close_trigger_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.close_submitted_at_ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;current_timestamp_ns&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Backtesting Misses This
&lt;/h2&gt;

&lt;p&gt;In backtesting, prices are usually bar-based (OHLC). The SL/TP check happens once per bar at the close. In live trading, you're checking every tick. A tick-based system might check SL/TP 100 times per second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bug manifests in live trading because:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Price crosses SL&lt;/li&gt;
&lt;li&gt;Your check runs, returns &lt;code&gt;TriggerSL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You submit close order&lt;/li&gt;
&lt;li&gt;Meanwhile, price bounces back above SL&lt;/li&gt;
&lt;li&gt;Your check runs again, sees price above SL, does nothing&lt;/li&gt;
&lt;li&gt;But your close order is still pending...&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The flag-based approach doesn't know that a close order is already in flight. It sees price above SL and would try to trade again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Production Failure
&lt;/h2&gt;

&lt;p&gt;Our original system had this flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Position opened at $100.00, SL = $98.00, TP = $102.00
2. Price drops to $97.50
3. check_sl_tp() sets sl_triggered = true
4. close_position() called
5. Position state set to "closing" (but not "closed")
6. Order submitted to exchange
7. Network latency = 50ms
8. Price bounces back to $99.00
9. check_sl_tp() runs again — price above SL, does nothing
10. Strategy continues to next tick
11. Exchange confirms fill at $97.50
12. Position is now closed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All good so far. But then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;13. Next tick arrives
14. Strategy sees: "no open position, price is $99, this looks like a buy"
15. New position opened at $99.00
16. Price drops again to $97.50
17. Another SL triggered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Steps 13-15 happened while the close order was still in flight. The strategy saw "no position" because the position was in "closing" state but hadn't confirmed "closed" yet.&lt;/p&gt;

&lt;p&gt;We fixed this by adding &lt;strong&gt;close order tracking&lt;/strong&gt; — the system now knows that a close is pending and doesn't allow new positions until the close is confirmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Two things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. State machines over flags.&lt;/strong&gt; Every position should follow a clear state machine: &lt;code&gt;Open → ClosePending → Closed&lt;/code&gt;. Transitions happen on &lt;em&gt;confirmed events&lt;/em&gt;, not on &lt;em&gt;trigger signals&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Backtesting lies to you.&lt;/strong&gt; The bug never appeared in backtesting because we checked once per bar. In live trading, the race condition happens between ticks. Your backtest looks perfect. Your live account doesn't.&lt;/p&gt;




&lt;p&gt;This is Chapter 1 of "The Circuit Breaker Problem" — one of five books in the &lt;a href="https://whop.com/joined/tse-publishing/products/trading-system-engineering-bundle/" rel="noopener noreferrer"&gt;Trading System Engineering Bundle&lt;/a&gt;. All written by an engineer who actually built a production trading engine from scratch. Code templates included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's in the bundle:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Order Engine Architecture — FIFO matching, order book data structures&lt;/li&gt;
&lt;li&gt;The Circuit Breaker Problem — SL/TP bugs, trailing stops, re-entry prevention&lt;/li&gt;
&lt;li&gt;Data Pipeline — TVC3 binary format, ring buffers, VPIN&lt;/li&gt;
&lt;li&gt;Risk Management Engineering — commission handling, Kelly criterion, drawdown&lt;/li&gt;
&lt;li&gt;Backtest Architecture — look-ahead bias, slippage modeling, walk-forward analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;$20 per book, $80 for the bundle. Free preview: &lt;a href="https://whop.com/joined/tse-publishing/free-preview-EtI2jE95ZvAIq9/app/" rel="noopener noreferrer"&gt;Book 2, Chapter 1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>algorithmictrading</category>
      <category>rust</category>
      <category>trading</category>
      <category>engineering</category>
    </item>
  </channel>
</rss>
