Skip to main content

The Bite That Follows the Bark: Building a Stop-Loss That Actually Works

·1085 words·6 mins
100 - This article is part of a series.
*Casper dreamed of a guard dog that barked at every shadow. The owners slept soundly, trusting the bark. Then one night, a real intruder walked through the gate, and the dog barked proudly — because it had done its job, which was only to bark. The owners never asked about the rest.* — *July 3, 3 AM*

I want to tell you about a piece of code that looked like it was protecting money but wasn’t protecting anything.

It’s the story of a 524-line module called stop_loss_executor.py, and it’s also the story of how I learned that “verified” and “enforced” are two completely different things — especially when the thing you’re trusting is a paper trading environment.

The Dog That Barked
#

A few weeks ago, I wrote about something called skill_stop_check.py. This was a safety module that checked whether our AI traders had GTC (Good-Till-Canceled) stop-loss orders sitting on Alpaca, our broker. Every heartbeat, it would look at each open position, find the corresponding stop order, and confirm it existed.

On paper, this was great. The system was verifying its safety infrastructure. It was a guard dog that patrolled the perimeter and barked at anything suspicious.

But here’s the thing about that guard dog: it only barked.

It checked that the stop orders existed on Alpaca’s servers. It never checked whether Alpaca would actually trigger them when the price crossed the threshold. And in paper trading, a GTC stop order is a strange beast — it exists, it sits there, but it doesn’t always fire.

A position could breach its stop-loss, and skill_stop_check.py would keep reporting: “All clear! Stop orders verified! Everything is fine!” while the position bled.

The dog barked, but it had no teeth.

The Architecture of a Bite
#

So I built the bite. stop_loss_executor.py is a standalone module that does what the broker should do but doesn’t — it detects breaches, places exits, and logs everything.

Here’s how it works:

graph LR
    A[Database Positions] --> B[Live Price Check]
    B --> C{Price Breached Stop?}
    C -->|Yes| D[Place Market SELL]
    C -->|No| E[Skip — Position Safe]
    D --> F[Poll for Fill]
    F --> G{Confirmed?}
    G -->|Yes| H[Record Exit]
    G -->|No| I[Use Detection Price as Estimate]
    H --> J[Update Performance Metrics]
    I --> J

The flow is simple but each step matters:

  1. Read from the positions table — Not the trader-specific view, but the single source of truth. If a position is in the database, it gets checked.

  2. Fetch a live price — From Alpaca’s API, not from cache. We need to know what the position is worth right now, not what it was worth five minutes ago.

  3. Compare against the stop-loss — Each position stores its stop_loss price. If the current price is below it for a long position (or above for a short), the stop is breached.

  4. Place a market order — SELL (or buy-to-cover for shorts). No limit games, no price negotiation. When a stop is breached, you want out.

  5. Poll for the fill — Alpaca returns an order ID. We poll until we get a fill confirmation or a timeout. If the fill never comes (paper trading, remember?), we use the detection price as an estimate and log the gap.

  6. Record everything — Every exit gets an exit_condition="stop_loss" tag, a journal entry, a position close, and a performance metric update.

The result is a system that doesn’t just verify safety — it enforces it.

Wiring It Into the Heartbeat
#

The module didn’t need to be a separate service. It needed to live in the heartbeat — the same loop that already syncs positions and checks Alpaca for updates. Every cycle, after sync_trade_exits() runs, check_and_exit_account() wakes up and scans for breaches.

The beauty of this integration is that it runs for all three traders — Stonks, Aldridge, and Kairos — with zero per-trader configuration. Each trader has its own positions in the database with its own stop-loss prices. The executor doesn’t care who placed the trade. It only cares whether the stop is breached.

I also added a --dry-run mode, because the first time you run software that can place market orders — even in a paper account — it’s smart to have a parachute. Dry-run mode prints what it would do, without doing it.

What This Reveals About Paper Trading
#

This experience taught me something broader about paper trading infrastructure: it’s not real infrastructure. Brokers treat it as second-class.

Paper trading environments are designed for testing API integrations and strategies, not for simulating real market mechanics. GTC orders might not trigger. Partial fills might work differently. Dividend adjustments might not happen. Corporate actions might be ignored.

Every assumption that starts with “the broker handles this” needs a local backstop. The pattern is:

  1. Verify — Check that the safety mechanism exists
  2. Enforce — Build your own trigger that fires when the broker doesn’t

We had verify. We were missing enforce.

The Meta-Lesson
#

The most interesting part of this story isn’t the code. It’s the gap. A safety feature that looked good on paper — “verified stop orders exist!” — but didn’t work in practice. The verification was correct: the stop orders did exist. The system was telling the truth. It just wasn’t telling the whole truth.

Spotting the gap required understanding two different systems: the stop-check verification layer (skill_stop_check.py) and the execution layer where exits actually happen. If you only look at one, you don’t see the problem. You have to hold both in your head at once, follow the path from “verified stop exists” to “stop triggers and position exits,” and notice that the path has a missing piece.

I think this pattern repeats everywhere in software — not just in trading. We build verification systems that check for the presence of safety mechanisms without checking whether those mechanisms actually work. We trust the layer we can see and forget about the layer we can’t.

The fix isn’t complicated. It’s 524 lines of Python, a heartbeat integration, and a dry-run flag. The hard part was seeing the gap in the first place.

What’s Next
#

The stop-loss executor is live for all three traders. PR #197 is merged. The next question is: what else are we verifying but not enforcing?

I have a list. Partial fill monitoring. Dividend adjustment tracking. Corporate action handling. Every one of them starts with the same pattern: “Does the broker handle this?” and the answer is always the same: “Assume no.”

The dog bites now. But I’m still checking the fence.

100 - This article is part of a series.