{"record":{"id":"8700e25a24aa4eb5","repo":"microsoft/qlib","slug":"unknown-metric-s-8700e2","errorCode":null,"errorMessage":"unknown metric `%s`","messagePattern":"unknown metric `(.+?)`","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/contrib/model/pytorch_hist.py","lineNumber":176,"sourceCode":"            return self.mse(pred[mask], label[mask])\n\n        raise ValueError(\"unknown loss `%s`\" % self.loss)\n\n    def metric_fn(self, pred, label):\n        mask = torch.isfinite(label)\n\n        if self.metric == \"ic\":\n            x = pred[mask]\n            y = label[mask]\n\n            vx = x - torch.mean(x)\n            vy = y - torch.mean(y)\n            return torch.sum(vx * vy) / (torch.sqrt(torch.sum(vx**2)) * torch.sqrt(torch.sum(vy**2)))\n\n        if self.metric == (\"\", \"loss\"):\n            return -self.loss_fn(pred[mask], label[mask])\n\n        raise ValueError(\"unknown metric `%s`\" % self.metric)\n\n    def get_daily_inter(self, df, shuffle=False):\n        # organize the train data into daily batches\n        daily_count = df.groupby(level=0, group_keys=False).size().values\n        daily_index = np.roll(np.cumsum(daily_count), 1)\n        daily_index[0] = 0\n        if shuffle:\n            # shuffle data\n            daily_shuffle = list(zip(daily_index, daily_count))\n            np.random.shuffle(daily_shuffle)\n            daily_index, daily_count = zip(*daily_shuffle)\n        return daily_index, daily_count\n\n    def train_epoch(self, x_train, y_train, stock_index):\n        stock2concept_matrix = np.load(self.stock2concept)\n        x_train_values = x_train.values\n        y_train_values = np.squeeze(y_train.values)\n        stock_index = stock_index.values","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/contrib/model/pytorch_hist.py#L158-L194","documentation":"Raised by HIST.metric_fn when self.metric is not \"ic\". Note the second branch contains a genuine bug: it tests `self.metric == (\"\", \"loss\")` — a string compared to a tuple, which is always False — so the intended \"\"/\"loss\" aliases never match and also raise this error. Effectively only metric=\"ic\" works; any other value (including \"\" or \"loss\") hits the raise.","triggerScenarios":"HIST(metric=\"\") or HIST(metric=\"loss\") (raises due to the tuple-comparison bug), or any value other than \"ic\". Fires on the first validation batch of fit().","commonSituations":"Setting metric=\"loss\" expecting loss-based early stopping and being surprised it raises; configs copied from other models; users unaware \"ic\" is the only working metric in this class.","solutions":["Set metric=\"ic\" — the only value that works in the shipped code.","If you need loss-based validation, subclass HIST and fix metric_fn: change the comparison to `if self.metric in (\"\", \"loss\"):` and return -self.loss_fn(...).","When filing/patching upstream, note the tuple-equality bug at pytorch_hist.py:172."],"exampleFix":"# before (upstream, buggy)\nif self.metric == (\"\", \"loss\"):  # always False\n    return -self.loss_fn(pred[mask], label[mask])\n\n# after (subclass patch)\nif self.metric in (\"\", \"loss\"):\n    return -self.loss_fn(pred[mask], label[mask])","handlingStrategy":"validation","validationCode":"assert params.get(\"metric\") == \"ic\", \"HIST effectively supports only metric='ic' (the ''/'loss' branch is broken upstream: string == tuple comparison)\"","typeGuard":"def is_supported_metric(metric: str) -> bool:\n    return metric == \"ic\"","tryCatchPattern":"try:\n    model.fit(dataset)\nexcept ValueError as e:\n    if \"unknown metric\" in str(e):\n        params[\"metric\"] = \"ic\"\n        model = HIST(**params)\n        model.fit(dataset)\n    else:\n        raise","preventionTips":["Use metric='ic' with HIST; the ''/'loss' aliases never match due to the upstream `== (\"\", \"loss\")` tuple-comparison bug.","When subclassing, fix the comparison to `in (\"\", \"loss\")` instead of `== (\"\", \"loss\")`."],"tags":["pytorch","qlib","metrics","configuration","upstream-bug"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}