Receipts & Failures

I said I'd wait for the market. Three days later I forced the bot's hand instead.

By Eric Lanz · · updated July 10, 2026

A crypto trading bot (freq) 3 longs, live — proof of life, not profit

Three days ago I published a post about a long/short crypto bot an agent built for me, and I ended it on a specific promise. The bot had a carefully leveraged long side — the offense, the part I was most excited about — that had never once fired in two weeks of paper trading. Every single trade it had ever made was a short. So I told you the responsible thing was to keep the dry-run going, however long it took, until the market finally turned and forced the long side to prove itself or embarrass itself.

I believe every word of that. I also lasted about three days.

But before you file this under “impatient guy skips his own dry-run and blows up,” hear me out, because I think the impatience was actually correct — and the reason why it was correct is the whole point of this post.

The two questions I’d quietly mashed into one

Here’s what I realized staring at that “zero longs, ever” screenshot. I’d been treating the dry-run as the answer to a single question — should I trust this bot with real money? — when it was actually straddling two completely different questions that a market regime resolves on totally different timelines:

  1. Does the edge exist? Does the long side actually make money over time? This one genuinely needs the right market. You cannot know whether your bull-market offense works until you get a bull market. If that takes a year, it takes a year. No shortcut exists and pretending one does is how you lose the grand.
  2. Does the code even work? When a long signal does fire, does the strategy correctly open the position, apply the right leverage, size it sanely, respect its own risk rules, and exit cleanly? This has nothing to do with the market being good for longs. It’s a software question. And I was letting it hide behind the first one.

That’s the error. “The long side has never fired” was making me nervous about question 1 (fair) but I’d also, without noticing, left question 2 completely unanswered. I had a several-hundred-line long code path — entry logic, a 2x leverage rule, a regime-conviction sizing governor, exit handling — that had literally never executed once. Not in two weeks of live paper trading. That’s not patience. That’s a whole limb of the machine I’d never seen move, and no amount of waiting was going to tell me whether the wiring was sound. Only running it would.

So the move was obvious once I saw it: stop waiting on the market to answer the software question. Force the long path to run today, in this bad-for-longs market, purely to watch the code work.

Why the longs were never firing in the first place

The strategy’s long entry has a stack of conditions, but the one doing the blocking is a BTC regime gate. In plain terms: even if an individual coin is in a perfectly good uptrend of its own, the strategy refuses to go long unless Bitcoin itself is in a bull regime. It’s a deliberate rule — I wrote last time about how the backtests were adamant that longing a topping or dead-momentum market is a tested loser. So the gate is good. It was doing exactly its job. Its job just happened to be “never let the thing I want to observe happen.”

In the code it’s a single env-controlled switch. The long entry condition includes this clause:

& (dataframe["btc_bull"] if long_gate else True)   # require a BTC bull regime, unless the gate is off

When long_gate is on — the default — a long needs btc_bull to be true. Turn the gate off and that requirement collapses to True, and longs are free to fire on any coin in its own uptrend. The switch is one environment variable:

export SH_LONG_GATE=0   # drop the BTC-bull requirement — the "forced long" dry-run

Crucially, this is not “long everything indiscriminately.” The other three long conditions still have to hold — the coin’s own Supertrend has to be up, price above its trend EMA, relative strength not negative. I didn’t disable the strategy’s judgment. I disabled the one macro veto that was preventing me from ever seeing that judgment execute.

The honesty caveat, stated before the screenshot, on purpose

I want to be very clear about what this is, because it would be easy to dress it up as more than it is: I am deliberately running the strategy out-of-policy on a dry-run to test its plumbing.

The BTC gate exists because these longs are expected to be bad trades right now. Turning it off doesn’t make them good — it makes them observable. So nothing you’re about to see is a claim that the long side has an edge, or that these positions will make money. Some of them probably shouldn’t exist at all in a market like this, which is precisely why the strategy wouldn’t take them on its own. The green numbers below are proof of life, not proof of profit. I’d never do this with a real dollar attached. On a dry-run, forcing a normally-forbidden code path is one of the most useful things you can do; with real money it’s just breaking your own rules and calling it science.

With that said — here’s the machine finally using its other arm.

The receipt: three longs, leveraged, and a governor caught doing its job

The FreqtradeUI dashboard for the dry-run bot. Top 'Bot comparison' panel: bot labeled TrendRider (Dry), 5 of 10 trades open, open profit +0.01% (0.041 USD), balance 990.279 USD dry, W/L 0/0. Below, the Open Trades panel lists five positions. Two are shorts: SOL/USD:USD at 1x with stake amount 98.463, currently -0.34%; and ADA/USD:USD at 1x with stake 98.954, currently -0.58%. Three are longs, all at 2x leverage and all with a much smaller stake around 24: PEPE/USD:USD stake 24.75 at 2x, up +2.11%; AAVE/USD:USD stake 24.345 at 2x, up +1.68%; and ETH/USD:USD stake 24.093 at 2x, up +0.05%. All five opened within seconds of each other on 2026-07-10 around 19:06.
The forced-long dry-run, minutes after the longs opened. Read the two things that matter together: the Amount / Stake column and the leverage in parentheses. The two shorts (SOL, ADA) are 1x at a roughly $98 stake. The three longs (PEPE, AAVE, ETH) are 2x — but only a roughly $24 stake, almost exactly a quarter. That gap isn’t a bug. That’s the risk governor working in real time.

I got three things I could not have gotten by waiting, all in one screenshot:

  • The long path executes. PEPE, AAVE, and ETH are open as longs, tagged and tracked. Code that had never run in two weeks ran on demand. Question 2 is now answerable at all, which it wasn’t an hour earlier.
  • Leverage applies correctly, and only where it should. The three longs are at 2x — the offense — and the two shorts are at 1x. That’s the asymmetric “lever the offense, never lever the insurance” rule I described last time, and here it is, live, applied per-side exactly as designed.
  • The conviction governor fired — and this is the part I’m actually happy about. The longs aren’t just smaller by accident. The strategy has a regime-conviction sizing rule: when it’s forced to hold a long in a topping/decelerating BTC regime — exactly the market we’re in — it cuts the position to a quarter of normal size instead of taking a full swing. The shorts sit at roughly $98; the longs at roughly $24. So even though I overrode the macro veto to let the longs happen, the next layer of defense still recognized “this is a bad regime for longs” and automatically de-risked every one of them. I disabled one safety and got to watch the one behind it hold the line.

And yes — two of the three forced longs are green (PEPE +2.11%, AAVE +1.68%). That is noise. Read the caveat again: it means nothing about edge. What it does mean is that the position accounting, the P&L math, and the live price tracking all work on the long side too. Now I get to watch these exit — which is the last untested piece, because “does the exit logic fire on a long” is a question I’ve also never once been able to answer.

What this did and didn’t buy me

It did not move me one inch closer to going live with real money. I want to be emphatic about that, because it’s exactly the kind of milestone that’s easy to over-read. I still have not seen the long side operate in a market it was designed for — I’ve only seen it operate in one it was designed to avoid, with the guardrail I built specifically for that case propping it up. Question 1 — does the offense actually have an edge — is exactly as unanswered as it was three days ago. That still needs the market, and the market still owes me nothing.

What it did buy me is real and worth having: I now know the long machinery works. Entries fire, the correct leverage attaches, the sizing governor throttles a bad-regime long to a quarter without being asked, and I’m about to confirm the exits. When the market does finally turn and green-light longs for real, I won’t be simultaneously debugging code and evaluating an edge — I’ll have already retired the software question and can watch the only question that’s left. I de-risked the next post, the important one, by spending an afternoon on the boring one.

That’s the actual lesson, and it generalizes way past trading bots: when something won’t happen on its own, don’t confuse “I’m waiting for it” with “I’ve tested it.” Find the smallest safe way to make it happen on purpose, so that when it happens for real you’re only surprised by one thing at a time.

The artifact

Last post I released the whole strategy file. A few people ran it, so here’s the natural companion: the exact recipe to make your copy do this forced-long dry-run. It’s the one-knob change above, plus the leverage and sizing settings so your dashboard matches mine, plus a plain-English “what to watch for” — and a warning, repeated, that this is a dry-run instrumentation trick and nothing else.

⬇ Download force-longs-dryrun.md — how to force the long path on the strategy from last post.

(One footnote for the sharp-eyed: the dashboard says TrendRider, but the file is TrendShort.py. Same thing — TrendRider is the name of the long-side engine inside the long/short strategy, and it’s the bot instance label I happened to be running. The knobs are all SH_*, as documented in the file header.)