#1451·RD-Agent

Bandit scheduler reward: ARR term never fires (trailing space in metric key), and sharpe is derived from it

Author: evgenii-bondCreated Aug 22, 2026Updated Sep 8, 2026

Checked against main at 6762f84f9bc0.

Both problems are in rdagent/scenarios/qlib/proposal/bandit.py, in extract_metrics_from_experiment. action_selection defaults to "bandit" in QuantBasePropSetting, so this is the default path for rdagent fin_quant.

1. Trailing space in the ARR key, so the term never fires

python
arr = result.get("1day.excess_return_with_cost.annualized_return ", 0.0)

The key qlib actually logs has no trailing space:

  • qlib/contrib/evaluate.py::risk_analysis returns {"annualized_return": ..., "information_ratio": ..., "max_drawdown": ...}
  • PortAnaRecord logs them as f"{_analysis_freq}.{k}", giving 1day.excess_return_with_cost.annualized_return
  • read_exp_res.py writes pd.Series(recorder.list_metrics()) to qlib_res.csv
  • QlibFBWorkspace.execute reads that CSV with index_col=0, so exp.result carries the index verbatim

The same key without the space is used elsewhere in this repo: IMPORTANT_METRICS in rdagent/scenarios/qlib/developer/feedback.py, and rdagent/log/ui/app.py:58.

Effect: arr is always the 0.0 default, so the ARR term (weight 0.25) contributes nothing.

2. sharpe inherits that zero, and is not a Sharpe ratio

python
sharpe = arr / -mdd if mdd != 0 else 0.0

With arr pinned to 0.0 this term (weight 0.20) is also always 0.0.

Separately from the typo: no volatility enters the expression. It is annualized return over max drawdown, i.e. a Calmar-type ratio, and it is a monotone function of arr, which the reward vector already contains as its own term.

Together the two are 0.45 of EnvController.weights = (0.1, 0.1, 0.05, 0.05, 0.25, 0.15, 0.1, 0.2), so in practice the scheduler is driven by IC / ICIR / Rank IC / Rank ICIR / IR / -MDD only.

3. Why it does not surface

result.get(key, 0.0) defaults silently, and extract_metrics_from_experiment wraps the whole body in except Exception: return Metrics(). A mistyped or missing key is therefore indistinguishable from a genuinely zero metric.

Suggested fix

Happy to send a PR if the direction is agreed:

  • share one set of metric-key constants between bandit.py and feedback.py;
  • treat a missing key as an error, or at least log it, rather than defaulting to 0.0;
  • decide what the eighth feature should be — a real Sharpe from the qlib metrics, or the return/max-drawdown ratio kept under a name that says so.