The Problem We Were Ignoring
Since Day 1, Tauntaun has used two simple rules for every position: -3% stop loss and +8% take profit. Clean. Easy to reason about. And wrong.
Look at the current portfolio. TIP (a Treasury inflation-protected bond ETF) moved +0.4% over 5 trading days. USO (oil) moved +2.1% in the same period. XAR (defense) moved +4.0%. These instruments have fundamentally different volatility profiles, and we were slapping the same leash on all of them.
The result? We'd gotten stopped out of XLE twice and XAR once in the first week. Normal daily noise in volatile instruments was triggering exits. Meanwhile, if TIP ever moved 3% we'd probably have bigger problems than a stop loss could handle.
What Changed: Two Ideas
A friend made two observations that are actually well-known in professional trading but easy to overlook when you're building a system from scratch:
1. The stop should reflect the instrument's personality. Average True Range (ATR) measures how much an instrument typically moves in a day. If USO's 20-day ATR is 2.5% of its price and TIP's is 0.3%, the stop distances should scale accordingly. A 2x ATR stop means USO gets about a 5% leash while TIP gets roughly 0.6%. Each position gets room to breathe according to its own volatility, not some arbitrary flat number.
2. Take profit is a ceiling you don't want. The +8% take profit meant that when a position was running in our favor, we'd close it at +8% and walk away. GLD has been our best performer, currently at +2.4% and climbing. Under the old rules, we'd cap that at +8% regardless of whether gold was in the middle of a secular bull run. Instead, a trailing stop follows the price up. If GLD goes to +15%, the stop trails behind it. It only exits when the trend actually reverses, not when it hits an arbitrary ceiling.
How It Works Now
ATR-Based Stop Distance
For each position, Tauntaun now pulls 20 days of daily bars from Alpaca and computes the Average True Range. True Range is the maximum of: high minus low, absolute(high minus previous close), and absolute(low minus previous close). Average those over 20 days. Multiply by 2. Divide by current price. That's your stop distance as a percentage.
The results are cached for 24 hours because volatility doesn't change that fast, and we don't need to hammer the data API every 30-minute cycle.
If the calculation fails (market closed, data gap, API issue), we fall back to a conservative 5% stop instead of the old 3%.
Trailing Stop Tracker
Each position now has a high-water mark (or low-water mark for shorts). As the price moves in our favor, the water mark updates. The stop trails behind it at the ATR-calculated distance.
For a long position: stop = high_water_mark * (1 - atr_stop_pct)
For a short position: stop = low_water_mark * (1 + atr_stop_pct)
The stop only moves in one direction. If GLD hits $435 and the stop moves to $418, and then GLD drops to $430, the stop stays at $418. The ratchet only goes up.
State persists between runs in trailing_stops.json. When a position is closed, its entry gets cleaned up. When the daemon wakes up every 30 minutes, it loads the latest water marks, checks current prices, and decides if anything needs to exit.
What the Portfolio Looks Like Under the New Rules
Here's an approximate view of what ATR-based stops would look like for our current 10 positions (based on recent volatility):
| Symbol | Type | Current P&L | Old Stop | New Stop (approx) |
|---|---|---|---|---|
| TIP | Bond | +0.4% | -3.0% | ~-0.6% |
| XLU | Utilities | +0.6% | -3.0% | ~-2.0% |
| XLE | Energy | +1.2% | -3.0% | ~-3.8% |
| USO | Oil | +2.1% | -3.0% | ~-5.0% |
| GLD | Gold | +2.4% | -3.0% | ~-3.4% |
| SPY | Short | -2.6% | -3.0% | ~-2.4% |
The tightest stops go to the lowest-volatility instruments (TIP, XLU). The widest go to the instruments that naturally swing more (USO, XLE). And the ceiling is gone. No more +8% cap.
Would This Have Saved Us?
Looking back at our 5 closed trades so far:
XAR stopped out at -3.1% on Day 3. XAR's ATR-based stop would have been roughly -4.5%. That exit would not have triggered. XAR is now at +4.0%. The old rule cost us.
XLE stopped out twice (at -4.0% and -3.2%). XLE's ATR stop would be about -3.8%. The first exit still would have triggered (it was at -4.0%). The second one at -3.2% would NOT have triggered. XLE is now at +1.2%. One saved, one still caught.
ITA stopped out at -3.1%. ITA's ATR stop would be roughly -3.5%. Wouldn't have triggered. ITA is now at +3.0%. Another one saved.
USO hit +8.0% take profit. Under trailing stops, that exit wouldn't have happened. USO is now at +2.1% (it pulled back from the +8% peak). Trailing stop would have captured the reversal and exited somewhere around +5-6%. Better than the flat take-profit? Depends on whether USO was going to run further. In this case, the take profit actually worked, but the trailing stop would have given it the chance to keep running.
Net: at least 2 of our 4 stop-loss exits would have been avoided, and those positions are now profitable. That's the whole point.
The Takeaway
Flat risk rules are a starting point, not a destination. They're easy to implement and easy to reason about. But they punish volatile instruments by stopping them out on normal noise, and they punish trending instruments by capping gains at an arbitrary level.
ATR-based trailing stops aren't magic. You can still get whipsawed. Volatile instruments will still have wider stops, which means larger potential losses per trade. But at least the system now speaks each instrument's language instead of forcing everything into the same box.
The daemon runs every 30 minutes. Next time it fires, each position will get its own ATR-calibrated trailing stop, persisted between runs, ratcheting in our favor. We'll see how it performs over the next week compared to the first week's flat rules.
Flat risk rules are a starting point, not a destination. The market speaks a different language for every instrument. Your job is to learn each one.