meanRevOscillator — Fixes
Every defect the audit found and how it was repaired — the same measurements as the engineering record, without the code.
3 issues found and repaired — 2 defects, 1 warning.
| # | Issue | Severity | Surface |
|---|---|---|---|
| 1 | The DCA ladder, its per-layer threshold and its regime guard were all unreachable | DEFECT | behaviour |
| 2 | Any setting typed as 0 was silently ignored — including the harness's own | DEFECT | user inputs |
| 3 | Side-panel rows with no explanation | WARNING | side panel |
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.
The DCA ladder, its per-layer threshold and its regime guard were all unreachable
Severity DEFECT · Surface behaviour · Sites 1
The block handling every sell path ended with a trailing return, so the accumulate path below it was unreachable for as long as a position was held — and this strategy holds one on 99% of bars.
Why it matters. dcaLevel is only incremented on the accumulate path and is reset to 0 by a full exit, so it could only ever go 0 → 1. That killed four things at once, not one:
MAX_DCA(5) — the ladder never advanced past its first rung.DCA_DECAY— rung size iseffectiveTL × DCA_DECAY ^ dcaLevel, andx ^ 0is always 1, so the decay never applied.- The per-layer threshold — the entry test is
z ≤ −(entryZ + dcaLevel × DCA_Z_STEP), deliberately demanding a deeper deviation for each successive layer. WithdcaLevelpinned at 0 the deepening never happened. - The regime-break guard — gated on
dcaLevel > 0, so the one mechanism written to stop the ladder buying into a deteriorating fit could never fire.
The last two matter because they are evidence the author intended a working ladder and designed safeguards for it. Both were dead code.
3 years × 7 markets, each version at its own calibrated capital
| ladder unreachable | ladder working | |
|---|---|---|
| Capital at full deployment | 111 quote | 338 quote |
| Buys across all markets | 45 | 92 |
| Median return | 8.1% | 6.1% |
| Median max drawdown | 4.2% | 2.2% |
| Return per unit of drawdown | 1.9 | 2.8 |
| Profitable markets | 7 / 7 | 5 / 7 |
This is a genuine trade-off, not a clear win. Drawdown halves and risk-adjusted return improves, but the headline return falls and two markets flip negative. The capital figure moves because a working ladder deploys roughly three times as much, which is also why the two return numbers are not directly comparable.
The fix. Removed the trailing return that closed the sell block. Every individual sell path inside it still returns on its own, so a cycle that sells does not also buy — falling through only reaches the accumulate path on a cycle where no sell fired, which is exactly when adding is intended.
Effect on live behaviour. This changes behaviour at the shipped defaults. The strategy now builds a position over up to five layers instead of one, deploys roughly three times the capital, and places twice as many orders. It was kept on for this strategy because drawdown halves and risk-adjusted return improves — but see the warning below, because that is not true everywhere.
Scope beyond this strategy. 16 strategies in the pack carry this shape. 14 were repaired by this tool, and two (
volHarvester,volRegimeMM) had been fixed by hand earlier: atrChannelMM, bbSqueezeMM, cciMeanRevMM, dipHunter, emaBounceMM, ichimokuCloudMM, keltnerChannelMM, meanRevOscillator, regimeArbitrageur, rsiMeanRevMM, stochMeanRevMM, superTrendMM, vwapMM, williamsPctRMM.
Any setting typed as 0 was silently ignored — including the harness's own
Severity DEFECT · Surface user inputs · Sites 30
Every numeric setting was read through a fallback idiom meaning use the operator's value, or the baked default. Zero is falsy in JavaScript, so a setting of zero was replaced by the baked default with no warning.
Why it matters. Zero is a meaningful value for most of these and the only way to turn several of them off. The same bug was also discarding the backtest harness's WARMUP_CYCLES: 0, which it passes because the engine already withholds a 2,600-bar warmup, so the strategy ran with its baked warmup of 10 instead.
Why the measured numbers moved — warmup isolated, 3 years
| Run | LINK | XRP |
|---|---|---|
| original, harness asks warmup=0 (ignored → 10) | 8 buys, 2.34% | 7 buys, 2.36% |
| fixed, harness asks warmup=0 (honoured) | 3 buys, 0.90% | 7 buys, 2.50% |
| fixed, warmup forced to 10 | 8 buys, 2.34% | 7 buys, 2.36% |
The fixed file at warmup=10 reproduces the original exactly on both markets, which proves the warmup setting was the whole difference and nothing else in this fix altered behaviour.
The fix. Replaced the idiom with a reader that separates "not set" from "set to zero", returning the default only for undefined, null, "" and a non-numeric value.
Effect on live behaviour. Identical at every setting that previously worked; the measured numbers move only for the isolated warmup reason above.
Scope beyond this strategy. Pack-wide: 1,743 sites across 70 files.
Side-panel rows with no explanation
Severity WARNING · Surface side panel · Sites 6
Several side-panel rows carried an empty tooltip or none at all.
Why it matters. The side panel is where an operator reads what a strategy is doing without opening the code. An unexplained number is something to guess at — and DCA layer 0/5 was actively misleading, since five layers were unreachable.
The fix. Filled from the shared side-panel label glossary. A label the glossary does not know is reported rather than guessed at.
Effect on live behaviour. None. Tooltips are display text.
Scope beyond this strategy. Pack-wide: 1,433 rows over 392 distinct labels.