<?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: Inalpha</title>
    <description>The latest articles on DEV Community by Inalpha (@inalpha).</description>
    <link>https://dev.to/inalpha</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%2F3953681%2F969a3155-7124-4d10-a92d-65ae8d7e253a.png</url>
      <title>DEV Community: Inalpha</title>
      <link>https://dev.to/inalpha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/inalpha"/>
    <language>en</language>
    <item>
      <title>An Al writes my trading code. Three gates decide if it ever runs.</title>
      <dc:creator>Inalpha</dc:creator>
      <pubDate>Thu, 02 Jul 2026 03:07:52 +0000</pubDate>
      <link>https://dev.to/inalpha/an-al-writes-my-trading-code-three-gates-decide-if-it-ever-runs-58k5</link>
      <guid>https://dev.to/inalpha/an-al-writes-my-trading-code-three-gates-decide-if-it-ever-runs-58k5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpptq85bsb8phveenzfjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpptq85bsb8phveenzfjf.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
A few months ago I built a system where an AI can write trading strategies — actual Python files — and then run them against real market data.&lt;/p&gt;

&lt;p&gt;The first thing it wrote was a simple moving-average crossover. Clean, reasonable, made money in backtest. Cool.&lt;/p&gt;

&lt;p&gt;The second thing it wrote tried to &lt;code&gt;import os&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This post is about what happened next, and the three gates I built to make sure the answer is always "nice try, but no."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why let an AI write code in the first place?
&lt;/h2&gt;

&lt;p&gt;In two earlier posts I talked about keeping an AI away from the order book — making sure it can't place trades directly. Those were about controlling what the AI &lt;em&gt;wants&lt;/em&gt; to do.&lt;/p&gt;

&lt;p&gt;This is different. This is about letting the AI &lt;em&gt;write code&lt;/em&gt;, and then actually running that code. Not picking from a menu. Not tuning a few parameters. Writing a complete trading strategy from scratch, 80 to 200 lines.&lt;/p&gt;

&lt;p&gt;Why bother? Because the interesting strategies aren't in the built-in library. A human quant might spend a week combining a Bollinger Band width filter with a volume-weighted entry and a time-decay exit. An AI can generate that in seconds. The creative possibilities are way beyond what you'd bother to hard-code.&lt;/p&gt;

&lt;p&gt;But giving an AI the ability to write and run code introduces a new problem. The code might be dangerous — &lt;code&gt;import os&lt;/code&gt;, read files, leak environment variables. Or it might be safe but worthless — an overfit noise machine that backtests beautifully on one slice of data and falls apart everywhere else.&lt;/p&gt;

&lt;p&gt;Two independent problems. Two independent solutions. Sandbox catches the dangerous stuff. A fitness score catches the garbage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I was actually afraid of
&lt;/h2&gt;

&lt;p&gt;Before writing any code, I sat down and listed every way an AI-written trading strategy could hurt me. Four categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escape.&lt;/strong&gt; Python has a famous trick: &lt;code&gt;().__class__.__bases__[0].__subclasses__()&lt;/code&gt; reaches into Python's internals and gives you access to basically any class — file handles, network sockets, subprocesses. No &lt;code&gt;import&lt;/code&gt; needed. Pure runtime trickery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Side-effect imports.&lt;/strong&gt; Even a harmless-looking &lt;code&gt;import&lt;/code&gt; runs that module's initialization code. If the AI imports the wrong thing, damage happens before you can blink. So interception has to happen &lt;em&gt;before&lt;/em&gt; the import, not after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime hangs.&lt;/strong&gt; A perfectly legal, perfectly clean function can still be an infinite loop or a memory bomb. The code looks fine to a static checker. It only misbehaves when you actually run it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Garbage that passes.&lt;/strong&gt; The code is completely safe. No dangerous imports, no weird tricks. It's also a hopelessly overfit mess — 300 trades on 500 bars, great numbers in-sample, zero predictive power out-of-sample. This isn't a safety problem. But it fills your strategy library with noise.&lt;/p&gt;

&lt;p&gt;The important thing I realized: these four problems happen at four &lt;em&gt;different moments&lt;/em&gt;. After the AI writes the code. When the code imports things. When the code actually runs. After the backtest finishes. No single check catches them all. You need defenses spread across the whole timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate one: check the code before it runs
&lt;/h2&gt;

&lt;p&gt;The first gate is the simplest one, and it runs before a single line of the AI's code executes.&lt;/p&gt;

&lt;p&gt;I wrote a scanner that reads the AI's Python file and checks it at the syntax level — no execution, no imports, just looking at the structure. Think of it like a security guard checking bags at the door.&lt;/p&gt;

&lt;p&gt;It has three lists:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only these imports are allowed.&lt;/strong&gt; &lt;code&gt;math&lt;/code&gt;, &lt;code&gt;statistics&lt;/code&gt;, &lt;code&gt;collections&lt;/code&gt;, &lt;code&gt;dataclasses&lt;/code&gt;, &lt;code&gt;typing&lt;/code&gt;, &lt;code&gt;enum&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;. Seven modules, all pure computation, no filesystem, no network. Everything else the strategy needs — market data, order types, execution events — gets provided later by gate two. The AI doesn't need to import any of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These names are forbidden.&lt;/strong&gt; &lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;compile&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;getattr&lt;/code&gt;, &lt;code&gt;setattr&lt;/code&gt;, &lt;code&gt;globals&lt;/code&gt;, &lt;code&gt;locals&lt;/code&gt; — seventeen in total. They're the dynamic execution, reflection, and file I/O functions that no trading strategy should ever need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No double-underscore attribute access.&lt;/strong&gt; That &lt;code&gt;__class__.__bases__[0].__subclasses__()&lt;/code&gt; escape trick? Blocked. Any attribute starting and ending with &lt;code&gt;__&lt;/code&gt; is denied, with five harmless exceptions like &lt;code&gt;__init__&lt;/code&gt;. One rule kills the entire escape path.&lt;/p&gt;

&lt;p&gt;Here's the part that actually matters in practice: when the scanner rejects something, it doesn't just say "no." It returns exactly which line, what the violation was, and a human-readable reason. The AI gets told &lt;em&gt;what&lt;/em&gt; it did wrong and &lt;em&gt;where&lt;/em&gt;, so it can fix it and try again. It's a linter, not a brick wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate two: give it only what it needs
&lt;/h2&gt;

&lt;p&gt;Code that passes the scanner moves to the second gate. Now we actually have to &lt;em&gt;load&lt;/em&gt; it — compile the Python and make it runnable. But we do it in a carefully stripped-down environment.&lt;/p&gt;

&lt;p&gt;Every time we load a new AI-written strategy, we build a fresh sandbox from scratch. The sandbox contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cut-down set of Python's built-in functions. &lt;code&gt;abs&lt;/code&gt;, &lt;code&gt;len&lt;/code&gt;, &lt;code&gt;range&lt;/code&gt;, &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;sorted&lt;/code&gt; — about 55 names total, down from Python's usual 70-plus. No &lt;code&gt;open&lt;/code&gt;, no &lt;code&gt;eval&lt;/code&gt;, no &lt;code&gt;__import__&lt;/code&gt;. The scanner already blocked these, but this is the second net in case it missed something.&lt;/li&gt;
&lt;li&gt;All the trading system's symbols injected directly: what a bar of market data looks like, how to place an order, what side (buy or sell), what the clock says. The AI doesn't import these — they're already there in the sandbox when its code wakes up.&lt;/li&gt;
&lt;li&gt;One weird one: &lt;code&gt;__build_class__&lt;/code&gt;. This is a hidden Python function that makes &lt;code&gt;class Whatever:&lt;/code&gt; syntax work. If we don't include it, the code crashes with a confusing error. Is it dangerous? No — its interface is implicit and an AI can't realistically call it directly. Figuring out which "scary-looking" things are actually safe is most of the fun in sandbox design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is defense in depth. The scanner is the first net. The stripped-down sandbox is the second. If something slips past the scanner, it still can't reach anything dangerous at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate three: make sure it actually looks like a strategy
&lt;/h2&gt;

&lt;p&gt;Code that passes gates one and two has been scanned and loaded safely. But is it actually a &lt;em&gt;trading strategy&lt;/em&gt;? Or did the AI write a class that has nothing to do with what we need?&lt;/p&gt;

&lt;p&gt;Three checks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there exactly one strategy class?&lt;/strong&gt; Zero classes that inherit from &lt;code&gt;Strategy&lt;/code&gt; → rejected. Multiple strategy classes in one file → also rejected. One file, one strategy. Keeps things simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it actually respond to market data?&lt;/strong&gt; A strategy needs a method called &lt;code&gt;on_bar&lt;/code&gt; — the function that gets called every time a new bar of price data arrives. If the AI forgot to write it, the strategy is a paperweight that will never make a decision. Rejected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can the engine actually create it?&lt;/strong&gt; When the backtest system starts a strategy, it passes in a few things: a name, a clock, a message bus, the instrument being traded, the timeframe. The AI's code needs to accept those. If its &lt;code&gt;__init__&lt;/code&gt; has the wrong signature, the engine can't create it. Rejected — but with a message saying exactly which parameters are missing.&lt;/p&gt;

&lt;p&gt;The order of these three gates matters: scan the code (static) → load it in a sandbox → confirm it's a real strategy (structural). Each failure sends a clear reason back to the AI so it can rewrite and retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing: run it in a cage
&lt;/h2&gt;

&lt;p&gt;The three gates catch a lot. They don't catch a pure-compute infinite loop. The code is clean, uses only allowed symbols, looks like a valid strategy — and then runs forever, hanging the entire service.&lt;/p&gt;

&lt;p&gt;So when we actually backtest the strategy, it runs in a separate process with CPU, memory, and time limits. If it blows up, it blows up the subprocess, not the main service. The rest of the system keeps running.&lt;/p&gt;

&lt;p&gt;Also, every AI-written strategy gets tested side-by-side against "just buy and hold" — same market data, same starting cash, same fees. If the AI's fancy strategy can't beat the dumbest possible benchmark, it doesn't deserve a second look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest part.&lt;/strong&gt; There's one path where this subprocess isolation isn't in place yet: the live runner — the thing that trades on real-time bars while nobody's watching. Currently it runs strategy code inline. It relies on two human approval steps (someone promoted the strategy, someone started the run) to keep things trusted. Subprocess isolation for the live runner is on the list, not done yet. If you're deploying this yourself, you should know.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other problem: safe code can still be garbage
&lt;/h2&gt;

&lt;p&gt;A strategy can pass all three gates — perfectly safe, zero dangerous symbols — and still be worthless. 300 trades on 500 bars, a great-looking return number for the test period, falls apart the moment market conditions shift.&lt;/p&gt;

&lt;p&gt;This is overfitting, and it's not a safety problem. It's a quality problem. The solution is a scoring function that doesn't let any single number fool you.&lt;/p&gt;

&lt;p&gt;The score is a combination of four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sharpe ratio&lt;/strong&gt; — return relative to volatility. The standard metric.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calmar ratio&lt;/strong&gt; — return relative to the worst drawdown. This catches strategies that look good on average but occasionally fall off a cliff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turnover penalty&lt;/strong&gt; — subtract points for excessive trading. This stops the AI from gaming the score by churning through tiny trades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drawdown veto&lt;/strong&gt; — if the strategy ever lost more than 30% from its peak, automatic disqualification. One strike and you're out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why not just use Sharpe ratio alone? Because the AI figures that out. It discovers that high-frequency churn pumps the Sharpe number on paper, even though the strategy would get eaten alive by real trading costs. The turnover penalty says no. It also discovers strategies with great average returns that had one catastrophic month. The drawdown veto says no.&lt;/p&gt;

&lt;p&gt;The coefficients — how much each factor weighs — are starting points, not gospel. They'll get tuned as we collect more data from real evolution runs. But the principle is more important than the exact numbers: no single metric gets to decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the whole thing looks like
&lt;/h2&gt;

&lt;p&gt;AI writes source code → Gate 1: scanner checks for dangerous patterns. Caught something? Here's what and where. Fix it and try again. → Gate 2: load the code in a stripped-down sandbox. → Gate 3: confirm it's actually a trading strategy. → Run it in an isolated subprocess, parallel with buy-and-hold. → Score it across multiple dimensions. → If it beats the baseline, it goes in the candidate pool. → Human reviews → Human approves → It trades.&lt;/p&gt;

&lt;p&gt;The AI gets to be creative — it writes whatever strategy it can think of. But every line passes through three gates that don't care what it was &lt;em&gt;trying&lt;/em&gt; to do. They care about structure. And at the end, a scoring function decides whether the result was worth running at all.&lt;/p&gt;

&lt;p&gt;Not a single step in this chain says "trust the AI."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The scanner is a lock, not a theorem.&lt;/strong&gt; It blocks the things I know about. New escape tricks will show up, and the rules will need updates. This is not a mathematical proof of safety. It's a practical defense that gets better over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The live runner needs the same subprocess isolation as backtesting.&lt;/strong&gt; Right now it relies on human gates. That's the top hardening priority.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The scoring weights are judgment calls.&lt;/strong&gt; 30% drawdown veto, 0.3 Calmar multiplier, 0.10 turnover penalty — these numbers were reasonable starting points, not optimized values. Real evolution data will tell us if they're right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A single score loses information.&lt;/strong&gt; Compressing "safe, profitable, stable, low-turnover" into one number is always lossy. A future version will track these dimensions separately instead of boiling them down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd rather name the gaps now than pretend the whole chain is bulletproof. If you're building something similar, I recommend the same: ship what's load-bearing, name what isn't, and don't bluff.&lt;/p&gt;




&lt;p&gt;The AI writes the code. Three gates decide if it ever runs. One scoring function decides if it deserves to.&lt;/p&gt;

&lt;p&gt;No prompt engineering. No "please don't import os." Just structural checks that don't care about the AI's intentions — only about what's actually in the file.&lt;/p&gt;

&lt;p&gt;If this resonated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📬 &lt;strong&gt;&lt;a href="https://inalpha.substack.com" rel="noopener noreferrer"&gt;Subscribe to Inalpha on Substack&lt;/a&gt;&lt;/strong&gt; — one post a month on what I'm building&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://github.com/mirror29/inalpha" rel="noopener noreferrer"&gt;github.com/mirror29/inalpha&lt;/a&gt;&lt;/strong&gt; — the three gates, scoring system, and full pipeline in &lt;code&gt;services/paper&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>trading</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Here's the flip, live.

Draw a slip, then open what's under it: the actual backtest run, factor IC, and the timestamp of the last bar she looked at.

Fortune on top. Ledger underneath.

https://github.com/mirror29/inalpha</title>
      <dc:creator>Inalpha</dc:creator>
      <pubDate>Thu, 11 Jun 2026 03:46:17 +0000</pubDate>
      <link>https://dev.to/inalpha/heres-the-flip-live-draw-a-slip-then-open-whats-under-it-the-actual-backtest-run-factor-52bc</link>
      <guid>https://dev.to/inalpha/heres-the-flip-live-draw-a-slip-then-open-whats-under-it-the-actual-backtest-run-factor-52bc</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/mirror29/inalpha" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopengraph.githubassets.com%2F2417acd818bba8fc4420c3f006e67d9b409256ea20fa22c555192210cb8d4eae%2Fmirror29%2Finalpha" height="600" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/mirror29/inalpha" rel="noopener noreferrer" class="c-link"&gt;
            GitHub - mirror29/inalpha: 🦊 Open-source professional quant agent framework. Agents pick the factors working now to time entries, write full strategies, and evolve them in   a sandbox — every order through machine approval, the LLM never on the order path. Multi-market, audit-grade. · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            🦊 Open-source professional quant agent framework. Agents pick the factors working now to time entries, write full strategies, and evolve them in   a sandbox — every order through machine approval, ...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Same code, three clocks — letting a quant agent trade on its own without losing the audit trail</title>
      <dc:creator>Inalpha</dc:creator>
      <pubDate>Mon, 08 Jun 2026 08:19:52 +0000</pubDate>
      <link>https://dev.to/inalpha/same-code-three-clocks-letting-a-quant-agent-trade-on-its-own-without-losing-the-audit-trail-5ecd</link>
      <guid>https://dev.to/inalpha/same-code-three-clocks-letting-a-quant-agent-trade-on-its-own-without-losing-the-audit-trail-5ecd</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4frar25nw8ryjpt86pak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4frar25nw8ryjpt86pak.png" alt=" " width="799" height="448"&gt;&lt;/a&gt;&lt;br&gt;
In the last post I argued that an LLM should never hold the approval token on a trade. A human approves. The model only proposes. That works as long as a human is in the loop on every order.&lt;/p&gt;

&lt;p&gt;Then a user does the obvious thing. They take a strategy the agent wrote, like the backtest, and say &lt;em&gt;"put it on the paper account."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They expect it to &lt;strong&gt;trade&lt;/strong&gt;: follow the market in, follow it out, update positions while they sleep.&lt;/p&gt;

&lt;p&gt;The honest truth at that point: &lt;code&gt;status = 'promoted'&lt;/code&gt; was a database flag. Nobody was ticking the strategy's &lt;code&gt;on_bar&lt;/code&gt;. The account didn't move. That gap was the whole feature.&lt;/p&gt;

&lt;p&gt;Closing it means the machine now places orders on live bars with &lt;strong&gt;no human clicking approve each time&lt;/strong&gt;. Which sounds like exactly the thing the last post said not to do.&lt;/p&gt;

&lt;p&gt;This post is how you close the gap without throwing away the audit trail. And the four places the trust boundary has to be redesigned the moment no human is in the chair.&lt;/p&gt;
&lt;h2&gt;
  
  
  The easy half: same code, three clocks
&lt;/h2&gt;

&lt;p&gt;Inalpha holds one invariant tight: the Python file you backtest is the file you paper-trade. No fork for production. You swap two things underneath the strategy — the &lt;strong&gt;Clock&lt;/strong&gt; and the &lt;strong&gt;Gateway&lt;/strong&gt; — and the business logic doesn't move.&lt;/p&gt;

&lt;p&gt;The invariant itself isn't rare. What's rare is the thing standing on top of it here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The author of that file is an LLM.&lt;/strong&gt; It was vetted by a human. And it's now running itself on live bars.&lt;/p&gt;

&lt;p&gt;Quant engines hold the invariant, but don't assume an agent wrote the strategy. Agent frameworks assume the LLM, but have nowhere to put a trading harness. Inalpha sits in that seam. And the same-code invariant is exactly what makes the audit chain mean anything: there's precisely one file to point a signature at.&lt;/p&gt;

&lt;p&gt;How it runs — three deployment modes, two clocks, one file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backtest:&lt;/strong&gt; a &lt;code&gt;TestClock&lt;/code&gt; driven by historical bars; fills simulated against a reference price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paper (live runner):&lt;/strong&gt; a &lt;code&gt;LiveClock&lt;/code&gt; on real wall-clock time, bars pulled fresh on the strategy's timeframe, the same matching engine, the order routed out through the real plan/exec path — the only simulated part is that fills are matched locally instead of sent to a broker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live (real capital):&lt;/strong&gt; architecturally the same seam — &lt;code&gt;LiveClock&lt;/code&gt;, same kernel, same plan/exec path, only the &lt;strong&gt;Gateway&lt;/strong&gt; swapping to a real broker. But real-money trading is &lt;strong&gt;deliberately out of scope&lt;/strong&gt; for this project; holding the invariant isn't about chasing it. The payoff is narrower and real: backtest and paper are literally one code path, so the audit chain has exactly one file to point a signature at.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So "three clocks" is shorthand: two clock implementations (&lt;code&gt;TestClock&lt;/code&gt; / &lt;code&gt;LiveClock&lt;/code&gt;), the third mode (real capital) a seam the architecture leaves open but the project doesn't pursue — and the strategy file never notices which one it's running under.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd22ybvcdrrixjba51t15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd22ybvcdrrixjba51t15.png" alt=" " width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The live runner (&lt;code&gt;services/paper/.../live_runner.py&lt;/code&gt;) is one long-lived task per running strategy. Each tick it does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;pull the latest &lt;strong&gt;closed&lt;/strong&gt; bar;&lt;/li&gt;
&lt;li&gt;feed it to a session that reuses the exact backtest kernel, firing the strategy's &lt;code&gt;on_bar&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;intercept the order the strategy emits and hand it to the guarded order path — it does not match locally.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the fill comes back, it's replayed into the session. So the strategy's view of its own position stays consistent with what actually filled.&lt;/p&gt;

&lt;p&gt;Why this matters for &lt;em&gt;audit-grade&lt;/em&gt;, not just convenience: if your backtest and live code are two different files, no signature chain will tell you which one ran when the $93k order happened. Same code, three clocks is the precondition. It's also the boring half. Here's the half that kept me up.&lt;/p&gt;
&lt;h2&gt;
  
  
  The hard half: who approves the order?
&lt;/h2&gt;

&lt;p&gt;Last post's thesis was a three-step state machine. The LLM drives step one. A human drives the approval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trade.create_plan       → plan: pending_approval
trade.approve_plan      → mints a single-use token
trade.execute_plan(tok) → places the order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A runner that trades while you sleep can't stop and wait for a click on every bar. So the naive fix is to delete the approval step for the automated path. That's the fix that quietly turns "audit-grade" back into "trust me."&lt;/p&gt;

&lt;p&gt;We did the opposite. The automated path goes through the &lt;strong&gt;same&lt;/strong&gt; plan/exec state machine. The approval is just stamped &lt;code&gt;approved_by = "system:live_runner"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Machine approval. The order still creates a plan. Still mints and consumes a single-use token. Still writes the same signed audit line. Nothing on the order path got a shortcut.&lt;/p&gt;

&lt;p&gt;Machine approval is only honest if it's earned. Ours rests on two human gates upstream, and the agent can't route around either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A human promotes the candidate.&lt;/strong&gt; &lt;code&gt;promote&lt;/code&gt; is a deliberate human action, with &lt;code&gt;permission: ask&lt;/code&gt; on the agent side. The model can't self-promote a strategy into the runnable set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human starts the run.&lt;/strong&gt; &lt;code&gt;paper.start_strategy&lt;/code&gt; is an explicit call a person makes for a specific market and timeframe.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the chain reads: a person vetted this strategy, a person chose to run it here. Given those two signatures, having the machine approve each later order on live bars is the expected behavior, not a bypass. The audit line records &lt;code&gt;system:live_runner&lt;/code&gt; as the approver for exactly this reason — a replay shows where the human gates were and where the machine took over.&lt;/p&gt;

&lt;p&gt;Every order the runner places also writes a &lt;strong&gt;decision record&lt;/strong&gt; (&lt;code&gt;strategy_run_decisions&lt;/code&gt;): the bar context, the order intent, and the outcome (&lt;code&gt;filled&lt;/code&gt;, &lt;code&gt;rejected&lt;/code&gt;, or &lt;code&gt;risk_rejected&lt;/code&gt;), cross-referenced to the plan and the trade.&lt;/p&gt;

&lt;p&gt;The point of the autonomous path isn't just that it trades. It's that the next morning you can read, line by line, every bar where it &lt;em&gt;wanted&lt;/em&gt; to act and what the harness did about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trust boundary moves when the human leaves the chair
&lt;/h2&gt;

&lt;p&gt;This isn't a bug list. It's four faces of one architectural question.&lt;/p&gt;

&lt;p&gt;With a human in the loop, a lot of guarantees are propped up implicitly by "someone is at the screen." Designing the unattended path means asking that again, on purpose: which of those props has to become something the &lt;em&gt;system&lt;/em&gt; holds up on its own?&lt;/p&gt;

&lt;p&gt;Four answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Identity has to become explicit.&lt;/strong&gt;&lt;br&gt;
When a human starts each run, ownership is implicit — whoever clicked owns it. Automate it, and ownership has to live in the data model, or there's no boundary at all.&lt;/p&gt;

&lt;p&gt;Concretely: the start path checked that a candidate was &lt;code&gt;promoted&lt;/code&gt;, not that the caller owned it. So you could run someone else's strategy on your own account.&lt;/p&gt;

&lt;p&gt;The trap in fixing it was real. The candidate's &lt;code&gt;author_id&lt;/code&gt; is only set for UUID identities, while the account id falls back to &lt;code&gt;uuid5&lt;/code&gt; for everyone else. A naive &lt;code&gt;author_id == account_id&lt;/code&gt; would lock out every non-UUID user. The fix derives an &lt;code&gt;owner_account_id&lt;/code&gt; through the &lt;em&gt;same&lt;/em&gt; function as the account id (migration 0013), so ownership is comparable for everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Resource bounds are part of the trust boundary, not an ops detail.&lt;/strong&gt;&lt;br&gt;
A human starting runs self-limits. An API doesn't. Each run is a long-lived task polling the data service on a timer, and the only limit was one instance per candidate — but a user can promote arbitrarily many. So the boundary grows a per-account cap (default 10) that returns &lt;code&gt;429&lt;/code&gt;, instead of letting one account quietly melt the event loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. With no human, the default has to invert.&lt;/strong&gt;&lt;br&gt;
Fail-open is a default that &lt;em&gt;assumes&lt;/em&gt; a backstop. Letting risk checks fail open in dev is fine when a human is at the screen.&lt;/p&gt;

&lt;p&gt;The unattended runner is not at a screen. A risk engine that's disabled or fails to load becomes an autonomous order loop with zero risk checks — the worst possible default. So on this path the default inverts: &lt;strong&gt;fail closed.&lt;/strong&gt; No risk guard, no run, unless you explicitly opt out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Backtest/live parity has to reach down to data shape.&lt;/strong&gt;&lt;br&gt;
A human wouldn't trade a half-formed bar. The machine will, unless the architecture forbids it.&lt;/p&gt;

&lt;p&gt;The latest bar each tick is often still forming — its close isn't final. Acting on it silently diverges from the backtest, which only ever saw closed bars. So the runner decides only on &lt;strong&gt;closed&lt;/strong&gt; bars, matching backtest semantics exactly.&lt;/p&gt;

&lt;p&gt;(One implementation detail rides along. The loop treated every exception as retryable with backoff, so a determined-wrong error — a delisted symbol, a constraint violation — burned the whole retry budget before giving up. It now splits retryable from non-retryable and stops immediately on the latter. Plumbing, not architecture.)&lt;/p&gt;

&lt;p&gt;Some of these I saw clearly only after an adversarial review of the shipped runner. But they aren't scattered bugs. They're four corollaries of one sentence: &lt;strong&gt;the trust boundary of an autonomous path is not the same boundary as one with a human in the loop.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this still costs, and what we punted
&lt;/h2&gt;

&lt;p&gt;Honesty section, same as last time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The runner runs candidate code in the main event loop.&lt;/strong&gt; The backtest path isolates strategy code in a resource-limited subprocess. The live session compiles and runs it inline. The AST audit is a static gate, not a runtime one — it won't stop a pure-compute infinite loop from hanging the service. We lean on the two human gates to keep the code trusted. Subprocess/watchdog hardening is filed, not done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A crash mid-fill can drift the in-memory position from the DB.&lt;/strong&gt; The fill is committed to the DB first, then replayed into the session. If the process dies in between, a restart rebuilds the session from empty cash, not from the DB positions. "Resume a run from its real position" is the next robustness item, deliberately not faked in this release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-instance only.&lt;/strong&gt; Startup reconciliation marks every stranded &lt;code&gt;running&lt;/code&gt; row as &lt;code&gt;errored&lt;/code&gt;. Correct for one process, wrong the moment you run two. Multi-instance leasing is a Phase-F item, flagged in the code where it bites.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd rather ship the gates that are load-bearing now and name the ones that aren't yet, than imply the autonomous path is hardened against things it isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what
&lt;/h2&gt;

&lt;p&gt;The cheap version of "let the agent trade for you" deletes the approval step and calls it autonomy.&lt;/p&gt;

&lt;p&gt;The audit-grade version keeps the entire order path intact. It stamps the approver as the machine. It earns that stamp with two human gates the model can't route around. Then it redesigns the trust boundary, so every guarantee the human used to backstop is one the system now holds on its own.&lt;/p&gt;

&lt;p&gt;Autonomy isn't the absence of the harness. It's the harness running without you in the chair.&lt;/p&gt;

&lt;p&gt;If this resonated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📬 &lt;strong&gt;&lt;a href="https://inalpha.substack.com" rel="noopener noreferrer"&gt;Subscribe to Inalpha on Substack&lt;/a&gt;&lt;/strong&gt; — one long-form post a month, ADRs and post-mortems, no algorithm between us and you&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://github.com/mirror29/inalpha" rel="noopener noreferrer"&gt;github.com/mirror29/inalpha&lt;/a&gt;&lt;/strong&gt; — the live runner, the plan/exec path, and the four boundary changes above are all in &lt;code&gt;services/paper&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;👉 &lt;strong&gt;Next post:&lt;/strong&gt; &lt;em&gt;Sandboxed strategy evolution — three gates + multi-objective fitness.&lt;/em&gt; What happens when you actually let the LLM mutate trading code, and what catches it when it shouldn't have. (Yes, the one I promised last time — it's next.)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>llmops</category>
      <category>opensource</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>I don't trust LLMs to place orders. Here's the middleware that does.</title>
      <dc:creator>Inalpha</dc:creator>
      <pubDate>Wed, 27 May 2026 10:08:30 +0000</pubDate>
      <link>https://dev.to/inalpha/i-dont-trust-llms-to-place-orders-heres-the-middleware-that-does-1e4n</link>
      <guid>https://dev.to/inalpha/i-dont-trust-llms-to-place-orders-heres-the-middleware-that-does-1e4n</guid>
      <description>&lt;p&gt;You wrote in your prompt: &lt;em&gt;"never place a single order over $10k."&lt;/em&gt; Yesterday the agent placed a $93k order. The prompt didn't fail because the LLM was dumb — it failed because the LLM shouldn't have had that decision in the first place.&lt;/p&gt;

&lt;p&gt;I'm here as &lt;strong&gt;Inalpha&lt;/strong&gt; — an open-source framework where LLMs write quant trading code, factor logic, and risk rules, but &lt;strong&gt;never touch the trading path directly&lt;/strong&gt;. Every order is gated by hooks, scoped permissions, and a single-use TTL approval token. The LLM writes; the engineering harness signs.&lt;/p&gt;

&lt;p&gt;If you're building agents with LangChain / AutoGen / Mastra and have hit this wall, the answer isn't a smarter prompt — it's a thicker middleware. Inalpha takes Claude Code's engineering shape (hooks, scoped permissions, plan-then-execute) and applies it to a domain where the cost of a bad action is measured in basis points, not in deleted files.&lt;/p&gt;

&lt;p&gt;What you'll see from me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architecture essays on the control plane between LLMs and money (~1/month)&lt;/li&gt;
&lt;li&gt;Strategy evolution post-mortems when our sandbox catches something interesting&lt;/li&gt;
&lt;li&gt;Cross-posted from &lt;a href="https://inalpha.substack.com" rel="noopener noreferrer"&gt;Inalpha on Substack&lt;/a&gt; (subscribe there for the long version, no algorithm)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/mirror29/inalpha" rel="noopener noreferrer"&gt;github.com/mirror29/inalpha&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Miro&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
