因子研究综合实验
本章目录
学完后,你应能独立完成
- 运行预先规定的多机制合成实验
- 解释开发选择、静态最终训练和评价覆盖
- 交付一页研究报告并说明真实市场迁移限制
前置建议:特征工程与模型增量 · GBDT、XGBoost 与 LightGBM
改变一个条件,观察发生什么
浏览器内即时教学计算;Python 实验结果另列于代码区。图形与数值一起核对。
本案例的具体研究问题
给定6个已知因子,对同日股票未来五日开盘到开盘收益预测并排序。数据为180个示意工作日×24股;因子具有AR(.6)持久性,一列与第一列高度相关。价格由过去已知因子的条件漂移及厚尾、共同噪声递推。没有读取本地行情。
四种机制分别是空信号、纯线性、线性加交互、后期线性关系改变。它们用于理解与排错,不代表现实市场规律。数据的状态转折是生成器设定,研究算法不能读取“机制名称”作为特征。
先锁定候选与选择政策
开发验证日期位置[80,105)、[105,130);最终测试[130,174),尾部六个无完整五日标签的日期不评分。每个开发折在前一信号日收盘训练,并筛成熟标签。
Ridge候选λ=.01/1,折内StandardScaler;HistGBDT候选15/40轮、7叶、min_samples_leaf=25、学习率.08,关闭内部早停。每模型2候选×2折,每机制共8个开发拟合。平均逐日Rank IC选参数,最终测试前各重训一次,不根据测试再改候选。
合理基线与评价集合
单因子预先用x0;等权预先用x0/x1/x2,按训练均值与尺度处理,再在训练内校准成收益单位。Ridge与GBDT使用全部6列。信息集合法,明确特征子集,四模型在完全相同的最终股票日期与标签上评价。
这不是给复杂模型刻意找弱对照:单因子可能表现最好,等权也可能没有增量。更完整的现实研究还需比较经过合理筛选的等权、显式交互的Ridge和不同经济假设,且都算选择压力。
页面结果来自本地真实运行
下面表格来自本轮Python脚本运行后的保存记录,seed固定42。模型评分是合成实验上的结果,数值不是手工填写。空机制的单次有限样本IC未必恰好零;交互机制也不保证GBDT在任意样本中获胜。
检查MSE、IC、Rank IC、评分覆盖和测试日期数。展示不同机制的全部结果,避免挑最好的一次来证明通用模型排名。算法只看到训练数据,机制切换在教学界面用于查看不同实验。
最终结果如何解释而不夸大
先描述固定政策和固定集合的结果,再提出替代解释。Δ_t逐日配对后可做依赖稳健分析;还要看测试长度、行业/规模、状态、随机性和搜索次数。本案例最终44日,只够作为流程演示。
没有可交易的持仓净值:五日诊断值不能直接日频连乘。费用、停牌、价格限制、历史成分、复权、交易与风险约束尚未落地,不能从这组IC宣布盈利。失败的机制或模型也要进入报告。
迁移到你的股票研究
先定义本地数据接口:历史股票资格、OHLCV及复权、实际可得时点、交易日历、标签与可买卖标记。重建原始小样本手核,再运行预先约定的单因子、等权、线性与非线性对照。
一页报告按“假设—数据—时间线—基线—验证—结果—失败与限制—下一实验”组织。新增特征的理由应来自训练期或独立依据;新的最终结论需要新的未使用样本。求职讲述要说明个人判断与实现,不只背库参数。
从算法到一次真实运行
代码在本地 Python 中执行,HTML 内展示参考实现和本轮运行结果。请先独立完成练习,再展开完整参考文件。
def make_panel(seed=42, T=180, N=24, k=5, mechanism="linear"):
"""Illustrative weekdays, 6 AR factors, future open-to-open k-day label."""
if mechanism not in {"null","linear","interaction","regime"}:
raise ValueError("Unknown mechanism")
rng=np.random.default_rng(seed)
dates=pd.bdate_range("2020-01-02",periods=T)
factors=np.empty((T,N,6)); factors[0]=rng.normal(size=(N,6))
for t in range(1,T):
factors[t]=.6*factors[t-1]+.8*rng.normal(size=(N,6))
factors[:,:,3]=.9*factors[:,:,0]+.15*rng.normal(size=(T,N))
prices=np.full((T,N),100.)
for t in range(T-1):
x=factors[max(0,t-1)]
signal=np.zeros(N)
if mechanism!="null": signal=.0015*x[:,0]
if mechanism in {"interaction","regime"}: signal += .0015*x[:,1]*x[:,2]
if mechanism=="regime" and t>=int(.65*T): signal -= .002*x[:,0]
noise=.006*rng.standard_t(5,N)/np.sqrt(5/3)+rng.normal(0,.002)
prices[t+1]=prices[t]*np.exp(signal+noise)
panel=pd.DataFrame(factors.reshape(T*N,6),columns=[f"x{i}" for i in range(6)])
panel["date"]=np.repeat(dates,N); panel["stock"]=np.tile([f"S{i:03}" for i in range(N)],T)
panel["tpos"]=np.repeat(np.arange(T),N); panel["open"]=prices.ravel()
panel["signal_time"]=panel.date+pd.Timedelta(hours=15)
panel["feature_available_time"]=panel.signal_time
labels=panel.groupby("stock").open
panel["y"]=labels.shift(-(k+1))/labels.shift(-1)-1
exit_dates=pd.Series(dates).shift(-(k+1)).to_numpy()[panel.tpos]
panel["label_available_time"]=exit_dates+pd.Timedelta(hours=9,minutes=30)
return panel
def validate_panel(frame):
if frame.duplicated(["date","stock"]).any():
raise ValueError("Duplicate (date,stock) keys")
if frame[["signal_time","feature_available_time"]].isna().any().any():
raise ValueError("Missing availability timestamps")
if (frame.feature_available_time>frame.signal_time).any():
raise ValueError("Feature was unavailable at decision time")
if (frame.y.notna() & frame.label_available_time.isna()).any():
raise ValueError("Known label has unknown availability time")
def time_fold(frame, start, end, train_start=0):
"""Train at prior signal day's 15:00; dates [start,end) are held out."""
validate_panel(frame)
dates=frame.date.drop_duplicates().sort_values().reset_index(drop=True)
if not 1<=start<end<=len(dates) or not 0<=train_start<start:
raise ValueError("Invalid date fold")
cutoff=dates.iloc[start-1]+pd.Timedelta(hours=15)
train=frame[(frame.date>=dates.iloc[train_start]) & (frame.date<dates.iloc[start])
& frame.y.notna() & (frame.label_available_time<=cutoff)].copy()
valid=frame[frame.date.isin(dates.iloc[start:end]) & frame.y.notna()].copy()
if train.empty or valid.empty: raise ValueError("Empty fold")
return train,valid,cutoff
def keyed_metrics(frame, predictions):
"""predictions is a (date,stock,pred) table; exact keys required, order free."""
if frame.duplicated(["date","stock"]).any() or predictions.duplicated(["date","stock"]).any():
raise ValueError("Duplicate prediction/evaluation keys")
aligned=frame[["date","stock","y"]].merge(predictions[["date","stock","pred"]],
on=["date","stock"],how="outer",validate="1:1",indicator=True)
if not aligned._merge.eq("both").all(): raise ValueError("Prediction keys do not match")
daily=[]
for date,group in aligned.groupby("date"):
finite=np.isfinite(group.y)&np.isfinite(group.pred)
g=group.loc[finite]; ic=rank_ic=spread=np.nan
if len(g)>=3 and g.y.nunique()>1 and g.pred.nunique()>1:
ic=g.y.corr(g.pred); rank_ic=g.y.rank().corr(g.pred.rank())
ordered=g.sort_values(["pred","stock"]); size=max(1,len(g)//5)
spread=ordered.tail(size).y.mean()-ordered.head(size).y.mean()
daily.append({"date":date,"n":len(g),"ic":ic,"rank_ic":rank_ic,"spread":spread})
table=pd.DataFrame(daily)
valid=np.isfinite(aligned.y)&np.isfinite(aligned.pred)
mse=float(np.mean((aligned.loc[valid,"y"]-aligned.loc[valid,"pred"])**2)) if valid.any() else np.nan
return {"mse":mse,"ic":float(table.ic.mean()),"rank_ic":float(table.rank_ic.mean()),
"coverage":float(table.ic.notna().mean()),"rows":int(valid.sum()),"dates":len(table)},table
def run_research(seed=42):
"""Small predeclared grid; future-date validation; one fixed final holdout."""
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
from sklearn.ensemble import HistGradientBoostingRegressor
outputs={}; features=[f"x{i}" for i in range(6)]
folds=[(80,105),(105,130)]; final=(130,174)
for mechanism in ["null","linear","interaction","regime"]:
panel=make_panel(seed=seed,mechanism=mechanism)
grids={"Ridge":[.01,1.],"GBDT":[15,40]}; selected={}; trial_log=[]
def build(name,p,n):
if name=="Ridge":return make_pipeline(StandardScaler(),Ridge(alpha=n*p))
return HistGradientBoostingRegressor(max_iter=p,max_leaf_nodes=7,
min_samples_leaf=25,learning_rate=.08,early_stopping=False,random_state=seed)
for name,grid in grids.items():
trials=[]
for parameter in grid:
scores=[]
for start,end in folds:
tr,va,_=time_fold(panel,start,end)
model=build(name,parameter,len(tr)).fit(tr[features],tr.y)
pred=va[["date","stock"]].assign(pred=model.predict(va[features]))
score,_=keyed_metrics(va,pred);scores.append(score["rank_ic"])
trials.append((np.mean(scores),parameter))
trial_log.append({"model":name,"parameter":parameter,"fold_rank_ic":scores})
selected[name]=max(trials,key=lambda v:v[0])[1]
tr,test,cutoff=time_fold(panel,*final); results={}
for name,parameter in selected.items():
model=build(name,parameter,len(tr)).fit(tr[features],tr.y)
pred=test[["date","stock"]].assign(pred=model.predict(test[features]))
results[name]=keyed_metrics(test,pred)[0]
# A predeclared single-factor and equal-weight baseline, train-only calibration.
for name,cols in [("单因子",["x0"]),("等权",["x0","x1","x2"])]:
mean,scale=tr[cols].mean(),tr[cols].std(ddof=0)
ts=((tr[cols]-mean)/scale).mean(axis=1)
vs=((test[cols]-mean)/scale).mean(axis=1)
coef=np.linalg.lstsq(np.column_stack([np.ones(len(ts)),ts]),tr.y,rcond=None)[0]
pred=test[["date","stock"]].assign(pred=coef[0]+coef[1]*vs)
results[name]=keyed_metrics(test,pred)[0]
outputs[mechanism]={"metrics":results,"selected":selected,"trials":trial_log,
"cutoff":str(cutoff),"test_dates":44,"seed":seed,"data":"synthetic",
"policy":"Frozen grid; 2 development folds; static final training; no trading NAV"}
return outputs"""Standalone synthetic teaching example. Run locally with Python, not in the HTML."""
import json
import numpy as np
import pandas as pd
from scipy.special import expit
def clean(value):
if isinstance(value, dict): return {str(k):clean(v) for k,v in value.items()}
if isinstance(value,(list,tuple,np.ndarray)): return [clean(v) for v in value]
if isinstance(value,(np.integer,)): return int(value)
if isinstance(value,(float,np.floating)): return float(value) if np.isfinite(value) else None
if isinstance(value,(np.bool_,)): return bool(value)
return value
def print_result(result):
print(json.dumps(clean(result),ensure_ascii=False,allow_nan=False))
def make_panel(seed=42, T=180, N=24, k=5, mechanism="linear"):
"""Illustrative weekdays, 6 AR factors, future open-to-open k-day label."""
if mechanism not in {"null","linear","interaction","regime"}:
raise ValueError("Unknown mechanism")
rng=np.random.default_rng(seed)
dates=pd.bdate_range("2020-01-02",periods=T)
factors=np.empty((T,N,6)); factors[0]=rng.normal(size=(N,6))
for t in range(1,T):
factors[t]=.6*factors[t-1]+.8*rng.normal(size=(N,6))
factors[:,:,3]=.9*factors[:,:,0]+.15*rng.normal(size=(T,N))
prices=np.full((T,N),100.)
for t in range(T-1):
x=factors[max(0,t-1)]
signal=np.zeros(N)
if mechanism!="null": signal=.0015*x[:,0]
if mechanism in {"interaction","regime"}: signal += .0015*x[:,1]*x[:,2]
if mechanism=="regime" and t>=int(.65*T): signal -= .002*x[:,0]
noise=.006*rng.standard_t(5,N)/np.sqrt(5/3)+rng.normal(0,.002)
prices[t+1]=prices[t]*np.exp(signal+noise)
panel=pd.DataFrame(factors.reshape(T*N,6),columns=[f"x{i}" for i in range(6)])
panel["date"]=np.repeat(dates,N); panel["stock"]=np.tile([f"S{i:03}" for i in range(N)],T)
panel["tpos"]=np.repeat(np.arange(T),N); panel["open"]=prices.ravel()
panel["signal_time"]=panel.date+pd.Timedelta(hours=15)
panel["feature_available_time"]=panel.signal_time
labels=panel.groupby("stock").open
panel["y"]=labels.shift(-(k+1))/labels.shift(-1)-1
exit_dates=pd.Series(dates).shift(-(k+1)).to_numpy()[panel.tpos]
panel["label_available_time"]=exit_dates+pd.Timedelta(hours=9,minutes=30)
return panel
def validate_panel(frame):
if frame.duplicated(["date","stock"]).any():
raise ValueError("Duplicate (date,stock) keys")
if frame[["signal_time","feature_available_time"]].isna().any().any():
raise ValueError("Missing availability timestamps")
if (frame.feature_available_time>frame.signal_time).any():
raise ValueError("Feature was unavailable at decision time")
if (frame.y.notna() & frame.label_available_time.isna()).any():
raise ValueError("Known label has unknown availability time")
def time_fold(frame, start, end, train_start=0):
"""Train at prior signal day's 15:00; dates [start,end) are held out."""
validate_panel(frame)
dates=frame.date.drop_duplicates().sort_values().reset_index(drop=True)
if not 1<=start<end<=len(dates) or not 0<=train_start<start:
raise ValueError("Invalid date fold")
cutoff=dates.iloc[start-1]+pd.Timedelta(hours=15)
train=frame[(frame.date>=dates.iloc[train_start]) & (frame.date<dates.iloc[start])
& frame.y.notna() & (frame.label_available_time<=cutoff)].copy()
valid=frame[frame.date.isin(dates.iloc[start:end]) & frame.y.notna()].copy()
if train.empty or valid.empty: raise ValueError("Empty fold")
return train,valid,cutoff
def keyed_metrics(frame, predictions):
"""predictions is a (date,stock,pred) table; exact keys required, order free."""
if frame.duplicated(["date","stock"]).any() or predictions.duplicated(["date","stock"]).any():
raise ValueError("Duplicate prediction/evaluation keys")
aligned=frame[["date","stock","y"]].merge(predictions[["date","stock","pred"]],
on=["date","stock"],how="outer",validate="1:1",indicator=True)
if not aligned._merge.eq("both").all(): raise ValueError("Prediction keys do not match")
daily=[]
for date,group in aligned.groupby("date"):
finite=np.isfinite(group.y)&np.isfinite(group.pred)
g=group.loc[finite]; ic=rank_ic=spread=np.nan
if len(g)>=3 and g.y.nunique()>1 and g.pred.nunique()>1:
ic=g.y.corr(g.pred); rank_ic=g.y.rank().corr(g.pred.rank())
ordered=g.sort_values(["pred","stock"]); size=max(1,len(g)//5)
spread=ordered.tail(size).y.mean()-ordered.head(size).y.mean()
daily.append({"date":date,"n":len(g),"ic":ic,"rank_ic":rank_ic,"spread":spread})
table=pd.DataFrame(daily)
valid=np.isfinite(aligned.y)&np.isfinite(aligned.pred)
mse=float(np.mean((aligned.loc[valid,"y"]-aligned.loc[valid,"pred"])**2)) if valid.any() else np.nan
return {"mse":mse,"ic":float(table.ic.mean()),"rank_ic":float(table.rank_ic.mean()),
"coverage":float(table.ic.notna().mean()),"rows":int(valid.sum()),"dates":len(table)},table
def run_research(seed=42):
"""Small predeclared grid; future-date validation; one fixed final holdout."""
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
from sklearn.ensemble import HistGradientBoostingRegressor
outputs={}; features=[f"x{i}" for i in range(6)]
folds=[(80,105),(105,130)]; final=(130,174)
for mechanism in ["null","linear","interaction","regime"]:
panel=make_panel(seed=seed,mechanism=mechanism)
grids={"Ridge":[.01,1.],"GBDT":[15,40]}; selected={}; trial_log=[]
def build(name,p,n):
if name=="Ridge":return make_pipeline(StandardScaler(),Ridge(alpha=n*p))
return HistGradientBoostingRegressor(max_iter=p,max_leaf_nodes=7,
min_samples_leaf=25,learning_rate=.08,early_stopping=False,random_state=seed)
for name,grid in grids.items():
trials=[]
for parameter in grid:
scores=[]
for start,end in folds:
tr,va,_=time_fold(panel,start,end)
model=build(name,parameter,len(tr)).fit(tr[features],tr.y)
pred=va[["date","stock"]].assign(pred=model.predict(va[features]))
score,_=keyed_metrics(va,pred);scores.append(score["rank_ic"])
trials.append((np.mean(scores),parameter))
trial_log.append({"model":name,"parameter":parameter,"fold_rank_ic":scores})
selected[name]=max(trials,key=lambda v:v[0])[1]
tr,test,cutoff=time_fold(panel,*final); results={}
for name,parameter in selected.items():
model=build(name,parameter,len(tr)).fit(tr[features],tr.y)
pred=test[["date","stock"]].assign(pred=model.predict(test[features]))
results[name]=keyed_metrics(test,pred)[0]
# A predeclared single-factor and equal-weight baseline, train-only calibration.
for name,cols in [("单因子",["x0"]),("等权",["x0","x1","x2"])]:
mean,scale=tr[cols].mean(),tr[cols].std(ddof=0)
ts=((tr[cols]-mean)/scale).mean(axis=1)
vs=((test[cols]-mean)/scale).mean(axis=1)
coef=np.linalg.lstsq(np.column_stack([np.ones(len(ts)),ts]),tr.y,rcond=None)[0]
pred=test[["date","stock"]].assign(pred=coef[0]+coef[1]*vs)
results[name]=keyed_metrics(test,pred)[0]
outputs[mechanism]={"metrics":results,"selected":selected,"trials":trial_log,
"cutoff":str(cutoff),"test_dates":44,"seed":seed,"data":"synthetic",
"policy":"Frozen grid; 2 development folds; static final training; no trading NAV"}
return outputs
if __name__ == "__main__":
print_result(run_research(seed=42))
查看本轮本地运行记录 · 2026-10-12
{
"null": {
"metrics": {
"Ridge": {
"mse": 0.0002024382539422924,
"ic": 0.032828228577948045,
"rank_ic": 0.0200197628458498,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"GBDT": {
"mse": 0.00020262226515402202,
"ic": 0.04344345092542356,
"rank_ic": 0.031189543184989822,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"单因子": {
"mse": 0.00020277079266334166,
"ic": -0.04150114299478816,
"rank_ic": -0.02990118577075098,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"等权": {
"mse": 0.00020237435429299735,
"ic": 0.023270767539945083,
"rank_ic": 0.047411067193675875,
"coverage": 1.0,
"rows": 1056,
"dates": 44
}
},
"selected": {
"Ridge": 1.0,
"GBDT": 15
},
"trials": [
{
"model": "Ridge",
"parameter": 0.01,
"fold_rank_ic": [
0.03530434782608694,
0.05412173913043478
]
},
{
"model": "Ridge",
"parameter": 1.0,
"fold_rank_ic": [
0.030260869565217386,
0.06194782608695651
]
},
{
"model": "GBDT",
"parameter": 15,
"fold_rank_ic": [
0.014061573828791736,
0.030152666846221865
]
},
{
"model": "GBDT",
"parameter": 40,
"fold_rank_ic": [
-0.005909707796926499,
-0.005338271931763443
]
}
],
"cutoff": "2020-07-01 15:00:00",
"test_dates": 44,
"seed": 42,
"data": "synthetic",
"policy": "Frozen grid; 2 development folds; static final training; no trading NAV"
},
"linear": {
"metrics": {
"Ridge": {
"mse": 0.0002158594408726212,
"ic": 0.2805537579177386,
"rank_ic": 0.2676877470355731,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"GBDT": {
"mse": 0.0002167885681356451,
"ic": 0.2451015680796714,
"rank_ic": 0.24150766773084734,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"单因子": {
"mse": 0.00021395293380612485,
"ic": 0.27151456091163106,
"rank_ic": 0.26418972332015805,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"等权": {
"mse": 0.0002233433696783631,
"ic": 0.091735840968381,
"rank_ic": 0.07229249011857707,
"coverage": 1.0,
"rows": 1056,
"dates": 44
}
},
"selected": {
"Ridge": 1.0,
"GBDT": 15
},
"trials": [
{
"model": "Ridge",
"parameter": 0.01,
"fold_rank_ic": [
0.1873043478260869,
0.1444173913043478
]
},
{
"model": "Ridge",
"parameter": 1.0,
"fold_rank_ic": [
0.19516521739130432,
0.14890434782608694
]
},
{
"model": "GBDT",
"parameter": 15,
"fold_rank_ic": [
0.19080110356373606,
0.09967646028675535
]
},
{
"model": "GBDT",
"parameter": 40,
"fold_rank_ic": [
0.17899456439012795,
0.08417918558779139
]
}
],
"cutoff": "2020-07-01 15:00:00",
"test_dates": 44,
"seed": 42,
"data": "synthetic",
"policy": "Frozen grid; 2 development folds; static final training; no trading NAV"
},
"interaction": {
"metrics": {
"Ridge": {
"mse": 0.000246534144898484,
"ic": 0.2599026222431165,
"rank_ic": 0.2362055335968379,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"GBDT": {
"mse": 0.0002426443938107238,
"ic": 0.2728217425785496,
"rank_ic": 0.25081812422946637,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"单因子": {
"mse": 0.00024458576483804963,
"ic": 0.25498628672647655,
"rank_ic": 0.24037549407114625,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"等权": {
"mse": 0.0002536787962981721,
"ic": 0.08624675454051949,
"rank_ic": 0.07201581027667986,
"coverage": 1.0,
"rows": 1056,
"dates": 44
}
},
"selected": {
"Ridge": 1.0,
"GBDT": 40
},
"trials": [
{
"model": "Ridge",
"parameter": 0.01,
"fold_rank_ic": [
0.17506086956521738,
0.07137391304347825
]
},
{
"model": "Ridge",
"parameter": 1.0,
"fold_rank_ic": [
0.18386086956521738,
0.08382608695652173
]
},
{
"model": "GBDT",
"parameter": 15,
"fold_rank_ic": [
0.15682763114644607,
0.06077303089778725
]
},
{
"model": "GBDT",
"parameter": 40,
"fold_rank_ic": [
0.15493058889724023,
0.06845350516001593
]
}
],
"cutoff": "2020-07-01 15:00:00",
"test_dates": 44,
"seed": 42,
"data": "synthetic",
"policy": "Frozen grid; 2 development folds; static final training; no trading NAV"
},
"regime": {
"metrics": {
"Ridge": {
"mse": 0.00024392814737696548,
"ic": -0.01192811463178478,
"rank_ic": -0.04179841897233202,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"GBDT": {
"mse": 0.00024356036617342243,
"ic": 0.06908501292711096,
"rank_ic": 0.038083236026873496,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"单因子": {
"mse": 0.00024926921255070695,
"ic": -0.03825518702962725,
"rank_ic": -0.05559288537549407,
"coverage": 1.0,
"rows": 1056,
"dates": 44
},
"等权": {
"mse": 0.0002395074547715798,
"ic": -0.05757267200251119,
"rank_ic": -0.07569169960474308,
"coverage": 1.0,
"rows": 1056,
"dates": 44
}
},
"selected": {
"Ridge": 1.0,
"GBDT": 40
},
"trials": [
{
"model": "Ridge",
"parameter": 0.01,
"fold_rank_ic": [
0.17506086956521738,
-0.03933913043478261
]
},
{
"model": "Ridge",
"parameter": 1.0,
"fold_rank_ic": [
0.18386086956521738,
-0.03881739130434781
]
},
{
"model": "GBDT",
"parameter": 15,
"fold_rank_ic": [
0.15682763114644607,
-0.03340877574882897
]
},
{
"model": "GBDT",
"parameter": 40,
"fold_rank_ic": [
0.15493058889724023,
-0.021581788012149272
]
}
],
"cutoff": "2020-07-01 15:00:00",
"test_dates": 44,
"seed": 42,
"data": "synthetic",
"policy": "Frozen grid; 2 development folds; static final training; no trading NAV"
}
}先回答,再看提示和答案
空信号机制出现正 IC,说明模拟有 bug吗?
一级提示
区分总体零信号和样本相关。
参考答案与条件
未必。有限样本随机偏差和开发选择会产生非零IC。先查流程、再看预定重复与区间,不要求每次恰好零。
综合案例的五日spread可以作为每天收益算净值吗?
一级提示
先写出目标、信息集和所需条件。
参考答案与条件
不可以,标签重叠且没有持仓和成交政策。需要独立的逐日持仓收益回测。
四个模型中最终测试最高者可以直接作为已验证赢家吗?
一级提示
先写出目标、信息集和所需条件。
参考答案与条件
固定候选比较可描述,但根据测试挑选未来部署模型仍有选择代价,后续需新样本确认。
把知识迁移到一个新任务
先不运行代码,用一页纸写出研究合同。运行全部四种机制后写三条发现和两条限制。把一个模型失败的结果加入项目口述,并提出不会污染当前最终测试的下一实验。
这里保存学习笔记,不在浏览器执行 Python。A/B/C 由你按独立完成程度填写。
参考与进一步阅读
在线文档可能比本地固定版本更新;API 行为以已验证代码和版本记录为准。
掌握状态与复习
关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。
记录尽量保存在当前浏览器,建议定期导出备份。