CNN-LSTM股票预测模型实现(Python)
下面是结合CNN和LSTM的混合模型来预测股票价格走势。这个模型使用技术指标作为特征,并采用多变量时间序列预测方法。
1. 数据准备与预处理
首先安装必要的库:
pip install yfinance pandas numpy matplotlib tensorflow ta scikit-learn
1.1 获取股票数据和技术指标
import yfinance as yf
import pandas as pd
import numpy as np
import ta
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
# 下载股票数据
def download_stock_data(ticker, start_date, end_date):
data = yf.download(ticker, start=start_date, end=end_date)
return data
# 计算技术指标
def add_technical_indicators(df):
# 动量指标
df['rsi'] = ta.momentum.RSIIndicator(df['Close'], window=14).rsi()
df['macd'] = ta.trend.MACD(df['Close'], window_slow=26, window_fast=12).macd()
# 趋势指标
df['ema_20'] = ta.trend.EMAIndicator(df['Close'], window=20).ema_indicator()
df['ema_50'] = ta.trend.EMAIndicator(df['Close'], window=50).ema_indicator()
# 波动率指标
df['atr'] = ta.volatility.AverageTrueRange(
df['High'], df['Low'], df['Close'], window=14).average_true_range()
# 成交量指标
df['obv'] = ta.volume.OnBalanceVolumeIndicator(
df['Close'], df['Volume']).on_balance_volume()
return df
# 数据预处理
def preprocess_data(df, look_back=60, test_size=0.2):
# 选择特征列
feature_columns = ['Close', 'rsi', 'macd', 'ema_20', 'ema_50', 'atr', 'obv']
df = df[feature_columns]
# 填充缺失值
df = df.fillna(method='bfill')
# 归一化
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(df)
# 创建时间序列数据集
X, y = [], []
for i in range(look_back, len(scaled_data)):
X.append(scaled_data[i-look_back:i, :])
y.append(scaled_data[i, 0]) # 只预测Close价格
X, y = np.array(X), np.array(y)
# 划分训练集和测试集
split = int((1 - test_size) * len(X))
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
return X_train, X_test, y_train, y_test, scaler
# 主流程
ticker = 'AAPL'
start_date = '2015-01-01'
end_date = '2023-01-01'
# 获取并处理数据
stock_data = download_stock_data(ticker, start_date, end_date)
stock_data = add_technical_indicators(stock_data)
X_train, X_test, y_train, y_test, scaler = preprocess_data(stock_data)
print(f"训练集形状: {X_train.shape}, 测试集形状: {X_test.shape}")
2. 构建CNN-LSTM模型
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense, Dropout, Flatten
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping
def build_cnn_lstm_model(input_shape):
model = Sequential()
# CNN部分 - 提取局部特征
model.add(Conv1D(filters=64, kernel_size=3, activation='relu',
input_shape=input_shape))
model.add(MaxPooling1D(pool_size=2))
model.add(Dropout(0.2))
model.add(Conv1D(filters=128, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=2))
model.add(Dropout(0.2))
# LSTM部分 - 捕捉时间依赖
model.add(LSTM(units=100, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(units=100))
model.add(Dropout(0.2))
# 全连接层
model.add(Dense(units=50, activation='relu'))
model.add(Dense(units=1))
# 编译模型
optimizer = Adam(learning_rate=0.001)
model.compile(optimizer=optimizer, loss='mean_squared_error')
return model
# 获取输入形状
n_timesteps, n_features = X_train.shape[1], X_train.shape[2]
model = build_cnn_lstm_model((n_timesteps, n_features))
model.summary()
# 训练模型
early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
history = model.fit(
X_train, y_train,
epochs=100,
batch_size=32,
validation_data=(X_test, y_test),
callbacks=[early_stopping],
verbose=1)
# 绘制训练曲线
plt.figure(figsize=(12, 6))
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.title('Model Loss Progression')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend()
plt.show()
3. 模型评估与预测
# 预测
y_pred = model.predict(X_test)
# 反归一化
y_test_actual = scaler.inverse_transform(np.concatenate((
y_test.reshape(-1, 1),
np.zeros((len(y_test), X_test.shape[2]-1)), axis=1))[:, 0]
y_pred_actual = scaler.inverse_transform(np.concatenate((
y_pred.reshape(-1, 1),
np.zeros((len(y_pred), X_test.shape[2]-1)), axis=1))[:, 0]
# 计算评估指标
from sklearn.metrics import mean_absolute_error, mean_squared_error
def calculate_metrics(y_true, y_pred):
mae = mean_absolute_error(y_true, y_pred)
mse = mean_squared_error(y_true, y_pred)
rmse = np.sqrt(mse)
mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100
print(f"MAE: {mae:.2f}")
print(f"MSE: {mse:.2f}")
print(f"RMSE: {rmse:.2f}")
print(f"MAPE: {mape:.2f}%")
return mae, mse, rmse, mape
calculate_metrics(y_test_actual, y_pred_actual)
# 可视化预测结果
plt.figure(figsize=(14, 7))
plt.plot(y_test_actual, label='Actual Price', color='blue')
plt.plot(y_pred_actual, label='Predicted Price', color='red', alpha=0.7)
plt.title('Stock Price Prediction')
plt.xlabel('Time')
plt.ylabel('Price')
plt.legend()
plt.show()
# 预测未来n天
def predict_future(model, last_sequence, scaler, days_to_predict=5):
future_predictions = []
current_sequence = last_sequence.copy()
for _ in range(days_to_predict):
# 预测下一天
next_pred = model.predict(current_sequence.reshape(1, n_timesteps, n_features))
# 保存预测结果
next_pred_full = np.zeros((1, n_features))
next_pred_full[0, 0] = next_pred[0, 0]
future_predictions.append(next_pred_full)
# 更新序列
current_sequence = np.roll(current_sequence, -1, axis=0)
current_sequence[-1] = next_pred_full
# 反归一化
future_predictions = np.array(future_predictions).reshape(-1, n_features)
future_prices = scaler.inverse_transform(future_predictions)[:, 0]
return future_prices
# 使用最后一段数据作为初始序列
last_sequence = X_test[-1]
future_prices = predict_future(model, last_sequence, scaler, days_to_predict=5)
print(f"未来5天的预测价格: {future_prices}")
4. 模型改进建议
特征工程优化:
添加更多技术指标(如布林带、KDJ等)
加入基本面数据(市盈率、财报数据等)
考虑市场情绪指标(新闻情感分析)
模型架构改进:
# 更复杂的混合架构示例
def build_advanced_model(input_shape):
input_layer = Input(shape=input_shape)
# CNN分支
cnn = Conv1D(64, 3, activation='relu')(input_layer)
cnn = MaxPooling1D(2)(cnn)
cnn = Conv1D(128, 3, activation='relu')(cnn)
cnn = GlobalMaxPooling1D()(cnn)
# LSTM分支
lstm = LSTM(100, return_sequences=True)(input_layer)
lstm = LSTM(100)(lstm)
# 合并
merged = concatenate([cnn, lstm])
merged = Dense(100, activation='relu')(merged)
output = Dense(1)(merged)
model = Model(inputs=input_layer, outputs=output)
model.compile(optimizer=Adam(0.001), loss='mse')
return model
交易策略整合:
将预测结果与风险管理规则结合
添加仓位大小控制
设置止损止盈阈值
回测系统:
# 简易回测框架
def backtest(predictions, actual_prices, initial_capital=10000):
positions = []
capital = initial_capital
for i in range(1, len(predictions)):
if predictions[i] > actual_prices[i-1]: # 预测上涨
shares = capital // actual_prices[i]
capital -= shares * actual_prices[i]
positions.append(shares)
else: # 预测下跌
if positions:
capital += positions.pop() * actual_prices[i]
# 清算剩余头寸
if positions:
capital += positions[-1] * actual_prices[-1]
return capital
5. 注意事项
市场不确定性:股票市场受众多不可预测因素影响,模型仅作为辅助工具
过拟合风险:使用交叉验证和正则化技术防止过拟合
实时数据:生产环境需要接入实时数据流
交易成本:实际交易需考虑手续费、滑点等成本
这个CNN-LSTM模型提供了股票预测的基础框架,你可以进一步优化特征选择、模型结构和超参数来提升性能。记得在实际交易前进行充分的回测和模拟交易验证。