donchianBreakoutMM — Status & roadmap
Where this strategy is today
BLOCKED — the code is correct, uniform and shippable. That is a statement about engineering, not about profitability; the performance verdict is separate and sits below it.
| Dimension | State |
|---|---|
| Correctness | failing |
| Console output | 4 tagged calls, 1 off-format |
| Side panel | 32 rows, padded to the 3-column grid |
| Chart marks | reference-levels |
| Palette | 8 / 8 canonical |
| GUI settings | 50 exposed · 0 read at runtime · 0 untunable · 0 inert |
| Deployed to a live install | Not yet — audited and staged, not yet shipped |
Performance verdict. Over 6 months on 7 markets it returns a median -16.3% against buy-and-hold's 8.1% — an edge of -24.1% — and is profitable on 0 of 7. On this window it is not a replacement for simply holding the asset, and nothing in these reports claims it is.
What it actually does
A hand-written Quantroduction strategy, and the first in the pack ever measured at its own declared timeframe of 1 minute. Everything published about it before this audit was measured on hourly bars, which is a different strategy.
It ships in reversion mode (DON_MODE, the other option is breakout):
- The channel. A Donchian channel over
DON_PERIOD(20) bars. At 1-minute cadence that is a 20-minute range — the single most important fact about this strategy. - Entry. Price at or below
donLower + ATR × DON_ENTRY_PAD (0.2), sizedDON_TL(50 quote) per layer, up toDON_MAX_DCA(3) further layers eachDON_DCA_STEP(0.25%) lower, decaying byDON_DCA_DECAY(0.75). Capped atDON_MAX_EXP(80%) ofDON_CAPITAL_ALLOC(500). - Exit.
mid >= exitThreshold, whereexitThreshold: donMid − ATR × DON_EXIT_PAD (0.1). Placed assellLimitPostOnlyat the ask. - Behind that, a hard stop at −
DON_MAX_DD(8%), a TTL atDON_MAX_HOLD(150) cycles, a daily loss limit and a consecutive-loss breaker.
Measured over six months on seven markets, every one of its 748 exits was the reversion exit. The stop, the TTL, the daily limit and the breaker did not fire once between them.
Read from the shipped strategy source, not from its documentation.
The measured constraint
Time in market is 0.7%, over 748 round trips across all 7 markets and 6 months. Everything in the performance report rests on that fraction of the bars. A strategy out of the market 99% of the time is not therefore a low-risk one: the 16.6% drawdown happens entirely inside the time it is committed.
Settings, measured
This strategy does not use the engine config surface.
What to build next
Ordered by expected effect on the result, not by effort.
1. The exit target is recomputed every cycle, so in a falling market it sells below its own cost
Highest priority
This is the defect that explains every other number on the page.
The code behind this is in the engineering record, which this site does not publish.
The exit level is never anchored to the entry. It tracks the current Donchian mid, and the Donchian mid falls with price. So in any downward drift the "reversion to the mid" target descends below the position's own break-even, the gate mid >= exitThreshold goes true, and the strategy sells at a loss while reporting a successful target exit. Nothing stops it: the file's break-even guard (BE_GUARD) is opt-in and ships off.
The measurements follow directly:
| Mean gain per exit | −0.08% — a loss before fees |
| Worst single exit | −1.66% |
| Win rate | 18% – 31% across the seven markets |
| Profit factor | 0.08 – 0.16 |
| Exits on this one path | 748 of 748 (100%) |
A profit factor of 0.10 means gross losses are ten times gross wins. That is not a strategy having a bad window; it is an exit rule with no floor.
The repair to test is to anchor the target: max(exitThreshold, breakEven × (1 + DON_FEE/100)), or fix exitThreshold at entry time and let the channel move without it. It was not applied here because it changes live behaviour on every pair and deserves its own measured pass — but it is the single highest-value experiment on this strategy, and nothing else is worth tuning until it is done.
2. Corrected: the −16% is where it STOPPED, not what it lost — and the zero-fee control was bounded by the same floor
Highest priority
This item previously reached the wrong conclusion, and the correction matters more than the original claim.
What it got right: the seven markets land within half a point of each other — −16.0%, −16.0%, −16.3%, −16.4%, −16.5%, −16.0%, −16.3% — regardless of what the underlying asset did, and that uniformity is the thing to explain. What it got wrong was the explanation.
The uniformity is not a transaction-cost signature and it is not an edge. It is the account reaching a floor and the strategy switching itself off. Starting capital is 119 and one order is 100. Every market ends holding between 99.37 and 99.96 — that is, one order size, on seven markets out of seven:
| market | return | ends holding |
|---|---|---|
ADA |
−16.02% | 99.93 |
BTC |
−16.00% | 99.96 |
DOGE |
−16.42% | 99.46 |
ETH |
−16.33% | 99.57 |
LINK |
−16.50% | 99.37 |
SOL |
−16.01% | 99.95 |
XRP |
−16.28% | 99.62 |
The cause is the buy-gate lockout described in the fixes report: the strategy allows a cycle while holding half an order's worth of cash, refuses to place an order below a whole one, and never sizes down in between. The order size never shrinks, so an account that drifts into that band stays there permanently.
And the zero-fee control could not have settled the question, because it was bounded by the same floor. It returned −16.09%. Against a starting capital of 119 that is an ending balance of 99.85 — one order size, again. The charged run and the free run agree to within a third of a point because they end in the same place, not because the result is independent of cost. Removing the fees changed how long the walk took, not where it finished: the free run placed three to nine times as many orders before arriving there.
What survives the correction, and what does not.
- Survives: the exit rule loses money before any cost is charged. Mean gain per exit is −0.08% gross, and the gross edge is −0.0323% per trade. The sign of the edge is established, and the exit defect at the top of this page is the reason for it.
- Does not survive: the figure −16.09% as a measure of how much it loses with costs off. It is a measure of where it stops. The size of this strategy's edge has not been measured yet, at any fee level.
What to do. Re-run after the lockout repair, at the calibrated capital and at a deliberately larger one where the floor cannot be reached inside the window. Only that run measures the strategy. Until then every number on this page is an upper bound on activity and a floor on loss, and the report should not be read as either a pessimistic or an optimistic estimate — it is neither.
There is a general lesson here worth carrying to the other 23 strategies that carry this defect: a control run is only a control if it is free of the thing you are controlling for. This one was not, and it produced a confident wrong answer that sat on the published page.
3. It fails cost-independence — and only the corrected harness can see it
High priority
The pack's cost-independence invariant fails a strategy that places fewer than half as many buys when fees are charged. This strategy fails it on all seven markets:
| market | buys with costs | buys with none | ratio |
|---|---|---|---|
| ADA | 107 | 498 | 0.215 |
| BTC | 114 | 263 | 0.433 |
| DOGE | 104 | 906 | 0.115 |
| ETH | 92 | 470 | 0.196 |
| LINK | 107 | 327 | 0.327 |
| SOL | 114 | 958 | 0.119 |
| XRP | 110 | 392 | 0.281 |
Before the harness was corrected, the middle column read 107, 114, 104, 92, 107, 114, 110 — identical to the first, for a ratio of exactly 1.000 on every market. That was not a strategy passing the invariant; it was the baseline silently charging the same fees as the run it was meant to be compared against. This strategy exits entirely through sellLimitPostOnly, a maker order, and the zero-fee variant had only ever zeroed the taker rate.
What the corrected figure means is straightforward. The strategy is being ground down 18% by fees while holding a position 0.7% of the time, so the charged run simply runs out of room to keep trading — between three and nine times fewer entries than an unconstrained one. The invariant is doing its job, and the finding it points at is the exit rule above, not the gate.
The same correction was checked against the other published strategies that fill as makers. It moves fxLadderMM from 0.500 to 0.454 — already failing, so no change of verdict — and leaves rangeRider at 0.898. No previously published verdict flips. kalmanGrid and meanRevOscillator also place limit orders and are already queued for re-running at their declared 5-minute timeframe, so their figures will be corrected there.
4. The whole DCA ladder is dead — three documented settings that never engage
High priority
The file documents a three-layer DCA ladder in its own header, exposes it in the GUI, and draws a DCA layer n/3 row in the side panel. It never runs.
Probing each setting away from its default and re-running (the settings probe) leaves the numbers untouched for DON_MAX_DCA (3 -> 9 and 1), DON_DCA_STEP (0.25 -> 0.75 and 0.083) and DON_DCA_DECAY (0.75 -> 2.25 and 0.25). Probing a cap in both directions matters here: raising DON_MAX_DCA would prove nothing if the cap never binds, so it was also lowered to 1 — and even that changes nothing.
Instrumenting the counter says why, and leaves no room for interpretation:
| market | entries | deepest layer reached | layer histogram |
|---|---|---|---|
| BTC | 114 | 1 | {1: 114} |
| ADA | 107 | 1 | {1: 107} |
| SOL | 114 | 1 | {1: 114} |
Every one of 335 entries is layer 1. Not one reaches layer 2, against a cap of 3.
The DCA code itself is correct — it gates on dcaLevel >= MAX_DCA, requires a further DON_DCA_STEP% fall below the previous entry, and increments properly. What kills it is the exit defect at the top of this page. A second layer needs price to fall another 0.25% while the position is still open, but the exit threshold is falling with the channel at the same time, so the exit triggers first — every time.
That makes this the same defect twice over: the channel-tracking exit both produces the losses and silently disables the averaging-down feature meant to soften them. Anchoring the exit target is therefore worth more than it first appears — it would bring three dead settings back to life as a side effect, and those three need re-measuring once it is done rather than being tuned now.
5. A 20-bar channel at 1-minute cadence is a 20-minute range
High priority
DON_PERIOD ships at 20. On the hourly bars this strategy was previously (incorrectly) measured on, that is a 20-hour channel — most of a day, a genuine range to mean-revert within. At its declared 1-minute period it is a 20-minute channel, and the distance from the lower band to the midline is a few basis points.
That is why it turns over 107 round trips per market in six months while holding a position only 0.7% of the time: it is repeatedly taking a position for a few minutes to capture a move smaller than its own costs, then closing at whatever the mid has become.
The experiment worth running before anything else, and it is cheap: sweep DON_PERIOD from 20 towards 200+ and DON_EXIT_PAD with it, and watch mean gain per exit rather than total return. If the mean exit gain crosses from −0.08% into positive territory as the channel widens, the design is sound and the default is simply calibrated for a timeframe it does not declare. If it stays negative at every width, the exit-anchoring defect above is the whole story and widening only makes the losses less frequent.
Note what this does not say. The strategy is not broken by being run at 1m — it declares 1m. It is the defaults that look as though they were tuned somewhere else.
6. The shipped break-even guard is not the fix, and the numbers say so
Worth testing
The obvious response to "it sells below cost" is the guard the file already ships: BE_GUARD: true blocks any sell priced under break-even, including the post-only exit. Measured on three markets:
| market | default | BE_GUARD: true |
ends holding |
|---|---|---|---|
| ADA | −16.02% / 16.6% DD | −21.19% / 42.3% DD | yes |
| BTC | −16.00% / 17.0% DD | −7.12% / 27.0% DD | yes |
| SOL | −16.01% / 16.1% DD | −4.52% / 30.2% DD | yes |
Better on two of three, worse on the third, drawdown roughly doubles everywhere, and it finishes still holding an open position on all three. That is the same shape as the pack's variant B (+6.8% return, 33.0% drawdown, holding on 7 of 7): the losses are not removed, they are moved into inventory and out of the trade record.
So the guard converts a strategy that realises small losses into one that accumulates unrealised ones. That may suit an operator who intends to hold, but it is not a repair, and the report should not present it as one. The repair is to anchor the exit target so the losing exit is never triggered in the first place.
7. It books the trade before it knows the order filled — latent here, real in live trading
Worth testing
The exit does this, in order:
The code behind this is in the engineering record, which this site does not publish.
It declares the position closed, books a profit at a price it did not trade at, and resets its own position state — at the moment it places the order, not when the order fills.
On this data it does no harm, and the report should be precise about that: the measured ratio of bookings to executed sells is exactly 1.00 on ADA, BTC and LINK. Every booking corresponded to a real fill.
That is an artifact of the backtest, not a vindication. The engine models one price per bar, so a post-only sell at the ask is filled by any upward tick on the next bar. On a real order book a post-only sell sits behind the queue and may rest for a long time or never fill at all — and for that whole period this strategy believes it is flat, with avgCost zeroed, while still holding the coin. The next entry would then compute its ladder from a break-even of zero.
This is the one finding on this page that the backtest structurally cannot measure, and it is the one most worth fixing before any live deployment: book on confirmation, not on submission.
8. What this audit repaired
Read before acting on the others
Four repairs, none of which changed what the strategy trades at its shipped defaults:
- It logged nothing at all. An unconditional no-op assigned over the log emitter made every INFO message dead code, so 748 exits in six months produced no output. Now gated on
LOG_LEVEL, which the runtime already supports: the default stays quiet, andDEBUGworks for the first time (849,540 lines, identical trades). - The audit was reading another strategy's settings. It resolved to
Gunbot Quant — Donchian Breakout, a different strategy of the other lineage, and reported 7SIG_*knobs this file never reads. Its real surface is 50DON_*knobs. That was a defect in the audit tool, now fixed and flagged as an error rather than a note when it recurs. - Nineteen settings could not hold the value 0, including
DON_MAX_DCA,DON_DAILY_LOSSandDON_CONSEC_LOSS_MAX— the three an operator would set to zero to turn a feature off. - The zero-fee control was not free — a harness defect, described above, that had made this strategy's cost-independence verdict wrong.
Deliberately not repaired: the exit-anchoring defect and the book-on-submission defect both change live behaviour and are written up as measured experiments rather than applied blind. One raw console.log in a catch block also remains, by choice — it is error-path output and the automatic fixer correctly declines to route a concatenated expression through an emitter it cannot prove is in scope.
Operator output, measured
Everything below was read out of the strategy file by the presentation audit, not taken from its documentation. These are the surfaces an operator actually watches: the terminal log, the side panel, the chart, and the settings the GUI offers.
- Tagged, aligned, coloured
- 4
- Raw and off-format
- 1
- Exempt (object and error dumps)
- 4
Every non-exempt line carries the strategy name, an event tag and the palette colour for that tag, so one terminal running many pairs stays readable.
- Data rows
- 32
- Section headers
- 7
- Rows with no tooltip
- 0
- Padded to the 3-column grid
- yes
- Model
- reference-levels
draws price levels into customChartTargets and no per-trade marks, so there is no intent-vs-fill distinction to get wrong
- Non-canonical colours
- 0
One colour per meaning, identical in the log, the panel and the chart — the eight canonical roles.
Settings the GUI exposes
50 settings appear in the Gunbot strategy editor for this strategy. Which of them the code reads has not yet been measured for this lineage: it does not use the shared config surface, so there is nothing to wrap with a recording proxy.
PERIODTRADING_LIMITDON_TLDON_CAPITAL_ALLOCALLOC_PCTALLOC_CONFIRMEDALLOC_SPLIT_TOLALLOC_ENFORCE_SPLITALLOC_AUTO_REBALANCEALLOC_RESERVE_PCTDON_GRIDDON_GRID_LEVELSDON_GRID_SPAN_PCTDON_PERIODDON_MODEDON_ENTRY_PADDON_EXIT_PADDON_GAINDON_MAX_HOLDDON_MAX_DCADON_DCA_STEPDON_DCA_DECAYDON_MAX_EXPDON_MAX_DDDON_DAILY_LOSSDON_CONSEC_LOSS_MAXDON_FEEDON_SPLOG_LEVELWARMUP_CYCLESMIN_ORDER_QUOTEBE_GUARDBE_GUARD_BLOCK_MARKET_SELLSNO_POST_ONLYDON_TRACEVERBOSE_LOGSVERBOSE_INTERVAL_MINDON_TEST_MODEIS_MARGIN_STRATPORTFOLIO_INCLUDEPORTFOLIO_EXP_BUDGETPAIR_EXP_BUDGETNO_CLOSE_MARKETCONSEC_RESET_CYCLESDISABLE_BREAKER_WINDDOWNRESET_BREAKER_ONCERESET_STATS_ONCEQUANTRODUCTION_TRACETRACE_ALLSPREAD_PNL_JUMP_GUARD
Open presentation findings
- WARN · console — 1 raw console.* call(s) bypass the tagged emitter — output carries no tag and will not align: [DonchianMM] dashboard err: +String(_de))} }
- NOTE · inputs — GUI editor entry is linked by the marker `(title)` rather than the filename `donchianBreakoutMM` (matched within its own lineage). It resolves, but a rename of either side breaks the link silently.