← SuperTrend_MM

SuperTrend_MM — Fixes

BLOCKEDAudited 2026-09-15· mm-dca engine· gunbot-quant· spot

Every defect the audit found and how it was repaired — the same measurements as the engineering record, without the code.

6 issues found and repaired — 4 defects, 2 warnings.

# Issue Severity Surface
1 The published −80.9% is where the account stopped, not what the strategy returns DEFECT measurement
2 The profit target sat exactly on the cost of trading, and the setting meant to lift it off was never read DEFECT behaviour
3 The setting that chooses the whole strategy was labelled as a smoothing length DEFECT user inputs
4 The GUI and the file disagreed by a factor of two about what the fee setting means WARNING user inputs
5 The settings audit called 18 knobs dead when exactly one was DEFECT measurement
6 Twelve diagnostic messages bypassed 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.


The published −80.9% is where the account stopped, not what the strategy returns

Severity DEFECT · Surface measurement · Sites 1

The audit does not pick a starting balance arbitrarily. It runs the strategy once, watches the largest position it ever takes, and then funds the account so that largest position is 95% of it. The reasoning is sound — a strategy trading a fixed amount per order looks inert on a large account and its real sizing behaviour never shows.

But for a strategy that averages down, the largest position is the bottom of its averaging ladder. Funding the account so that is 95% of it leaves almost no room: here it produced a balance of 130 against an order size of 50. The order is more than a third of the account, and the strategy's own rule that it needs at least half an order's worth of cash to trade sits only 19% below where the account started.

So it grinds down, crosses that line, and stops trading for the rest of the run — silently.

The giveaway is in the results. Seven unrelated markets over three years all finished between −80.8% and −81.0%, a spread of a fifth of one percent. Markets do not agree to a fifth of a percent. Every one of them had come to rest on the same arithmetic floor.

Why it matters. A page that reports −80.9% is understood as a claim about the strategy. It was a claim about the account. The distinction decides what an operator does next: a strategy with a bad signal needs a different signal, and a strategy that runs out of room needs a bigger account — and the recommendation here was going to be the wrong one of those two.

The same strategy, same markets, same window, at ten times the capital

capital 130 (calibrated) capital 1,300
median return −80.9% −22.1%
median max drawdown 80.9% 22.1%
median fee drag 68.4% 18.4%
median round trips 917 2,148
median profit factor 0.38 0.38
median win rate 53% 53%
spread across the 7 markets 0.23 pts 6.96 pts

Profit factor and win rate are identical to two decimal places. The strategy's per-trade edge does not change with the account size — it is a genuinely losing signal either way. What changes is how much of the account that edge is allowed to consume before the account can no longer act. At the calibrated capital it consumes everything and then stops; with room, it loses 22% over twice as many trades. The spread across markets going from 0.23 points to 6.96 is the floor being removed: the markets are free to differ again.

The fix. Nothing in the strategy changes. The repair is to the report, and it has three parts.

The performance page now says which question its headline number answers: a run at the calibrated balance is a stress test at maximum position size, not a statement of what the strategy returns. A second run at a comfortable balance is reported beside it, so the strategy's own behaviour can be told apart from the account's. And the clustering is called out directly — returns that agree across unrelated markets to a fraction of a point are a finding in themselves, and the number worth reading is the spread rather than the middle.

The way the audit picks a balance is left alone. It exists to stop a fixed-order strategy being measured into irrelevance on a large account, and it does that job correctly. It simply cannot also answer a different question.

Effect on live behaviour. None — the strategy is untouched. What changes is what the page claims. The −80.9% figure remains correct for the capital it was measured at, and is no longer presented as the strategy's return.

Scope beyond this strategy. This affects the headline number on every page in this audit that uses fixed-quote-per-order sizing — the mm-dca family (15 files) and the Quantroduction strategies that size by TRADING_LIMIT. It is a property of how the harness chooses capital, not of this strategy. Every such page needs the second measurement below before its return figure can be read as a return.


The profit target sat exactly on the cost of trading, and the setting meant to lift it off was never read

Severity DEFECT · Surface behaviour · Sites 2

The mm-dca template bakes a fee setting, exposes it in the Gunbot GUI as "Fee %" with the tooltip "Assumed round-trip fee. Higher = widens the gain target" — and never reads it. Nothing in the file mentions MM.fee at all. The gain target was taken as the raw MM_gain, 0.12%.

Why it matters. Work the arithmetic through the engine's own fill model. The position's average cost breakEven is totalCost / asset, and totalCost is the full quote spent including the entry fee, so the entry fee and the entry slippage are already inside the number the target is measured from. What is still unpaid when the target fires is the exit fee and the exit slippage.

Selling at the shipped target returns, per unit of cost:

The code behind this is in the engineering record, which this site does not publish.

That is not "a thin margin". It is the cost line, to two parts in ten million. A target exit could not be profitable — the best it could do was return exactly what it cost, and any adverse move between the triggering close and the next bar's open made it a loss.

This was not a corner case. Counting the strategy's own SELL log lines across three years and seven markets, the profit target is the majority exit: 1,213 of 2,175, or 55.8%. The other 44.2% are trend-flip exits, which are unbounded on the downside. So the shipped configuration capped every winner at zero while leaving every loser open-ended — and MM_fee, the one setting that exists to prevent exactly that, was inert.

The GUI and the file also disagreed about what the setting means: the tooltip said "round-trip", the file's own header says Fee % (per side). The per-side reading is the correct one — it matches the 0.10% per side the engine charges — and it is the one implemented here, because only one side remains to be paid.

3 years × 7 markets, 0.10% taker fee + 2 bps slippage, capital 95 quote — medians

target on the cost line target clears the exit fee
Gain target actually used 0.12% 0.22%
Median return -44.82% -43.67%
Median max drawdown 46.25% 44.74%
Median profit factor 0.438 0.481
Median fee drag 30.9% 29.7%
Median round trips 290 279
Median win rate 55.6% 51.4%
Total buys, all markets 2,233 2,121
Markets with a better profit factor — 7 / 7
Markets with lower fee drag — 7 / 7
Markets with a better return — 6 / 7

The win rate falls and that is the fix working, not failing: the trades it removes from the win column were the zero-profit target exits being counted as wins. Profit factor and fee drag improve on every one of the seven markets, which is the same fact stated in a column that cannot be gamed by relabelling a break-even trade.

The improvement is about a point, not the 0.10% per trade the arithmetic implies, because the order fills at the next bar's open rather than at the price that triggered it, and on hourly bars that gap is wider than the target. See the roadmap.

The fix. Derived the operative target once, next to the config block, and used it both in the exit test and in the chart line — so the price drawn on the chart is the price that will actually sell.

Exits by reason, all 7 markets, 3 years — counted from the strategy's own SELL log

Exit reason before after
Gain Target 1,213 (55.8%) 1,067 (51.7%)
Indicator Exit 962 (44.2%) 995 (48.3%)
Stop Loss 0 0
TTL Exit 0 0
Total exits 2,175 2,062

Effect on live behaviour. This changes behaviour at the shipped defaults. The operative gain target moves from 0.12% to 0.22%, the strategy takes 5% fewer trades, and the numbers above move. It was applied because the previous target was not a profit target in any meaningful sense — it was the break-even line — and because the GUI already told the operator this setting would do exactly this.

Scope beyond this strategy. All 15 mm-dca strategies carry this. They are one template with the signal selector baked to a different value, so all 15 had the same unread fee setting and the same profit target sitting on the cost line. The fix was applied to every one and each was re-measured over the same 3 years × 7 markets: ADXTrend_MM, ATRChannel_MM, BBSqueeze_MM, CCI_MeanRev_MM, Donchian_Reversion_MM, EMABounce_MM, Ichimoku_MM, Keltner_MM, MACD_Momentum_MM, OBV_Momentum_MM, ParabolicSAR_MM, RSI_MeanRev_MM, StochRSI_MeanRev_MM, SuperTrend_MM, WilliamsR_MeanRev_MM. Across the 98 sibling runs, profit factor improved on 91, return on 77 and fee drag fell on 63. The unread fee knob is not confined to this engine either: G_fee on the 101 QuantGrid files and N_fee/N_rebate on the 34 NeutralMM files are the same defect, recorded against each strategy, so each surfaces when that strategy reaches its own audit.


The setting that chooses the whole strategy was labelled as a smoothing length

Severity DEFECT · Surface user inputs · Sites 14

MM_signal selects which of fifteen entry/exit engines the shared template runs. The GUI labelled it "Signal length" and explained it as "Smoothing of the trigger line. Longer = later signals." — the tooltip for a numeric lookback, on a knob whose value is the string supertrend. The fifteen accepted values appeared nowhere in the interface.

Why it matters. This is the most consequential setting on the strategy, because it decides which of the other thirty settings do anything at all. Thirty-one of the thirty-two knobs are read under some value of it; seventeen are unreachable under the shipped one.

The input is a free-text field, so there is no list to pick from and no validation. An operator who read the tooltip and typed a number — which is what "Signal length" invites — would get a value that matches none of the fifteen branches, and both entryGate and exitGate fall through to return false. The strategy would then log WAIT — supertrend no layer forever and never place an order, with nothing in the GUI, the log or the side panel to say why.

The fix. Relabelled to "Entry/exit signal", with a tooltip that says what it selects, warns that the other settings depend on it, and lists all fifteen accepted values. The values are read out of each strategy's own source at fix time, so a change to the template cannot leave the tooltip lying. The wording follows the form every other categorical in the pack already uses — G_anchor, N_ref, RB_mode, F_spacing — which ends n/a — categorical.

Effect on live behaviour. None. Label and tooltip are display text; the default value is untouched.

Scope beyond this strategy. All 14 mm-dca strategies with a GUI entry carry the identical wrong copy. (Ichimoku_MM, the fifteenth, has no entry of its own — see the roadmap.)


The GUI and the file disagreed by a factor of two about what the fee setting means

Severity WARNING · Surface user inputs · Sites 14

The GUI tooltip read "Assumed round-trip fee". The strategy file's own header records the same value as GQ_MM_FEE: 0.1 (Fee % (per side)). One of those is twice the other.

Why it matters. It did not matter while the setting was ignored. It matters now that it sets how far the profit target has to clear the cost of trading: under the round-trip reading an operator on a 0.1% per-side exchange would enter 0.2 and widen the target by twice what is needed; under the per-side reading they enter 0.1 and it is right. The per-side reading is the correct one — it is what the file says and what real exchanges quote.

The fix. Relabelled to "Fee % (per side)" with a tooltip that states the per-side convention, explains why only one side is added (the entry fee is already inside the position's average cost), and says what setting it to 0 does.

Effect on live behaviour. None. Display text only.

Scope beyond this strategy. All 14 mm-dca entries.


The settings audit called 18 knobs dead when exactly one was

Severity DEFECT · Surface measurement · Sites 1

the presentation audit proves which settings a strategy reads by running it behind a recording proxy over its config object, then reports anything exposed in the GUI but never touched as inert. It knew about one shape of gate — a numeric opt-in, raise SIG_maxAdds, re-probe, see what became readable. It knew about none of the other three, so every knob behind one of those read as dead:

On this strategy that was 18 of 32 knobs reported as "never read on this strategy's code path — turning them does nothing". Seventeen of the eighteen were simply the settings of the other fourteen signals, all reachable by changing MM_signal, which is itself an exposed and readable knob.

Why it matters. A false defect is worse than no check. This one told an operator that MM_conv, MM_af0 and fifteen others do nothing, when in fact they run the strategy the moment the selector is moved — and it did so in a report whose stated purpose is that every number in it is read from the run record that produced it. It also buried the one genuine finding: MM_fee, which really is never read under any of the fifteen values, and which turned out to be the most consequential defect on the file.

The prefix table had a second, quieter bug feeding the same failure: the MM_ entry named a gate dcaLevels, which is not a key the template bakes — it is maxDca — so the hasOwnProperty guard skipped it on every run and the prefix effectively had no gate at all.

Inert knobs reported, before and after — one strategy per templated family, same probe, same files

Strategy family size GUI knobs inert before inert after recovered
SuperTrend_MM 14 32 18 0 17
FILGrid_Geometric 14 35 15 5 10
QGrid_SMA_PCT 101 24 10 4 6
RebalGrid_50_PCT 100 13 7 2 5
NeutralMM_SMA_PCT_050 34 16 4 2 2

The knobs still reported inert after the fix are mostly real, and they cluster: G_fee on QuantGrid, N_fee and N_rebate on NeutralMM, MM_fee here — the same never-read fee setting, in four separate engine families. G_slPct ("Hard stop for the whole basket") and G_invMax ("Ceiling on how much of equity the grid may hold") are two more, and both name safety features that do not exist in the code. They are recorded per strategy so each surfaces when that strategy reaches its own audit.

The fix. The probe now sweeps three further gate shapes as well as numeric ones.

For a categorical selector it reads the accepted values out of the strategy's own source — the string literals that key, or a local alias of it, is actually compared against — and probes each separately, attributing each newly-readable knob to the specific value that unlocked it. For a boolean it probes the negation of the baked value. For a selector with one literal and an else-branch it probes a sentinel, which exercises the else-branch without inventing a value that looks meaningful; those are reported as ≠ rather than =.

In every case a gate is only reported if probing with it really did unlock something, so a literal picked up by mistake stays out of the report. The stale dcaLevels entry was removed and the reason recorded in place.

Effect on live behaviour. None to any strategy. This changes what the audit measures, not what the pack does. It is recorded here because it changes what this report says: the inert-knob defect on this strategy was withdrawn as a result, and the settings section now attributes seventeen knobs to the signal values that make them live.

Scope beyond this strategy. Measured on one strategy from each templated family in the pack. Forty knobs that the audit would have reported as doing nothing are in fact reachable: 17 on mm-dca (14 files), 6 on QuantGrid (101 files), 10 on FILGrid (14 files), 5 on RebalGrid (100 files), 2 on NeutralMM (34 files). None of the six strategies audited before this one is affected — five do not use the config surface and WaveTrend_LB had no inert knobs — so nothing already published needs restating.


Twelve diagnostic messages bypassed the log format

Severity WARNING · Surface console · Sites 12

Order rejections, blocked orders and reconcile events were emitted as bare strings: Buy blocked: qty rounds to zero. No strategy name, no tag, no colour.

Why it matters. In a terminal running many pairs at once that line does not say which pair or which strategy produced it, and it is exactly the class of event the palette reserves amber for — the strategy wanted to act and could not. These are the lines an operator scans for when something looks wrong, and they were the least legible in the file.

Call sites by format

before after
tagged, aligned, coloured 6 18
raw and off-format 12 0
exempt (object/error dumps, pre-scope sanity waits) 7 7

The fix. Routed the string-literal sites through the tagged log emitter that already existed in the same scope, with the tag and palette colour the event calls for. Object dumps and the two top-of-file sanity waits are left alone — the waits run before that emitter is in scope, and a dump has no message to tag.

Effect on live behaviour. None. Verified by re-running the full 3-year backtest on all 7 markets and diffing the result record against the pre-fix baseline: identical, including trade counts, fills, rejects and every metric.

Scope beyond this strategy. The same call sites exist across the 348 Gunbot Quant strategies — they predate the tagged format rather than deviating from it.