一、什么是股票池?#
1.1 基本概念#
股票池(Universe)是量化策略交易的标的资产集合。就像厨师需要选择优质食材一样,量化交易者需要精心挑选股票池。常见的股票池类型包括:
-
行业特定池(如科技股)
-
规模导向池(如沪深300成分股)
-
因子驱动池(如高ROE股票)
1.2 重要性说明#
(演进过程示例):
2008年金融危机期间,仅包含金融股的策略普遍失效,而全市场策略表现更好。这说明:
1.3 数据获取实践#
import yfinance as yf
import pandas as pd
# 获取多只股票数据示例
tickers = ['AAPL', 'MSFT', 'GOOG']
start_date = '2020-01-01'
end_date = '2023-01-01'
data = yf.download(tickers, start=start_date, end=end_date)['Adj Close']
print(data.head())
# 计算日收益率
returns = data.pct_change().dropna()
print(returns.describe())小练习:尝试添加TSLA股票数据,比较其收益率波动性
二、构建股票池的关键要素#
2.1 行业分布分析#
使用yfinance获取行业信息(示例):
# 获取单只股票信息
aapl = yf.Ticker("AAPL")
print(aapl.info['sector']) # 输出行业分类
# 行业分布可视化
sector_counts = pd.Series({
'Technology': 150,
'Financials': 120,
'Healthcare': 80
})
sector_counts.plot(kind='pie', autopct='%1.1f%%')2.2 流动性筛选#
常用流动性指标计算公式:
20日移动平均流动性筛选:
def liquidity_screen(data, window=20, threshold=1e6):
volume = data['Volume']
close = data['Close']
dollar_volume = (volume * close).rolling(window).mean()
return dollar_volume > threshold2.3 换手率控制#
换手率计算公式:
对比实验:
# 原始股票池与平滑后对比
original_turnover = [...] # 原始换手率数据
smoothed_turnover = [...] # 使用移动平均后的数据
pd.DataFrame({
'原始': original_turnover,
'平滑': smoothed_turnover
}).plot(title='换手率对比')三、高级筛选技巧#
3.1 多条件复合筛选#
def build_universe(data):
# 条件1:市值大于100亿
market_cap = data['MarketCap'] > 10e9
# 条件2:过去20日平均成交量大于100万股
volume_avg = data['Volume'].rolling(20).mean() > 1e6
# 条件3:股价高于5美元
price_condition = data['Close'] > 5
return market_cap & volume_avg & price_condition3.2 动态调整机制#
使用3个月再平衡周期示例:
from dateutil.relativedelta import relativedelta
current_date = pd.to_datetime('2022-01-01')
rebalance_dates = [current_date + relativedelta(months=3*i) for i in range(4)]
print(rebalance_dates)小练习:尝试修改再平衡周期为1个月,观察股票池变动频率
四、最佳实践案例:QTradableStocksUS#
4.1 筛选标准#
| 指标 | 阈值 | 作用 |
|---|---|---|
| 市值 | >5亿美元 | 排除小盘股风险 |
| 日均成交额 | >250万美元(200日) | 确保流动性 |
| 股价 | >5美元 | 降低交易摩擦成本 |
4.2 实现逻辑#
def QTradableStocksUS(data):
# 市值筛选
market_cap = data['MarketCap'] > 5e8
# 流动性筛选
dollar_volume = (data['Volume'] * data['Close']).rolling(200).median() > 2.5e6
# 价格筛选
price_filter = data['Close'] > 5
# 其他条件(示例)
primary_share = data['IsPrimary'] # 假设有是否为优先股字段
no_adr = ~data['IsADR'] # 排除美国存托凭证
return market_cap & dollar_volume & price_filter & primary_share & no_adr五、常见误区与解决方案#
5.1 过拟合问题#
错误做法:在历史数据上不断调整参数直到表现完美
正确方法:
# 使用时间序列交叉验证
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(data):
train_data = data.iloc[train_idx]
test_data = data.iloc[test_idx]
# 执行回测...5.2 幸存者偏差#
解决方案:使用历史时点数据进行回测,包括已退市股票
示例数据集结构:
Date | Ticker | Price | Exists
2020-01-02 | ABC | 50.2 | True
2021-05-15 | ABC | NaN | False # 标记退市六、课后练习#
-
使用yfinance构建一个包含10只科技股的股票池
-
计算该股票池过去一年的行业分布变化
-
实现一个简单的换手率控制模块(阈值设为5%)
-
对比不同市值门槛(50亿 vs 100亿)对股票池数量的影响
# 练习模板代码
def tech_sector_universe():
tech_tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META']
data = yf.download(tech_tickers, period='1y')
# 添加你的筛选逻辑...
return selected_stocks(注:本教程示例代码需要配合实际市场数据使用,部分筛选条件需要根据具体数据字段调整实现)
七、历史股票池审查清单#
-
股票池必须按历史时点重建,并区分成分变更的公告日与生效日;今天从
yfinance获取的代码列表只能用于演示,不能证明历史回测有效 -
保留已退市、并购、破产和更名证券,并按当时可得的公司行动数据调整价格与标识符
-
筛选流动性、市值和基本面数据时加入真实发布延迟,不能让当期尚未披露的字段进入信号
-
在每个再平衡日检查停牌、涨跌停、借券、最小成交额和容量约束,并记录无法成交的证券
-
分别报告“固定当前成分”与“point-in-time 成分”结果;两者差距本身就是幸存者偏差诊断