microsoft/qlib · error · ValueError
unknown metric `%s`
Error message
unknown metric `%s`
What it means
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.
Source
Thrown at qlib/contrib/model/pytorch_hist.py:176
return self.mse(pred[mask], label[mask])
raise ValueError("unknown loss `%s`" % self.loss)
def metric_fn(self, pred, label):
mask = torch.isfinite(label)
if self.metric == "ic":
x = pred[mask]
y = label[mask]
vx = x - torch.mean(x)
vy = y - torch.mean(y)
return torch.sum(vx * vy) / (torch.sqrt(torch.sum(vx**2)) * torch.sqrt(torch.sum(vy**2)))
if self.metric == ("", "loss"):
return -self.loss_fn(pred[mask], label[mask])
raise ValueError("unknown metric `%s`" % self.metric)
def get_daily_inter(self, df, shuffle=False):
# organize the train data into daily batches
daily_count = df.groupby(level=0, group_keys=False).size().values
daily_index = np.roll(np.cumsum(daily_count), 1)
daily_index[0] = 0
if shuffle:
# shuffle data
daily_shuffle = list(zip(daily_index, daily_count))
np.random.shuffle(daily_shuffle)
daily_index, daily_count = zip(*daily_shuffle)
return daily_index, daily_count
def train_epoch(self, x_train, y_train, stock_index):
stock2concept_matrix = np.load(self.stock2concept)
x_train_values = x_train.values
y_train_values = np.squeeze(y_train.values)
stock_index = stock_index.valuesView on GitHub (pinned to 79633dd950)
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.
Example fix
# before (upstream, buggy)
if self.metric == ("", "loss"): # always False
return -self.loss_fn(pred[mask], label[mask])
# after (subclass patch)
if self.metric in ("", "loss"):
return -self.loss_fn(pred[mask], label[mask]) Defensive patterns
Strategy: validation
Validate before calling
assert params.get("metric") == "ic", "HIST effectively supports only metric='ic' (the ''/'loss' branch is broken upstream: string == tuple comparison)" Type guard
def is_supported_metric(metric: str) -> bool:
return metric == "ic" Try / catch
try:
model.fit(dataset)
except ValueError as e:
if "unknown metric" in str(e):
params["metric"] = "ic"
model = HIST(**params)
model.fit(dataset)
else:
raise Prevention
- 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")`.
When it happens
Trigger: 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().
Common situations: 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.
Related errors
AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15).
Data as JSON: /api/errors/8700e25a24aa4eb5.
Report an issue: GitHub.