{"record":{"id":"330c427da5ca9026","repo":"microsoft/qlib","slug":"unknown-metric-s-330c42","errorCode":null,"errorMessage":"unknown metric `%s`","messagePattern":"unknown metric `(.+?)`","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/contrib/model/pytorch_sfm.py","lineNumber":434,"sourceCode":"    def mse(self, pred, label):\n        loss = (pred - label) ** 2\n        return torch.mean(loss)\n\n    def loss_fn(self, pred, label):\n        mask = ~torch.isnan(label)\n\n        if self.loss == \"mse\":\n            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 in (\"\", \"loss\"):\n            return -self.loss_fn(pred[mask], label[mask])\n\n        raise ValueError(\"unknown metric `%s`\" % self.metric)\n\n    def predict(self, dataset: DatasetH, segment: Union[Text, slice] = \"test\"):\n        if not self.fitted:\n            raise ValueError(\"model is not fitted yet!\")\n\n        x_test = dataset.prepare(segment, col_set=\"feature\", data_key=DataHandlerLP.DK_I)\n        index = x_test.index\n        self.sfm_model.eval()\n        x_values = x_test.values\n        sample_num = x_values.shape[0]\n        preds = []\n\n        for begin in range(sample_num)[:: self.batch_size]:\n            if sample_num - begin < self.batch_size:\n                end = sample_num\n            else:\n                end = begin + self.batch_size\n","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/contrib/model/pytorch_sfm.py#L416-L452","documentation":"Raised by SFM model metric_fn in qlib/contrib/model/pytorch_sfm.py:434 when self.metric is neither '' nor 'loss'. The only implemented early-stopping metric is the negative training loss; passing 'ic', 'auc', etc. raises ValueError at the first validation scoring inside fit().","triggerScenarios":"Passing metric='ic' or any unsupported token in SFM kwargs; the raise occurs inside the fit() evaluation loop, after training has already started for the epoch.","commonSituations":"Workflow configs ported from DNNModel-based examples that use IC; users assuming qlib's analysis metrics are available as training metrics.","solutions":["Use metric: '' (default) or metric: 'loss'.","Subclass and override metric_fn() to implement IC or another custom early-stopping metric."],"exampleFix":"# before\nkwargs:\n  metric: ic\n\n# after\nkwargs:\n  metric: \"\"   # or 'loss'","handlingStrategy":"validation","validationCode":"metric = config.get(\"metric\", \"\")\nassert metric in (\"\", \"loss\"), f\"SFM metric must be '' or 'loss', got {metric!r}\"","typeGuard":"def is_supported_sfm_metric(metric: str) -> bool:\n    return metric in (\"\", \"loss\")","tryCatchPattern":"try:\n    model.fit(dataset)\nexcept ValueError as e:\n    if \"unknown metric\" in str(e):\n        raise ValueError(\"SFM early stopping only tracks the loss; set metric='' or 'loss'\") from e\n    raise","preventionTips":["Leave metric unset in configs unless you know the model's supported set.","Read the model's metric_fn source when porting configs between models."],"tags":["qlib","pytorch","metric","config","early-stopping","sfm"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}