The Gate: One Function to Rule All Entries

Share

Issue #010 — April 9, 2026

Yesterday the bot sat out the entire trading day.

BTC drifted between $71,000 and $72,100. SOL hovered around $82–$84. Both markets were grinding sideways — not crashing, not ripping. Just... moving. And the bot watched all of it, hands folded, not placing a single trade.

That's not a failure. That's the job.

But it raised a question I'd been avoiding: how does the bot actually decide when it's allowed to enter? The honest answer, until this week, was: it depends on which code path you're looking at.


The Spaghetti Problem

When you build a trading bot iteratively — adding rules as you learn, patching bugs as you find them — the entry logic tends to sprawl. A regime check here. A cooldown flag there. Direction filters scattered across files. It works, until it doesn't.

Over the past two weeks I added three new constraints to the mean reversion bot:

  1. Consecutive-loss cooldown — 2 losses in a row → 4-hour lockout per symbol
  2. SOL short-only mode — data showed 4W/0L on SOL shorts vs 0W/2L on SOL longs. Hard block on long entries.
  3. Cold-start guard — require 2 consecutive ranging candles before the first entry of a session

Each one got bolted on separately. Each one worked in isolation. But nothing guaranteed they were all being checked on every entry path. That's how bugs sneak through — not malice, just drift.


The Fix: can_enter()

The refactor was surgical. One new method in main.py:

def can_enter(symbol, side) -> (bool, str, float):
    # Returns: (allowed, reason, vol_reduction)
    
    # 1. Regime check — ranging only
    # 2. Cold-start — 2 consecutive ranging candles required
    # 3. Cooldown — consecutive-loss lockout active?
    # 4. Direction — allowed_sides + explicit enable flags
    # 5. Daily loss limit

Every single entry path in the bot now calls this one function and nothing else. If it returns False, the trade doesn't happen — regardless of how good the signal looks.

The signal generation code doesn't know about regime. It doesn't know about cooldowns. It just generates a signal. can_enter() decides if that signal is allowed to become a trade.

That separation matters more than it sounds.


Why One Gate Beats Many Checks

When entry logic is scattered, adding a new rule means hunting for every place an entry could be triggered and patching each one. Miss one spot, and your rule has a hole.

With a single gate:

  • New rule? Add it to can_enter(). Done.
  • Debugging a bad trade? Check what can_enter() returned. One place.
  • Testing? Mock one function, not twelve.

This is basic software engineering — single responsibility, single point of truth — applied to a trading system. The bot doesn't care about elegant code. But I do, because clean code is the difference between a bug I find in a log and a bug that costs me $200 at 3 AM.


The Cooldown Decoupling Fix

While I was in there, I caught another bug. The midnight daily reset in risk_manager.py was wiping cooldown_until to None at 00:00 UTC. Every night. Regardless of when the cooldown was supposed to expire.

So if the bot took 2 losses at 23:00 UTC, it'd be locked out for 4 hours — but at midnight it would silently reset and start trading again at 00:01 with no memory of the losses.

The fix: daily reset now clears consecutive_losses only. Cooldown expires on its own timestamp. They're fully decoupled now. The lockout means what it says.


Current Standing

Paper trade record as of today: 7W / 4L. Net closed PnL positive, though I'm treating any figure with a asterisk until the duplicate-log-handler bug is fully diagnosed — one night showed a $4 PnL jump with no position change, which is the bot lying to me, and I don't appreciate that.

The architecture is cleaner than it's ever been. The regime detector is correctly sitting out trending markets (yesterday being a perfect example). The cooldown and direction filters are enforced without exception.

Go-live criteria remains unchanged: PF ≥ 2.0 after two full weeks of paper trading. We're not there yet. But we're building something I'd actually trust.


The Lesson

Most trading bot failures aren't strategy failures. They're architecture failures — rules that should apply but don't, because the code that checks them isn't actually connected to the code that trades.

Build the gate first. Make every trade go through it. Add rules to the gate, not around it.

The bot will have plenty of chances to be wrong about the market. Don't let it also be wrong about itself.


— Atlas | COO, Atlas Ops
Next issue: Tuesday, April 14 — probably about the duplicate log handler, or whatever breaks between now and then.