<?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:  WWP</title>
    <description>The latest articles on DEV Community by  WWP (@tatsuyawwp).</description>
    <link>https://dev.to/tatsuyawwp</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%2F4060221%2F7b3b90d3-d53b-4852-aa58-7041a70c662e.png</url>
      <title>DEV Community:  WWP</title>
      <link>https://dev.to/tatsuyawwp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tatsuyawwp"/>
    <language>en</language>
    <item>
      <title>My bot logged a fresh decision every 5 minutes for over an hour. The price it was deciding on never moved once.</title>
      <dc:creator> WWP</dc:creator>
      <pubDate>Sun, 30 Aug 2026 07:49:35 +0000</pubDate>
      <link>https://dev.to/tatsuyawwp/my-bot-logged-a-fresh-decision-every-5-minutes-for-over-an-hour-the-price-it-was-deciding-on-never-40ch</link>
      <guid>https://dev.to/tatsuyawwp/my-bot-logged-a-fresh-decision-every-5-minutes-for-over-an-hour-the-price-it-was-deciding-on-never-40ch</guid>
      <description>&lt;p&gt;Another one from the same one-person-AI-company setup I've written about before: Claude Code builds and maintains the code, several agents run unattended on a schedule, nobody's watching in real time. This one's from earlier in the project than the incidents I've posted about before, and it's the simplest of the bunch - which is what makes it worth writing up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;A crypto trading bot polls a broker's API every 5 minutes, pulls the last hour of price bars, and decides whether to buy, sell, or hold based on recent price movement. Every cycle, it logs its decision - symbol, price, reasoning - to a file. That log was the only thing anyone (human or otherwise) was checking to confirm the bot was doing its job.&lt;/p&gt;

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

&lt;p&gt;The code asked the broker's API for "the last N bars" by passing a &lt;code&gt;limit&lt;/code&gt; parameter and nothing else - no explicit start time. That seemed like a reasonable way to ask for "the most recent bars." It wasn't. Without an explicit start time, the API silently returned a &lt;em&gt;fixed&lt;/em&gt; window - bars starting from midnight UTC that day, sorted oldest-first - instead of the most recent N bars. Two very different requests that happen to share a &lt;code&gt;limit&lt;/code&gt; parameter, with no error, no warning, nothing in the response shape that would tip you off.&lt;/p&gt;

&lt;p&gt;Practical effect: the bot kept asking every 5 minutes, kept getting an answer, and for over an hour that answer was &lt;em&gt;exactly the same set of bars&lt;/em&gt; - the same price, to three decimal places, cycle after cycle. The log showed a fresh timestamp and a fresh "decision" every 5 minutes the whole time. Nothing about the log looked wrong. It just wasn't true - the bot wasn't deciding anything based on current information, it was re-deciding the same stale snapshot over and over and calling it live.&lt;/p&gt;

&lt;p&gt;I only caught it by actually looking at the price values across consecutive log entries during an unrelated review and noticing they were identical - not "similar," identical to the decimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one's worth separating from the others
&lt;/h2&gt;

&lt;p&gt;I've written before about two other incidents from this same project - a safety check that fired correctly but never got logged, and a scheduled task that crashed for three weeks while still reporting success. Those both involved something breaking. This one didn't. The bug was in a single missing keyword argument to an API call, the API itself never errored, the process never crashed, nothing timed out. Every individual component did exactly what it was told to do. The bot was, by every internal measure it had, working.&lt;/p&gt;

&lt;p&gt;That's the part that generalizes past this one API's quirk: "the log says something happened every cycle" and "something meaningfully different happened every cycle" are not the same claim, and nothing about a healthy-looking log distinguishes them. A monitoring setup built around "did the process log something recently" - which is most of what I had at the time - is structurally blind to this exact failure mode. It would need to check whether the &lt;em&gt;content&lt;/em&gt; changed, not just whether output kept arriving on schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm taking from it
&lt;/h2&gt;

&lt;p&gt;Between this and the other two incidents, I've now got three distinct ways an unattended agent looked completely fine from the outside while doing nothing useful: a real result that never got recorded, a crash that got recorded as success, and correct-looking output that was quietly frozen. Three different bugs, same underlying gap - nothing was checking "is the actual work still happening," only "is the process still running."&lt;/p&gt;

&lt;p&gt;Curious whether others running scheduled/unattended agents have run into the frozen-but-technically-successful version specifically - it's the quietest of the three failure modes I've hit, in the sense that there's no error anywhere to eventually trip over. You'd only catch it by actually reading the values, which is exactly the kind of check nobody does once something's been running fine for weeks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>monitoring</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My bot logged a fresh decision every 5 minutes for over an hour. The price it was deciding on never moved once.</title>
      <dc:creator> WWP</dc:creator>
      <pubDate>Tue, 25 Aug 2026 06:11:28 +0000</pubDate>
      <link>https://dev.to/tatsuyawwp/my-bot-logged-a-fresh-decision-every-5-minutes-for-over-an-hour-the-price-it-was-deciding-on-never-2g2m</link>
      <guid>https://dev.to/tatsuyawwp/my-bot-logged-a-fresh-decision-every-5-minutes-for-over-an-hour-the-price-it-was-deciding-on-never-2g2m</guid>
      <description>&lt;p&gt;Another one from the same one-person-AI-company setup I've written about before: Claude Code builds and maintains the code, several agents run unattended on a schedule, nobody's watching in real time. This one's from earlier in the project than the incidents I've posted about before, and it's the simplest of the bunch - which is what makes it worth writing up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;A crypto trading bot polls a broker's API every 5 minutes, pulls the last hour of price bars, and decides whether to buy, sell, or hold based on recent price movement. Every cycle, it logs its decision - symbol, price, reasoning - to a file. That log was the only thing anyone (human or otherwise) was checking to confirm the bot was doing its job.&lt;/p&gt;

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

&lt;p&gt;The code asked the broker's API for "the last N bars" by passing a &lt;code&gt;limit&lt;/code&gt; parameter and nothing else - no explicit start time. That seemed like a reasonable way to ask for "the most recent bars." It wasn't. Without an explicit start time, the API silently returned a &lt;em&gt;fixed&lt;/em&gt; window - bars starting from midnight UTC that day, sorted oldest-first - instead of the most recent N bars. Two very different requests that happen to share a &lt;code&gt;limit&lt;/code&gt; parameter, with no error, no warning, nothing in the response shape that would tip you off.&lt;/p&gt;

&lt;p&gt;Practical effect: the bot kept asking every 5 minutes, kept getting an answer, and for over an hour that answer was &lt;em&gt;exactly the same set of bars&lt;/em&gt; - the same price, to three decimal places, cycle after cycle. The log showed a fresh timestamp and a fresh "decision" every 5 minutes the whole time. Nothing about the log looked wrong. It just wasn't true - the bot wasn't deciding anything based on current information, it was re-deciding the same stale snapshot over and over and calling it live.&lt;/p&gt;

&lt;p&gt;I only caught it by actually looking at the price values across consecutive log entries during an unrelated review and noticing they were identical - not "similar," identical to the decimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one's worth separating from the others
&lt;/h2&gt;

&lt;p&gt;I've written before about two other incidents from this same project - a safety check that fired correctly but never got logged, and a scheduled task that crashed for three weeks while still reporting success. Those both involved something breaking. This one didn't. The bug was in a single missing keyword argument to an API call, the API itself never errored, the process never crashed, nothing timed out. Every individual component did exactly what it was told to do. The bot was, by every internal measure it had, working.&lt;/p&gt;

&lt;p&gt;That's the part that generalizes past this one API's quirk: "the log says something happened every cycle" and "something meaningfully different happened every cycle" are not the same claim, and nothing about a healthy-looking log distinguishes them. A monitoring setup built around "did the process log something recently" - which is most of what I had at the time - is structurally blind to this exact failure mode. It would need to check whether the &lt;em&gt;content&lt;/em&gt; changed, not just whether output kept arriving on schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm taking from it
&lt;/h2&gt;

&lt;p&gt;Between this and the other two incidents, I've now got three distinct ways an unattended agent looked completely fine from the outside while doing nothing useful: a real result that never got recorded, a crash that got recorded as success, and correct-looking output that was quietly frozen. Three different bugs, same underlying gap - nothing was checking "is the actual work still happening," only "is the process still running."&lt;/p&gt;

&lt;p&gt;Curious whether others running scheduled/unattended agents have run into the frozen-but-technically-successful version specifically - it's the quietest of the three failure modes I've hit, in the sense that there's no error anywhere to eventually trip over. You'd only catch it by actually reading the values, which is exactly the kind of check nobody does once something's been running fine for weeks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>monitoring</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My scheduled task reported "success" every 5 minutes for 3 weeks. The process inside it had been crashing the whole time.</title>
      <dc:creator> WWP</dc:creator>
      <pubDate>Sun, 23 Aug 2026 02:15:33 +0000</pubDate>
      <link>https://dev.to/tatsuyawwp/my-scheduled-task-reported-success-every-5-minutes-for-3-weeks-the-process-inside-it-had-been-28m5</link>
      <guid>https://dev.to/tatsuyawwp/my-scheduled-task-reported-success-every-5-minutes-for-3-weeks-the-process-inside-it-had-been-28m5</guid>
      <description>&lt;p&gt;I run a one-person AI company: Claude Code writes and maintains the code, and most of what it builds runs unattended — several trading bots on Windows Task Scheduler, each polling a broker API every 5 minutes, 24/7, with nobody watching in real time. I wrote before about the agent breaking things it had write access to (&lt;a href="https://dev.to/tatsuyawwp/i-let-an-ai-agent-run-my-trading-bots-unattended-it-broke-twice-before-i-built-a-gate-to-stop-it-40db"&gt;I let an AI agent run my trading bots unattended&lt;/a&gt;). This one's different: nothing broke the code. The monitoring itself was confidently wrong, twice, in two different ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mild version: a real event, never logged
&lt;/h2&gt;

&lt;p&gt;One bot has a circuit breaker — if cumulative paper losses cross a threshold, it force-closes everything and halts. It fired for real: losses crossed the line, the position closed, confirmed directly against the broker's own API.&lt;/p&gt;

&lt;p&gt;Except the close was never written to the trade history file. The reporting script had no record of it. So my daily automated status report — a script that reads every bot's logs and has an AI model summarize what's going on — looked at a "still open" position that had actually been closed for days, and confidently told me the bot might have crashed. It hadn't. It had done exactly what it was supposed to do, and the thing telling me otherwise was itself misreading stale data as current.&lt;/p&gt;

&lt;p&gt;Annoying, but honest about being wrong once you dug in. The next one wasn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real version: 18,300 tracebacks, and every single check said "fine"
&lt;/h2&gt;

&lt;p&gt;A different bot's scheduled task kept reporting success — exit code 0, every 5-minute run, for over three weeks straight. The scheduler's own logs showed nothing but green.&lt;/p&gt;

&lt;p&gt;Inside, the actual Python process had been crashing on nearly every cycle that whole time: an authentication error from the broker's API, unhandled, caught only by the outer process wrapper, which then dutifully reported "the wrapper ran and exited" as success — which was technically true and completely useless. Three weeks of 5-minute cycles is over 8,000 attempts; more than 18,000 tracebacks piled up in the log because a few different code paths kept trying and kept failing. Zero real trades got recorded in that entire window. Nothing about the scheduler's own view of the world ever turned red.&lt;/p&gt;

&lt;p&gt;I only found it because I went and read the raw log file directly, not because anything monitoring the system told me to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the tools I already had didn't (and wouldn't have) caught this
&lt;/h2&gt;

&lt;p&gt;The LLM-observability tools I know of trace individual API calls while you're actively building — good for "why did this one prompt cost so much" or "why did this one call return garbage," not built to watch a background job nobody's looking at.&lt;/p&gt;

&lt;p&gt;The classic dead-man's-switch tools (the "ping us every N minutes or we alert you" category) would have shown green the entire three weeks, too — the wrapper process itself never stopped running or stopped pinging. That category answers "did the job run." It has no way to know what the job was actually supposed to accomplish, so it can't tell you the job ran and did nothing.&lt;/p&gt;

&lt;p&gt;What both incidents have in common: the failure was invisible to anything that only checks "did the process exit 0" or "did something get logged as a plain string." Neither incident involved the code lying — the wrapper genuinely didn't crash, and the "open position" genuinely had been open at some point. The gap was between "the shell of the job looks fine" and "the job actually did the thing it exists to do."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm taking from this
&lt;/h2&gt;

&lt;p&gt;An agent that's competent while you're watching it, and an agent whose failures you'll actually notice once you stop watching, are not the same property — same lesson as the write-access incidents, different failure shape. Uptime monitoring answers "is it alive." Nobody was asking the more useful question: "is it still doing the thing," specifically for a background AI agent where "the thing" is something more structured than "return HTTP 200."&lt;/p&gt;

&lt;p&gt;I'm looking at building a small monitoring layer specifically for solo-developer/small-team unattended AI agents — schedule-aware, understands that "the process exited 0" and "the agent did its job" are different claims, and flags the gap between them instead of only the process dying outright.&lt;/p&gt;

&lt;p&gt;If you're running any kind of unattended agent — a scraper, a bot, a pipeline — on a schedule with nobody watching, I'd like to know if you've had your own version of "everything said green and it wasn't." Trying to figure out if this generalizes past my own two data points, same as last time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>monitoring</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I let an AI agent run my trading bots unattended. It broke twice before I built a gate to stop it.</title>
      <dc:creator> WWP</dc:creator>
      <pubDate>Wed, 19 Aug 2026 10:12:37 +0000</pubDate>
      <link>https://dev.to/tatsuyawwp/i-let-an-ai-agent-run-my-trading-bots-unattended-it-broke-twice-before-i-built-a-gate-to-stop-it-40db</link>
      <guid>https://dev.to/tatsuyawwp/i-let-an-ai-agent-run-my-trading-bots-unattended-it-broke-twice-before-i-built-a-gate-to-stop-it-40db</guid>
      <description>&lt;p&gt;I run a one-person AI company: Claude Code writes and maintains the code, I make the calls that need a human. Most of what it builds runs unattended — a live strategy bot on a 5-minute scheduler, a weekly content pipeline that drafts and publishes without me reading the draft first (I don't read English well enough to review it myself).&lt;/p&gt;

&lt;p&gt;That arrangement worked, until two specific moments where "the agent is competent" and "the agent is safe to leave unattended" turned out to be different properties. Here's what happened both times, and the gate I built after the second one so it can't happen a third way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Incident 1: it edited a live bot, and the scheduler ran the half-reviewed version
&lt;/h2&gt;

&lt;p&gt;I asked the agent to replace a losing trading strategy with a new one, backtested and cleared against a profit-factor gate. It rewrote &lt;code&gt;run.py&lt;/code&gt;, saved the file, and was partway into explaining the change to me.&lt;/p&gt;

&lt;p&gt;The bot's Windows Task Scheduler entry runs every 5 minutes, unconditionally, whether or not a human is mid-review. It fired. The new code had a bug: it treated the current day's still-forming price candle as a closed one, and used it to make a real (paper) sell decision on incomplete data.&lt;/p&gt;

&lt;p&gt;Caught it fast, pulled the scheduled task, and went looking for how deep the problem went. It was deeper than the one bug:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The circuit breaker — the thing meant to halt everything after a loss threshold — only checked the &lt;em&gt;first&lt;/em&gt; open position it found, not all of them.&lt;/li&gt;
&lt;li&gt;A multi-position anomaly (there should only ever be one) was logged as a warning, not treated as a halt condition.&lt;/li&gt;
&lt;li&gt;The circuit-breaker code path itself had no exception handling, so if it ever threw, it would fail silently instead of halting anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three separate holes in the safety net, found only because the first one fired. Fixed all four, verified live, re-enabled the schedule. Lesson kept for next time, in plain language: &lt;strong&gt;disable the scheduled task before you let an agent touch the file it drives — not after you find out why that mattered.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Incident 2: recovering from bug #1 destroyed the fix for bug #1
&lt;/h2&gt;

&lt;p&gt;Partway through the fixes above, I ran &lt;code&gt;git reset --hard HEAD~1&lt;/code&gt; to undo one bad test commit. &lt;code&gt;--hard&lt;/code&gt; doesn't undo one commit — it discards every uncommitted change in the working tree. That included the security fixes from the incident above, not yet committed. Redid them from scratch.&lt;/p&gt;

&lt;p&gt;Nothing sophisticated went wrong here. A destructive git command did exactly what it's documented to do, and an agent moving fast used it without pausing to check what else was sitting uncommitted. The kind of mistake a careful senior engineer makes maybe once a decade. An agent making hundreds of commits a week will make it a lot sooner than that, if nothing checks first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Incident 3 wasn't an incident — the gate I'd built by then actually caught it
&lt;/h2&gt;

&lt;p&gt;By the time the content pipeline existed — draft, security-check, publish, no human reads the English first — I'd learned enough not to ship that pipeline without a hard gate in front of the publish step. Three layers, any one of them blocks: a hand-maintained deny-list of known-sensitive strings, a live scan that reads every &lt;code&gt;.env&lt;/code&gt; file and checks whether any current secret value appears verbatim in the draft, and a semantic pass from a second model call asking "does this look safe to publish" with the actual draft in front of it, not a rule list.&lt;/p&gt;

&lt;p&gt;First real test, unplanned: the pipeline drafted a post about the week's engineering work. The gate caught three real account numbers in the draft and blocked the publish before it reached git or the blog. One of those numbers wasn't even on the hand-written deny-list — it got caught by a generic "this looks like an account-number-shaped string" pattern, which is exactly the point of having more than one layer. Draft sits in a gitignored folder to this day, never published, cursor never advanced past it.&lt;/p&gt;

&lt;p&gt;That's the difference between the first two incidents and the third: the first two, I found out about after something already happened. The third, I found out about because the gate was already there and did its job before anything happened. That gap — noticing after vs. preventing before — is the whole reason this is a post and not just an incident log.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm taking from this
&lt;/h2&gt;

&lt;p&gt;An AI agent that writes good code and an AI agent that's safe to run unattended are not the same claim, and testing the first one tells you very little about the second. Every hole above got found by something actually going wrong first, not by anyone predicting it — the circuit breaker's blind spots only surfaced because the first bug fired; the git-reset danger only got named after it ate real work.&lt;/p&gt;

&lt;p&gt;I'm pulling the gate — the deny-list plus live-secret-scan plus semantic-check pattern — into a standalone tool for solo developers and small teams running AI agents with real write access (a live bot, a publish pipeline, a deploy step) without a human reviewing every action in real time. Not a general secrets scanner — there are good funded ones already (GitGuardian, gitleaks). This is specifically for the moment an agent is about to do something irreversible unattended, and nobody's watching that specific second.&lt;/p&gt;

&lt;p&gt;If you're running an agent with any kind of unattended write access and have your own version of incident 1 or 2, I'd like to hear it — trying to build this from more than my own two data points.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>Trading the Gap: How We Built a 91% Win-Rate Basis Bot After a 217% Buy-and-Hold Reality Check</title>
      <dc:creator> WWP</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:00:42 +0000</pubDate>
      <link>https://dev.to/tatsuyawwp/trading-the-gap-how-we-built-a-91-win-rate-basis-bot-after-a-217-buy-and-hold-reality-check-1idl</link>
      <guid>https://dev.to/tatsuyawwp/trading-the-gap-how-we-built-a-91-win-rate-basis-bot-after-a-217-buy-and-hold-reality-check-1idl</guid>
      <description>&lt;p&gt;After months of letting the agent build technical indicator bots, we finally asked it to run the simplest test possible: what if we just bought BTC/JPY eight years ago and did absolutely nothing?&lt;/p&gt;

&lt;p&gt;The result was a +217.1% return. Over 2,891 days, the "Buy and Hold" benchmark outperformed every single timed entry/exit strategy we’d spent weeks building. The closest runner-up (C5) only managed a fraction of that gain (~7.9M JPY vs the benchmark's ~21.7M JPY). It was a blunt reality check: our bots were so focused on avoiding pullbacks that they were missing the massive, multi-year appreciation of the underlying asset.&lt;/p&gt;

&lt;p&gt;The flip side, of course, was the pain. The buy-and-hold strategy suffered a maximum drawdown of 54.49% — a stomach-churning drop that would have liquidated most retail accounts. Our bots, meanwhile, kept drawdowns in the 13–17% range. This reframed the entire experiment. The goal wasn't just to "beat" the market; it was to find a way to capture that upside without the 50% wipeout risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying to build a "Free Lunch" via Portfolio Blending
&lt;/h2&gt;

&lt;p&gt;The agent's next move was to stop looking for one perfect bot and start looking for a portfolio. We tested three different blends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The 6-leg blend&lt;/strong&gt; (Buy-and-hold + C3 through C7): This produced a +65.4% return with a 22.7% drawdown. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 3-leg blend&lt;/strong&gt; (Buy-and-hold + the two "survivor" bots, C3 and C7): This hit a +99.5% return, but the drawdown spiked to 31.8%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 5-leg "Optimized" blend&lt;/strong&gt;: By removing a known-loser (C4), the return jumped to +84.8%, but the drawdown actually &lt;em&gt;rose&lt;/em&gt; to 23.6%. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We found a counterintuitive reality: even the losing bot (C4) was providing diversification because its failures didn't correlate with the others. Removing it made the equity curve "cleaner" but more fragile. It was a reminder that in a portfolio, "bad" strategies can sometimes act as insurance for "good" ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 91% Win-Rate Basis Trade
&lt;/h2&gt;

&lt;p&gt;When I told the agent that even doubling the money felt "too low" for the complexity of automated trading, it suggested something structurally different: a market-neutral basis-fade. This involves betting on the price gap between GMO’s BTC_JPY leverage product and its BTC spot product.&lt;/p&gt;

&lt;p&gt;The results looked like a different sport:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Win Rate&lt;/strong&gt;: 91.3% (63 wins, 6 losses).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max Drawdown&lt;/strong&gt;: 0.39%. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profit Factor&lt;/strong&gt;: 45.06.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even here, the agent found real problems. First, it caught its own "phantom loss" bug: it was double-counting the entry cost of the spot leg, which initially made a high-win-rate strategy look like it was losing 33% instantly. Then it found a "silent zero" bug where financing fees weren't being attributed to trades at all.&lt;/p&gt;

&lt;p&gt;Most importantly, the agent pointed out a structural decay: the strategy's trade frequency dropped from 55 trades in the first four years to just 14 in the most recent four. The "edge" wasn't a constant; it was a symptom of early-market volatility that is slowly being squeezed out as the market matures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Grave of Rejected Hypotheses (C8 and C9)
&lt;/h2&gt;

&lt;p&gt;We didn't stop at the winners. We put classic Japanese technical analysis through the same "gate" (Profit Factor &amp;gt; 1.2):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;C8 (Ichimoku Strategy)&lt;/strong&gt;: Classic triple-confirmation cloud trading. It failed the gate with a PF of 1.19. Interestingly, its buy-side was profitable (PF 1.33) while its sell-side was a loser (PF 1.03), providing our third independent confirmation of a structural long-side bias in crypto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C9 (RCI Multi-Timeframe)&lt;/strong&gt;: A total failure with a PF of 0.78. The agent traced the failure not to a logic bug, but to the circuit breaker tripping early during high-volatility periods, effectively "truncating" its own recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also tested timeframe shifts. Day trading (30-minute bars) was a decisive loser (PF 0.65). Weekly trading (1-day bars) had an incredible PF of 2.74, but it only generated 17 trades in 8 years. Mathematically, it’s a strategy you can’t trust because you’ll be dead before you have a statistically significant sample size.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned about the Agent
&lt;/h2&gt;

&lt;p&gt;The agent has now built and backtested nine distinct candidates. Only two (C3 and C7) have survived every stress test we’ve thrown at them. &lt;/p&gt;

&lt;p&gt;The takeaway isn't that the AI is a "genius" trader. It’s that the AI is a world-class skeptic. It was happy to build a 91% win-rate bot, but it was just as happy to find the math error that made that win rate look better than it was. It didn't get "attached" to the Ichimoku strategy just because the code was elegant; it rejected it the moment the Profit Factor hit 1.19.&lt;/p&gt;

&lt;p&gt;We are currently left with a choice: take the high-yield/high-pain path of the benchmark, or the slow, managed grind of the portfolio blends. &lt;/p&gt;

&lt;p&gt;The code, the full backtest engine, and the logs of every rejected strategy are public here: &lt;a href="https://github.com/tatsuyawwp/ai-trading-bot-experiment" rel="noopener noreferrer"&gt;github.com/tatsuyawwp/ai-trading-bot-experiment&lt;/a&gt;. This remains a paper-trading experiment—no real money was used, and nothing here is financial advice.&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>I had an AI agent build 3 trading bots. It was losing to HFT before it even started.</title>
      <dc:creator> WWP</dc:creator>
      <pubDate>Mon, 03 Aug 2026 08:58:05 +0000</pubDate>
      <link>https://dev.to/tatsuyawwp/i-had-an-ai-agent-build-3-trading-bots-it-was-losing-to-hft-before-it-even-started-1kia</link>
      <guid>https://dev.to/tatsuyawwp/i-had-an-ai-agent-build-3-trading-bots-it-was-losing-to-hft-before-it-even-started-1kia</guid>
      <description>&lt;p&gt;I run a one-person AI company. A few weeks ago I pointed Claude Code — an AI coding agent — at a simple brief: build a paper-trading bot, watch it run, tell me honestly whether it works.&lt;/p&gt;

&lt;p&gt;It built three: crypto, equities, and options, each against &lt;a href="https://alpaca.markets" rel="noopener noreferrer"&gt;Alpaca&lt;/a&gt;'s paper trading API. Full code is public: &lt;a href="https://github.com/tatsuyawwp/ai-trading-bot-experiment" rel="noopener noreferrer"&gt;github.com/tatsuyawwp/ai-trading-bot-experiment&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is about what happened when I asked it the one question that actually mattered, and made it answer honestly instead of just shipping something that looked done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bots got built. That part was never the hard part.
&lt;/h2&gt;

&lt;p&gt;Within a few sessions there were three working bots: entry/exit logic, stop-losses and take-profits, a circuit breaker that halts everything after a cumulative paper loss, email alerts, isolated paper accounts per asset class so one bot's drawdown couldn't false-trigger another's, and a decision logger that records &lt;em&gt;every&lt;/em&gt; evaluation cycle — not just fills — so nothing could hide in a gap between logs.&lt;/p&gt;

&lt;p&gt;The agent also found and fixed its own bugs along the way. Early on, the crypto bot's price feed was silently broken: a missing parameter meant the API was returning a fixed window from midnight instead of the most recent bars, so the bot had been making live trading decisions off a BTC price frozen at exactly the same number for over an hour. Later, the options bot's stop-loss slipped from -30% to -42% on a real (paper) fill, which turned out to be poll-interval gap plus thin-spread slippage on a real illiquid contract, not a logic bug. These are the kind of bugs that are easy to miss and expensive to leave in — the kind you actually want an agent that never gets bored re-checking for.&lt;/p&gt;

&lt;p&gt;None of that is the interesting part of this post. Building working trading infrastructure is a solved problem. The interesting part is what happened when I stopped asking "does it run" and started asking "does it win."&lt;/p&gt;

&lt;h2&gt;
  
  
  The question that mattered: can this actually compete?
&lt;/h2&gt;

&lt;p&gt;I asked the agent, bluntly: does a retail bot like this have any realistic edge, or is this just an elaborate way to lose money slowly?&lt;/p&gt;

&lt;p&gt;It didn't have an opinion of its own worth trusting on this — so it went and checked. The honest numbers that came back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: colocated HFT infrastructure sits 10,000–100,000x closer to the exchange than a REST API call from a home machine. Not a rounding error — a different sport.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: at $20/trade with 5-minute polling, realistic round-trip cost (taker fees + real spread, not the optimistic best-case number) runs 0.65–1.08% per crypto symbol. A technical-indicator strategy trying to scalp small moves on that timescale is fighting a cost structure that eats the edge before it exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The blunt verdict, once we ran the actual numbers instead of assuming: a retail agent trying to out-trade HFT on speed has zero chance, full stop. The viable move isn't "tune the parameters harder" — it's stop competing on a timescale where the fee structure and the speed gap both work against you, and move to a timescale where they don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  So we tested it properly — and it kept failing
&lt;/h2&gt;

&lt;p&gt;Once the frame was "prove it, don't assume it," the actual work was a strategy going through a real gate: propose a hypothesis, backtest it against real historical data, only ever adopt something that clears a pre-agreed bar (profit factor &amp;gt; 1.2), and — critically — reject it if it doesn't, instead of quietly lowering the bar.&lt;/p&gt;

&lt;p&gt;Five hypotheses went through that gate for the crypto bot alone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The strategy already running live&lt;/strong&gt; (price-deviation + momentum): 469 trades, 26.9% win rate, profit factor 0.36. A real, statistically real loser — not bad luck on a small sample.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bollinger Band breakout&lt;/strong&gt; (Gemini's first suggestion for something structurally different): 6,712 trades, profit factor 0.06. The exit band was so tight the strategy was closing on every normal pullback before ever reaching its own stop-loss or take-profit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same entry, fixed stop-loss/take-profit instead of the band exit&lt;/strong&gt;: 339 trades, profit factor 0.36 again — same ceiling, worse win rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 4-hour EMA crossover trend strategy&lt;/strong&gt;, tested on 1 year / 4 symbols: profit factor 1.23. This &lt;em&gt;looked&lt;/em&gt; like a pass. It wasn't — 2 outlier trades accounted for almost the entire profit. Extended to 4 years / 10 symbols per the same discipline that flagged the first result as too small to trust: profit factor 0.42. Decisive rejection, and a good reminder that a strategy passing on a small sample is usually a coincidence wearing a lab coat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A liquidation-cascade mean-reversion catcher&lt;/strong&gt;: profit factor 0.68. An early version looked much better (1.36–1.44) until a more conservative cost assumption on some added symbols collapsed it — the "good" number turned out to be an unverified-cost artifact, not real edge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Somewhere in there, the backtest tooling itself turned out to have a real bug: Alpaca's historical crypto data has genuine multi-hundred-day gaps for some symbols, and the lookup logic didn't detect them — it silently used a stale pre-gap price as "current," which manufactured one fake +672% trade that briefly made an early run look like it had a profit factor of 3.51. Found, fixed, re-verified. The whole point of building the gate was that it had to be trustworthy enough to actually kill bad ideas instead of just rubber-stamping whatever came out of the last backtest run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sixth one passed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cross-sectional momentum rotation&lt;/strong&gt; — rank the watchlist by 7-day return, hold only whichever one is #1, rotate daily — cleared the bar: profit factor 1.55 on 116 trades, no small-sample red flags, and every individual trade traced back to a real, explainable market event (BTC's 2023 recovery, ETH's 2024–25 run, DOGE's 2024 spike) rather than a lucky fluke.&lt;/p&gt;

&lt;p&gt;It's live now, on the real (paper) account, holding at most one position at a time. Rolling it out wasn't clean, either — minutes after the new code was saved, the unattended 5-minute scheduler ran it before a code review caught a bug where a still-forming daily candle got treated as a closed one, producing one real (paper) trade on partial data. Caught, fixed, re-verified, redeployed. The fix for &lt;em&gt;that&lt;/em&gt; was procedural, not just technical: disable the scheduled task before editing a live bot's code, not after you find out it did something during the edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually take away from this
&lt;/h2&gt;

&lt;p&gt;An AI agent turned out to be genuinely good at the parts of this that reward being tireless and honest: building real risk controls, catching its own stale-data and off-by-one bugs, running five backtests back to back without getting attached to any of them, and rejecting its own best-looking result when a bigger sample said otherwise.&lt;/p&gt;

&lt;p&gt;It was not, on its own, a source of alpha. Nothing here found an edge because the agent was clever — it found one (maybe) because the process refused to accept "looks promising" as good enough, five times in a row, before something finally survived a harder test.&lt;/p&gt;

&lt;p&gt;Code, real numbers (including the losing ones), and the honest per-bot writeups are here: &lt;a href="https://github.com/tatsuyawwp/ai-trading-bot-experiment" rel="noopener noreferrer"&gt;github.com/tatsuyawwp/ai-trading-bot-experiment&lt;/a&gt;. Paper money only — nothing in this post or the repo is a recommendation to trade anything.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>trading</category>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
