因子评测 Evaluation 设计

挖出的因子(T×C) → 两部分评测:① IC 类统计指标(纯统计,判断有无预测力)+ ② 回测(能否扣成本后赚钱)。本页定义目标、标签、全部公式与设计细节。
输入:因子值 factor(T×C,行=10s窗口 time,列=股票)+ K线 close(T×C,来自 L1 dynamic/output/kline-freq10s/)。 评测在 T×C 矩阵上做,与因子怎么来的无关。无论因子由多少个 sub因子/字段经 ops 组合而来,最终拿去算 IC/回测的都是一个 T×C。

因子输入:两种模式(开关,见 eval/factor_loader.py

mode输入说明
parquet已算好的因子 T×C parquet直接测评。最快
exprops 表达式树(JSON) + 变量池目录现场求值:调 ops 把多变量表达式算成 T×C 再测评。变量叶子按名从变量池(sub因子/字段/K线 parquet)读
表达式树形如 {op:[arg1,arg2,...]}(op∈ops.REGISTRY,叶子=变量名/标量),例: {"cs_rank":[{"ts_decay_linear":[{"sub":["big_buy_amt","big_sell_amt"]},6]}]}现场算的因子若被「调用方」accept,调 factor_loader.save_accepted() 落盘成 parquet(缓存),下次走 parquet 模式。 accept 判定由调用方负责,评测只提供保存接口。

一、标签:预测目标

定义

fwd_ret[t] = close[t + H] / close[t + OFFSET] − 1 # 默认 OFFSET=60(未来10分钟), H=180(未来30分钟) # 即「未来第10分钟 → 第30分钟」这 20 分钟的收益
t (决策点) 因子用 ≤t 信息 t+60 (+10min) t+180 (+30min) 标签 = 这 20 分钟收益 跳过的 10min (避开即时噪声)

设计要点(已确认)

约定
A. 价格来源close[t+60]close[t+180] 都是按 win_end 从 L1 K线表索引到的窗口收盘价(L1 已有)。
B. 不跨日标签按 (code, 交易日) 内 shift;若 t+180 超出当日连续竞价最后一窗(14:57),该样本无标签、丢弃。
每段连续竞价(上午720窗/下午702窗)尾部 180 窗无标签。暂定如此,未来可考虑跨段/跨日延展(见待办)。
无前视因子用 ≤t 信息,标签用 [t+60, t+180] 未来信息 → 干净无前视。
配置化OFFSETH 写进 config,可调(如改成 0→180 即标准未来30min)。
⚠️ close 偏误(已知问题,后续处理):close=窗口最后一笔成交价,成交必落在买一/卖一上,含半个买卖价差, 其正负随"最后一笔是主动买还是主动卖"跳动(bid-ask bounce),人为制造反转相关。 实测一个反转因子对 close 标签 RankIC 高达 0.067、ICIR 0.67、胜率 76%——这种偏高很可能是 bounce 而非真信号,回测里被价差吃掉、赚不到
注意:换 VWAP(全窗成交量加权均价)能减弱 bounce,但也非零偏差——买卖不均衡时仍残留、且残留与订单流(因子)系统相关,且"时段均价≠时点价";最干净的 midprice 需盘口数据(L1 未存)。 本设计先用 close 跑通;标签价做成配置(close/vwap/next_open),靠「多标签对照 + 回测验真」识别偏误,具体后续再定(见待办)。

二、IC 类指标(纯统计,不交易)

目标:快速判断因子有没有预测力、多强稳不稳。逐个给公式。

1) 截面 IC(每个时刻一个)

IC_t = corr( factor[t, :] , fwd_ret[t, :] ) # Pearson,跨股票截面 RankIC_t = corr( rank(factor[t,:]) , rank(fwd_ret[t,:]) ) # Spearman,抗异常值,主用
每个时刻 t,把全市场股票的 (因子值, 未来收益) 两列做相关。当时刻有效股票数 < 阈值(如20)则跳过。NaN 股先剔除。

2) IC 序列的汇总统计

指标公式含义
IC 均值mean(IC_t) over t整体预测力 & 方向
IC 标准差std(IC_t)预测力稳定性
ICIR(主指标)mean(IC) / std(IC)信噪比;对采样频率不敏感,选因子首看它
IC 胜率#{IC_t > 0} / T方向稳定性(如 76% = 多数时刻同向)
RankICIRmean(RankIC)/std(RankIC)RankIC 版信噪比

3) 显著性 t 值(必须处理重叠)

⚠️ 重叠 overlapping:10s 取样 + 30min 标签 → 相邻样本标签 99% 重合,样本不独立。 naive t值 = ICIR×√T 会被严重虚高(夸大约 √(H/stride) 倍)。不是前视,是样本独立性问题。
naive_t = ICIR × √T # ❌ 虚高,不可信 # ✅ Newey-West 修正(lag = H = 180),吸收重叠自相关: SE_NW = √( [ γ₀ + 2·Σ_{k=1}^{L}(1 − k/(L+1))·γ_k ] / T ) t_NW = mean(IC) / SE_NW γ_k = IC序列的 k 阶自协方差, L = 180
结论(已确认方案)主看 ICIR + 胜率(对重叠不敏感),t值用 Newey-West(lag=180) 报,不信 naive t值
📊 真实例子(IC):反转因子(=−过去30min收益) vs 标签 close[t+180]/close[t+60]−1, 前400股×39天,min_stocks=30:
RankIC 均值 = 0.0219 std = 0.1009 ICIR = 0.217 胜率 = 59.9% T = 41418 # 算法:每个时刻 t 取全市场 (factor, y) 两列 → Spearman相关 → 得 IC 序列 → 求均值/std/ICIR/胜率
对比:同因子若用「未来30min(offset=0)」标签,ICIR 高达 0.67、胜率 76%(疑似 close 偏误虚高); 换成 offset=60 的中段标签后回落到 0.22——标签设计显著影响 IC,这也是为何要多标签对照。

4)(可选)分位 IC / 单调性

把因子分 N 层,看各层未来收益是否单调——是 IC 之外的稳健性佐证(和回测分层呼应,见下)。

三、回测(Backtest)

IC 只说"相关",回测说"扣成本后能不能赚钱"。已确认方案如下,每项给公式 + 真实例子。
⚠️ 先分清两个时间参数(重要,否则易混):
· 标签 horizon(=180):评测/预测用——「因子预测未来多久的收益」,只用于算 IC 和给回测打分。
· 调仓周期 rebalance(=180):交易用——「回测里多久换一次仓」。
两者解耦,可不相等。当前都设 180(30分钟),即「用因子预测未来30min收益,且每30min调一次仓」,最直观。

1) 数据切分 train / validation / test(配置)

按时间顺序切,不打乱(金融时序不能随机切,否则未来信息泄漏到训练)。train 调参、val 选因子、test 只跑一次看真实表现。
train: [start, t1) validation: [t1, t2) test: [t2, end) # 按日期顺序,配置给 t1/t2

2) 组合形式(两个都做)

(a) 分层多空(看因子单调有效性,学术金标准):
每个调仓点 t:按因子值把股票排序均分 N 层(默认 N=10),层内等权。 层收益 R_g(t) = mean( ret_持有期 | 股票∈层g ) 多空收益 R_LS(t) = R_顶层(t) − R_底层(t) # 好因子应:层0→层N-1 收益单调递增,多空 R_LS 持续为正
📊 真实例子(分层,反转因子,10层,前400股×39天),各层平均未来收益(每窗,bp):
层0(因子最低) −4.17 | 层1 −1.96 | 层4 −1.48 | 层9(最高) −0.70 # 大体单调↑ 多空(层9 − 层0) = +3.48 bp / 每窗 # 顶层比底层每窗多赚3.48bp,因子有单调性
(b) Top-K 多头(A股做空难,更接近实盘):
每个调仓点:买因子最高的 K 只(默认 K=50),等权,持有到下一调仓点。 组合收益 R(t) = mean( ret_持有期 | 股票∈TopK ) # top_k_drop_n 缓冲(降换手):drop_n>0 时不每期全换—— 持仓中跌出当前 Top-K 的才换出,且每次最多换 drop_n 只(跌得最狠的优先), 再从当前 Top-K 中未持仓者按因子从高到低补满到 K。drop_n=0 即原始 Top-K(每期全选)。
📊 真实例子(本系统实测):同一因子,drop_n=0 换手 97.9% → drop_n=5 换手 78.9% → drop_n=2 换手 72.1%。 drop_n 越小换手越低,高频因子可借此压成本。

3) 换手率(决定成本,关键)

单边换手 turnover(t) = |持仓_t △ 持仓_{t−1}| / (2 × 持仓数) # △=对称差,换掉的比例
📊 真实例子(换手率):top层组合,rebalance=180(每30min):
平均单边换手 ≈ 84.5% # 每30分钟,top层股票池有八成被换掉 → 高频因子换手极高

4) 成本与净收益(买卖分开,按 A 股实际费率;test 同时给「有费/无费」)

A 股买卖费率不对称:印花税只在卖出时收(0.05%),所以卖比买贵。成本按买、卖两侧分开配置。
费项买入卖出开关
佣金5 bp15 bp必含(买卖不对称)
印花税5 bp(仅卖出)stamp_tax_on
过户费~0.1 bp~0.1 bptransfer_fee_on
合计(默认全开)买入 ≈ 5.1 bp卖出 ≈ 20.1 bp
佣金口径 {buy: 0.0005, sell: 0.0015}(买5bp/卖15bp,A股买卖不对称、始终计入);印花税、过户费在佣金之上另加,各有开关(stamp_tax_on/transfer_fee_on),便于敏感性分析/政策变动一键切换。
每次调仓:换手 turnover(t) 比例的旧仓卖出、等比例新仓买入 扣费净收益 R_net(t) = R_gross(t) − turnover(t) × (cost_buy_bps + cost_sell_bps) 买卖分开:卖旧仓收 sell 费,买新仓收 buy 费
📊 真实例子(买卖分开扣费,反转因子多空,换手84.5%)
买入 5.1bp(佣金5+过户0.1) + 卖出 20.1bp(佣金15+印花税5+过户0.1) → 每次调仓成本 84.5%×(5.1+20.1) = 21.3 bp
多空毛收益 +4.19 bp/调仓 − 成本 21.3bp = 净 −17.1 bp
由赚转大亏。(A股佣金买卖不对称:卖出15bp远高于买入5bp,加上卖出还收印花税,卖出侧成本是大头。)

5) 可成交约束(配置开关)

开关 tradable_filter:开启时,停牌前可正常交易,停牌当刻起不可买卖(持仓冻结到复牌)。 关闭时理想化(假设都能成交)。建议先关看上限,再开看真实。涨跌停过滤同理,后续可加。

6) 回测三大主指标:AR / IR / MDD

回测的"黄金三角"——收益、风险调整收益、尾部风险。以多空(或Top-K)组合的每期净收益序列 R(t) 为输入。
NAV(t) = Π(1 + R(τ)) , τ≤t # 累计净值曲线 AR(年化收益) = (1 + mean(R))^freq − 1 # freq=年化调仓频次≈2000(每天~8调仓×250天) IR(信息比率) = mean(R) / std(R) × √freq # 单位风险收益(多空收益即超额,IR≈夏普) MDD(最大回撤) = max_t [ (峰值 − NAV(t)) / 峰值 ] # 峰=max(NAV[≤t]),历史最坏回撤
指标含义越…越好
AR一年能赚多少越大越好
IR每承担一单位波动赚多少(稳不稳)越大越好(>1 不错,>2 强)
MDD历史最坏从高点亏到多少越小(接近0)越好
📊 真实例子(AR/IR/MDD —— 无费 vs 扣费,反转因子多空,231个调仓期)
无费 扣费(换手84.5%,买5.1+卖20.1bp) AR +131.1% −96.7% # 年化收益 IR 6.36 −25.9 # 信息比率 MDD −2.07% 深度持续回撤 # 净收益转负→单调回撤
无费看 AR131%、IR6.36 像神因子,按 A 股佣金(买5/卖15bp)+印花税扣费后 AR −97%。这就是为什么 test 必须同时报有费/无费。
⚠️ 注意 IR=6.36 这种离谱高值,部分来自高频调仓的重叠/自相关使年化频次虚高(与 IC 的 NW 修正同源问题);解读 IR 时同样要打折,别只看绝对值。

7) 其他汇总指标(辅助)

指标公式
累计收益NAV_end − 1
CalmarAR / |MDD|(收益回撤比)
分层单调性层收益从低到高是否递增(秩相关)
换手率/胜率见 §三3 / 调仓期 R(t)>0 占比

四、配置项与参数含义(写进 config)

参数默认含义
label —— 标签
offset60标签起点偏移(10s窗)。60=未来10分钟
horizon180标签终点(10s窗)。180=未来30分钟。标签=close[t+horizon]/close[t+offset]−1
priceclose取价字段,配置可读 open/close(vwap/next_open 待加,剥离 bounce)
cross_dayfalse标签是否跨日。false=t+horizon 越出当日则丢弃
ic —— IC 评测
methodrankrank(Spearman,主) / pearson
min_stocks30截面有效股票数下限,低于则该时刻不算 IC(经验值:<30 截面相关不稳)
neutralizenonenone(裸IC) / industry+size(待实现)
nw_lag180Newey-West 滞后阶,=horizon,修正重叠
sample_stride1IC 取样间隔(窗)。1=每窗都算;可设180做不重叠对照
backtest —— 回测
rebalance180调仓周期(10s窗),与标签horizon解耦。180=每30分钟调一次仓
n_groups10分层多空的层数
top_k50Top-K 多头的只数
top_k_drop_n0Top-K缓冲:每次最多换出n只(仅换跌出Top-K者),降换手;0=不缓冲
weightingequal层内/组合内加权。equal=等权(因子加权待加)
commission.buy0.0005买入佣金(=5bp),始终计入
commission.sell0.0015卖出佣金(=15bp),A股买卖不对称,始终计入
stamp_tax_bps5.0印花税(bp),仅卖出,在佣金之上另加。受 stamp_tax_on 控制
stamp_tax_ontrue印花税开关
transfer_fee_bps0.1过户费(bp),买卖双边,在佣金之上另加。受 transfer_fee_on 控制
transfer_fee_ontrue过户费开关
tradable_filterfalse可成交开关。true=停牌后不可交易(持仓冻结);false=理想化
split.t1 / .t2train/val/test 分界日期。[start,t1)训练 [t1,t2)验证 [t2,end)测试
eval: label: {offset: 60, horizon: 180, price: close, cross_day: false} ic: {method: rank, min_stocks: 30, neutralize: none, nw_lag: 180, sample_stride: 1} backtest: rebalance: 180 n_groups: 10 top_k: 50 top_k_drop_n: 0 # Top-K缓冲:每次最多换出n只(仅换跌出Top-K者),降换手;0=不缓冲 weighting: equal commission: {buy: 0.0005, sell: 0.0015} # 佣金:买5bp/卖15bp,A股不对称,始终计入 stamp_tax_bps: 5.0 # 印花税,仅卖出,佣金之上另加 stamp_tax_on: true # 印花税开关 transfer_fee_bps: 0.1 # 过户费,双边,佣金之上另加 transfer_fee_on: true # 过户费开关 # 买入侧 = 佣金buy + (过户费 if on); 卖出侧 = 佣金sell + (印花税 if on) + (过户费 if on) tradable_filter: false split: {t1: '20231001', t2: '20240601'} # 示例日期

五、待办 / 可扩展(写明,暂不实现)

0. 多个因子「基于模型」的联合测评(TODO,先不管):当前为单因子测评(批量多因子=逐个调用)。 未来用模型评估多个因子组合的增量价值/正交性/相互冗余等——超出单因子 IC/回测范畴,单列。
1. 中性化 IC:当前 裸 IC。后续可对行业/市值截面回归取残差再算 IC(需行业/市值数据,L1 现无)。
2. 标签价格剥离偏误:close 有 bid-ask bounce(见一、警告);后续加 VWAP / 下一窗 open 标签对照。
3. 标签跨段/跨日:当前 t+horizon 越当日则丢弃;未来可跨午休段/跨日(注意隔夜跳空)。
4. 涨跌停过滤、因子值加权、冲击成本模型:可成交约束目前只做停牌开关,其余后续加。
评测在 T×C 上做 · 标签 close[t+180]/close[t+60]−1 · IC主看ICIR(NW修正重叠) · 回测主指标 AR/IR/MDD(报有费/无费) · 裸IC(可扩展中性化)