python
# 导入PyTorch相关库
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
# 导入数值计算和数据处理库
import numpy as np
import pandas as pd
# 导入可视化库
import matplotlib.pyplot as plt
import seaborn as sns
# 导入数据预处理库
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
# 导入系统库
import warnings
import os
from datetime import datetime, timedelta
# 导入金融数据获取库
import yfinance as yf
# 导入进度条库
from tqdm import tqdm
# 导入JSON处理库
import json
# 设置matplotlib中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 忽略警告信息
warnings.filterwarnings('ignore')
class StockDataProcessor:
"""股票数据处理类:负责数据下载、特征工程和数据预处理"""
def __init__(self,
sequence_length=60, # 时间序列长度(历史数据窗口大小)
prediction_days=5, # 预测未来天数
feature_columns=['Open', 'High', 'Low', 'Close', 'Volume'], # 特征列
target_column='Close', # 目标列(预测目标)
scaler_type='minmax', # 标准化类型('minmax'或'standard')
train_ratio=0.8, # 训练集比例
val_ratio=0.1): # 验证集比例
"""
初始化数据处理器
Args:
sequence_length: 序列长度,用于LSTM输入
prediction_days: 预测天数
feature_columns: 特征列名
target_column: 目标列名
scaler_type: 标准化类型 ('minmax' 或 'standard')
train_ratio: 训练集比例
val_ratio: 验证集比例
"""
# 保存初始化参数
self.sequence_length = sequence_length #sequence_length: 序列长度,用于LSTM输入
self.prediction_days = prediction_days #预测天数
self.feature_columns = feature_columns #特征列名
self.target_column = target_column #目标列名
self.scaler_type = scaler_type #标准化类型
self.train_ratio = train_ratio #train_ratio
self.val_ratio = val_ratio #val_ratio
# 初始化标准化器
# 使用 minmax(适合价格预测)
if scaler_type == 'minmax':
"""
特点:
适用场景:数据分布不遵循正态分布时(如股票价格波动剧烈时)。
对离群点敏感:如果数据中存在极端值(如股价暴涨暴跌),会导致缩放后的数据集中在某一端。
保留原始分布形状:仅调整范围,不改变分布形态。
"""
self.feature_scaler = MinMaxScaler() # 特征缩放到 [0, 1]
self.target_scaler = MinMaxScaler() # 目标变量缩放到 [0, 1]
else:
"""
特点:
适用场景:数据近似服从正态分布时(如股票收益率)。
对离群点鲁棒性更强:均值和标准差受极端值影响较小。
可能改变分布形状:即使原始数据非正态,标准化后可能更接近正态
"""
# 使用 standard(适合收益率或波动率预测)
self.feature_scaler = StandardScaler() # 特征标准化器 # 特征标准化为均值0、方差1
self.target_scaler = StandardScaler() # 目标变量标准化器 # 目标变量标准化为均值0、方差1
def download_stock_data(self, symbol, start_date, end_date):
"""下载股票数据"""
try:
# 使用yfinance下载股票数据
stock_data = yf.download(symbol, start=start_date, end=end_date)
return stock_data
except Exception as e:
print(f"下载数据失败: {e}")
return None
def add_technical_indicators(self, data):
"""添加技术指标"""
df = data.copy()
# 移动平均线指标(MA)
df['MA5'] = df['Close'].rolling(window=5).mean() # 5日均线
df['MA10'] = df['Close'].rolling(window=10).mean() # 10日均线
df['MA20'] = df['Close'].rolling(window=20).mean() # 20日均线
df['MA50'] = df['Close'].rolling(window=50).mean() # 50日均线
# RSI指标(相对强弱指数)
delta = df['Close'].diff() # 计算收盘价变化
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean() # 计算上涨幅度均值
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean() # 计算下跌幅度均值
rs = gain / loss # 计算RS值
df['RSI'] = 100 - (100 / (1 + rs)) # 计算RSI
# MACD指标(指数移动平均线)
exp1 = df['Close'].ewm(span=12, adjust=False).mean() # 快速EMA
exp2 = df['Close'].ewm(span=26, adjust=False).mean() # 慢速EMA
df['MACD'] = exp1 - exp2 # DIF线
df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean() # DEA线
df['MACD_Histogram'] = df['MACD'] - df['MACD_Signal'] # MACD柱
# 布林带指标(Bollinger Bands)
df['BB_Middle'] = df['Close'].rolling(window=20).mean() # 中轨
df['BB_Upper'] = df['BB_Middle'] + 2 * df['Close'].rolling(window=20).std() # 上轨
df['BB_Lower'] = df['BB_Middle'] - 2 * df['Close'].rolling(window=20).std() # 下轨
# 成交量移动平均
df['Volume_MA'] = df['Volume'].rolling(window=10).mean() # 10日成交量均线
# 价格波动率(标准差)
df['Volatility'] = df['Close'].rolling(window=10).std() # 10日价格波动率
# 收益率(收益率计算)
df['Returns'] = df['Close'].pct_change() # 百分比变化
# K线形态特征(上下影线和实体大小)
df['Upper_Shadow'] = df['High'] - df[['Open', 'Close']].max(axis=1) # 上影线
df['Lower_Shadow'] = df[['Open', 'Close']].min(axis=1) - df['Low'] # 下影线
df['Body_Size'] = abs(df['Close'] - df['Open']) # 实体大小
# 删除含有NaN的行
df = df.dropna()
return df
def prepare_data(self, data):
"""准备训练数据"""
# 添加技术指标
data = self.add_technical_indicators(data)
# 更新特征列(包含新添加的技术指标)
self.feature_columns = [col for col in data.columns if col != self.target_column]
# 特征和目标变量提取
features = data[self.feature_columns].values # 特征矩阵
target = data[self.target_column].values.reshape(-1, 1) # 目标变量
# 特征标准化
features_scaled = self.feature_scaler.fit_transform(features) # 对特征进行标准化
target_scaled = self.target_scaler.fit_transform(target) # 对目标变量进行标准化
# 创建序列数据(将数据转换为LSTM需要的格式)
X, y = [], []
for i in range(self.sequence_length, len(features_scaled) - self.prediction_days + 1):
# 提取历史序列
X.append(features_scaled[i-self.sequence_length:i])
# 提取预测目标(未来prediction_days天的数据)
y.append(target_scaled[i:i+self.prediction_days].flatten())
X = np.array(X) # 转换为numpy数组
y = np.array(y)
# 分割数据集(训练集、验证集、测试集)
train_size = int(len(X) * self.train_ratio) # 计算训练集大小
val_size = int(len(X) * self.val_ratio) # 计算验证集大小
# 切分数据集
X_train = X[:train_size] # 训练特征
y_train = y[:train_size] # 训练标签
X_val = X[train_size:train_size + val_size] # 验证特征
y_val = y[train_size:train_size + val_size] # 验证标签
X_test = X[train_size + val_size:] # 测试特征
y_test = y[train_size + val_size:] # 测试标签
return (X_train, y_train), (X_val, y_val), (X_test, y_test) # 返回数据集
def inverse_transform_target(self, scaled_data):
"""反标准化目标变量"""
return self.target_scaler.inverse_transform(scaled_data.reshape(-1, 1)).flatten()
class StockDataset(Dataset):
"""PyTorch数据集封装,用于加载股票数据"""
def __init__(self, X, y):
"""
初始化数据集
Args:
X: 特征数据 (numpy array)
y: 标签数据 (numpy array)
"""
self.X = torch.FloatTensor(X) # 转换为PyTorch张量
self.y = torch.FloatTensor(y)
def __len__(self):
"""返回数据集大小"""
return len(self.X)
def __getitem__(self, idx):
"""
获取单个样本
Args:
idx: 样本索引
Returns:
tuple: (特征张量, 标签张量)
"""
return self.X[idx], self.y[idx]
class LSTMStockPredictor(nn.Module):
"""LSTM股价预测模型:包含双向LSTM、注意力机制和残差连接的复杂网络结构"""
def __init__(self,
input_size, # 输入特征维度
hidden_size=128, # LSTM隐藏层大小
num_layers=3, # LSTM层数
output_size=5, # 输出维度(预测天数)
dropout=0.2, # Dropout概率
bidirectional=True, # 是否使用双向LSTM
use_attention=True, # 是否使用注意力机制
use_residual=True): # 是否使用残差连接
"""
初始化LSTM模型
Args:
input_size: 输入特征数
hidden_size: 隐藏层大小
num_layers: LSTM层数
output_size: 输出大小(预测天数)
dropout: Dropout率
bidirectional: 是否使用双向LSTM
use_attention: 是否使用注意力机制
use_residual: 是否使用残差连接
"""
super(LSTMStockPredictor, self).__init__()
# 保存模型参数
self.hidden_size = hidden_size #隐藏层大小
self.num_layers = num_layers #LSTM层数
self.bidirectional = bidirectional #是否使用双向LSTM
self.use_attention = use_attention #是否使用注意力机制
self.use_residual = use_residual #是否使用残差连接
# 输入投影层:将输入特征映射到隐藏层维度
self.input_projection = nn.Linear(input_size, hidden_size)
# LSTM层:核心序列处理模块
self.lstm = nn.LSTM(
input_size=hidden_size, # 输入维度
hidden_size=hidden_size, # 隐藏层维度
num_layers=num_layers, # LSTM层数
batch_first=True, # 输入数据格式(batch, seq, feature)
dropout=dropout if num_layers > 1 else 0, # 除最后一层外的Dropout
bidirectional=bidirectional # 双向LSTM
)
# 确定LSTM输出维度(考虑双向性)
lstm_output_size = hidden_size * 2 if bidirectional else hidden_size
# 多头注意力机制(可选)
if use_attention:
self.attention = nn.MultiheadAttention(
embed_dim=lstm_output_size, # 嵌入维度
num_heads=8, # 头数
dropout=dropout, # Dropout率
batch_first=True # 输入数据格式
)
# 批归一化层
self.batch_norm1 = nn.BatchNorm1d(lstm_output_size) # 第一层BN
self.batch_norm2 = nn.BatchNorm1d(hidden_size) # 第二层BN
# 全连接层
self.fc1 = nn.Linear(lstm_output_size, hidden_size) # 第一层全连接
self.fc2 = nn.Linear(hidden_size, hidden_size // 2) # 第二层全连接
self.fc3 = nn.Linear(hidden_size // 2, output_size) # 输出层
# 激活函数和正则化
self.relu = nn.ReLU() # ReLU激活
self.leaky_relu = nn.LeakyReLU(0.1) # LeakyReLU激活
self.dropout = nn.Dropout(dropout) # Dropout层
self.layer_norm = nn.LayerNorm(hidden_size) # 层归一化
# 残差连接的投影层(如果启用)
if use_residual:
self.residual_projection = nn.Linear(lstm_output_size, hidden_size)
def forward(self, x):
"""
前向传播
Args:
x: 输入张量,形状为(batch_size, sequence_length, input_size)
Returns:
Tensor: 输出张量,形状为(batch_size, output_size)
"""
batch_size = x.size(0) # 获取批次大小
# 输入投影:将输入特征映射到隐藏层维度
x = self.input_projection(x)
# LSTM前向传播
lstm_out, _ = self.lstm(x)
# 注意力机制(如果启用)
if self.use_attention:
attn_out, _ = self.attention(lstm_out, lstm_out, lstm_out)
lstm_out = lstm_out + attn_out # 残差连接
# 取最后一个时间步的输出
lstm_out = lstm_out[:, -1, :]
# 批归一化
lstm_out = self.batch_norm1(lstm_out)
# 全连接层
out = self.relu(self.fc1(lstm_out)) # 第一层全连接+ReLU
out = self.dropout(out) # Dropout
# 残差连接(如果启用)
if self.use_residual:
residual = self.residual_projection(lstm_out)
out = out + residual
# 层归一化
out = self.layer_norm(out)
out = self.batch_norm2(out)
# 继续前向传播
out = self.leaky_relu(self.fc2(out)) # 第二层全连接+LeakyReLU
out = self.dropout(out) # Dropout
out = self.fc3(out) # 输出层
return out
# 后续类的注释类似,需要详细解释每个方法的作用和实现细节
# 由于篇幅限制,这里展示部分关键代码的注释
# 完整注释应覆盖所有类和方法,包括:
# - StockPredictionTrainer(训练器类)
# - StockPredictionSystem(系统主类)
# - 数据可视化方法
# - 模型评估指标计算
# - 模型保存/加载方法
# - 完整的训练流程
-------------------------------------------------以下是调用流程图------------------------------------------------------
