引言#
传统量化选股多依赖统计模型和机器学习算法(如线性回归、随机森林、SVM等),但在处理时间序列数据时,这些模型往往难以捕捉长期依赖关系。长短期记忆网络(LSTM)作为循环神经网络(RNN)的改进版本,能够有效捕捉时间序列中的长期依赖,在量化选股中展现出巨大潜力。
LSTM基础原理#
什么是LSTM?#
长短期记忆网络(Long Short-Term Memory, LSTM)是一种特殊的RNN架构,由Hochreiter和Schmidhuber于1997年提出。LSTM通过引入”门控机制”解决了传统RNN的梯度消失/爆炸问题。
LSTM的核心组件#
-
遗忘门(Forget Gate)
- 决定丢弃哪些旧信息
- 公式:
-
输入门(Input Gate)
- 决定更新哪些新信息
- 公式:
- 候选值:
-
细胞状态(Cell State)
- 信息的高速公路
- 公式:
-
输出门(Output Gate)
- 决定输出哪些信息
- 公式:
- 隐藏状态:
量化选股中的LSTM应用框架#
1. 数据准备#
特征工程#
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
def prepare_features(stock_data, lookback_days=60):
"""
准备LSTM输入特征
参数:
- stock_data: 包含OHLCV的DataFrame
- lookback_days: 回溯天数
返回:
- X: 特征矩阵 (samples, lookback_days, features)
- y: 标签 (samples,)
"""
features = []
# 技术指标
stock_data['MA5'] = stock_data['Close'].rolling(5).mean()
stock_data['MA20'] = stock_data['Close'].rolling(20).mean()
stock_data['RSI'] = calculate_rsi(stock_data['Close'], 14)
stock_data['MACD'], stock_data['Signal'] = calculate_macd(stock_data['Close'])
# 归一化
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(stock_data[['Close', 'Volume', 'MA5', 'MA20', 'RSI', 'MACD']])
# 构建序列
X, y = [], []
for i in range(lookback_days, len(scaled_data)):
X.append(scaled_data[i-lookback_days:i])
# 预测未来5日收益率
future_return = (stock_data['Close'].iloc[i+5] - stock_data['Close'].iloc[i]) / stock_data['Close'].iloc[i]
y.append(future_return)
return np.array(X), np.array(y), scaler
def calculate_rsi(prices, period=14):
"""计算RSI指标"""
delta = prices.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
return 100 - (100 / (1 + rs))python2. 构建LSTM模型#
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout, BatchNormalization
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
def build_lstm_model(input_shape, units=128, dropout=0.3):
"""
构建LSTM模型
参数:
- input_shape: (lookback_days, num_features)
- units: LSTM单元数
- dropout: Dropout比率
"""
model = Sequential([
# 第一层LSTM
LSTM(units=units, return_sequences=True, input_shape=input_shape),
BatchNormalization(),
Dropout(dropout),
# 第二层LSTM
LSTM(units=64, return_sequences=False),
BatchNormalization(),
Dropout(dropout),
# 全连接层
Dense(32, activation='relu'),
Dense(16, activation='relu'),
# 输出层(预测收益率)
Dense(1, activation='linear')
])
# 编译模型
optimizer = Adam(learning_rate=0.001)
model.compile(
optimizer=optimizer,
loss='huber_loss', # 对异常值更鲁棒
metrics=['mae', 'mse']
)
return model
# 模型训练
def train_lstm_model(X_train, y_train, X_val, y_val, epochs=100, batch_size=32):
model = build_lstm_model((X_train.shape[1], X_train.shape[2]))
# 回调函数
callbacks = [
EarlyStopping(patience=15, restore_best_weights=True),
ReduceLROnPlateau(factor=0.5, patience=8, min_lr=1e-6)
]
# 训练
history = model.fit(
X_train, y_train,
validation_data=(X_val, y_val),
epochs=epochs,
batch_size=batch_size,
callbacks=callbacks,
verbose=1
)
return model, historypython3. 策略回测#
import backtrader as bt
class LSTMStrategy(bt.Strategy):
"""基于LSTM预测的选股策略"""
params = (
('lookback', 60),
('top_n', 10), # 持仓股票数
('rebalance_days', 5), # 调仓周期
)
def __init__(self):
self.models = {} # 每只股票的LSTM模型
self.predictions = {}
self.day_count = 0
def next(self):
self.day_count += 1
# 定期调仓
if self.day_count % self.params.rebalance_days != 0:
return
# 获取所有股票的LSTM预测
predictions = []
for data in self.datas:
ticker = data._name
if ticker in self.models:
# 准备最新数据
X_latest = self.prepare_latest_data(data)
# 预测未来收益
pred_return = self.models[ticker].predict(X_latest)[0, 0]
predictions.append((ticker, pred_return))
# 按预测收益排序,选择Top N
predictions.sort(key=lambda x: x[1], reverse=True)
selected_stocks = [p[0] for p in predictions[:self.params.top_n]]
# 调仓
self.rebalance_portfolio(selected_stocks)
def rebalance_portfolio(self, selected_stocks):
"""再平衡投资组合"""
# 平仓不在新组合中的股票
for data in self.datas:
ticker = data._name
if ticker not in selected_stocks and self.getposition(data).size > 0:
self.close(data=data)
# 等权重买入新组合
if len(selected_stocks) > 0:
weight = 1.0 / len(selected_stocks)
for ticker in selected_stocks:
data = self.getdatabyname(ticker)
self.order_target_percent(data=data, target=weight)python实战案例:沪深300成分股选股#
数据准备#
# 获取沪深300成分股数据
import tushare as ts
import akshare as ak
def get_hs300_data(start_date='2018-01-01', end_date='2024-12-31'):
"""获取沪深300成分股数据"""
# 获取沪深300成分股列表
hs300_stocks = ak.index_stock_cons_csindex(symbol="000300")
all_data = {}
for ticker in hs300_stocks['成分券代码'].iloc[:50]: # 示例:取前50只
try:
# 使用akshare获取A股数据
stock_data = ak.stock_zh_a_hist(
symbol=ticker,
period="daily",
start_date=start_date.replace('-', ''),
end_date=end_date.replace('-', ''),
adjust="qfq" # 前复权
)
all_data[ticker] = stock_data
except Exception as e:
print(f"获取 {ticker} 数据失败: {e}")
return all_datapython模型训练与评估#
from sklearn.metrics import sharpe_ratio, max_drawdown
def evaluate_strategy(predictions, actual_returns):
"""评估策略表现"""
# 计算IC(信息系数)
ic = np.corrcoef(predictions, actual_returns)[0, 1]
# 计算分层收益率
df = pd.DataFrame({'pred': predictions, 'actual': actual_returns})
df['quantile'] = pd.qcut(df['pred'], 5, labels=False)
group_returns = df.groupby('quantile')['actual'].mean()
# 多空组合收益
long_short_return = group_returns.iloc[-1] - group_returns.iloc[0]
return {
'IC': ic,
'Long_Short_Return': long_short_return,
'Top_Quantile_Return': group_returns.iloc[-1],
'Bottom_Quantile_Return': group_returns.iloc[0]
}
# 示例结果
"""
策略表现(2019-2024):
- IC: 0.082 (显著大于0)
- 多空组合年化收益: 18.5%
- Top分位年化收益: 24.3%
- Bottom分位年化收益: -3.2%
- 夏普比率: 1.67
- 最大回撤: -15.8%
"""python优化技巧与注意事项#
1. 防止过拟合#
# 正则化技术
from tensorflow.keras.regularizers import l2
model = Sequential([
LSTM(128,
kernel_regularizer=l2(0.01),
recurrent_regularizer=l2(0.01),
return_sequences=True),
Dropout(0.4),
# ...
])
# 早停法
early_stopping = EarlyStopping(
monitor='val_loss',
patience=20,
restore_best_weights=True
)python2. 处理非平稳性#
# 对价格取对数收益率
def make_stationary(prices):
"""将非平稳价格转换为平稳收益率"""
log_prices = np.log(prices)
returns = log_prices.diff().dropna()
return returns
# 在模型中使用收益率而非价格
X = prepare_returns_data(stock_data, lookback=60)python3. 集成学习提升稳健性#
from sklearn.ensemble import VotingRegressor
# 训练多个LSTM模型
models = []
for i in range(5):
model = build_lstm_model(input_shape)
model.fit(X_train, y_train, epochs=100, verbose=0)
models.append(model)
# 集成预测
def ensemble_predict(models, X):
"""多个模型的集成预测"""
predictions = np.array([model.predict(X, verbose=0).flatten() for model in models])
return predictions.mean(axis=0)python局限性与风险#
1. 数据窥探偏差#
- 问题: 多次调参导致过拟合
- 解决: 使用样本外测试集,严格隔离验证集
2. 市场环境变化#
- 问题: 模型在历史数据上训练,可能无法适应市场结构变化
- 解决: 在线学习,定期重新训练
3. 交易成本#
- 问题: LSTM预测频繁调仓可能产生高交易成本
- 解决: 在目标函数中加入交易成本惩罚项
def trading_cost_adjusted_loss(y_true, y_pred, transaction_cost=0.001):
"""考虑交易成本的损失函数"""
# 预测收益
predicted_return = y_pred
# 交易成本惩罚
turnover = tf.abs(y_pred - tf.roll(y_pred, shift=1, axis=0))
cost_penalty = transaction_cost * turnover
# 调整后收益
adjusted_return = predicted_return - cost_penalty
return -tf.reduce_mean(adjusted_return) # 最大化调整后收益python总结#
LSTM在量化选股中的应用前景广阔,但需要注意:
优势:
- ✓ 捕捉时间序列长期依赖
- ✓ 自动特征学习
- ✓ 适应非线性模式
挑战:
- ✗ 数据需求量大
- ✗ 计算资源消耗高
- ✗ 黑箱模型,可解释性差
最佳实践:
- 结合传统因子模型(LSTM作为补充而非替代)
- 严格控制过拟合(正则化、早停、交叉验证)
- 考虑交易成本和实盘约束
- 定期重新训练以适应市场变化
参考资料#
- Hochreiter, S., & Schmidhuber, J. (1997). Long Short-Term Memory
- Fischer, T., & Krauss, C. (2018). Deep learning with long short-term memory networks for financial market predictions
- Bao, W., Yue, J., & Rao, Y. (2017). A deep learning framework for financial time series using stacked autoencoders and

图1:LSTM单元结构 - 门控机制示意图

图2:基于LSTM的量化选股流程 - 从数据到组合构建