深度学习--lstm---股票预测

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(系统主类)
# - 数据可视化方法
# - 模型评估指标计算
# - 模型保存/加载方法
# - 完整的训练流程

-------------------------------------------------以下是调用流程图------------------------------------------------------