donchianBreakoutMM — Fixes
Every defect the audit found and how it was repaired — the same measurements as the engineering record, without the code.
5 issues found and repaired — 4 defects, 1 warning.
| # | Issue | Severity | Surface |
|---|---|---|---|
| 1 | It switched itself off below one order size — on all seven markets — and that is what the published −16% measured | DEFECT | behaviour |
| 2 | It took 748 exits in six months and announced none of them | DEFECT | console |
| 3 | The audit was reading a different strategy's settings surface | DEFECT | inputs |
| 4 | Nineteen settings could not be set to zero | DEFECT | inputs |
| 5 | One diagnostic still bypasses the log format | WARNING | console |
Every fix was verified by re-running the full backtest across all 7 markets and comparing the result record against the pre-fix baseline. Where a fix is stated to change no behaviour, the two records are identical to the byte.
It switched itself off below one order size — on all seven markets — and that is what the published −16% measured
Severity DEFECT · Surface behaviour · Sites 1
The strategy asks two questions before buying, and they disagree. The first allows a cycle to run while the account still holds half an order's worth of cash. The second refuses to place anything at all unless a whole order's worth is present, and never scales the order down to fit what is there.
Every balance between those two levels passes the first and fails the second. The order size never shrinks, so an account that drifts into that band stays there — and the strategy stops trading for the rest of the run without saying so.
Why it matters. It is the explanation for this strategy's headline number, and it supersedes the one previously published here.
Starting capital is 119 and one order is 100. All seven markets end holding between 99.37 and 99.96 — one order size. The published −16% is 119 → 100. The seven markets agree to within half a point because they all stopped in the same place, not because the strategy behaves consistently across assets.
It also invalidated this report's own zero-fee control. That run returned −16.09%, which against 119 is an ending balance of 99.85 — the same floor. The charged run and the free run agreed because they finished in the same place; removing the fees changed only how long the walk took, and the free run placed three to nine times as many orders getting there. A control run is only a control if it is free of the thing being controlled for, and this one was not.
Where the account ended, against one order size of 100, starting from 119
| market | return | ends holding | orders at 0.10% fee | orders at 0% fee | ratio |
|---|---|---|---|---|---|
ADA |
−16.02% | 99.93 | 107 | 498 | 0.21 |
BTC |
−16.00% | 99.96 | 114 | 263 | 0.43 |
DOGE |
−16.42% | 99.46 | 104 | 906 | 0.11 |
ETH |
−16.33% | 99.57 | 92 | 470 | 0.20 |
LINK |
−16.50% | 99.37 | 107 | 327 | 0.33 |
SOL |
−16.01% | 99.95 | 114 | 958 | 0.12 |
XRP |
−16.28% | 99.62 | 110 | 392 | 0.28 |
The cost ratios are the same fact seen from the other side: with the fees removed the strategy places three to nine times as many orders before arriving at the same balance. That is what failing cost-independence looks like when the cause is a floor rather than a gate.
The fix. Size the order to the cash actually available, and apply the strategy's own half-order threshold at the point of placement so the two tests agree. The fraction comes from the file's own allowance rather than being chosen, so the repair implements what those two lines together already described.
Not yet applied to this strategy. It changes behaviour, so it goes in with its own before-and-after measurement, and this strategy's re-run is queued behind the one it was found on. What is published here now is the finding and its consequence for the numbers already on this page — which does not wait for the repair, because the reader is being misled today.
Effect on live behaviour. Will change behaviour, by design — the point of the repair is that the strategy keeps trading below one order size instead of stopping. It is therefore measured before and after rather than asserted, and that re-measurement is queued.
What the earlier reports established and this does not overturn: the exit rule loses money before any cost is charged — mean gain per exit −0.08% gross, gross edge −0.0323% per trade. What it does overturn is the −16% figure as a measure of how much. That number is where the strategy stopped, not what it lost, and the size of this strategy's edge has not been measured yet at any fee level.
Scope beyond this strategy. 24 strategies carry this, found by a scan written after it was first seen on
adxTrendMM. Two are measured actually doing it: that strategy and this one. On the other 22 it is latent at the capital each is calibrated to, and active on any smaller account.
It took 748 exits in six months and announced none of them
Severity DEFECT · Surface console · Sites 1
One line, unconditional, once per cycle, immediately after the startup check:
The code behind this is in the engineering record, which this site does not publish.
It replaces the runtime's logger with no-ops, so every rt.log(...) in the file is dead code. rt.warn and rt.error survive; everything at INFO dies — including every message about an entry or an exit.
Why it matters. Measured at this strategy's declared 1-minute timeframe over six months on seven markets: 748 exits, and not one of them printed anything. An operator watching the log sees positions open and close in silence.
The motivation was real. At 1-minute cadence a line per cycle is 263,000 lines per pair per six months, and this strategy is one of the pack's sub-hourly set. But the runtime already solves it: it reads the operator's LOG_LEVEL, defaulting to NORMAL, and prints ordinary lines at NORMAL and above and debug lines only at DEBUG. An operator who wants silence sets LOG_LEVEL: 'SILENT'.
What this line did was override that choice in the other direction, and make it impossible to undo from the GUI: setting LOG_LEVEL: 'DEBUG' did nothing at all, because the logger was replaced immediately afterwards.
Log output at each setting, BTCUSDT 1m, 263,000 bars
| before | after | |
|---|---|---|
At the shipped default (NORMAL) |
silent | silent — unchanged |
At LOG_LEVEL: 'DEBUG' |
silent | 849,540 lines |
| Trades at the default | 114 buys / 114 sells | 114 buys / 114 sells |
| Return at the default | −16.0029% | −16.0029% |
The shipped default still silences, so nothing changes for an operator who has not asked for logs. DEBUG now does what the GUI says it does, and the trade count is identical at both levels, which is how the change was confirmed to be log-only.
The fix. Gate the silencing on the operator's own setting instead of forcing it.
Effect on live behaviour. None at the shipped default — identical by construction, since the guard is true whenever LOG_LEVEL is unset, and confirmed by measurement above.
Scope beyond this strategy. 23 strategies — almost exactly the pack's sub-hourly set. All 23 repaired: cartaJaiMicroMM, cciMeanRevMM, cointegrationPairs, donchianBreakoutMM, glostenMilgrom, harrisScalpMM, hawkesMM, ichimokuCloudMM, inventoryBalancer, kalmanGrid, macdMomentumMM, meanRevOscillator, microPriceMM, obvMomentumMM, ofiFlashMM, parabolicSarMM, queueReactiveMM, rollModelMM, rsiMeanRevMM, stochMeanRevMM, twapScalperMM, vwapMM, williamsPctRMM.
The audit was reading a different strategy's settings surface
Severity DEFECT · Surface inputs · Sites 1
resolveEditorEntry() matched a strategy to its GUI editor entry using only the info.hardcode marker, and only disambiguated by lineage when more than one candidate matched.
This file's own entry — Quantroduction — Donchian Breakout MM — carries the marker donchianmm, which does not normalise onto the filename donchianBreakoutMM. The only marker that did was Donchian_Breakout, belonging to Gunbot Quant — Donchian Breakout: a different strategy, of the other lineage. Being the sole candidate, it was accepted without a lineage check.
Why it matters. The audit therefore reported this strategy's settings surface as 7 SIG_* knobs it never reads, when its real surface is 50 DON_* knobs. Every conclusion about what an operator can and cannot tune here would have been wrong, and the page would have listed controls belonging to an unrelated strategy.
Resolution before and after
| before | after | |
|---|---|---|
| Editor entry resolved to | Gunbot Quant — Donchian Breakout |
Quantroduction — Donchian Breakout MM |
| Lineage of that entry | wrong (gunbot-quant) | own (quantroduction) |
| Knobs reported | 7 (SIG_*) |
50 (DON_*) |
Controls re-checked and unchanged: ATRChannel_MM 32, FIL_Grid_Engine 23, GQ_Indicator_Engine 14, all resolving within their own lineage.
The fix. Three changes to the resolver: also match on the normalised entry title, not only the marker; prefer the file's own lineage whenever any same-lineage candidate exists rather than only when there is more than one; and return a sole cross-lineage match flagged, so checkEditor raises a DEFECT instead of a soft note.
Effect on live behaviour. None to the strategy — this repairs the audit tool. The strategy file was not touched.
Scope beyond this strategy. A defect in the presentation audit, not in this strategy. Two further strategies are now correctly flagged as genuinely mis-linked in the editor data —
Ichimoku_MMandmoonPhases_SPOT— and that is a separate repair to the editor file itself.
Nineteen settings could not be set to zero
Severity DEFECT · Surface inputs · Sites 26
Every tunable was read through a fallback idiom meaning use the operator's value, or the baked default. In JavaScript zero is falsy, so an operator who set a value to zero silently got the shipped default instead.
Why it matters. Zero is how most of these features are turned off. DON_MAX_DCA: 0 was meant to disable the DCA ladder; DON_DAILY_LOSS: 0 and DON_CONSEC_LOSS_MAX: 0 were meant to disable the safety halts. The GUI accepted the value, the settings readout echoed it back, and the strategy ignored it — the worst of the three possible outcomes, because it looks like it worked.
Settings that can now hold 0
| value | |
|---|---|
| Call sites rewritten | 26 |
| Distinct settings | 19 |
| Including | DON_TL, DON_PERIOD, DON_ENTRY_PAD, DON_EXIT_PAD, DON_GAIN, DON_MAX_DCA, DON_DCA_STEP, DON_DCA_DECAY, DON_MAX_HOLD, DON_MAX_EXP, DON_MAX_DD, DON_FEE, DON_SP, DON_DAILY_LOSS, DON_CONSEC_LOSS_MAX, DON_CAPITAL_ALLOC, TRADING_LIMIT, WARMUP_CYCLES, MIN_ORDER_QUOTE |
The fix. Rewritten to a reader that falls back only when the setting is genuinely absent or unparseable. The fixer skips any file where an affected knob is used as a divisor, so a zero cannot turn a working strategy into a division by zero.
Effect on live behaviour. None at the shipped defaults — verified by the byte-identical re-run on all seven markets. It changes behaviour only for an operator who had actually set one of these to zero, which is the point.
Scope beyond this strategy. 1,743 call sites across 70 files pack-wide; this is this file's share.
One diagnostic still bypasses the log format
Severity WARNING · Surface console · Sites 1
The dashboard builder's catch block writes an untagged line carrying only "dashboard err: " directly, with no tag and no colour.
Why it matters. Left unrepaired on purpose. It sits inside a catch whose whole job is to report that the normal path failed, and the automatic fixer correctly declines to route a concatenated expression through the tagged emitter without knowing the emitter is reachable at that point. Rewriting it by hand is safe but it is one line of error-path output, so it is recorded rather than changed.
The fix. Not applied.
Effect on live behaviour. None — nothing was changed.
Scope beyond this strategy. This file only.