microsoft/qlib · error · QlibException

MultiPassPortAnaRecord require the passed in strategy to be

Error message

MultiPassPortAnaRecord require the passed in strategy to be a dict

What it means

MultiPassPortAnaRecord.__init__ deep-copies the strategy portion of the port analysis config (self.strategy_config) and requires it to be a dict, because it must mutate strategy['kwargs']['signal'] between passes (replacing the prediction dataframe for random-init scoring). If the copied strategy is not a dict, the constructor raises QlibException immediately — the record cannot do multi-pass backtests on a non-dict strategy spec.

Source

Thrown at qlib/workflow/record_temp.py:613

        """
        Parameters
        ----------
        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

View on GitHub (pinned to 79633dd950)

Solutions

  1. Make the strategy parameter a full dict spec, e.g. {'class': 'TopkDropoutStrategy', 'module_path': 'qlib.contrib.strategy.signal_strategy', 'kwargs': {...}}
  2. Cross-check against a working PortAnaRecord/MultiPassPortAnaRecord example config in qlib's examples (benchmarks/workflows) and fix yaml indentation so strategy stays a mapping
  3. If you genuinely need an object strategy, use plain PortAnaRecord, which does not rewrite the signal between passes

Example fix

# before
record = MultiPassPortAnaRecord(
    recorder=rec,
    config={'strategy': 'TopkDropout', 'backtest': bt},  # string -> QlibException
)

# after
record = MultiPassPortAnaRecord(
    recorder=rec,
    config={
        'strategy': {
            'class': 'TopkDropoutStrategy',
            'module_path': 'qlib.contrib.strategy.signal_strategy',
            'kwargs': {'signal': <PRED>, 'topk': 50},
        },
        'backtest': bt,
    },
)
Defensive patterns

Strategy: validation

Validate before calling

def validate_multipass_config(config: dict):
    strategy = config.get('strategy')
    if not isinstance(strategy, dict):
        raise TypeError(
            f"strategy must be a dict spec, got {type(strategy).__name__}; "
            "expected {{'class': ..., 'module_path': ..., 'kwargs': {{...}}}}"
        )
    if 'signal' not in strategy.get('kwargs', {}):
        raise TypeError('strategy["kwargs"] must contain "signal"')
    return config

Type guard

from typing import Any, Dict

def is_strategy_dict_spec(strategy: Any) -> bool:
    return (
        isinstance(strategy, dict)
        and isinstance(strategy.get('kwargs'), dict)
        and 'signal' in strategy['kwargs']
    )

Try / catch

try:
    MultiPassPortAnaRecord(recorder=rec, config=config)
except QlibException as e:
    raise ValueError(f'invalid multi-pass port analysis config: {e}; see {validate_multipass_config.__name__}') from e

Prevention

When it happens

Trigger: Passing a strategy config that is a string, list, or instantiated strategy object where a dict config is expected, e.g. MultiPassPortAnaRecord(recorder, strategy='TopkDropout', ...) or a yaml section where 'strategy:' parsed to a scalar; building the port_analysis record block by hand instead of mirroring the standard PortAnaRecord config shape {class: ..., module_path: ..., kwargs: {...}}.

Common situations: Hand-writing the 'task.record' config for portfolio analysis and omitting the nested dict structure; mixing up the constructor order and passing backtest config where strategy belongs; yaml indentation collapsing the strategy mapping into a string.

Related errors


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