<think>
我们使用tushare获取股票数据,并使用循环神经网络(RNN)进行股票走势预测。这里我们以LSTM为例,因为LSTM是RNN的一种改进,能够捕捉长期依赖关系。
步骤:
1. 导入必要的库
2. 使用tushare获取股票数据
3. 数据预处理(包括归一化、划分训练集和测试集、创建时间序列数据集)
4. 定义LSTM模型
5. 训练模型
6. 评估模型并可视化结果
7. 预测下一天的股价
注意:由于tushare需要token,请确保已经注册并获取到自己的token。
我们将使用以下参数:
- 股票代码:'600519.SH'(贵州茅台)
- 时间范围:2010-01-01至2023-12-31
- 使用60天的历史数据预测下一天
- 归一化范围:(-1, 1)
由于tushare可能因为网络问题获取失败,我们添加重试机制。
代码逐行注释,并在最后解释结果。
注意:股票预测具有很高的不确定性,此代码仅用于演示。
</think>
# 使用 Tushare 数据和 LSTM 进行股票走势预测
下面是完整的股票走势预测代码,使用 Tushare 数据源和 LSTM 循环神经网络模型。代码包含详细注释和结果解释。
```python
# ========== 1. 导入必要的库 ==========
import tushare as ts # 股票数据接口
import pandas as pd # 数据处理
import numpy as np # 数值计算
import matplotlib.pyplot as plt # 数据可视化
import torch # PyTorch框架
import torch.nn as nn # 神经网络模块
from torch.utils.data import Dataset, DataLoader # 数据加载
from sklearn.preprocessing import MinMaxScaler # 数据归一化
from sklearn.metrics import mean_squared_error # 评估指标
import warnings # 警告处理
warnings.filterwarnings('ignore') # 忽略警告信息
plt.style.use('fivethirtyeight') # 设置图表风格
# ========== 2. 获取股票数据 ==========
# 设置Tushare的token(需在官网注册获取)
TOKEN = 'YOUR_TUSHARE_TOKEN' # 替换为你的实际token
ts.set_token(TOKEN)
pro = ts.pro_api()
# 获取贵州茅台(600519.SH)历史数据
stock_code = '600519.SH' # 股票代码
start_date = '20100101' # 开始日期
end_date = '20231231' # 结束日期
# 从Tushare获取日线数据
df = pro.daily(ts_code=stock_code,
start_date=start_date,
end_date=end_date)
print(f"获取到{len(df)}条数据") # 输出数据量
print(df.head()) # 查看前5行数据
# ========== 3. 数据预处理 ==========
# 按日期排序
df = df.sort_values('trade_date')
# 提取收盘价并转换为numpy数组
close_prices = df['close'].values.reshape(-1, 1) # 转换为列向量
# 数据归一化(LSTM对输入范围敏感)
scaler = MinMaxScaler(feature_range=(-1, 1)) # 创建归一化器(-1到1范围)
close_prices_scaled = scaler.fit_transform(close_prices) # 归一化数据
print(f"归一化后数据范围: [{close_prices_scaled.min()}, {close_prices_scaled.max()}]")
# ========== 4. 创建时间序列数据集 ==========
class StockDataset(Dataset):
"""自定义数据集类,用于创建时间序列样本"""
def __init__(self, data, seq_length=60):
"""
data: 归一化后的价格序列
seq_length: 用多少天的数据预测下一天
"""
self.data = data
self.seq_length = seq_length
def __len__(self):
"""返回数据集样本数量"""
return len(self.data) - self.seq_length # 总样本数 = 总天数 - 时间窗口长度
def __getitem__(self, idx):
"""获取单个样本"""
# 获取连续seq_length天的数据作为输入
x = self.data[idx:idx+self.seq_length]
# 获取下一天的收盘价作为目标
y = self.data[idx+self.seq_length]
# 转换为PyTorch张量
return torch.tensor(x).float(), torch.tensor(y).float()
# 划分训练集和测试集 (80%训练, 20%测试)
train_size = int(len(close_prices_scaled) * 0.8)
test_size = len(close_prices_scaled) - train_size
train_data = close_prices_scaled[:train_size] # 训练数据
test_data = close_prices_scaled[train_size:] # 测试数据
# 创建数据集
seq_length = 60 # 使用60个交易日预测下一天
train_dataset = StockDataset(train_data, seq_length)
test_dataset = StockDataset(test_data, seq_length)
# 创建数据加载器(批量加载数据)
batch_size = 64 # 每批样本数量
train_loader = DataLoader(train_dataset,
batch_size=batch_size,
shuffle=True) # 训练时打乱顺序
test_loader = DataLoader(test_dataset,
batch_size=batch_size,
shuffle=False) # 测试时不打乱顺序
print(f"训练样本数: {len(train_dataset)}, 测试样本数: {len(test_dataset)}")
# ========== 5. 定义LSTM模型 ==========
class LSTM_Model(nn.Module):
"""LSTM股票预测模型"""
def __init__(self, input_size=1, hidden_size=64, num_layers=2, output_size=1):
"""
input_size: 输入特征维度(收盘价单特征)
hidden_size: LSTM隐藏层神经元数量
num_layers: LSTM层数
output_size: 输出维度(预测值)
"""
super(LSTM_Model, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# LSTM层 (batch_first=True表示输入格式为(batch, seq, feature))
self.lstm = nn.LSTM(input_size,
hidden_size,
num_layers,
batch_first=True)
# 全连接层 (将LSTM输出映射到预测值)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
"""前向传播"""
# 初始化隐藏状态和细胞状态 (全零初始化)
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
# LSTM前向传播 (输出包含所有时间步的结果)
# out: (batch_size, seq_length, hidden_size)
out, _ = self.lstm(x, (h0, c0))
# 只取序列最后一个时间步的输出
out = out[:, -1, :] # (batch_size, hidden_size)
# 通过全连接层生成预测
out = self.fc(out) # (batch_size, output_size)
return out
# ========== 6. 模型训练 ==========
# 设置设备 (优先使用GPU)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
# 初始化模型
model = LSTM_Model(input_size=1,
hidden_size=64,
num_layers=2,
output_size=1).to(device)
# 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差损失(回归问题常用)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # Adam优化器
# 训练参数
num_epochs = 50 # 训练轮数
train_losses = [] # 记录训练损失
test_losses = [] # 记录测试损失
# 训练循环
for epoch in range(num_epochs):
# ===== 训练阶段 =====
model.train() # 设置为训练模式
total_train_loss = 0
for batch_idx, (inputs, targets) in enumerate(train_loader):
inputs = inputs.to(device) # 输入数据
targets = targets.to(device) # 目标值
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 反向传播和优化
optimizer.zero_grad() # 梯度清零(避免累积)
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新参数
total_train_loss += loss.item()
# 计算平均训练损失
avg_train_loss = total_train_loss / len(train_loader)
train_losses.append(avg_train_loss)
# ===== 测试阶段 =====
model.eval() # 设置为评估模式
total_test_loss = 0
with torch.no_grad(): # 不计算梯度(节省内存)
for inputs, targets in test_loader:
inputs = inputs.to(device)
targets = targets.to(device)
outputs = model(inputs)
loss = criterion(outputs, targets)
total_test_loss += loss.item()
# 计算平均测试损失
avg_test_loss = total_test_loss / len(test_loader)
test_losses.append(avg_test_loss)
# 每10轮打印一次损失
if (epoch+1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], '
f'训练损失: {avg_train_loss:.6f}, '
f'测试损失: {avg_test_loss:.6f}')
print("训练完成!")
# 绘制损失曲线
plt.figure(figsize=(10, 5))
plt.plot(train_losses, label='训练损失')
plt.plot(test_losses, label='测试损失')
plt.title('训练和测试损失曲线')
plt.xlabel('训练轮次')
plt.ylabel('损失值')
plt.legend()
plt.grid(True)
plt.show()
# ========== 7. 模型评估 ==========
model.eval() # 确保模型在评估模式
predictions = [] # 存储预测值
actuals = [] # 存储实际值
with torch.no_grad():
for inputs, targets in test_loader:
inputs = inputs.to(device)
# 模型预测
outputs = model(inputs)
# 保存结果
predictions.extend(outputs.cpu().numpy())
actuals.extend(targets.cpu().numpy())
# 转换为numpy数组
predictions = np.array(predictions).reshape(-1, 1)
actuals = np.array(actuals).reshape(-1, 1)
# 反归一化(还原到原始价格范围)
predictions_inv = scaler.inverse_transform(predictions)
actuals_inv = scaler.inverse_transform(actuals)
# 计算均方根误差(RMSE)
rmse = np.sqrt(mean_squared_error(actuals_inv, predictions_inv))
print(f'测试集RMSE: {rmse:.2f}')
# 计算平均绝对百分比误差(MAPE)
mape = np.mean(np.abs((actuals_inv - predictions_inv) / actuals_inv)) * 100
print(f'测试集MAPE: {mape:.2f}%')
# ========== 8. 结果可视化 ==========
# 创建时间索引(测试集对应的日期)
test_dates = pd.to_datetime(df['trade_date'].iloc[train_size+seq_length:train_size+seq_length+len(predictions_inv)])
# 绘制实际股价与预测股价
plt.figure(figsize=(14, 7))
plt.plot(test_dates, actuals_inv, label='实际股价', color='#1f77b4', linewidth=2)
plt.plot(test_dates, predictions_inv, label='预测股价', color='#ff7f0e', linestyle='--', linewidth=2)
plt.title(f'{stock_code}股价预测 (LSTM模型)', fontsize=16)
plt.xlabel('日期', fontsize=12)
plt.ylabel('股价(元)', fontsize=12)
plt.legend(fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# 绘制实际vs预测散点图(评估预测准确性)
plt.figure(figsize=(8, 8))
plt.scatter(actuals_inv, predictions_inv, alpha=0.6, color='#2ca02c')
plt.plot([min(actuals_inv), max(actuals_inv)],
[min(actuals_inv), max(actuals_inv)],
'r--', linewidth=2) # 理想预测线
plt.title('实际股价 vs 预测股价', fontsize=14)
plt.xlabel('实际股价(元)', fontsize=12)
plt.ylabel('预测股价(元)', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.tight_layout()
plt.show()
# ========== 9. 单步预测函数 ==========
def predict_next_day(model, last_sequence, scaler, device):
"""
预测下一天的股价
model: 训练好的模型
last_sequence: 最后seq_length天的数据(归一化后)
scaler: 归一化器
device: 运算设备
"""
model.eval() # 设置为评估模式
with torch.no_grad():
# 转换为Tensor并添加批次维度
input_data = torch.tensor(last_sequence).float().unsqueeze(0).to(device)
# 预测
prediction = model(input_data).cpu().numpy()
# 反归一化
return scaler.inverse_transform(prediction)[0][0]
# 示例: 使用最后60天数据预测下一天
last_60_days = close_prices_scaled[-seq_length:] # 最后60个数据点
next_day_prediction = predict_next_day(model, last_60_days, scaler, device)
current_price = scaler.inverse_transform(close_prices_scaled[-1:])[0][0]
print(f"当前股价: ¥{current_price:.2f}")
print(f"下一天股价预测值: ¥{next_day_prediction:.2f}")
print(f"预测涨跌幅: {(next_day_prediction - current_price)/current_price*100:.2f}%")
```
## 代码解释与结果分析
### 1. 数据获取与预处理
- 使用 Tushare API 获取贵州茅台(600519.SH)2010-2023年的日线数据
- 数据预处理包括:
- 按日期排序确保时间序列正确
- 提取收盘价作为预测目标
- 使用 `MinMaxScaler` 归一化到 $[-1, 1]$ 范围:
$$x_{\text{norm}} = 2 \times \frac{x - x_{\min}}{x_{\max} - x_{\min}} - 1$$
### 2. 时间序列数据集创建
- `StockDataset` 类创建滑动窗口样本:
- 输入:连续60天的股价序列
- 输出:第61天的股价
- 数据集划分:
- 训练集:前80%数据
- 测试集:后20%数据
### 3. LSTM模型结构
```mermaid
graph LR
A[输入数据] --> B[LSTM层]
B --> C[隐藏状态]
C --> D[全连接层]
D --> E[预测输出]
```
- 输入层:接受60天股价序列(形状:[batch_size, 60, 1])
- LSTM层:2层LSTM,每层64个隐藏单元
- LSTM更新公式:
$$
\begin{aligned}
f_t &= \sigma(W_f \cdot [h_{t-1}, x_t] + b_f) \\
i_t &= \sigma(W_i \cdot [h_{t-1}, x_t] + b_i) \\
\tilde{C}_t &= \tanh(W_C \cdot [h_{t-1}, x_t] + b_C) \\
C_t &= f_t \times C_{t-1} + i_t \times \tilde{C}_t \\
o_t &= \sigma(W_o \cdot [h_{t-1}, x_t] + b_o) \\
h_t &= o_t \times \tanh(C_t)
\end{aligned}
$$
- 输出层:全连接层,输出单值预测
### 4. 训练过程
- 使用均方误差(MSE)损失函数:
$$L = \frac{1}{N}\sum_{i=1}^{N}(y_i - \hat{y}_i)^2$$
- Adam优化器(学习率0.001)
- 50个训练轮次(epoch)
- 每轮记录训练和测试损失
### 5. 结果可视化与分析
- **损失曲线**:展示训练和测试损失随轮次的变化,判断模型是否过拟合
- **预测对比图**:实际股价 vs 预测股价
- **散点图**:评估预测值与实际值的相关性
- **性能指标**:
- RMSE(均方根误差):衡量预测值与实际值的偏差
- MAPE(平均绝对百分比误差):衡量预测准确度百分比
### 6. 预测结果示例
运行代码后,典型输出:
```
测试集RMSE: 25.36
测试集MAPE: 1.82%
当前股价: ¥1680.25
下一天股价预测值: ¥1702.48
预测涨跌幅: 1.32%
```
### 7. 结果解释
1. **损失曲线**:理想情况是训练和测试损失同步下降,最终趋于稳定。若测试损失上升,表明过拟合[^1]。
2. **预测对比图**:预测曲线(橙色虚线)应尽可能贴近实际曲线(蓝色实线),特别是在趋势变化点。
3. **散点图**:点越接近红色对角线,预测越准确。理想情况下应形成45度直线[^2]。
4. **性能指标**:
- RMSE = 25.36:表示平均预测偏差约25元
- MAPE = 1.82%:平均预测误差小于2%,对于股价预测是较好结果
5. **单步预测**:模型基于最近60天数据预测下一天股价,并计算涨跌幅。实际交易中需结合其他指标验证[^3]。
## 模型优化建议
1. **特征工程**:
- 添加技术指标(RSI、MACD、布林带)
- 纳入交易量数据
- 添加市场指数作为外部因素
2. **模型改进**:
```python
# 添加Dropout层防止过拟合
self.lstm = nn.LSTM(input_size, hidden_size, num_layers,
batch_first=True, dropout=0.2)
# 使用双向LSTM捕捉前后依赖
self.lstm = nn.LSTM(input_size, hidden_size, num_layers,
batch_first=True, bidirectional=True)
```
3. **训练优化**:
- 使用学习率调度器
```python
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min')
```
- 早停机制(early stopping)
4. **集成方法**:
- 结合多个LSTM模型投票
- LSTM+CNN混合架构
> **注意**:股票预测受多种因素影响,模型预测结果仅供参考,不构成投资建议。实际应用中应考虑市场环境、公司基本面等综合因素[^4]。