<?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: Valerii Sakara</title>
    <description>The latest articles on DEV Community by Valerii Sakara (@arakas4488cmd).</description>
    <link>https://dev.to/arakas4488cmd</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%2F4076876%2F331b07a6-6bcf-400b-9fa3-6d37807ffe8a.png</url>
      <title>DEV Community: Valerii Sakara</title>
      <link>https://dev.to/arakas4488cmd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arakas4488cmd"/>
    <language>en</language>
    <item>
      <title>The Safety Net That Isn't There</title>
      <dc:creator>Valerii Sakara</dc:creator>
      <pubDate>Thu, 13 Aug 2026 23:49:58 +0000</pubDate>
      <link>https://dev.to/arakas4488cmd/the-safety-net-that-isnt-there-1cb</link>
      <guid>https://dev.to/arakas4488cmd/the-safety-net-that-isnt-there-1cb</guid>
      <description>&lt;p&gt;&lt;em&gt;Two more bugs from live trading bot audits — where the protection looked real everywhere except in the one place that mattered.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A stop-loss that fires and a stop-loss that logs "fired" are not the same thing. Neither are a circuit breaker that trips and a circuit breaker that mathematically can't move. Both bugs below share a shape I keep running into: the protective mechanism runs, updates its own bookkeeping, tells the operator it did its job — and never actually reaches the part of the system that would have made it real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Protective code paths — stop-losses, kill switches, daily loss limits, emergency exits — get exercised far less often than the code that opens positions. An entry function runs on every single trade; a kill switch might fire once in weeks of live running, if ever. That asymmetry means bugs in the protective path survive far longer before anyone notices, because nothing forces them to prove themselves the way constant use forces bugs in the hot path to surface.&lt;/p&gt;

&lt;p&gt;Two live audits from the last few weeks turned up the same underlying failure, in two different disguises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 1: the exit that logs "closed" and stops there
&lt;/h2&gt;

&lt;p&gt;In one bot I reviewed — real capital, real markets — the entry path was solid: build an order, route it through the exchange's real order-submission client, wait for confirmation, record the actual fill. The automatic exit logic (stop-loss, take-profit, and a maximum-holding-time rule) looked, at a glance, like it followed the same pattern. It didn't.&lt;/p&gt;

&lt;p&gt;When an exit condition triggered, the code wrote a trade record tagged &lt;code&gt;SIMULATED&lt;/code&gt;, with a &lt;code&gt;dry_run&lt;/code&gt; flag hardcoded &lt;code&gt;True&lt;/code&gt; regardless of whether the bot was actually running live — then deleted the position from its own tracking table. Nothing in that path built an order. Nothing called the exchange. The function that actually submits and confirms orders — the same one the entry logic used — was never referenced anywhere in the exit code.&lt;/p&gt;

&lt;p&gt;The practical effect: the position that was supposed to be closed is still open, on the real exchange, with real exposure — and the bot no longer knows it exists, because it just deleted its own only record of it. Logs, dashboard, and any alerting all say "closed." The one system that would show otherwise — the exchange itself — was never asked.&lt;/p&gt;

&lt;p&gt;Worth noting: this bot's documentation advertised a fully-built exit system with six named strategies, including an explicit kill-switch meant to "force exit all positions." That code existed, tested, in the repository — just never imported anywhere outside its own test file. A correct, well-tested kill switch that nothing in the live code path ever calls provides exactly as much protection as no kill switch at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 2: the risk limit that's mathematically incapable of triggering
&lt;/h2&gt;

&lt;p&gt;A different bot, a different failure mode, same root shape. This one ran a cross-exchange hedge strategy — open a position on one exchange, immediately hedge it on another — with a daily loss limit meant to halt trading if losses crossed a threshold.&lt;/p&gt;

&lt;p&gt;The loss-limit check itself was fine: it compared an accumulated running total against a configured maximum and blocked new trades if breached. The problem was one level up. The only place in the entire codebase that updated that running total was a single function call after every successful fill — and that call never passed a PnL argument. The function's own signature gave it a default value of zero. Every single trade, win or loss, added exactly &lt;code&gt;0&lt;/code&gt; to the total.&lt;/p&gt;

&lt;p&gt;The running total was therefore mathematically incapable of ever becoming negative enough to trip the limit — not "unlikely to," but structurally unable to, by construction. The same permanently-zero number also fed the bot's own status display, so the operator-facing dashboard read "today's P&amp;amp;L: 0.00" continuously, whether the bot was flat or bleeding — indistinguishable from the outside. This particular strategy carries a specific extra risk: if one leg of the hedge fails to fill while the other goes through, the position is briefly — or not so briefly — directional and unhedged. That's exactly the scenario the loss limit exists to catch, and exactly the scenario it was structurally blind to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this survives so long
&lt;/h2&gt;

&lt;p&gt;Neither of these bugs is subtle once you're looking at the right ten lines. Both survived because nobody was forced to look at those ten lines under real conditions. A stale-price bug fires on every trade — it's loud, eventually, just by volume. A dead kill-switch or a zeroed-out loss counter is quiet by nature: it only needed to work once, in the one moment things went wrong, and by then it's too late to notice the check that should have stopped it never could have.&lt;/p&gt;

&lt;p&gt;Standard unit tests don't reliably catch this either. A unit test that calls the risk function directly with a real loss value would pass fine — the bug isn't in what the function does with a real argument, it's that the one real call site in production never supplies one. That's a wiring problem, not a logic problem, and wiring problems only show up when you trace a value from where it originates to where it's actually used, not when you test each function in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check in your own bot
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For every stop-loss, kill-switch, or emergency-exit path, trace it to an actual order-submission call — the same one your entry logic uses. If the exit path reaches a different, thinner function (or none), that's worth a hard look.&lt;/li&gt;
&lt;li&gt;For every risk counter that's supposed to accumulate over time (daily P&amp;amp;L, loss streak, drawdown), find every call site that updates it and confirm each one actually passes a real, non-default value — not just that the accumulator logic itself is correct in isolation.&lt;/li&gt;
&lt;li&gt;Don't trust "well-tested" as a proxy for "connected." A correctly implemented safety class that's never imported into the live path is just documentation.&lt;/li&gt;
&lt;li&gt;If you can, deliberately trigger your emergency path once in a controlled setting and confirm — from the exchange's own records, not your bot's logs — that an order actually happened. Your bot's own logs aren't an independent witness; if the bug is that they lie, they'll lie convincingly.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Both of these were live when I found them, not abandoned side projects or paper-trading demos. That's usually the case: the protective code doesn't get skipped, it gets written, tested in isolation, wired in mostly right, and then quietly fails to do the one thing it exists for. I wrote about a related pattern — stale prices undermining stop-losses — in an earlier post; this is the same lesson from a different angle. What a bot logs about its own safety and what its safety actually does are two separate claims, and only one of them is checkable from the outside.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://arakas4488-cmd.github.io/honest-backtest/blog/safety-net-that-isnt-there.html" rel="noopener noreferrer"&gt;Honest Backtest blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>fintech</category>
      <category>cryptocurrency</category>
    </item>
    <item>
      <title>Stale Price Bug: Why Your Stop-Loss Might Not Protect You</title>
      <dc:creator>Valerii Sakara</dc:creator>
      <pubDate>Thu, 13 Aug 2026 23:48:02 +0000</pubDate>
      <link>https://dev.to/arakas4488cmd/stale-price-bug-why-your-stop-loss-might-not-protect-you-2ap1</link>
      <guid>https://dev.to/arakas4488cmd/stale-price-bug-why-your-stop-loss-might-not-protect-you-2ap1</guid>
      <description>&lt;p&gt;&lt;em&gt;A pattern found across three independent trading bots — including one I run myself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A stop-loss at -7.5% sounds like a hard guarantee. It isn't, unless the price it's measured &lt;em&gt;from&lt;/em&gt; is the price your order actually executed at — not the price the bot glanced at a few seconds, or a few minutes, earlier.&lt;/p&gt;

&lt;p&gt;This gap — between the price a bot &lt;em&gt;decides&lt;/em&gt; on and the price it actually &lt;em&gt;gets&lt;/em&gt; — is one of the most common bugs I run into auditing live trading systems. It's not exotic. It doesn't look like a bug when you read the code casually. It only shows up when you trace one specific value across the full path from signal to fill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Almost every bot has a moment where it reads a price, makes a decision, and then &lt;em&gt;waits&lt;/em&gt; — for an order to route, a swap to confirm on-chain, a limit order to fill. The wait can be 200ms or it can be five minutes. Whatever price the bot used to set its protective stop-loss/take-profit was captured &lt;em&gt;before&lt;/em&gt; that wait, not after.&lt;/p&gt;

&lt;p&gt;If nothing re-reads the price once the position is actually open, the "protection" is anchored to a number that may no longer describe reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 1: the swap that outran its own stop-loss
&lt;/h2&gt;

&lt;p&gt;In a Solana DEX trading bot I reviewed, the stop-loss/take-profit levels were computed from a candle price captured right before a swap was submitted. The swap itself wasn't instant — it polled for on-chain confirmation inside a nested retry loop, worst case around five minutes between "price read" and "swap actually landed."&lt;/p&gt;

&lt;p&gt;The bot's own swap function &lt;em&gt;knew&lt;/em&gt; the real fill price — it computed and logged it — but only logged it. The function returned a plain &lt;code&gt;True&lt;/code&gt;/&lt;code&gt;False&lt;/code&gt;, so the caller never saw the number and fell back to the stale pre-swap price for the stop-loss reference.&lt;/p&gt;

&lt;p&gt;Concretely: signal at $100 → intended stop at $92.50. While the swap sits in flight, the market drops to $90 and the swap fills there. The bot believes it entered at $100 with $7.50 of room. It actually entered at $90, already most of the way to a stop that was never recalculated from the real entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 2: an argument shift that silently swapped market-close for a slow limit order
&lt;/h2&gt;

&lt;p&gt;A different bug, same root cause — the exit path wasn't tested end-to-end. In a futures bot, the function that closes a position on stop-loss/take-profit had grown new leading parameters over time. The call sites that trigger exits still passed the old, shorter argument list — every argument landed one slot to the right of where it was supposed to.&lt;/p&gt;

&lt;p&gt;The practical effect: a parameter that defaults to &lt;code&gt;100&lt;/code&gt; (truthy) ended up bound to the internal flag that decides whether to close the position immediately at market, or place a passive limit order and slowly chase the price instead. Every stop-loss and take-profit exit fired the slow path — with no configuration change needed to trigger it — exactly in the scenario (a fast move) where an immediate close was the entire point of having a stop-loss.&lt;/p&gt;

&lt;p&gt;Nothing crashed. Nothing logged an error. The log line even printed "stop-loss triggered." The order just didn't behave the way the code around it assumed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 3: it happened in my own bot too
&lt;/h2&gt;

&lt;p&gt;I hold my own systems to the same standard I audit others against, so here's an honest one: in an equity swing-trading bot I run on Interactive Brokers, I found the identical bug class in a live trade. Stop-loss and take-profit were computed from the &lt;em&gt;signal&lt;/em&gt; price, then sent as an atomic bracket order — but the entry used a limit order that could sit unfilled for up to the order timeout window while price kept moving.&lt;/p&gt;

&lt;p&gt;One real trade: signal at $16.87 → SL at $16.77 (10¢ risk), TP at $17.06 (19¢ target). The limit order actually filled at $17.03. Measured from the real entry, risk was 26¢ and the target was 3¢ away — the risk/reward ratio was almost inverted, and the take-profit was nearly already hit at the moment the position opened.&lt;/p&gt;

&lt;p&gt;I didn't catch this by reading the code and spotting it abstractly. I caught it by pulling one specific live trade from the audit log and checking the signal price against the actual fill. That's the only way this class of bug reliably surfaces — code review alone gets you 80% of the way; tracing a real execution gets you the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this keeps happening
&lt;/h2&gt;

&lt;p&gt;None of these are careless bugs. Every one sits in code that's otherwise well-structured, with retry logic, error handling, logging. The gap survives because most testing — and most code review — focuses on the entry/signal logic, where the interesting strategy decisions live. The exit path, where money actually gets protected, gets audited far less rigorously, even though it's the part doing the actual risk management.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check in your own bot
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Trace the exact variable your stop-loss/take-profit is computed from, from the moment it's read to the moment the protective order is placed. Is anything re-read after a fill, or is it the same value the whole way through?&lt;/li&gt;
&lt;li&gt;If an order function computes a real execution price internally, confirm that price actually gets returned to the caller — not just logged.&lt;/li&gt;
&lt;li&gt;Check every call site of your position-close function for argument order, especially after any signature change. A shifted keyword argument won't raise an exception; it'll just quietly change behavior.&lt;/li&gt;
&lt;li&gt;Pull one real trade from your logs and manually verify signal price → decision → fill price → protective order levels, end to end. Reading the code is not the same as checking what actually happened.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;One of the bugs above — reported through the same review process, in a public repository — was confirmed and merged as a fix within 24 hours: &lt;a href="https://github.com/0xfnzero/sol-trade-sdk/pull/112" rel="noopener noreferrer"&gt;github.com/0xfnzero/sol-trade-sdk/pull/112&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://arakas4488-cmd.github.io/honest-backtest/blog/stale-price-bug.html" rel="noopener noreferrer"&gt;Honest Backtest blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
