{"record":{"id":"2d83a04fea90b835","repo":"microsoft/qlib","slug":"multipassportanarecord-require-the-passed-in-strat","errorCode":null,"errorMessage":"MultiPassPortAnaRecord require the passed in strategy to be a dict","messagePattern":"MultiPassPortAnaRecord require the passed in strategy to be a dict","errorType":"exception","errorClass":"QlibException","httpStatus":null,"severity":"error","filePath":"qlib/workflow/record_temp.py","lineNumber":613,"sourceCode":"        \"\"\"\n        Parameters\n        ----------\n        recorder : Recorder\n            The recorder used to save the backtest results.\n        pass_num : int\n            The number of backtest passes.\n        shuffle_init_score : bool\n            Whether to shuffle the prediction score of the first backtest date.\n        \"\"\"\n        self.pass_num = pass_num\n        self.shuffle_init_score = shuffle_init_score\n\n        super().__init__(recorder, **kwargs)\n\n        # Save original strategy so that pred df can be replaced in next generate\n        self.original_strategy = deepcopy_basic_type(self.strategy_config)\n        if not isinstance(self.original_strategy, dict):\n            raise QlibException(\"MultiPassPortAnaRecord require the passed in strategy to be a dict\")\n        if \"signal\" not in self.original_strategy.get(\"kwargs\", {}):\n            raise QlibException(\"MultiPassPortAnaRecord require the passed in strategy to have signal as a parameter\")\n\n    def random_init(self):\n        pred_df = self.load(\"pred.pkl\")\n\n        all_pred_dates = pred_df.index.get_level_values(\"datetime\")\n        bt_start_date = pd.to_datetime(self.backtest_config.get(\"start_time\"))\n        if bt_start_date is None:\n            first_bt_pred_date = all_pred_dates.min()\n        else:\n            first_bt_pred_date = all_pred_dates[all_pred_dates >= bt_start_date].min()\n\n        # Shuffle the first backtest date's pred score\n        first_date_score = pred_df.loc[first_bt_pred_date][\"score\"]\n        np.random.shuffle(first_date_score.values)\n\n        # Use shuffled signal as the strategy signal","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/workflow/record_temp.py#L595-L631","documentation":"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.","triggerScenarios":"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: {...}}.","commonSituations":"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.","solutions":["Make the strategy parameter a full dict spec, e.g. {'class': 'TopkDropoutStrategy', 'module_path': 'qlib.contrib.strategy.signal_strategy', 'kwargs': {...}}","Cross-check against a working PortAnaRecord/MultiPassPortAnaRecord example config in qlib's examples (benchmarks/workflows) and fix yaml indentation so strategy stays a mapping","If you genuinely need an object strategy, use plain PortAnaRecord, which does not rewrite the signal between passes"],"exampleFix":"# before\nrecord = MultiPassPortAnaRecord(\n    recorder=rec,\n    config={'strategy': 'TopkDropout', 'backtest': bt},  # string -> QlibException\n)\n\n# after\nrecord = MultiPassPortAnaRecord(\n    recorder=rec,\n    config={\n        'strategy': {\n            'class': 'TopkDropoutStrategy',\n            'module_path': 'qlib.contrib.strategy.signal_strategy',\n            'kwargs': {'signal': <PRED>, 'topk': 50},\n        },\n        'backtest': bt,\n    },\n)","handlingStrategy":"validation","validationCode":"def validate_multipass_config(config: dict):\n    strategy = config.get('strategy')\n    if not isinstance(strategy, dict):\n        raise TypeError(\n            f\"strategy must be a dict spec, got {type(strategy).__name__}; \"\n            \"expected {{'class': ..., 'module_path': ..., 'kwargs': {{...}}}}\"\n        )\n    if 'signal' not in strategy.get('kwargs', {}):\n        raise TypeError('strategy[\"kwargs\"] must contain \"signal\"')\n    return config","typeGuard":"from typing import Any, Dict\n\ndef is_strategy_dict_spec(strategy: Any) -> bool:\n    return (\n        isinstance(strategy, dict)\n        and isinstance(strategy.get('kwargs'), dict)\n        and 'signal' in strategy['kwargs']\n    )","tryCatchPattern":"try:\n    MultiPassPortAnaRecord(recorder=rec, config=config)\nexcept QlibException as e:\n    raise ValueError(f'invalid multi-pass port analysis config: {e}; see {validate_multipass_config.__name__}') from e","preventionTips":["Validate the strategy block before constructing the record; fail at config-load time","Start from a working example config from qlib/examples and edit values only","Watch yaml indentation: strategy must stay a nested mapping, not a scalar"],"tags":["qlib","record-temp","portfolio-analysis","config"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}