<?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 Permanent Halt That Lasts Until the Next Restart</title>
      <dc:creator>Valerii Sakara</dc:creator>
      <pubDate>Sat, 26 Sep 2026 18:56:26 +0000</pubDate>
      <link>https://dev.to/arakas4488cmd/the-permanent-halt-that-lasts-until-the-next-restart-56dk</link>
      <guid>https://dev.to/arakas4488cmd/the-permanent-halt-that-lasts-until-the-next-restart-56dk</guid>
      <description>&lt;p&gt;Prop-firm challenges come with hard rules: lose more than a set percentage in a day, or in total, and the account is finished. So traders reach for a small utility that watches the account and enforces the rules for them — when a limit is hit, close everything, raise a shared flag that every other expert advisor on the terminal checks before trading, and stay halted. The one I reviewed was a good idea, cleanly written, with documentation promising that a breach of the maximum drawdown halts trading &lt;em&gt;permanently&lt;/em&gt;. It didn't, quite.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I found this by reading the public source, not by running it. I posted it as a comment on the tool's public page, the author replied there, and I'm leaving the product and the author unnamed here.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug: startup undoes the halt
&lt;/h2&gt;

&lt;p&gt;Two things combined. First, the "breached" state — one flag for the daily limit, one for the maximum — lived only in the running program's memory; nothing wrote it anywhere durable and nothing read it back. Second, the startup routine began by unconditionally setting the shared halt flag back to zero, then took a fresh snapshot of "equity at the start of the day" from whatever the account held at that moment.&lt;/p&gt;

&lt;p&gt;On a prop-firm setup, restarts are routine: a VPS reboot, a terminal update, recompiling the tool, or just detaching and re-attaching it to a chart. So the first restart after a breach did two things at once. It cleared the halt, and it re-anchored the baseline to the already-reduced equity. The account was unblocked at exactly the moment it was closest to blown, and the limits now measured from a lower starting point than the rules allowed.&lt;/p&gt;

&lt;p&gt;Nothing in the tool's logs would look wrong. From the program's point of view it started cleanly and initialized itself per its own code. The bug is in the gap between what happened before the restart and what the program believes afterwards — and nothing compares the two.&lt;/p&gt;

&lt;h2&gt;
  
  
  The response: one day, and a detail I hadn't thought of
&lt;/h2&gt;

&lt;p&gt;The author replied the next day, agreed it was a serious catch, and shipped a fixed version within hours of that. The breach state and the day-start snapshot are now saved to a file per account; startup restores them instead of writing zero, and shutting the tool down no longer clears an active breach. A new setting lets you deliberately clear a stored halt when you start a new challenge — because "permanent" needs an explicit, intentional way out, not an accidental one.&lt;/p&gt;

&lt;p&gt;The author's first reply also included a point I would have missed. The terminal's built-in global variables — the obvious place to store a flag — are deleted after about four weeks without access. A "permanent" halt kept only there would quietly expire if the account sat idle over a holiday. That's why the fix mirrors the state to a file and treats the file as the source of truth. A durable-looking place to store state is not the same as a durable one; where it lives matters as much as whether it's saved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "it worked in testing" doesn't catch this
&lt;/h2&gt;

&lt;p&gt;The usual test triggers a breach and confirms trading stops. That passes. The failing scenario needs a second step most tests never take: breach, &lt;em&gt;then remove the tool and put it back&lt;/em&gt;. A test suite that runs one session end to end never restarts the process in the middle of the state it is trying to protect.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Trip your own halt or loss limit on a demo account, then restart the process — and also detach and re-attach the bot, redeploy, and reboot the machine. Does trading stay stopped after every one of those?&lt;/li&gt;
&lt;li&gt;Read your startup code for anything that writes a "clean" value — zero, false, "not halted", a fresh baseline — before it reads what was stored. Initialization that overwrites before it restores is the whole bug.&lt;/li&gt;
&lt;li&gt;If a halt is supposed to be permanent, find the deliberate way to clear it. If the only way out is "restart it", the halt is not permanent.&lt;/li&gt;
&lt;li&gt;Check how long the place you store state keeps it. Some stores expire, get cleared on update, or are per-machine while the bot can run on several.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This is what a good disclosure looks like when the author cares: a specific report, a fix within a day, and a fix that went further than the report. The bug is a common one — I described its cousin in &lt;a href="https://honest-backtest.com/blog/risk-state-in-memory.html" rel="noopener noreferrer"&gt;the risk state that only exists in RAM&lt;/a&gt;, where a restart wipes a loss counter — and a related case, where a guard checks the wrong list of exposures, in &lt;a href="https://honest-backtest.com/blog/wrong-thing-guard.html" rel="noopener noreferrer"&gt;the guard that counts the wrong thing&lt;/a&gt;. The common lesson: a safety rule is only as strong as the weakest event it has never been tested against, and "the process restarted" is that event more often than anyone expects.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>security</category>
      <category>fintech</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Guard That Counts the Wrong Thing</title>
      <dc:creator>Valerii Sakara</dc:creator>
      <pubDate>Sat, 26 Sep 2026 18:54:40 +0000</pubDate>
      <link>https://dev.to/arakas4488cmd/the-guard-that-counts-the-wrong-thing-oi4</link>
      <guid>https://dev.to/arakas4488cmd/the-guard-that-counts-the-wrong-thing-oi4</guid>
      <description>&lt;p&gt;Most risk guards are one line long. "Don't open a trade if one is already open." "Stop trading if today's loss passes the limit." The line is usually correct. The bug is in what it looks at: a guard is only as good as the list of places it checks, and a bot can create exposure in more places than the guard knows about. The two cases below come from one open-source MetaTrader expert advisor I reviewed by reading its public source. Both are the same shape — a guard that is right about the thing it counts and blind to the thing it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I found these by static code review, not by running the bot. I filed them as an ordinary public issue on the repository, and I'm leaving the project unnamed here on purpose.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 1: the "one trade at a time" guard that can't see resting orders
&lt;/h2&gt;

&lt;p&gt;The bot's entry logic opened with a check: if there is an open position, return. That's the entire duplicate-trade guard, and for market orders it works. But this bot didn't enter with market orders — it placed pending sell-limit orders at levels it expected price to reach. A pending order isn't a position. It sits in a different list until it fills, and the guard never looked at that list; a search of the whole source found no call that counts resting orders at all.&lt;/p&gt;

&lt;p&gt;The only throttle was a cooldown of about a minute between placements. On a one-minute chart, that means a fresh sell-limit could be stacked roughly every candle for as long as the entry conditions kept re-triggering and nothing had filled yet. Each stacked order was sized correctly against the README's "maximum risk per trade" — but they were sized independently. If price then spiked through several of those levels at once, the realized simultaneous risk was a multiple of the single-trade cap the documentation promised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 2: the "daily" loss limit that isn't daily
&lt;/h2&gt;

&lt;p&gt;The second guard compared a starting balance against the current balance and disabled trading once the difference passed a limit. The comparison itself was fine. The starting balance was the problem: it was captured once, when the bot launched, and no code ever reset it when a new calendar day began. So the limit wasn't "maximum loss per day", it was "maximum loss since this process started" — and once it tripped, nothing in the code re-enabled trading. It stayed off until someone restarted the bot by hand.&lt;/p&gt;

&lt;p&gt;This is the mirror image of a bug I wrote about earlier, where a routine restart silently &lt;em&gt;wipes&lt;/em&gt; the day's loss counter to zero (&lt;a href="https://honest-backtest.com/blog/risk-state-in-memory.html" rel="noopener noreferrer"&gt;the risk state that only exists in RAM&lt;/a&gt;). Here the counter is never wiped at all. Both bugs share a root: the code never decided, explicitly, what event starts a new "day" for the risk counter. A restart, a midnight rollover and a manual reset are three different events, and each needs an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened next
&lt;/h2&gt;

&lt;p&gt;The maintainer replied about seven weeks after I filed, apologized for the delay, and confirmed both points publicly. The fix, opened as a pull request, allows only one resting sell-limit at a time, cancels any that sits unfilled past a configurable expiry (30 minutes by default), and resets the daily starting balance when the server day rolls over.&lt;/p&gt;

&lt;p&gt;It also added something I had not flagged: when the daily loss limit trips, resting orders are now cancelled too. That's the third instance of the same lesson. Before, hitting the limit stopped the bot from placing &lt;em&gt;new&lt;/em&gt; orders — but any order already resting could still fill afterwards, after trading was supposedly halted. "Stop trading" has to mean stopping the exposure that already exists, not only the exposure that would be created next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "it passed testing" doesn't catch this
&lt;/h2&gt;

&lt;p&gt;A test that places one order and checks the guard blocks a second passes, because it checks positions — the one list the guard knows about. A test that runs a single simulated day never crosses midnight, so the reset that was never written can't fail. Both bugs need a scenario the code's author didn't picture: two pending orders alive at once, or a process running across two days.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;List every place your bot can hold exposure — open positions, resting limit and stop orders, orders in flight, partially filled orders. Then check that each guard counts all of them, not just the one the author was thinking about.&lt;/li&gt;
&lt;li&gt;Place an entry signal twice in a row without letting the first order fill. Does the bot stack a second order, or does it notice the first is still resting?&lt;/li&gt;
&lt;li&gt;For any "daily" limit, find the line that starts a new day. If you can't find one, the limit is really "since launch". Then find what re-enables trading after a trip, and whether that's what you actually want.&lt;/li&gt;
&lt;li&gt;When a loss limit trips, check what happens to orders that are already resting. Halting should cancel them, not just stop creating new ones.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;None of this is exotic, and the maintainer's response is what responsible disclosure looks like when it works: a specific report, a fix, and a fix that went a step further than the report. The bug isn't in wanting a duplicate-trade guard or a daily limit. It's in the gap between what the guard was written to count and everything the bot can actually do. Related: &lt;a href="https://honest-backtest.com/blog/safety-net-that-isnt-there.html" rel="noopener noreferrer"&gt;a risk limit that is mathematically incapable of triggering&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>security</category>
      <category>fintech</category>
      <category>testing</category>
    </item>
    <item>
      <title>The 2:1 Setup That Paid Out at 0.75:1</title>
      <dc:creator>Valerii Sakara</dc:creator>
      <pubDate>Sun, 30 Aug 2026 19:07:34 +0000</pubDate>
      <link>https://dev.to/arakas4488cmd/the-21-setup-that-paid-out-at-0751-2hll</link>
      <guid>https://dev.to/arakas4488cmd/the-21-setup-that-paid-out-at-0751-2hll</guid>
      <description>&lt;p&gt;This is one from a bot I run myself, not a client audit — but the same shape shows up often enough in code I review that it's worth writing up on its own. "Backtest looked fine, live looks worse" usually gets diagnosed as slippage or a broken exit. This one wasn't either. The stop-loss and take-profit fired exactly where they were supposed to. The problem was one line above that: what "reward" and "risk" actually meant once real costs were counted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the setup was supposed to be
&lt;/h2&gt;

&lt;p&gt;The stop-loss was ATR-based — sized to the instrument's recent volatility, not a fixed dollar amount. The take-profit was set to roughly double that distance, a standard 2:1 target. Checking the actual price levels on every trade in the sample against entry, stop, and target confirmed the designed reward:risk ratio really was there in price terms: &lt;strong&gt;≈1.94:1&lt;/strong&gt;, almost exactly as intended. Nothing wrong with the setup on paper.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the fills actually showed
&lt;/h2&gt;

&lt;p&gt;Pulling realized PnL from the broker's own commission reports — not derived, not estimated, read directly off the fills — and comparing it to that same price-based risk and reward told a different story. Average realized win: &lt;strong&gt;$1.57&lt;/strong&gt;, slightly &lt;em&gt;above&lt;/em&gt; the average designed reward of $1.41 — so the win side wasn't the issue, if anything it was doing a little better than planned. The losing side was where the gap showed up: average realized loss exceeded the pure price-based stop-loss risk by &lt;strong&gt;$1.37&lt;/strong&gt; on average (median $1.60), on every losing trade in the sample. That's not a rounding error — it's a consistent, one-directional tax on every loser and none of the winners.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the missing dollars went
&lt;/h2&gt;

&lt;p&gt;Round-trip commission on this setup ran &lt;strong&gt;$0.92–$1.78&lt;/strong&gt; per trade — and that range fully accounts for the gap on its own, no other explanation needed. The mechanism is arithmetic, not behavioral: commission is close to a fixed dollar amount per round trip, regardless of how far price moves. An ATR-based stop keeps the loss side &lt;em&gt;narrow&lt;/em&gt; in dollar terms by design — that's the point of volatility-based sizing. A take-profit twice that distance is correspondingly &lt;em&gt;wide&lt;/em&gt;. The same roughly-fixed commission is a large fraction of a narrow loss and a small fraction of a wide win. It barely touches the winners and eats disproportionately into every loser — quietly dragging the realized ratio down from a designed ~2:1 toward under 1:1, with zero bugs in the stop or target logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the fix isn't the exit logic
&lt;/h2&gt;

&lt;p&gt;The natural first move is to tighten the exits — add a trailing stop, take partial profit earlier, tune the take-profit distance. None of that addresses the actual cause, because the exit levels were never the problem. The three levers that actually move this number:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Widen the stop.&lt;/strong&gt; A wider ATR-based stop makes fewer trades "commission-marginal" — the same fixed cost becomes a smaller fraction of a bigger price-based risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Size up per trade.&lt;/strong&gt; The same commission spread over a larger dollar risk has less relative drag — this only works if your risk-per-trade rules have room for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price it in up front.&lt;/strong&gt; Subtract expected round-trip commission from your minimum edge threshold before a signal is approved, instead of finding out about it in the PnL after the fact.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to check this in your own numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pull PnL from actual fills or commission reports — not the backtest's internal accounting — and compare it trade-by-trade to the price-based risk and reward implied by your stop and target. Don't assume they match.&lt;/li&gt;
&lt;li&gt;Compute your round-trip commission in dollars for your typical size, and compare it to your typical stop-loss distance in dollars. If commission is a double-digit percentage of that distance, your realized reward:risk will measurably underperform your designed reward:risk — every time, not occasionally.&lt;/li&gt;
&lt;li&gt;Check whether most backtest engines even model your real commission schedule by default. Plenty default to zero or a flat generic value that has nothing to do with your actual broker's tier — which means the price-based ratio your backtest reports can be correct and still not be the ratio you'll realize.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Nothing about this required a bug. The stop fired where it should have, the target fired where it should have, and the price-based math behind both was right. The gap was entirely in what "reward" and "risk" meant once a mostly-fixed dollar cost got applied to a narrow side and a wide side unevenly. A backtest that only checks prices — not fills, not commission — can be completely honest about the setup and still hand you a ratio you will not actually get.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://honest-backtest.com/blog/reward-risk-commission-gap.html" rel="noopener noreferrer"&gt;Honest Backtest&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The request.security() Value That Isn't There Yet</title>
      <dc:creator>Valerii Sakara</dc:creator>
      <pubDate>Sat, 29 Aug 2026 15:55:13 +0000</pubDate>
      <link>https://dev.to/arakas4488cmd/the-requestsecurity-value-that-isnt-there-yet-6k1</link>
      <guid>https://dev.to/arakas4488cmd/the-requestsecurity-value-that-isnt-there-yet-6k1</guid>
      <description>&lt;p&gt;"Profitable in the Strategy Tester, losing money live" usually gets blamed on slippage. One specific, mechanically checkable cause is worth ruling out first — and it's documented behavior, not a rare edge case.&lt;/p&gt;

&lt;p&gt;This one isn't from a client audit — it's a platform mechanic every Pine Script strategy that pulls in a higher timeframe should be checked against, because it's easy to introduce by accident and TradingView's own documentation explicitly warns about it. If your backtest and your live or paper results have quietly stopped agreeing and you've already ruled out fees and slippage, this is the next thing to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  What request.security() actually does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;request.security()&lt;/code&gt; pulls a value computed on a different timeframe (or symbol) into your script's current context — the standard way to check, say, the 1-hour trend from a 15-minute chart. It takes a &lt;code&gt;lookahead&lt;/code&gt; parameter that controls how it fills in values on historical bars. As of the current Pine Script version, its documented default is &lt;code&gt;barmerge.lookahead_off&lt;/code&gt; — meaning out of the box, it does not leak information from the future into the past.&lt;/p&gt;

&lt;p&gt;The bug shows up when someone explicitly sets &lt;code&gt;lookahead=barmerge.lookahead_on&lt;/code&gt;. People usually do this on purpose, not by accident — it removes what looks like an annoying one-bar lag on the higher-timeframe value, and plenty of forum answers recommend it for exactly that reason. TradingView's own docs carry a direct warning about this setting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"When using barmerge.lookahead_on without an offset, ensure that it does not compromise the integrity of the script's logic by leaking future data into historical chart bars."&lt;/p&gt;
&lt;/blockquote&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//@version=6
strategy("HTF trend filter")
htfClose = request.security(syminfo.tickerid, "60", close,
     lookahead=barmerge.lookahead_on)

if close &amp;gt; htfClose
    strategy.entry("Long", strategy.long)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On historical bars, this compares the current price against the &lt;em&gt;closed&lt;/em&gt; 1-hour value — including, for intraday bars still inside that hour, a close that in a live sense hasn't happened yet. The backtest engine has the full historical record, so it fills in that value without complaint. Every entry signal in the Strategy Tester is quietly using information that wasn't actually available at the moment a live bot would have had to act on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it doesn't show up while you're watching it live
&lt;/h2&gt;

&lt;p&gt;This is the part that makes the bug hard to catch by eyeballing the chart. TradingView's docs describe exactly this: on the currently-forming realtime bar, a lookahead series and a no-lookahead series plot the &lt;em&gt;same&lt;/em&gt; value — there's no future data to leak yet, because the bar hasn't closed. The divergence only appears retroactively, once that realtime bar becomes historical and gets recalculated with the full record available. The docs call this &lt;strong&gt;repainting&lt;/strong&gt;: the value you watched happen live and the value the same bar shows after a reload are two different numbers, and only the second one is what your backtest actually ran on.&lt;/p&gt;

&lt;p&gt;So a live paper-trading session can look like it's confirming the strategy works — the signals you watch fire in real time match what the backtest predicted — right up until you reload the chart and the historical bars quietly repaint into a different story.&lt;/p&gt;

&lt;h2&gt;
  
  
  A second, easy-to-miss variant
&lt;/h2&gt;

&lt;p&gt;The same function has a related but separate gotcha when the requested timeframe is at or below your chart's own resolution — for example, requesting 1-minute data on a 1-minute chart, or any case where the "higher" timeframe you're pulling isn't actually higher. TradingView's docs are specific about what you get back in that case:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"When calling this function to access a lower timeframe, it will evaluate the expression from the LTF context. However, it returns the results from only a single intrabar (LTF bar) on each chart bar."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which intrabar you get depends on the same &lt;code&gt;lookahead&lt;/code&gt; setting — the first intrabar of the period with &lt;code&gt;lookahead_on&lt;/code&gt;, the last with &lt;code&gt;lookahead_off&lt;/code&gt;. It's a different mechanism from the repainting issue above, but the practical effect is the same shape of problem: the value your logic reacts to in a backtest isn't necessarily the value a live bot would have seen at that moment, and the mismatch is invisible unless you go looking for it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Grep every &lt;code&gt;request.security()&lt;/code&gt; call in your script for the &lt;code&gt;lookahead&lt;/code&gt; argument. If it's not set, you're on the safe default. If it's set to &lt;code&gt;barmerge.lookahead_on&lt;/code&gt;, that call needs a second look.&lt;/li&gt;
&lt;li&gt;For any call using &lt;code&gt;lookahead_on&lt;/code&gt;, check whether the requested series has an explicit historical offset (a trailing &lt;code&gt;[1]&lt;/code&gt;) applied. Without one, you're very likely leaking data into your backtest that a live bot would not have had.&lt;/li&gt;
&lt;li&gt;If you're requesting a timeframe at or below your chart's own resolution, confirm which intrabar you're actually getting back and whether that's the one your logic assumes.&lt;/li&gt;
&lt;li&gt;Reload the script after watching a live/realtime session and compare: did any of the values on the most recently closed bars change from what you watched happen? That's the direct, observable symptom of repainting.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;None of this requires an exotic setup — a higher-timeframe trend filter is one of the most common things people add to a strategy, and &lt;code&gt;lookahead_on&lt;/code&gt; is a documented, legitimate parameter with real uses. The failure isn't in wanting the feature; it's that the difference between "correct on historical bars" and "correct at the moment a live bot has to decide" is easy to miss unless you know specifically where to look. It's the same underlying lesson as the audit bugs I've written about before: what a bot's numbers show and what was actually knowable at the time are two separate claims, and a backtest alone won't tell you which one you're looking at.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is one failure shape out of several I check for when a bot's live results stop matching its backtest — I run a technical audit service, &lt;a href="https://honest-backtest.com" rel="noopener noreferrer"&gt;Honest Backtest&lt;/a&gt;, for exactly this kind of gap. Originally published at &lt;a href="https://honest-backtest.com/blog/repainting-request-security.html" rel="noopener noreferrer"&gt;honest-backtest.com/blog/repainting-request-security.html&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>security</category>
      <category>fintech</category>
      <category>webdev</category>
    </item>
    <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://honest-backtest.com/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://honest-backtest.com/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>
