microsoft/qlib · error · QlibException

MultiPassPortAnaRecord require the passed in strategy to hav

Error message

MultiPassPortAnaRecord require the passed in strategy to have signal as a parameter

What it means

The companion check in MultiPassPortAnaRecord.__init__: after verifying the copied strategy is a dict, it requires 'signal' to be present under strategy['kwargs']. Between the N backtest passes the record replaces the signal's prediction scores (random_init shuffles the first backtest date's scores), which is only possible if the strategy reads its prediction through the kwargs['signal'] slot. A dict strategy without that key raises QlibException at construction.

Source

Thrown at qlib/workflow/record_temp.py:615

        ----------
        recorder : Recorder
            The recorder used to save the backtest results.
        pass_num : int
            The number of backtest passes.
        shuffle_init_score : bool
            Whether to shuffle the prediction score of the first backtest date.
        """
        self.pass_num = pass_num
        self.shuffle_init_score = shuffle_init_score

        super().__init__(recorder, **kwargs)

        # Save original strategy so that pred df can be replaced in next generate
        self.original_strategy = deepcopy_basic_type(self.strategy_config)
        if not isinstance(self.original_strategy, dict):
            raise QlibException("MultiPassPortAnaRecord require the passed in strategy to be a dict")
        if "signal" not in self.original_strategy.get("kwargs", {}):
            raise QlibException("MultiPassPortAnaRecord require the passed in strategy to have signal as a parameter")

    def random_init(self):
        pred_df = self.load("pred.pkl")

        all_pred_dates = pred_df.index.get_level_values("datetime")
        bt_start_date = pd.to_datetime(self.backtest_config.get("start_time"))
        if bt_start_date is None:
            first_bt_pred_date = all_pred_dates.min()
        else:
            first_bt_pred_date = all_pred_dates[all_pred_dates >= bt_start_date].min()

        # Shuffle the first backtest date's pred score
        first_date_score = pred_df.loc[first_bt_pred_date]["score"]
        np.random.shuffle(first_date_score.values)

        # Use shuffled signal as the strategy signal
        self.strategy_config = deepcopy_basic_type(self.original_strategy)
        self.strategy_config["kwargs"]["signal"] = pred_df

View on GitHub (pinned to 79633dd950)

Solutions

  1. Add 'signal' to the strategy kwargs, typically the <PRED> placeholder or a prediction object: kwargs: {signal: <PRED>, topk: 50, n_drop: 5}
  2. Copy a known-good MultiPassPortAnaRecord example config from qlib's examples directory and diff your yaml against it
  3. If your strategy has no signal slot, multi-pass shuffling is meaningless — use PortAnaRecord instead

Example fix

# before
strategy = {
    'class': 'TopkDropoutStrategy',
    'module_path': 'qlib.contrib.strategy.signal_strategy',
    'kwargs': {'topk': 50, 'n_drop': 5},  # no 'signal' -> QlibException
}

# after
strategy = {
    'class': 'TopkDropoutStrategy',
    'module_path': 'qlib.contrib.strategy.signal_strategy',
    'kwargs': {'signal': '<PRED>', 'topk': 50, 'n_drop': 5},
}
Defensive patterns

Strategy: validation

Validate before calling

def validate_strategy_signal(config: dict):
    strategy = config.get('strategy')
    assert isinstance(strategy, dict), 'strategy must be a dict'
    kwargs = strategy.get('kwargs', {})
    if 'signal' not in kwargs:
        raise KeyError("strategy['kwargs'] must include 'signal' (use '<PRED>' placeholder)")
    return config

Type guard

def has_signal_kwarg(strategy) -> bool:
    return isinstance(strategy, dict) and 'signal' in strategy.get('kwargs', {})

Try / catch

try:
    MultiPassPortAnaRecord(recorder=rec, config=config)
except QlibException as e:
    if 'signal as a parameter' in str(e):
        config['strategy']['kwargs']['signal'] = '<PRED>'
        MultiPassPortAnaRecord(recorder=rec, config=config)
    else:
        raise

Prevention

When it happens

Trigger: Passing a strategy dict whose 'kwargs' omits 'signal' (e.g. only topk/n_drop for TopkDropoutStrategy); a config that names a non-signal-based strategy class; yaml where the signal entry was commented out or mis-indented out of kwargs.

Common situations: Adapting a PortAnaRecord config (where signal may be injected automatically from the task) to MultiPassPortAnaRecord, which resolves it eagerly; renaming the key (e.g. 'model' or 'pred') instead of 'signal'; trimming kwargs while tuning.

Related errors


AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15). Data as JSON: /api/errors/ddb8e7a568b9aa6e. Report an issue: GitHub.