""" ResearchAgent — 因子发现与评估。 遍历注册因子,计算 IC/IC_IR/分层收益,输出 Top 因子。 """ import numpy as np import pandas as pd from agents.base import BaseAgent class ResearchAgent(BaseAgent): """因子发现 Agent。""" name = "Research" description = "因子发现与评估" def execute( self, factor_names: list[str] | None = None, ts_codes: list[str] | None = None, lookahead: int = 5, top_n: int = 10, ) -> dict: """ 遍历因子,计算评估指标。 参数: factor_names: 待评估因子列表,None=全部注册因子 ts_codes: 股票列表,None=DataManager 全部 lookahead: 前向收益窗口 top_n: 返回 Top N 因子 返回: {"top_factors": [...], "all_results": DataFrame, "evaluated": int} """ from factors.registry import list_factors, get_factor if factor_names is None: # 只评估技术+基本面因子(情绪因子需要额外数据) categories = ["动量", "RSI", "MACD", "量价", "布林", "ATR", "均线", "波动率", "换手率", "振幅", "基本面"] factor_names = [] for cat in categories: factor_names.extend(list_factors(cat)) if ts_codes is None: stocks = self.dm.get_stock_list() # 默认选 50 只代表股(前50只 + 避免API过载) ts_codes = list(stocks.index[:50]) if self.dm else ["000001.SZ", "600519.SH"] self.log(f"评估 {len(factor_names)} 个因子 × {len(ts_codes)} 只股票") results = [] for fn in factor_names: metrics = self._evaluate_factor(fn, ts_codes, lookahead) if metrics: results.append(metrics) self.log(f" {fn}: IC={metrics.get('ic_mean', 0):.4f}" if metrics else f" {fn}: SKIP") if not results: return {"top_factors": [], "all_results": pd.DataFrame(), "evaluated": 0} df = pd.DataFrame(results).sort_values("score", ascending=False) top = df.head(top_n) return { "top_factors": top.to_dict("records"), "all_results": df, "evaluated": len(results), } def _evaluate_factor( self, factor_name: str, ts_codes: list[str], lookahead: int ) -> dict | None: """对单个因子计算 IC/IC_IR。""" from factors.registry import get_factor from models.features import FeatureEngine try: factor = get_factor(factor_name) except KeyError: return None fe = FeatureEngine(lookahead=lookahead, label_type="regression") ics = [] long_rets = [] short_rets = [] success = 0 for ts_code in ts_codes: try: daily = self.dm.get_daily(ts_code) if daily is None or daily.empty: continue daily = daily.set_index("trade_date").sort_index() factor_df = self.fe.compute(ts_code, [factor]) if factor_df is None or factor_df.empty: continue X, y = fe.build(factor_df, daily, fit=True) if X.empty or y.empty or factor_name not in X.columns: continue fv = X[factor_name].dropna() yv = y.loc[fv.index] if len(fv) < 30: continue ic = fv.corr(yv, method="spearman") ics.append(ic) # 分层收益 top_idx = fv.nlargest(int(len(fv) * 0.2)).index bot_idx = fv.nsmallest(int(len(fv) * 0.2)).index long_rets.append(yv.loc[top_idx.intersection(yv.index)].mean()) short_rets.append(yv.loc[bot_idx.intersection(yv.index)].mean()) success += 1 except Exception: continue if success < 3: return None ic_series = pd.Series(ics) ic_mean = ic_series.mean() ic_std = ic_series.std() icir = ic_mean / ic_std if ic_std > 0 else 0 # 综合得分 = IC × IC_IR 加权 score = abs(ic_mean) * max(icir, 0) return { "name": factor_name, "ic_mean": round(float(ic_mean), 4), "ic_std": round(float(ic_std), 4), "icir": round(float(icir), 4), "long_ret": round(float(np.mean(long_rets)), 2) if long_rets else 0, "short_ret": round(float(np.mean(short_rets)), 2) if short_rets else 0, "stocks_evaluated": success, "score": round(float(score), 4), }