{"record":{"id":"ddb8e7a568b9aa6e","repo":"microsoft/qlib","slug":"multipassportanarecord-require-the-passed-in-strat-ddb8e7","errorCode":null,"errorMessage":"MultiPassPortAnaRecord require the passed in strategy to have signal as a parameter","messagePattern":"MultiPassPortAnaRecord require the passed in strategy to have signal as a parameter","errorType":"exception","errorClass":"QlibException","httpStatus":null,"severity":"error","filePath":"qlib/workflow/record_temp.py","lineNumber":615,"sourceCode":"        ----------\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\n        self.strategy_config = deepcopy_basic_type(self.original_strategy)\n        self.strategy_config[\"kwargs\"][\"signal\"] = pred_df","sourceCodeStart":597,"sourceCodeEnd":633,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/workflow/record_temp.py#L597-L633","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add 'signal' to the strategy kwargs, typically the <PRED> placeholder or a prediction object: kwargs: {signal: <PRED>, topk: 50, n_drop: 5}","Copy a known-good MultiPassPortAnaRecord example config from qlib's examples directory and diff your yaml against it","If your strategy has no signal slot, multi-pass shuffling is meaningless — use PortAnaRecord instead"],"exampleFix":"# before\nstrategy = {\n    'class': 'TopkDropoutStrategy',\n    'module_path': 'qlib.contrib.strategy.signal_strategy',\n    'kwargs': {'topk': 50, 'n_drop': 5},  # no 'signal' -> QlibException\n}\n\n# after\nstrategy = {\n    'class': 'TopkDropoutStrategy',\n    'module_path': 'qlib.contrib.strategy.signal_strategy',\n    'kwargs': {'signal': '<PRED>', 'topk': 50, 'n_drop': 5},\n}","handlingStrategy":"validation","validationCode":"def validate_strategy_signal(config: dict):\n    strategy = config.get('strategy')\n    assert isinstance(strategy, dict), 'strategy must be a dict'\n    kwargs = strategy.get('kwargs', {})\n    if 'signal' not in kwargs:\n        raise KeyError(\"strategy['kwargs'] must include 'signal' (use '<PRED>' placeholder)\")\n    return config","typeGuard":"def has_signal_kwarg(strategy) -> bool:\n    return isinstance(strategy, dict) and 'signal' in strategy.get('kwargs', {})","tryCatchPattern":"try:\n    MultiPassPortAnaRecord(recorder=rec, config=config)\nexcept QlibException as e:\n    if 'signal as a parameter' in str(e):\n        config['strategy']['kwargs']['signal'] = '<PRED>'\n        MultiPassPortAnaRecord(recorder=rec, config=config)\n    else:\n        raise","preventionTips":["Use the <PRED> placeholder in strategy kwargs in workflow yaml","Diff against a known-good MultiPassPortAnaRecord example before running","If the strategy has no signal input, use plain PortAnaRecord instead"],"tags":["qlib","record-temp","portfolio-analysis","config"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}