Forty-one percent, on paper
Kerdoos is an automated day-trading system. Every five minutes a scheduler runs one loop — sync the fills, secure the open positions, scan the watchlist, strike on whatever qualifies — against a broker's API, for stocks during the session and crypto around the clock. It is live at kerdoos.com, it is built on Laravel, and it trades a paper account: $100,000 of pretend money, real prices, real fees, no risk to anyone. That is the frame for everything below, and it is also what makes the number below writable. On the third of September, after thirty-seven trading days, the account stood at $58,747.
This is the project write-up, which means it is a post-mortem. I am not going to dress it up. The system did what it was built to do, every five minutes, for two months, and lost forty-one percent doing it — and the interesting part is not that it lost but where the money went, because almost none of it went where I would have guessed. Measured against the broker's own ledger rather than the app's reporting, five things fell out, and each of them is getting an article of its own over the autumn. This post is the map.
What it is
Four strategies, all long-only, all from the day-trading canon: a momentum_breakout on a volume-backed break of the lookback high, a mean_reversion on an oversold stretch below the lower band with RSI washed out, an opening_range_breakout on the first close above the opening thirty minutes, and a vwap_pullback on a reclaim of a rising session VWAP after a dip. Each is a class implementing one interface; each evaluates a symbol's recent five-minute bars and either emits a signal or does not. The watchlist is rebuilt before the open from the broker's most-actives and movers screens and refreshed through the day; a risk manager sizes every signal at a fixed fraction of equity, enforces a position cap and a daily-loss breaker, and the executor places bracket orders for stocks and market orders with client-side exits for crypto.
Around that loop sits the rest of a normal application: a dashboard, a performance page, a fee ledger that pulls the broker's activity feed, a backtester that replays history through the same strategy classes, and an optimiser that grid-searches their parameters with an out-of-sample split. Three hundred-odd tests, most of them tracing back to something that went wrong live. That last clause is the whole of this post.
The curve is the broker's, not mine — daily closes from its portfolio history. July is the steep part. The −$12,400 and +$10,300 pair on the last day of July and the third of August is one overnight crypto hold whipsawing. On the fifth of August I switched the VWAP strategy off for crypto and put a 0.5 % floor under every stop, and from there it is a steady bleed of five hundred to two thousand dollars a day, with no single day better than +$2,400. Nothing about that shape says bad luck. It says structural, and the ledger agreed.
Where the money went
The account fell $37,463. The app's own performance page had an answer for that — about $13,000 lost on price and about $11,800 in fees, with the rest unexplained — and the answer was wrong, because the fee ledger was recording half the commission as zero. Pulled from the broker's activity feed and reconciled, the real split is this.
| Strategy × market | Trades | Net win % | On price | Fees | Net |
|---|---|---|---|---|---|
vwap_pullback × crypto | 285 | 9 % | −4,159 | 16,558 | −20,718 |
vwap_pullback × stocks | 524 | 29 % | −7,382 | 177 | −7,559 |
mean_reversion × crypto | 75 | 13 % | −1,038 | 4,779 | −5,818 |
momentum_breakout × crypto | 48 | 19 % | +346 | 2,015 | −1,670 |
mean_reversion × stocks | 124 | 40 % | −678 | 37 | −715 |
momentum_breakout × stocks | 141 | 28 % | −505 | 43 | −548 |
| All | 1,197 | 24 % | −13,416 | 23,609 | −37,028 |
Read the fees column before anything else. Sixty-four cents of every dollar lost was commission. The three crypto rows lost $4,851 on price and paid $23,352 to do it; the three stock rows paid $257 in fees across 789 trades and lost their money the honest way, on price. "Net win %" counts a trade as a win only after its share of fees, and by that measure eighty-eight crypto trades that were winners on the chart were losers in the account. After the fifth of August the crypto book was −$4,091 on ninety trades, with fees twenty-three times the price result. That is not a strategy problem. That is a toll booth. Which brings me to the five things.
1 · The fees were the strategy
The broker charges a commission on crypto and sends it as activity rows, and the rows come in two shapes. The buy-side fee is charged in the coin: the row's symbol is ETHUSD with no slash, its dollar amount is literally "0", and the charge sits in a quantity field with a price beside it. The ledger read the zero first and did not recognise the slashless symbol as a crypto pair, so 818 rows were filed at $0.00 — $11,693 of commission. The sell-side fee arrives in dollars with no symbol at all, and the attributor that spreads fees across trades skipped symbol-less rows, so no fee ever reached a trade's net figure and every "net" number on the performance page was gross. The one brake in the system keyed to net P/L was blind in exactly the case it was built for.
The arithmetic underneath is the useful part. The round trip costs about 0.37 % of notional. A trade with a 1 % target and a 0.5 % stop breaks even at a 33 % win rate with no fee and at 58 % with it. The fee does not change the odds of any trade; it changes how often you have to be right. The article on this is the first of the five.
2 · Stops inside the noise
The stock book lost its money differently, and the shape is stop geometry. Stops sat at 1.5 times the average true range of a five-minute bar, which on a watchlist screened for names moving 1.5 % or more a day works out to about 0.6 % of price — roughly a bar and a half of ordinary wiggle. Stops were hit 744 times against 277 targets at a payoff of about two to one, and 194 trades were dead inside ten minutes for −$8,468. Bucketed by initial stop width, the sub-1 % stops lost $10,621 across 635 trades and the two widest buckets were the only positive cells in the book, on 150 trades between them. Every out-of-sample sweep I have run since has put the widest stop on the grid at the top. The stops article is the second.
3 · The bot I tested was not the bot I ran
This is the one that reorganised how I think about the whole project. The scanner asked the broker for bars with limit=120 and nothing else — no start, no sort — and the broker pages from the start of the day and keeps the first hundred and twenty. A crypto day is 288 five-minute bars, so from about ten in the morning UTC every crypto scan, since the thirteenth of July, evaluated the morning's chart and then bought at the afternoon's price. The same response also carried the bar still being built, and a scan takes four to five minutes, so symbols late in the list were judged on three minutes of trading dressed as a close.
The backtester, meanwhile, entered on closed bars only, charged no slippage, and filled a take-profit the moment a bar's high touched the target. Over the four weeks from the fifth of August the live book lost $7,633 on the VWAP strategy; a replay of the same four weeks on the recorded watchlist made $5,097 on it, and reproduced only 213 of the 587 trades the bot actually took. The simulator and the bot were not making the same decisions. Every parameter the optimiser had ever chosen was chosen for a bot that did not exist. With slippage calibrated from live fills and targets required to trade through, the same replay returns −17.4 % against the account's real −14 % for the window; the old model had said −3.1 %. The backtesting article is the third.
4 · Fifteen days halted
Fifteen of the thirty-seven trading days ended with the daily-loss breaker tripped. It worked every time: no new entries after the line. It also overshot every time — halts fired between −5.0 % and −7.09 % of the day's opening equity — because a breaker stops the next trade and the positions already open keep moving, and on the worst days the ten-minute stop-outs had run up most of the tab before the line was reached. And nobody was told. Eight hundred and sixty-five halt warnings and 1,475 "positions full" warnings were raised with notifications switched off, and the worker on the trading machine was found to have been running two-week-old code, so a cooldown brake that shipped in mid-August had never fired once. A brake that nobody knows has fired is a brake you cannot learn from. The fourth article is about what a circuit breaker buys and what it does not.
5 · How often should a portfolio trade at all?
Eleven hundred and ninety-four trades in thirty-seven days. The rest of my money trades a handful of times a year, and two months of watching a machine pay a broker fifty-seven dollars a trade to lose twelve made me want to write down the opposite rule properly: a target allocation, a band around it, and no trade at all until the band is crossed. A 60/40 portfolio has to see its stocks beat its bonds by 23.8 % before it drifts to 65/35. Most years it does not, and a calendar rule rebalances anyway. The fifth article is that arithmetic, and it is the only one of the five not drawn from Kerdoos's own numbers — it is what Kerdoos taught me to value.
What was fixed, and how I know
Four defects, each measured on the live data before it was touched and each closed with a test that reproduces it: the stale and unfinished bars; the fee ledger's two row shapes; a position cap that was checked once per scan rather than once per order, so a scan raising six signals with four positions open and a cap of five placed all six; and the simulator's missing friction. The suite went to 305. Crypto entries are off for every strategy — one line in config reverses it, and the condition for reversing it is a fee-and-slippage backtest that holds up out of sample, which none has. The optimiser's stop grid was widened to ask the question the bucket table raises.
What I am not going to tell you is how the week since the fix looked, because one week is not a sample and every number before it was produced by a bot reading the wrong bars. The first week of closed-bar data is the first honest one, and the honest thing to do with it is wait for more. The system was built to be measured; the measurement is now trustworthy; the strategies may or may not be. That is the state of the project.
The bot in production was never the bot the backtester measured.
A note on what this is not
It is not advice, and none of this is a recommendation to trade anything. It is a paper account, which is the only reason I can write the number at the top with a straight face; day trading with real money is a reliable way to lose it, and the project's own README says so before it says anything else. What Kerdoos is, for me, is an instrument: a way to find out whether a strategy that looks like an edge on a chart survives contact with fees, slippage and a scanner that runs on a clock. So far the answer is no, four times, and the instrument is better calibrated for having said so.