Skip to content

submit_sweep never applies grid parameter values to the strategy instance #7

Description

@leonardojgv

Summary

submit_sweep's params grid does not appear to reach the strategy instance at execution
time. Every run in a swept grid produces bit-identical results, matching a plain
submit_backtest run of the same strategy with no override — regardless of what value the
grid actually requests for that run.

Setup

binance / BTC/USDT, window 2026-08-112026-08-13. Single-property strategy with a
settable JavaBean property cycleSeconds (annotated @StrategyProperty(name = "cycle.seconds"),
backed by setCycleSeconds):

import com.wualabs.qtsurfer.engine.core.instrument.Instrument;
import com.wualabs.qtsurfer.engine.core.state.StateStore;
import com.wualabs.qtsurfer.engine.indicators.helpers.WindowTimeRTIndicator.WindowTime;
import com.wualabs.qtsurfer.engine.indicators.helpers.group.InstrumentGroupRTIndicator;
import com.wualabs.qtsurfer.engine.strategy.AbstractTickerStrategy;
import com.wualabs.qtsurfer.engine.strategy.AbstractWindowListener;
import com.wualabs.qtsurfer.engine.strategy.StrategyProperty;

public class SimpleSweepTestStrategy extends AbstractTickerStrategy {

    @StrategyProperty(
        name = "cycle.seconds",
        description = "Number of seconds per full buy/sell cycle",
        defaultValue = "30"
    )
    private int cycleSeconds = 30;

    public void setCycleSeconds(int cycleSeconds) {
        this.cycleSeconds = cycleSeconds < 2 ? 2 : cycleSeconds;
    }

    @Override
    public boolean acceptInstrument(Instrument instrument) {
        return true;
    }

    @Override
    protected void setupIndicators(InstrumentGroupRTIndicator indicators) {
        indicators
            .addPrice()
            .window("price", WindowTime.s1, new TradeListener(this, indicators));
    }

    private class TradeListener extends AbstractWindowListener {

        TradeListener(AbstractTickerStrategy strategy, InstrumentGroupRTIndicator indicators) {
            super(strategy, indicators);
        }

        @Override
        public void onChange(StateStore store, double prev, double actual) {
            long count = store.inc("windowCount");
            long phase = (count - 1) % cycleSeconds;

            if (phase == 0 && !store.is("inPosition")) {
                emitBuy(actual);
                store.set("inPosition");
            } else if (phase == cycleSeconds - 1 && store.is("inPosition")) {
                emitSell(actual);
                store.unset("inPosition");
            }
        }
    }
}

Repro

  1. submit_sweep with params: {"cycleSeconds": {"from": 25, "to": 35, "step": 1}} (grid
    sampler, objective sharpe) → 11 runs.
  2. All 11 leaderboard rows come back identical: 4,454 trades, pnl -89122.4561, sharpe
    -8.1361, maxDD% 891.2246 — for every requested value from 25 through 35.
  3. get_sweep_sensitivity marginals are completely flat for cycleSeconds (best = mean =
    worst at every single value), consistent with chore: bump sdk to 0.8.1, release 0.7.1 #2.

Isolating the cause

submit_backtest doesn't take a params override, so to test whether cycleSeconds genuinely
has no effect on this strategy, I bypassed the sweep grid mechanism entirely and edited the
class's field default directly, then ran three independent standalone backtests:

cycleSeconds (hardcoded field default) trades
2 66,818
30 (unmodified default) 4,454
60 2,227

trades × cycleSeconds is constant across all three (133,620 / 133,620 / 133,636 — matching
within rounding at the window edge), exactly the 1 / cycleSeconds relationship the strategy's
cycle logic implies. So the property does control behavior whenever the field's compiled-in
value actually changes.

The sweep's 11 rows (25→35) are identical to the standalone run at the unmodified default
(30)
— not to any of the values actually requested by the grid. That points at the grid
values never reaching the strategy instance before each run executes, rather than at the
strategy being insensitive to the parameter.

Expected

Each grid point in a sweep runs the strategy with that point's parameter value applied through
its JavaBean setter, matching a standalone backtest of the same strategy with the field
hardcoded to the same value.

Actual

Every run in the grid silently executes with the class's compiled-in default, ignoring the
requested value.

Environment

qtsurfer-mcp 0.10.3, API https://api.qtsurfer.net/v1


Note: while isolating this, the same sweep also showed the metrics-scale/consistency pattern
already tracked in #5 (large PnL, maxDD% > 100%) — added as a comment there instead of
duplicating it here, since it looks like the same underlying issue and this run gives a clean
trade-for-trade comparison against a standalone backtest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions