因子评测 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 | 直接测评。最快 |
expr | ops 表达式树(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 分钟的收益
设计要点(已确认)
| 点 | 约定 |
|---|---|
| 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] 未来信息 → 干净无前视。 |
| 配置化 | OFFSET 与 H 写进 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),靠「多标签对照 + 回测验真」识别偏误,具体后续再定(见待办)。
注意:换 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% = 多数时刻同向) |
| RankICIR | mean(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调一次仓」,最直观。
· 标签 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):
(b) Top-K 多头(A股做空难,更接近实盘):
层0(因子最低) −4.17 | 层1 −1.96 | 层4 −1.48 | 层9(最高) −0.70 # 大体单调↑
多空(层9 − 层0) = +3.48 bp / 每窗 # 顶层比底层每窗多赚3.48bp,因子有单调性
每个调仓点:买因子最高的 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 bp | 15 bp | 必含(买卖不对称) |
| 印花税 | 无 | 5 bp(仅卖出) | stamp_tax_on |
| 过户费 | ~0.1 bp | ~0.1 bp | transfer_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) → 每次调仓成本
多空毛收益 +4.19 bp/调仓 − 成本 21.3bp = 净 −17.1 bp。
由赚转大亏。(A股佣金买卖不对称:卖出15bp远高于买入5bp,加上卖出还收印花税,卖出侧成本是大头。)
买入 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个调仓期):
⚠️ 注意 IR=6.36 这种离谱高值,部分来自高频调仓的重叠/自相关使年化频次虚高(与 IC 的 NW 修正同源问题);解读 IR 时同样要打折,别只看绝对值。
无费 扣费(换手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 |
| Calmar | AR / |MDD|(收益回撤比) |
| 分层单调性 | 层收益从低到高是否递增(秩相关) |
| 换手率/胜率 | 见 §三3 / 调仓期 R(t)>0 占比 |
四、配置项与参数含义(写进 config)
| 参数 | 默认 | 含义 |
|---|---|---|
| label —— 标签 | ||
offset | 60 | 标签起点偏移(10s窗)。60=未来10分钟 |
horizon | 180 | 标签终点(10s窗)。180=未来30分钟。标签=close[t+horizon]/close[t+offset]−1 |
price | close | 取价字段,配置可读 open/close(vwap/next_open 待加,剥离 bounce) |
cross_day | false | 标签是否跨日。false=t+horizon 越出当日则丢弃 |
| ic —— IC 评测 | ||
method | rank | rank(Spearman,主) / pearson |
min_stocks | 30 | 截面有效股票数下限,低于则该时刻不算 IC(经验值:<30 截面相关不稳) |
neutralize | none | none(裸IC) / industry+size(待实现) |
nw_lag | 180 | Newey-West 滞后阶,=horizon,修正重叠 |
sample_stride | 1 | IC 取样间隔(窗)。1=每窗都算;可设180做不重叠对照 |
| backtest —— 回测 | ||
rebalance | 180 | 调仓周期(10s窗),与标签horizon解耦。180=每30分钟调一次仓 |
n_groups | 10 | 分层多空的层数 |
top_k | 50 | Top-K 多头的只数 |
top_k_drop_n | 0 | Top-K缓冲:每次最多换出n只(仅换跌出Top-K者),降换手;0=不缓冲 |
weighting | equal | 层内/组合内加权。equal=等权(因子加权待加) |
commission.buy | 0.0005 | 买入佣金(=5bp),始终计入 |
commission.sell | 0.0015 | 卖出佣金(=15bp),A股买卖不对称,始终计入 |
stamp_tax_bps | 5.0 | 印花税(bp),仅卖出,在佣金之上另加。受 stamp_tax_on 控制 |
stamp_tax_on | true | 印花税开关 |
transfer_fee_bps | 0.1 | 过户费(bp),买卖双边,在佣金之上另加。受 transfer_fee_on 控制 |
transfer_fee_on | true | 过户费开关 |
tradable_filter | false | 可成交开关。true=停牌后不可交易(持仓冻结);false=理想化 |
split.t1 / .t2 | — | train/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(可扩展中性化)