Python+LSTM 做 A 股量化选股:从零搭建 AI 量化交易完整策略,附可运行回测代码
【AI辅助创作声明:本文由 AI 辅助整理与撰写,内容已经过人工审校与调整。】
本文是一篇面向有Python基础但无量化经验的开发者的入门教程,手把手带你用Python+LSTM搭建完整的A股量化选股策略。从数据获取、特征工程、模型训练到策略回测,包含全部可运行代码和详细中文注释。读完本文,你将能够独立搭建一个基于深度学习的量化选股系统。
本文能让你做到什么
读完本文,你将能够独立搭建一个基于LSTM的完整A股量化选股策略,包括数据获取、特征工程、模型训练、策略回测全流程。全部代码可直接运行,无需额外配置。
环境依赖:
- Python >= 3.8
- akshare == 1.11.0(A股数据接口)
- pandas == 2.1.4(数据处理)
- numpy == 1.26.0(数值计算)
- tensorflow == 2.15.0(LSTM模型)
- scikit-learn == 1.3.2(数据预处理)
- matplotlib == 3.8.2(可视化)
- TA-Lib == 0.4.32(技术指标)
目标读者: 有Python基础但没做过量化交易的开发者。
第一步:数据获取(akshare)
1.1 为什么选择akshare?
akshare是一个开源的Python金融数据接口库,提供免费的A股行情数据、财务数据等。相比商业数据接口(如Tushare Pro),akshare无需注册付费,适合初学者入门。
1.2 安装和配置akshare
pip install akshare==1.11.0
1.3 获取股票历史数据
# 所需库:akshare==1.11.0, pandas==2.1.4
import akshare as ak
import pandas as pd
from datetime import datetime, timedelta
def get_stock_data(stock_code, start_date, end_date):
"""
获取股票历史行情数据
:param stock_code: 股票代码(如'000001')
:param start_date: 开始日期(格式:'YYYYMMDD')
:param end_date: 结束日期(格式:'YYYYMMDD')
:return: 包含OHLCV数据的DataFrame
"""
# 使用akshare获取日线数据
df = ak.stock_zh_a_hist(
symbol=stock_code,
period="daily",
start_date=start_date,
end_date=end_date,
adjust="qfq" # 前复权(分红送股调整)
)
# 重命名列,方便后续处理
df.columns = ['date', 'open', 'close', 'high', 'low', 'volume',
'amount', 'amplitude', 'pct_change', 'price_change', 'turnover']
# 转换日期格式
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
return df
# 示例:获取平安银行(000001)近一年数据
end_date = datetime.now().strftime('%Y%m%d')
start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
df = get_stock_data('000001', start_date, end_date)
print(f"获取到 {len(df)} 条数据")
print(df.head())
1.4 获取全部A股股票列表
def get_all_stocks():
"""
获取全部A股股票列表
:return: 包含股票代码和名称的DataFrame
"""
# 获取股票列表
stock_df = ak.stock_zh_a_spot_em()
# 筛选:排除ST股票、退市股票、上市不满一年的新股
stock_df = stock_df[~stock_df['名称'].str.contains('ST|退', na=False)]
return stock_df[['代码', '名称']]
# 示例:获取股票列表
stocks = get_all_stocks()
print(f"共 {len(stocks)} 只股票")
print(stocks.head(10))
避坑提示: akshare的接口可能会变动,如果遇到报错,更新akshare:pip install --upgrade akshare。
第二步:特征工程(5个核心因子)
特征工程是量化策略的核心。我们选择5个具有明确金融含义的因子。
2.1 因子1:价格动量(MOM)
金融含义: 衡量价格变化的速度,价格上涨通常会延续一段时间(动量效应)。
def calculate_mom(df, period=20):
"""
计算价格动量
:param df: 价格DataFrame
:param period: 计算周期(默认20天)
:return: 动量值
"""
# 动量 = 当前价格 / N天前价格 - 1
mom = df['close'] / df['close'].shift(period) - 1
return mom
2.2 因子2:波动率(VOL)
金融含义: 衡量价格波动幅度,低波动通常意味着价格更稳定。
def calculate_volatility(df, period=20):
"""
计算价格波动率(标准差)
:param df: 价格DataFrame
:param period: 计算周期
:return: 波动率值
"""
# 计算日收益率
returns = df['close'].pct_change()
# 滚动标准差
volatility = returns.rolling(window=period).std()
return volatility
2.3 因子3:成交量比率(VR)
金融含义: 衡量成交量变化,异常放量通常伴随价格变动。
def calculate_volume_ratio(df, period=20):
"""
计算成交量比率
:param df: 价格DataFrame
:param period: 计算周期
:return: 成交量比率值
"""
# 当前成交量 / 过去N天平均成交量
avg_volume = df['volume'].rolling(window=period).mean()
vr = df['volume'] / avg_volume
return vr
2.4 因子4:均线乖离率(MA Spread)
金融含义: 衡量价格相对于均线的偏离程度,价格在均线上方表示趋势向上。
def calculate_ma_spread(df, short=5, long=20):
"""
计算均线乖离率
:param df: 价格DataFrame
:param short: 短期均线周期
:param long: 长期均线周期
:return: 乖离率值
"""
# 计算短期和长期均线
ma_short = df['close'].rolling(window=short).mean()
ma_long = df['close'].rolling(window=long).mean()
# 乖离率 = (短期均线 - 长期均线) / 长期均线
ma_spread = (ma_short - ma_long) / ma_long
return ma_spread
2.5 因子5:RSI相对强弱指标
金融含义: 衡量价格变动的速度和幅度,RSI>70为超买,RSI<30为超卖。
# 所需库:TA-Lib==0.4.32
import talib
def calculate_rsi(df, period=14):
"""
计算RSI指标
:param df: 价格DataFrame
:param period: RSI周期(默认14)
:return: RSI值
"""
rsi = talib.RSI(df['close'], timeperiod=period)
return rsi
2.6 特征工程完整代码
def create_features(df):
"""
创建特征集
:param df: 价格DataFrame
:return: 包含特征的DataFrame
"""
features = pd.DataFrame(index=df.index)
# 原始价格特征
features['close'] = df['close']
features['volume'] = df['volume']
# 技术指标
features['mom'] = calculate_mom(df, 20)
features['volatility'] = calculate_volatility(df, 20)
features['volume_ratio'] = calculate_volume_ratio(df, 20)
features['ma_spread'] = calculate_ma_spread(df, 5, 20)
features['rsi'] = calculate_rsi(df, 14)
# 目标变量:未来5天收益率
features['target'] = df['close'].shift(-5) / df['close'] - 1
# 删除包含NaN的行
features = features.dropna()
return features
# 示例:创建特征
features_df = create_features(df)
print(features_df.head())
print(f"特征矩阵形状:{features_df.shape}")
第三步:模型选择(LSTM)
3.1 为什么选择LSTM?
LSTM(长短期记忆网络)是一种特殊的RNN,适合处理时间序列数据。我们选择LSTM的原因是:
- 时序特性: 股价是时间序列数据,LSTM能捕捉长期依赖关系。
- 非线性关系: 股价与因子之间存在复杂的非线性关系,LSTM能学习这些非线性模式。
- 广泛应用: LSTM在金融量化领域有大量成功应用案例。
3.2 LSTM模型结构
# 所需库:tensorflow==2.15.0
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
def build_lstm_model(input_shape):
"""
构建LSTM模型
:param input_shape: 输入数据形状(时间步长,特征数)
:return: 编译好的LSTM模型
"""
model = Sequential([
# 第一层LSTM
# 50个神经元,返回序列用于堆叠下一层LSTM
LSTM(50, return_sequences=True, input_shape=input_shape),
Dropout(0.2), # 随机丢弃20%神经元,防止过拟合
# 第二层LSTM
LSTM(50, return_sequences=False),
Dropout(0.2),
# 全连接层
Dense(25, activation='relu'),
# 输出层:预测收益率
Dense(1)
])
# 编译模型
model.compile(
optimizer='adam', # Adam优化器,适合大多数场景
loss='mse', # 均方误差损失函数
metrics=['mae'] # 平均绝对误差评估指标
)
return model
# 示例:创建模型
# 假设输入形状为(20, 5),即20个时间步,5个特征
model = build_lstm_model((20, 5))
model.summary()
参数解释:
- LSTM(50): 50个神经元,既有足够容量,又不容易过拟合。
- Dropout(0.2): 随机丢弃20%神经元,防止过拟合。
- adam优化器: 自适应学习率,训练稳定。
- mse损失函数: 适合回归问题。
第四步:模型训练
4.1 数据预处理
# 所需库:scikit-learn==1.3.2
from sklearn.preprocessing import MinMaxScaler
import numpy as np
def prepare_data(features_df, lookback=20):
"""
准备训练数据
:param features_df: 特征DataFrame
:param lookback: 回溯窗口大小
:return: 训练集和测试集
"""
# 选择特征列
feature_cols = ['mom', 'volatility', 'volume_ratio', 'ma_spread', 'rsi']
target_col = 'target'
# 数据归一化
scaler_x = MinMaxScaler()
scaler_y = MinMaxScaler()
X = features_df[feature_cols].values
y = features_df[target_col].values.reshape(-1, 1)
X_scaled = scaler_x.fit_transform(X)
y_scaled = scaler_y.fit_transform(y)
# 创建时间序列样本
X_seq, y_seq = [], []
for i in range(lookback, len(X_scaled)):
X_seq.append(X_scaled[i-lookback:i])
y_seq.append(y_scaled[i])
X_seq = np.array(X_seq)
y_seq = np.array(y_seq)
# 划分训练集和测试集(80%训练,20%测试)
train_size = int(len(X_seq) * 0.8)
X_train, X_test = X_seq[:train_size], X_seq[train_size:]
y_train, y_test = y_seq[:train_size], y_seq[train_size:]
return X_train, X_test, y_train, y_test, scaler_x, scaler_y
# 示例:准备数据
X_train, X_test, y_train, y_test, scaler_x, scaler_y = prepare_data(features_df)
print(f"训练集大小:{X_train.shape}")
print(f"测试集大小:{X_test.shape}")
4.2 模型训练
def train_model(model, X_train, y_train, X_test, y_test, epochs=50, batch_size=32):
"""
训练LSTM模型
:param model: LSTM模型
:param X_train: 训练特征
:param y_train: 训练目标
:param X_test: 测试特征
:param y_test: 测试目标
:param epochs: 训练轮数
:param batch大小: 批次大小
:return: 训练历史
"""
# 早停机制,防止过拟合
early_stop = tf.keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=10, # 验证损失10轮不改善则停止
restore_best_weights=True
)
# 训练模型
history = model.fit(
X_train, y_train,
epochs=epochs,
batch_size=batch_size,
validation_data=(X_test, y_test),
callbacks=[early_stop],
verbose=1
)
return history
# 示例:训练模型
history = train_model(model, X_train, y_train, X_test, y_test)
# 评估模型
train_loss = model.evaluate(X_train, y_train, verbose=0)
test_loss = model.evaluate(X_test, y_test, verbose=0)
print(f"训练集损失:{train_loss[0]:.4f}")
print(f"测试集损失:{test_loss[0]:.4f}")
4.3 损失曲线可视化
# 所需库:matplotlib==3.8.2
import matplotlib.pyplot as plt
def plot_loss(history):
"""
绘制训练损失曲线
:param history: 训练历史
"""
plt.figure(figsize=(12, 4))
# 损失曲线
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='训练损失')
plt.plot(history.history['val_loss'], label='验证损失')
plt.title('模型损失')
plt.xlabel('轮数')
plt.ylabel('损失')
plt.legend()
# MAE曲线
plt.subplot(1, 2, 2)
plt.plot(history.history['mae'], label='训练MAE')
plt.plot(history.history['val_mae'], label='验证MAE')
plt.title('模型MAE')
plt.xlabel('轮数')
plt.ylabel('MAE')
plt.legend()
plt.tight_layout()
plt.show()
# 示例:绘制损失曲线
plot_loss(history)
损失曲线解读:
- 训练损失: 训练集上的损失,越低说明模型对训练数据拟合越好。
- 验证损失: 验证集上的损失,反映泛化能力。
- 理想状态: 训练损失和验证损失都下降并趋于稳定,两者差距不大。
第五步:策略回测
5.1 回测框架
class Backtester:
"""
简单回测框架
"""
def __init__(self, initial_capital=100000):
self.initial_capital = initial_capital
self.capital = initial_capital
self.positions = {} # 当前持仓
self.trades = [] # 交易记录
self.daily_returns = [] # 每日收益
def predict_return(self, model, features, scaler_x):
"""
使用模型预测收益率
:param model: 训练好的LSTM模型
:param features: 当前特征
:param scaler_x: 特征归一化器
:return: 预测收益率
"""
# 归一化特征
features_scaled = scaler_x.transform(features.reshape(1, -1))
# 创建序列(简化处理,实际使用时需要回溯窗口)
# 这里假设特征已经是回溯窗口后的输出
pred = model.predict(features_scaled.reshape(1, 1, -1), verbose=0)
return pred[0][0]
def run_backtest(self, df, model, scaler_x, threshold=0.02):
"""
运行回测
:param df: 价格DataFrame
:param model: 训练好的模型
:param scaler_x: 特征归一化器
:param threshold: 买卖阈值(预测收益率>阈值则买入)
"""
for i in range(20, len(df)): # 从20开始(回溯窗口)
date = df.index[i]
# 获取当前特征
current_df = df.iloc[i-20:i]
features = create_features(current_df)
if len(features) < 1:
continue
latest_features = features[['mom', 'volatility', 'volume_ratio',
'ma_spread', 'rsi']].iloc[-1].values
# 预测收益率
pred_return = self.predict_return(model, latest_features, scaler_x)
# 交易逻辑
if pred_return > threshold and len(self.positions) == 0:
# 买入信号
buy_price = df['close'].iloc[i]
shares = int(self.capital * 0.9 / buy_price) # 90%仓位
if shares > 0:
self.positions['stock'] = {
'shares': shares,
'buy_price': buy_price,
'buy_date': date
}
self.capital -= shares * buy_price
self.trades.append({
'date': date,
'action': 'buy',
'price': buy_price,
'shares': shares
})
elif pred_return < -threshold and 'stock' in self.positions:
# 卖出信号
sell_price = df['close'].iloc[i]
position = self.positions['stock']
self.capital += position['shares'] * sell_price
return_rate = (sell_price - position['buy_price']) / position['buy_price']
self.trades.append({
'date': date,
'action': 'sell',
'price': sell_price,
'shares': position['shares'],
'return': return_rate
})
del self.positions['stock']
# 记录每日总市值
total_value = self.capital
if 'stock' in self.positions:
total_value += self.positions['stock']['shares'] * df['close'].iloc[i]
self.daily_returns.append({
'date': date,
'total_value': total_value
})
return self.get_performance()
def get_performance(self):
"""
计算回测绩效
:return: 绩效指标
"""
if not self.daily_returns:
return {}
returns_df = pd.DataFrame(self.daily_returns)
returns_df['return'] = returns_df['total_value'].pct_change()
# 总收益率
total_return = (returns_df['total_value'].iloc[-1] / self.initial_capital) - 1
# 年化收益率(假设252个交易日)
n_days = len(returns_df)
annual_return = (1 + total_return) ** (252 / n_days) - 1
# 最大回撤
returns_df['cummax'] = returns_df['total_value'].cummax()
returns_df['drawdown'] = (returns_df['total_value'] - returns_df['cummax']) / returns_df['cummax']
max_drawdown = returns_df['drawdown'].min()
# 夏普比率(假设无风险利率2%)
excess_return = returns_df['return'].mean() * 252 - 0.02
volatility = returns_df['return'].std() * np.sqrt(252)
sharpe_ratio = excess_return / volatility if volatility > 0 else 0
return {
'total_return': total_return,
'annual_return': annual_return,
'max_drawdown': max_drawdown,
'sharpe_ratio': sharpe_ratio,
'trades': len(self.trades)
}
# 示例:运行回测
backtester = Backtester(initial_capital=100000)
performance = backtester.run_backtest(df, model, scaler_x)
print("回测结果:")
print(f"总收益率:{performance['total_return']:.2%}")
print(f"年化收益率:{performance['annual_return']:.2%}")
print(f"最大回撤:{performance['max_drawdown']:.2%}")
print(f"夏普比率:{performance['sharpe_ratio']:.2f}")
print(f"交易次数:{performance['trades']}")
5.2 基准对比
def get_benchmark_data(start_date, end_date):
"""
获取沪深300基准数据
:param start_date: 开始日期
:param end_date: 结束日期
:return: 沪深300收益率
"""
# 获取沪深300数据
df = ak.index_zh_a_hist(symbol="000300", period="daily",
start_date=start_date, end_date=end_date)
# 计算收益率
start_price = df['close'].iloc[0]
end_price = df['close'].iloc[-1]
benchmark_return = (end_price - start_price) / start_price
return benchmark_return
# 示例:获取基准收益率
benchmark_return = get_benchmark_data(start_date, end_date)
print(f"沪深300基准收益率:{benchmark_return:.2%}")
print(f"策略超额收益:{performance['total_return'] - benchmark_return:.2%}")
第六步:风险控制
6.1 止损策略
class RiskManager:
"""
风险管理器
"""
def __init__(self, stop_loss=0.05, take_profit=0.10, max_position=0.3):
"""
初始化风险参数
:param stop_loss: 止损比例(默认5%)
:param take_profit: 止盈比例(默认10%)
:param max_position: 最大单仓位(默认30%)
"""
self.stop_loss = stop_loss
self.take_profit = take_profit
self.max_position = max_position
def check_stop_loss(self, position, current_price):
"""
检查止损条件
:param position: 持仓信息
:param current_price: 当前价格
:return: 是否卖出
"""
if not position:
return False
return_rate = (current_price - position['buy_price']) / position['buy_price']
# 止损或止盈
if return_rate <= -self.stop_loss or return_rate >= self.take_profit:
return True
return False
def calculate_position_size(self, capital, stock_price, confidence):
"""
计算仓位大小
:param capital: 总资金
:param stock_price: 股票价格
:param confidence: 模型置信度(0-1)
:return: 买入股数
"""
# 仓位大小 = 总资金 × 最大仓位比例 × 置信度
position_value = capital * self.max_position * confidence
shares = int(position_value / stock_price)
return shares
# 示例:使用风险管理器
risk_mgr = RiskManager(stop_loss=0.05, take_profit=0.10)
# 实际交易中,检查止损
# if risk_mgr.check_stop_loss(position, current_price):
# sell()
6.2 仓位管理
def position_sizing(pred_return, confidence, capital, max_position=0.3):
"""
动态仓位管理
:param pred_return: 预测收益率
:param confidence: 模型置信度
:param capital: 总资金
:param max_position: 最大仓位比例
:return: 仓位比例(0-1)
"""
# 基础仓位
base_position = 0.1
# 根据预测收益率和置信度调整仓位
if pred_return > 0.05 and confidence > 0.7:
position = min(base_position * 3, max_position) # 高置信度、高收益率,重仓
elif pred_return > 0.02 and confidence > 0.5:
position = base_position * 2 # 中等仓位
else:
position = base_position # 低仓位
return position
诚实声明:本策略的局限性
在你急于实盘交易之前,我必须诚实说明本策略的局限性:
1. 过拟合风险
LSTM模型容易过拟合,特别是数据量较小的情况下。回测结果可能看起来很好,但实盘表现可能很差。
解决方案:
- 使用更多数据训练(至少3-5年)。
- 增加Dropout比例。
- 使用交叉验证。
2. 未考虑交易成本
上面的回测未考虑:
- 佣金(通常万3)
- 印花税(卖出时千1)
- 滑点
实际影响: 高频交易会侵蚀大部分收益。
3. A股流动性问题
模型假设能立即成交,但现实中:
- 小盘股流动性差
- 大单会冲击价格
4. 市场机制变化
模型基于历史数据训练,但市场机制会变化:
- 政策调整
- 宏观环境变化
- 投资者结构变化
引入EasyClaw:更便捷的量化工具
如果你觉得上面的代码太复杂,我推荐使用 EasyClaw。这是一款专为量化投资设计的AI自动化工具,集成了多种金融技能。
EasyClaw的金融炒股功能:
- 妙想资讯搜索:监控东方财富新闻,自动提醒。
- 妙想金融数据:查询A股实时行情、财务指标、股东数据等股票金融数据。
- 股价速查:一键获取全球股票实时行情,秒级返回涨跌与成交量,助力投资决策。
- 妙想智能选股:从东方财富股票实时数据库中筛选符合要求的A股。
- 妙想模拟炒股:在东方财富模拟账户中进行A股买卖、查询持仓与资金,零风险练习股票投资策略。
- 量化策略回测分析:策略开发、回测、风险指标、组合优化一站完成,结果仅供研究参考。
- 金融图表生成:根据数据生成高质量图表图片,支持折线、柱状、K线、饼图、热力图等多种类型。
- Tushare金融数据:通过Tushare Pro接口获取A股、港股、美股、基金、期货、债券及宏观经济等220+类金融数据。
EasyClaw下载地址: https://easyclaw.cn/?f=239
EasyClaw的优势是一键安装、无需写代码,非常适合想快速入门量化投资的新手。
本文仅供参考,不构成任何投资建议。股市有风险,投资需谨慎。文中提及的任何策略均不代表未来收益保证。
觉得有帮助的麻烦点下好文要顶,欢迎评论区交流!

浙公网安备 33010602011771号