简介:面向金融量化与程序化交易场景,这份资源围绕沪深300指数增强策略,提供基于Python的完整建模实现,兼顾入门学习与工程参考。压缩包共8个文件,其中6个py脚本分别承担因子预处理、单因子检验、因子列表管理、指数增强模型构建等核心模块,另附1张模型结构示意图和1份因子列表Excel,便于快速定位代码与策略逻辑。资源整体约1.67MB,已有1476人学习。内容覆盖历史行情数据清洗、技术指标与基本面特征构造、机器学习模型训练、回测验证及风险控制等关键环节,并考虑了交易成本和滚动窗口训练等问题;读者可依据代码框架直接修改因子和参数,复现沪深300增强效果,并进一步扩展实时交易与性能优化模块,适合作为课程设计、毕业设计或量化实习的起步模板。
1. 指数增强不是选牛股,而是用python在沪深300里做“超额收益的稳定生意”
很多人一听“基于python的沪深300指数增强模型”,第一反应是“用python选出一批比指数涨得更多的股票”。真做起来才发现,方向反了。指数增强的本质是:先保证组合跟住沪深300,再在这个约束下尽量多拿一点超额收益。如果组合跑偏太多,涨的时候比指数多,跌的时候也可能比指数惨,那就不叫增强,叫赌博。用python实现这套模型,80%的工作量不在选股逻辑,而在数据清洗、因子计算、权重约束和回测排错。适合已经会用pandas处理表格数据、想从“看盘选股”跨到“可复现量化策略”的python入门进阶者。下面这套流程,是我把常见做法拆成的最小可行版本。
2. 先拆解收益来源:指数增强模型到底在增强什么,以及数据准备的最小闭环
2.1 超额收益从哪里来:把alpha和beta拆开看
指数增强策略的收益可以粗略写成:组合收益 = 沪深300涨跌(beta) + 超额收益(alpha)。Beta部分只要买了股票,多少都会跟市场一起动,这部分不需要你贡献任何智力。真正要做的,是在不偏离指数太远的前提下,让选股和权重分配带来稳定的alpha。偏离程度用跟踪误差(Tracking Error)衡量,回测里通常用组合收益序列减去基准收益序列的标准差,年化后越小说明跟踪越紧。
所以模型的目标不是“收益最大化”,而是“在跟踪误差不超过某个阈值的前提下,最大化超额收益”。这句话听起来像废话,但很多新手栽跟头就是因为在策略里只写了“选打分最高的50只股票”,完全没有约束偏离度。最后回测曲线很漂亮,一对照沪深300,超额波动大得吓人,根本不敢上实盘。
动手之前,先把收益拆解的逻辑用python写清楚,后面每一步都在这个框架下做取舍:
import pandas as pd import numpy as np # 假设组合和基准的日收益率序列已经算好 # daily_ret: 组合日收益率, bench_ret: 沪深300日收益率 def calc_alpha_te(daily_ret, bench_ret, rf=0.0): # 日超额收益 excess = daily_ret - bench_ret # 年化超额收益:日超额均值 * 252,这里用几何累乘更严谨,先按算术近似 annual_excess = excess.mean() * 252 # 年化跟踪误差:日超额标准差 * sqrt(252) tracking_error = excess.std() * np.sqrt(252) # 信息比率:年化超额 / 年化跟踪误差 ir = annual_excess / tracking_error if tracking_error != 0 else 0 return annual_excess, tracking_error, ir # 用法: # ann_ex, te, ir = calc_alpha_te(port_ret, hs300_ret) # 一般IR>0.5算合格,>1.0是优秀,但回测里IR超过2就要警惕过拟合参数说明:rf无风险利率,超额收益要不要算无风险成本,取决于你的模型有没有用CAPM;这里默认0,因为指数增强通常看相对基准的超额,不涉及绝对收益定价。annual_excess和tracking_error都是年化口径,方便和公开报告对比。回测时我会把所有曲线的指标都打印成这张表,而不是只看累计收益。
2.2 数据准备:用python拿到沪深300成分股和日线行情
做指数增强,数据至少有两块:基准指数(沪深300)的行情,以及成分股列表和个股行情。常见做法是用akshare或tushare这类新浪/东财接口,免注册的akshare对入门最友好,但接口字段会随网页改版变化,代码要写容错。下面这段用akshare获取沪深300成分股和指数行情,注意把复权因子一并拿到,后面会专门讲复权坑。
import akshare as ak import pandas as pd # 1. 获取沪深300成分股列表 # 返回字段通常包含股票代码、名称、行业 stock_info = ak.index_stock_cons_csindex(symbol="000300") stock_info.columns = ["date", "code", "name", "industry", "weight"] # 按实际返回调整 # 2. 获取沪深300指数日线行情(用前复权) index_df = ak.stock_zh_index_daily(symbol="sh000300") index_df["date"] = pd.to_datetime(index_df["date"]) index_df = index_df.set_index("date") index_df["ret"] = index_df["close"].pct_change() # 3. 对每只成分股下载日线数据,这里只演示3只,全量请循环 from concurrent.futures import ThreadPoolExecutor def fetch_stock(code): df = ak.stock_zh_a_daily(symbol=code, adjust="qfq") df["date"] = pd.to_datetime(df["date"]) df = df.set_index("date") df["ret"] = df["close"].pct_change() return code, df # 实际使用时建议加try/except,跳过停牌或退市股 sample_codes = stock_info["code"].head(3).tolist() with ThreadPoolExecutor(max_workers=3) as ex: results = dict(ex.map(fetch_stock, sample_codes))逻辑说明:先取成分股列表,再取指数日线,最后循环取个股行情。用qfq前复权是为了让历史价格可比较,但这里已经埋了一个大坑:前复权价格会随着每次除权除息而重算历史数值,如果后面把多期数据拼在一起做滚动回测,复权因子会“漂移”,导致因子计算错乱。更稳的做法是取未复权价格自己算后复权因子,或者用hfq并记录调整日。这个坑放到避坑章细说。
参数说明:symbol="000300"是指数代码,sh000300是上证格式,akshare里不同接口对代码前缀要求不同,常见报错就是“symbol不存在”。adjust="qfq"指定前复权,如果不填,返回未复权价,计算收益率在除权日会有假跳空。ThreadPoolExecutor控制并发,没必要开太多线程,接口限频会ban IP,本地调试线程数1-3就够。
2.3 组合基准和成分股权重:没有权重数据怎么办
沪深300是市值加权指数,成分股权重每半年调整一次,权重数据本身也是策略输入。如果直接从akshare拿到的字段里没有权重,可以用当日总市值近似算出个股权重。这个近似在大部分时间够用,因为指数基准权重基本和市值权重一致,遇到新股快速纳入或定期调整生效日,权重可能偏几天。
# 假设已经拿到成分股当天的市值数据 def calc_index_weight(market_cap_series): # 只用沪深300成分股内的市值计算权重 total = market_cap_series.sum() weight = market_cap_series / total return weight # 用权重构造“虚拟基准日收益” def calc_bench_return_from_stocks(returns_df, weights): # returns_df: 每列是一只成分股日收益率,index是日期 # weights: Series,index是股票代码 bench = (returns_df * weights).sum(axis=1) return bench逻辑说明:先算个股市值占比,再用个股日收益率按权重加权,合成基准日收益。这样做的好处是,后面计算跟踪误差时,基准收益序列和组合收益序列使用的是同一套成分股数据,避免“指数收益率来自官方,组合收益率来自个股”之间的口径偏差。
参数说明:returns_df需要先对齐日期索引和股票列名;weights的索引必须和returns_df列名完全一致,否则*运算会出现NaN。如果部分股票停牌导致收益率为空,要先用fillna(0)填充,或直接剔除停牌日超过一定比例的个股,否则权重会被静默丢掉。
3. 用python实现多因子选股:从因子计算到打分排序
3.1 因子计算:估值、动量、质量三类因子的pandas实现
指数增强的选股端,最常见方案是多因子模型。因子不是越多越好,先抓三类基本面/量价因子:估值(便宜)、动量(近期涨得好)、质量(盈利稳)。对沪深300这种大盘蓝筹指数,估值因子和动量因子相关性低,质量因子用来过滤暴雷股。
def calc_factors(price_df, financial_df): """ price_df: 个股日线收盘价,列名是多级索引(股票代码, 日期)或宽表 financial_df: 包含每股收益EPS、净资产BPS等财务指标 返回因子DataFrame,index=日期, columns=股票代码 """ factors = {} # 1. 估值因子:EP = 每股收益 / 股价,取倒数让值越大越便宜 ep = financial_df["eps"] / price_df factors["ep"] = ep # 2. 动量因子:过去20日收益率(剔除最近1日避免短期反转干扰) mom = price_df.pct_change(20).shift(1) factors["mom"] = mom # 3. 质量因子:ROE = 净利润 / 净资产,用财务数据近似 roe = financial_df["net_profit"] / financial_df["equity"] factors["roe"] = roe factor_df = pd.concat(factors, axis=1) return factor_df逻辑说明:ep用EPS除以股价,市盈率的倒数,数值越高代表单位股价对应的盈利越多,符合“便宜”的定义。mom用20日收益率前移一天,是为了避开T日当天涨跌对因子的自相关,否则选股结果会在尾盘被当日的极端行情扭曲。roe直接用财务数据,需要先把财务数据对齐到交易日,通常用最近一期财报,且要确保财报发布日期早于因子计算日期,否则就是未来函数。
参数说明:20日动量是A股回测里比较稳的窗口,短了变成追热点,长了变成慢变量。shift(1)这个动作很多人会漏,漏掉等于变相使用了当天收盘信息,回测收益凭空高几个点。财务数据对齐时,建议把每个季报的公告日单独存一列,用merge_asof把发布日期在计算日之前的最近一期财报取出来,别用“报告期”去对齐。
3.2 因子标准化和中性化:去掉行业和市值的影响
原始因子值不能直接相加,因为量纲不同,EP可能是0.05,ROE可能是0.15,动量可能是0.2。先做去极值和z-score,再考虑行业和市值中性化——沪深300里银行股EP天然高,消费股ROE天然高,如果不对行业做中性化,选股结果会常年集中在某几个行业,跟踪误差被行业偏离拉大。
from scipy.stats import mstats def winsorize_and_zscore(factor, limits=0.05): # 去极值:用分位数截断,而不是直接删数据 factor_w = mstats.winsorize(factor, limits=limits) # z-score标准化 factor_z = (factor_w - factor_w.mean()) / factor_w.std() return factor_z def industry_neutralize(factor, industry_series): """ factor: 标准化后的因子值, Series, index为股票代码 industry_series: 行业分类, Series 做法:对每个行业内的因子值做均值归零,相当于行业内排序 """ factor_neu = factor.groupby(industry_series).transform( lambda x: x - x.mean() ) return factor_neu逻辑说明:mstats.winsorize把上下5%的极值拉回分位数边界,避免一只股票因子值巨大把z-score带偏。z-score做完,不同因子都是均值0、标准差1,可以加权相加。行业中性化用行业内减去均值,相当于只看行业内的相对强弱,这样选出来的股票天然不会偏好某个行业。
参数说明:limits=0.05意味着上下各截断5%,对沪深300这种300只股票的池子,等于每端截掉15只。如果因子质量差,极值分布广,可以放宽到0.1。行业中性化这里用的是最简单方式,更严谨的做法是回归化:factor ~ 行业哑变量取残差,但业内均值归零在300只的池子里效果差不太多,还省一次回归。做中性化时注意,groupby后transform(lambda x: x - x.mean())会自动保留索引顺序。
3.3 合成因子与选股:综合打分,选前N只股票
因子合成有两种常见方式:等权加权和IC加权。等权简单稳定,适合第一个版本。IC加权是计算每个因子与未来收益的相关系数,用IC做权重,但IC本身不稳定,容易过拟合。先把等权版本跑通,再逐步替成动态权重。
def select_stocks(composite_factor, top_n=50, exclude_list=None): """ composite_factor: DataFrame, index=日期, columns=股票代码 返回每个调仓日选中的股票代码列表 """ selected = {} for date in composite_factor.index: # 取该日所有股票的因子值 day_factor = composite_factor.loc[date].dropna() # 剔除停牌或ST股,假设exclude_list是字典{date: [code...]} if exclude_list: day_factor = day_factor.drop(exclude_list.get(date, [])) # 降序排列,取前top_n day_factor = day_factor.sort_values(ascending=False) selected[date] = day_factor.index[:top_n].tolist() return selected逻辑说明:dropna很重要,沪深300里有新上市或长期停牌的股票,因子值缺失直接丢弃。sort_values默认升序,ascending=False取最大。组合因子定义在前面合成时,等权合成就是factor_df.mean(axis=1),但要注意axis=1是按行求均值,即对同一日所有因子求平均。
参数说明:top_n=50大约是沪深300的六分之一,个股覆盖度适中。如果top_n太小,组合收益方差大;太大,超额被稀释。我第一次做时选了30只,回测超额高但回撤也大,后来改成50只才把跟踪误差压到可接受范围。选股频率这里是每个交易日都选,实际生产中会结合下一章说的再平衡周期做,频率太高换手成本爆炸。
4. 组合优化与权重分配:用scipy把超额收益控制在风险预算内
4.1 从等权到优化权重:为什么不能只看打分
选股只确定了“买哪些”,没确定“各买多少”。最简单做法是等权分配,但这会让组合的行业暴露和市值暴露偏离基准很大。比如选出来的股票恰好集中在金融和白酒,那组合表现就变成“金融+白酒”的beta,而不是沪深300的beta。为了在获取超额的同时控制偏离,要用组合优化:给定每只股票的预期收益(用因子打分替代),求解一组权重,使风险/收益目标最优。
from scipy.optimize import minimize import numpy as np def optimize_weights(expected_ret, cov_matrix, index_weight, te_limit=0.02, w_max=0.01): """ expected_ret: 个股预期收益向量, Series cov_matrix: 个股收益协方差矩阵, DataFrame index_weight: 基准权重向量 te_limit: 年化跟踪误差上限(如2%) w_max: 个股最大权重限制 返回最优权重 """ n = len(expected_ret) # 决策变量是组合权重 w # 跟踪误差近似:TE = sqrt((w - w_b)^T * Sigma * (w - w_b)) def objective(w): # 最大化预期收益,即最小化负预期收益 return -w @ expected_ret def te_constraint(w): te = np.sqrt((w - index_weight) @ cov_matrix @ (w - index_weight)) return te_limit - te # 约束条件 constraints = [ {"type": "eq", "fun": lambda w: w.sum() - 1}, # 满仓 {"type": "ineq", "fun": te_constraint}, # 跟踪误差小于上限 ] bounds = [(0, w_max)] * n # 每只股票权重0~1% # 初始权重:用基准权重,再微调 w0 = index_weight.copy() res = minimize(objective, w0, method="SLSQP", bounds=bounds, constraints=constraints) if not res.success: print("优化失败:", res.message) return None return res.x逻辑说明:目标函数是预期收益最大化,预期收益直接用前面合成因子映射到预期收益,因子值越高预期收益越高。约束条件第一个是权重之和等于1,第二个是跟踪误差不超过2%。协方差矩阵用历史收益估计,常见做法是取过去60个交易日的日收益协方差,再年化。SLSQP是scipy里处理带等式和不等式约束最常用的序列二次规划算法,对这类100维以下的问题够用。
参数说明:te_limit=0.02表示年化跟踪误差不超过2%,这是很紧的约束,对应信息比率可能被压得很低。实盘里通常放宽到4%~6%,否则优化器找不到解。w_max=0.01表示单只股票最多1%,沪深300里茅台权重可能接近6%,如果你的基准权重里茅台大于1%,这个约束会导致组合从一开始就大幅偏离基准。所以w_max要和基准权重匹配,或者改成“相对基准权重的偏离不超过2%”这种相对约束。
4.2 行业中性化和个股权重约束:让组合不偏离指数太多
组合优化里的协方差矩阵和跟踪误差天然会控制相关性风险,但不够直观。更工程化的做法是显式加入行业权重偏离约束。比如沪深300里银行权重15%,如果优化后的组合银行权重变成25%,等于你下了10个百分点的行业赌注,这和“指数增强”的初衷背离。
def add_industry_constraints(weights, industry_series, index_industry_weight, industry_dev_limit=0.03): # 这个函数演示如何构造行业偏离约束,供scipy使用 constraints = [] unique_industries = industry_series.unique() for ind in unique_industries: # 选出属于该行业的股票索引 idx = industry_series[industry_series == ind].index.tolist() idx_positions = [list(weights.index).index(code) for code in idx] # 组合行业权重 - 基准行业权重 <= 偏离上限 def ind_lower_bound(w, positions=idx_positions): # 下限约束:组合权重 >= 基准权重 - 偏离 w_ind = w[positions].sum() bench_ind = index_industry_weight[ind] return w_ind - bench_ind + industry_dev_limit def ind_upper_bound(w, positions=idx_positions): w_ind = w[positions].sum() bench_ind = index_industry_weight[ind] return bench_ind + industry_dev_limit - w_ind constraints.append({"type": "ineq", "fun": ind_lower_bound}) constraints.append({"type": "ineq", "fun": ind_upper_bound}) return constraints逻辑说明:这里把行业约束拆成下限和上限两条不等式,industry_dev_limit=0.03表示行业权重偏离基准不超过3个百分点。注意w是numpy数组时,w[positions].sum()可以直接切片求和,positions是股票在这个数组里的位置索引。
参数说明:行业分类怎么来?akshare的成分股列表里通常有行业字段,或者用申万一级行业分类。如果只用沪深300,行业数量在20~30个,约束条数翻倍后,优化器求解时间明显增加。万一遇到“优化失败”,先检查约束过紧还是协方差矩阵奇异,后者需要做正则化或改用Ledoit-Wolf收缩估计。
4.3 再平衡与交易成本:过高的换手率会吃掉超额
优化器给出的权重是“目标权重”,实际交易还要考虑从当前持仓调整到目标权重的换手成本。如果每期都大改,交易费、冲击成本会把超额收益磨光。常见做法是在目标函数里加入换手惩罚项,并限制单期换手率。
def optimize_with_cost(expected_ret, cov_matrix, index_weight, current_weight, cost_rate=0.001, turnover_max=0.2): n = len(expected_ret) # 用当前权重作为起点 w0 = current_weight def objective(w): # 预期收益 - 换手成本惩罚 turnover = np.abs(w - current_weight).sum() return -(w @ expected_ret) + cost_rate * turnover constraints = [ {"type": "eq", "fun": lambda w: w.sum() - 1}, {"type": "ineq", "fun": lambda w: turnover_max - np.abs(w - current_weight).sum()}, ] bounds = [(0, 0.05)] * n res = minimize(objective, w0, method="SLSQP", bounds=bounds, constraints=constraints) return res.x逻辑说明:cost_rate=0.001是单边交易成本,含佣金、印花税和部分冲击成本。当调仓买卖双向时,实际成本翻倍。目标函数里+ cost_rate * turnover相当于每换手1%就扣0.1%的收益,让优化器在“多赚alpha”和“少换手”之间找平衡。turnover_max=0.2限制单边换手率不超过20%,如果调仓日换手超过20%,优化器会被约束强行勒住。
参数说明:换手限制数值要和再平衡周期匹配。月度再平衡,单边换手20%已经很高,一个季度再平衡可以放到30%。交易成本不要只设万1,沪深300大资金冲击成本占比很大,小资金回测用万2~万3比较贴近实盘。回测里把cost_rate设成0会得到漂亮曲线,但上实盘会被打回原形。
5. 避坑:指数增强模型回测中的5个常见问题
5.1 成分股调整日数据不齐,导致选入停牌股
现象:回测某一天组合持仓里出现连停5天的股票,净值却还在变动;或者调仓日给出了一只已经进入退市整理期的股票。
原因:沪深300成分股列表有生效日期,很多人的代码只取了“最新成分股列表”,然后拖着历史价格回测,等于用今天才知道会入选的股票去做历史交易。另外,选股日没有过滤停牌股,停牌股票价格不变,买入等于“以停牌价挂单”,回测里成交,实盘根本买不进。
解决:调仓日只使用当日可交易股票名单,剔除停牌、ST和上市不足一年的次新股。停牌用“量能”或“是否无成交”判断,更简单的是看当日是否开盘价存在。另外成分股列表要按季度快照存储,不能用最新的倒推历史。我一般会在每个调仓日打印实际入选股票数,如果低于预期,优先怀疑数据缺失。
5.2 复权因子用错,导致收益虚高
现象:回测年化超额8%,但拆开看某些个股贡献了异常的跳涨收益,尤其除权除息密集的月份。
原因:用前复权价格计算因子,后来新增的复权因子会改变历史价格,导致同一段价格在不同日期读取时数值不同。更隐蔽的是,如果一只股票在回测区间内除权两次,前复权会把第一次除权前的价格也修正掉,但你的因子计算是从一个固定日期开始累进的,前后两段价格的复权基准不一致,算出来的动量因子出现假跳涨。
解决:用后复权价格做因子计算和收益计算,后复权以最新价格为基准,价格序列只更新新增的除权事件,历史数据不会倒退式修正。如果一定要用前复权,就锁定下载日期,不要在回测中途刷新数据。另外不要混用复权价和未复权价算财务因子,比如EP因子里的股价用复权价,EPS是未复权财务数据,两者匹配会出错。
5.3 因子极值未处理,导致选股结果被个别股票绑架
现象:换仓后组合里频繁出现同一只股票,而它只因为某个因子值特别夸张,比如某银行股PE只有2倍,EP=0.5,把其他股票全压下。
原因:原始因子没做去极值,直接标准化后合成时,极端值的z-score可能高达8,等权加权后这只股票在综合因子里的权重被拉爆。沪深300里面这种极端值虽然少,但银行、地产板块常有。
解决:每期因子先winsorize再zscore,而且winsorize要在横截面上做,也就是对同一交易日的所有股票做,不能对时间序列做。另外每次调仓后检查综合因子前10股票是否来自同一行业、同一风格,如果行业集中度异常高,说明中性化失效或因子有mapping错误。
5.4 交易成本设置过低,回测超额被费率和滑点吃掉
现象:回测年化超额10%,信息比率1.8,但扣掉双边万5的交易成本后只有6%,再考虑冲击成本可能只剩3%。
原因:很多人只在每次调仓时扣一次佣金,忽略了印花税(卖出单边千1)、滑点、冲击成本,以及停牌股无法成交导致的“隐形成本”。指数增强策略换手率通常月均20%~50%,成本按年化算非常可观。
解决:回测框架里至少设置:佣金万2.5双向,印花税千1卖出,滑点0.1%(或按成交额比例),冲击成本按当日成交量占比动态估计。调仓日计算换手时,用“实际成交的权重差”而不是“目标-现状差”,因为部分股票买不到。我现在的习惯是先用高成本参数跑出模型,再逐步降低成本看曲线的过度反应,如果成本从万5降到万2超额变化很大,说明策略靠高频微利赚钱,风险很高。
5.5 跟踪误差约束不收敛:优化器给不出权重
现象:minimize连续报错“Positive directional derivative for linesearch”或“Singular matrix C”,返回的res.success=False。
原因:协方差矩阵是奇异的——股票数量多,时间窗口短,或存在完全相关的股票。还有约束条件本身冲突:每只股票权重上限0.5%,同时要求跟踪误差小于1%,在300只股票池里根本无解。
解决:先用Ledoit-Wolf收缩估计协方差矩阵,或把历史窗口从60天延长到120天。然后检查权重上限和跟踪误差阈值是否相容,用随机生成100组权重测试约束是否可行。实在不行,把跟踪误差约束改成惩罚项放进目标函数,用拉格朗日方法替代硬约束,成功率更高。
6. 验证模型是否真的有效:滚动回测与参数敏感性检查
模型跑出漂亮的回测曲线后,不要急着上实盘。先做两件事:滚动回测和参数敏感性分析。
滚动回测的做法是把历史数据切成多段,每段包含训练期和验证期。训练期用来确定因子权重或调优参数,验证期用来检验策略在“未知”数据上的表现,然后一段段向后滚动。常见的坑是只用全区间一次性回测,参数在数据里已经偷偷过拟合。我一般把2018到2023年的数据按季度滚动,每次用前4季度训练、后1季度验证,这样能看到超额收益在不同市场环境下的稳定性。
最小实现可以用一个循环完成:
def rolling_backtest(periods, train_func, backtest_func): results = [] for train_start, train_end, test_start, test_end in periods: train_data = load_data(train_start, train_end) test_data = load_data(test_start, test_end) params = train_func(train_data) result = backtest_func(test_data, params) results.append(result) return results参数敏感性分析更简单:选出因子合成里最关键的参数,比如动量窗口20日、跟踪误差上限2%、换手上限20%,每次只动一个参数,扫一遍取值范围,看超额收益和IR的变化。如果IR在某个参数值附近剧烈波动,说明模型对参数太敏感,真实市场里参数漂移一点就失效。
我的个人习惯是:把最后一段数据(比如最近半年)锁起来,整个建模过程中坚决不看,等所有参数、因子权重定稿后,才用它做一次终极验证。这个习惯帮我避开了至少两次自我欺骗——回测里很漂亮的模型,一碰“从未见过”的数据就露馅。这个方案本身不保证赚钱,但至少能让你的策略是“可持续评估”而不是“碰巧拟合”。希望帮到你。
本文还有配套的精品资源,点击获取