news 2026/9/16 3:55:23

Django+Keras+ECharts股票预测全栈实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django+Keras+ECharts股票预测全栈实践

简介:这是一套面向计算机专业本科生的毕业设计级智能股票分析系统完整实现,聚焦金融数据分析场景,融合Web开发、深度学习与数据可视化三大技术栈。系统基于Django构建稳健后端服务,Keras训练股价预测与情感分析模型,前端通过Axios调用接口,ECharts动态渲染K线图、预测趋势及多维指标图表,形成端到端可运行的AI+金融应用闭环。资源包共113个文件,含20个核心Python后端逻辑与模型脚本、14个HTML+JS前端页面、7个CSS样式文件、2个CSV股票数据样本(如000001.SZ.csv)、1个SQLite3数据库及若干图表资源与文档,整体3.56MB,结构清晰、模块解耦,便于理解MVC分层与前后端交互流程。目前已有152人学习下载,提供从环境配置、模型训练、接口联调到可视化部署的全流程参考,特别适合毕设选题、课程设计或全栈AI项目入门实践。

1. 这不是又一个“股票预测网页”,而是一套可验证、可调试、可部署的端到端分析链路

很多毕业设计卡在“模型跑通但前端不显示”“后端返回数据但 ECharts 不渲染”“Keras 模型本地能训,Django 里加载就报错”——本质不是技术不会,而是缺乏对Django 服务层与 Keras 模型推理的耦合边界Axios 请求与 Django REST 响应格式的契约约定ECharts 数据结构与 Python 后端输出的映射逻辑这三层关键衔接的系统性梳理。本项目聚焦真实场景:用历史行情+技术指标构建轻量 LSTM 模型,通过 Django 提供结构化 API,由 Axios 在前端发起带参数的时序请求,ECharts 动态渲染 K 线叠加预测折线、多维度指标热力图及行业板块占比饼图。它不追求黑箱预测精度,而是确保从python manage.py runserver到浏览器控制台看到chart.setOption()成功执行的每一步都可控、可查、可改。适合需要交付完整可运行代码、且需应对答辩中“这个接口怎么调的”“模型文件放哪了”“为什么这里用 POST 不用 GET”的同学。


2. Django 后端:构建稳定、可扩展、符合 REST 规范的数据服务层

Django 在本系统中承担三重角色:用户请求路由中枢、模型加载与推理调度器、结构化 JSON 数据生成器。它不是静态页面托管工具,而是连接 Python 科学计算生态与 Web 前端的协议转换桥。选择 Django 而非 Flask,核心在于其内置的 ORM(支持后续接入 MySQL 存储训练日志)、Admin 后台(快速验证数据存取)、以及清晰的 App 隔离机制——我们将把股票数据预处理、模型加载、API 接口严格拆分为独立 App,避免views.py膨胀成万行脚本。

2.1 创建专用 stock_analysis App 并配置模型加载路径

首先在 Django 项目根目录下创建新 App:

python manage.py startapp stock_analysis

stock_analysis/apps.py中定义 App 配置类,显式声明模型加载逻辑:

# stock_analysis/apps.py from django.apps import AppConfig import os import keras from keras.models import load_model class StockAnalysisConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'stock_analysis' def ready(self): # 仅在 Django 启动时加载一次模型,避免每次请求重复加载 from django.conf import settings model_path = os.path.join(settings.BASE_DIR, 'models', 'lstm_stock.h5') if os.path.exists(model_path): try: # 使用 keras 2.15+ 的推荐方式加载 self.model = load_model(model_path, compile=False) print(f"[INFO] LSTM 模型已加载: {model_path}") except Exception as e: print(f"[ERROR] 模型加载失败: {e}") self.model = None else: print(f"[WARN] 模型文件未找到: {model_path}") self.model = None

提示ready()方法是 Django App 初始化钩子,此处加载模型比在视图函数内加载更高效。compile=False可跳过编译步骤(因预测无需反向传播),加快加载速度。模型文件.h5必须放在项目根目录下的models/文件夹中,该路径需与settings.pyBASE_DIR一致。

2.2 定义 RESTful API 接口:GET /api/predict/ 支持参数化请求

stock_analysis/views.py中编写基于APIView的类视图,明确接收symbol(股票代码)、days(预测天数)和feature_type(技术指标类型)三个查询参数:

# stock_analysis/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status import numpy as np import pandas as pd from django.apps import apps from django.conf import settings import os class PredictView(APIView): def get(self, request): # 1. 参数校验 symbol = request.query_params.get('symbol', '').strip().upper() days = request.query_params.get('days', '7') feature_type = request.query_params.get('feature_type', 'close') if not symbol or len(symbol) < 2: return Response( {"error": "缺少有效股票代码,如 'SH600000'"}, status=status.HTTP_400_BAD_REQUEST ) try: days = int(days) if days < 1 or days > 30: return Response( {"error": "预测天数应在 1-30 之间"}, status=status.HTTP_400_BAD_REQUEST ) except ValueError: return Response( {"error": "days 必须为整数"}, status=status.HTTP_400_BAD_REQUEST ) # 2. 获取已加载模型 app_config = apps.get_app_config('stock_analysis') model = getattr(app_config, 'model', None) if not model: return Response( {"error": "模型未加载,请检查 models/lstm_stock.h5 文件"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) # 3. 模拟数据获取(实际应替换为数据库或 CSV 读取) # 此处简化:生成 100 天收盘价模拟序列 np.random.seed(42) base_price = 10.0 prices = [base_price] for _ in range(99): change = np.random.normal(0, 0.02) # 日波动率约 2% prices.append(prices[-1] * (1 + change)) df = pd.DataFrame({'close': prices}, index=pd.date_range('2023-01-01', periods=100, freq='D')) # 4. 构造输入特征(标准化 + 滑窗) data = df[feature_type].values scaler = lambda x: (x - np.mean(x)) / (np.std(x) + 1e-8) # 简单标准化 scaled_data = scaler(data) window_size = 60 X = [] for i in range(len(scaled_data) - window_size): X.append(scaled_data[i:i + window_size]) X = np.array(X).reshape(-1, window_size, 1) # 5. 模型预测(仅取最后 1 个样本做演示) if len(X) == 0: return Response({"error": "数据不足,无法构造输入窗口"}, status=status.HTTP_400_BAD_REQUEST) last_input = X[-1:].astype(np.float32) # 确保 float32 类型 pred = model.predict(last_input).flatten() # 反标准化(此处简化,实际需保存 scaler 对象) pred_original = pred * np.std(data) + np.mean(data) # 6. 构建 ECharts 兼容的响应结构 dates = pd.date_range(df.index[-1] + pd.Timedelta(days=1), periods=days, freq='D') result = { "symbol": symbol, "prediction": { "dates": [d.strftime('%Y-%m-%d') for d in dates], "values": [float(v) for v in pred_original[:days]] }, "history": { "dates": [d.strftime('%Y-%m-%d') for d in df.index[-20:]], "values": [float(v) for v in df[feature_type].tail(20).values] } } return Response(result, status=status.HTTP_200_OK)

2.3 配置 URL 路由与跨域支持(关键!Axios 请求必过此关)

stock_analysis/urls.py中注册路由:

# stock_analysis/urls.py from django.urls import path from . import views urlpatterns = [ path('api/predict/', views.PredictView.as_view(), name='predict'), ]

并在主urls.py中包含:

# myproject/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('stock_analysis.urls')), ]

注意:Axios 默认发送 CORS 请求,Django 默认拒绝。必须安装并配置django-cors-headers

pip install django-cors-headers

settings.py中添加:

INSTALLED_APPS = [ # ... 其他 app 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # 必须放在 SecurityMiddleware 之后 # ... 其他 middleware ] CORS_ALLOW_ALL_ORIGINS = True # 开发阶段允许所有源;生产环境请设为具体域名列表

3. Keras 模型:轻量 LSTM 构建、训练与导出,确保 Django 环境兼容性

本系统采用 Keras(TensorFlow 后端)构建单变量时间序列预测模型,核心目标是在保证推理速度的前提下,使模型文件能被 Django 进程稳定加载。避免使用tf.keras高级 API(如tf.data)或自定义层,全部使用keras.layers原生组件,确保.h5格式导出后无依赖问题。

3.1 模型定义与训练脚本(train_model.py)

该脚本应独立于 Django 项目运行,用于生成models/lstm_stock.h5

# train_model.py import numpy as np import pandas as pd from keras.models import Sequential from keras.layers import LSTM, Dense, Dropout from sklearn.preprocessing import MinMaxScaler import os def create_dataset(data, lookback=60): X, y = [], [] for i in range(lookback, len(data)): X.append(data[i-lookback:i, 0]) y.append(data[i, 0]) return np.array(X), np.array(y) # 1. 加载并预处理数据(此处用模拟数据替代真实 CSV) np.random.seed(42) base = 10.0 prices = [base] for _ in range(1000): prices.append(prices[-1] * (1 + np.random.normal(0, 0.01))) df = pd.DataFrame({'close': prices}) # 2. 标准化 scaler = MinMaxScaler(feature_range=(0, 1)) scaled_data = scaler.fit_transform(df[['close']]) # 3. 构造训练集 lookback = 60 X_train, y_train = create_dataset(scaled_data, lookback) X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) # 4. 构建模型 model = Sequential([ LSTM(50, return_sequences=True, input_shape=(lookback, 1)), Dropout(0.2), LSTM(50, return_sequences=False), Dropout(0.2), Dense(25), Dense(1) ]) model.compile(optimizer='adam', loss='mean_squared_error') # 5. 训练 model.fit(X_train, y_train, batch_size=32, epochs=10, verbose=1) # 6. 保存为 .h5 格式(兼容性最强) os.makedirs('models', exist_ok=True) model.save('models/lstm_stock.h5') print("模型已保存至 models/lstm_stock.h5")

参数说明lookback=60表示用过去 60 天数据预测下一天;LSTM(50)表示隐藏层单元数;Dropout(0.2)防止过拟合;batch_size=32平衡内存与收敛速度。训练完成后,lstm_stock.h5文件即为 Django 后端加载的目标。

3.2 Django 中模型加载的兼容性验证

在 Django Shell 中手动验证模型加载是否成功:

python manage.py shell
>>> from django.apps import apps >>> app = apps.get_app_config('stock_analysis') >>> app.model # 应输出 <keras.engine.sequential.Sequential object at 0x...> >>> app.model.predict(np.random.random((1, 60, 1)).astype('float32')) # 应返回 shape (1, 1) 的数组

关键点:若出现ValueError: Unknown layer: LSTM,说明 Keras 版本不匹配。务必统一使用keras==2.15.0(对应 TensorFlow 2.15)或keras==2.13.1(TF 2.13),避免混用tf.keras和独立keras包。可通过pip list | grep keras确认。


4. 前端交互:Axios 封装请求 + ECharts 动态渲染,实现数据驱动可视化

前端采用纯 HTML + Vue(或原生 JS)组合,核心是让 Axios 请求结果精准映射到 ECharts 的option配置项。重点解决三个常见断点:请求失败无提示日期轴错位饼图数据未归一化

4.1 Axios 请求封装与错误处理(Vue 组件示例)

static/js/main.js中定义可复用的请求方法:

// static/js/main.js import axios from 'axios'; // 创建 axios 实例,设置基础 URL(Django 开发服务器地址) const apiClient = axios.create({ baseURL: 'http://127.0.0.1:8000', // 与 Django runserver 地址一致 timeout: 10000, headers: { 'Content-Type': 'application/json', } }); // 封装预测请求 export function fetchStockPrediction(symbol, days = 7, feature = 'close') { return apiClient.get('/api/predict/', { params: { symbol, days, feature_type: feature } }).catch(error => { console.error('API 请求失败:', error.response?.data || error.message); throw new Error(`请求异常: ${error.response?.data?.error || '网络错误'}`); }); }

4.2 ECharts 折线图初始化与动态更新(K 线+预测线)

HTML 结构中预留容器:

<!-- templates/index.html --> <div id="chart-container" style="width: 100%; height: 500px;"></div> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <script src="{% static 'js/main.js' %}"></script>

JavaScript 初始化图表并绑定数据:

// static/js/chart.js import { fetchStockPrediction } from './main.js'; let chart; let option = { tooltip: { trigger: 'axis' }, legend: { data: ['历史价格', '预测价格'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', boundaryGap: false, axisLabel: { rotate: 30 } }, yAxis: { type: 'value' }, series: [ { name: '历史价格', type: 'line', smooth: true, symbol: 'none' }, { name: '预测价格', type: 'line', smooth: true, lineStyle: { type: 'dashed' }, symbol: 'circle' } ], dataZoom: [{ type: 'inside' }, { type: 'slider' }] }; document.addEventListener('DOMContentLoaded', () => { const dom = document.getElementById('chart-container'); chart = echarts.init(dom); // 首次加载默认数据 loadChart('SH600000'); // 绑定按钮事件 document.getElementById('predict-btn').addEventListener('click', () => { const symbol = document.getElementById('symbol-input').value.trim(); const days = parseInt(document.getElementById('days-input').value) || 7; if (symbol) loadChart(symbol, days); }); }); function loadChart(symbol, days = 7) { chart.showLoading(); fetchStockPrediction(symbol, days) .then(response => { const data = response.data; // 合并历史与预测数据,确保 x 轴连续 const allDates = [...data.history.dates, ...data.prediction.dates]; const allValues = [...data.history.values, ...data.prediction.values]; // 更新 series 数据 option.series[0].data = data.history.values.map((v, i) => [data.history.dates[i], v]); option.series[1].data = data.prediction.values.map((v, i) => [data.prediction.dates[i], v]); // 设置 x 轴数据(ECharts 5+ 推荐用 data 数组而非 category) option.xAxis.data = allDates; chart.hideLoading(); chart.setOption(option, true); // true 表示不合并,完全替换 }) .catch(err => { chart.hideLoading(); alert(`加载失败: ${err.message}`); }); }

关键细节option.xAxis.data必须是字符串数组(如['2023-01-01', '2023-01-02', ...]),ECharts 会自动识别为时间序列;series[n].data格式为[x, y]二元组数组,确保日期与数值严格对应;chart.setOption(option, true)中的true参数防止配置项累积导致图表错乱。

4.3 ECharts 饼图展示行业分布(补充业务维度)

在另一容器中渲染行业占比饼图,数据由 Django 提供额外接口/api/industry/返回:

{ "industries": [ {"name": "半导体", "value": 32}, {"name": "新能源", "value": 28}, {"name": "消费电子", "value": 22}, {"name": "其他", "value": 18} ] }

对应 ECharts 配置:

// 饼图 option const pieOption = { tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left' }, series: [{ name: '行业分布', type: 'pie', radius: '50%', data: response.data.industries, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } }] };

注意legendleft值需根据容器宽度调整;radius控制饼图大小;data直接传入后端返回的数组,ECharts 自动计算百分比。


5. 部署与调试:宝塔面板一键部署 Django + Nginx 反向代理实战要点

毕业设计最终需在 Linux 服务器上稳定运行,宝塔面板因其可视化操作成为学生首选。但直接上传代码常因权限、Python 环境、静态文件路径等问题失败。以下为经实测的最小可行部署路径。

5.1 宝塔环境准备与项目上传

  1. 在宝塔「软件商店」安装Python 项目管理器(非 Python 环境);
  2. 创建新站点,域名设为stock.yourdomain.com,PHP 版本选“纯静态”;
  3. 进入站点根目录/www/wwwroot/stock.yourdomain.com,上传整个 Django 项目(含manage.py);
  4. 在「Python 项目管理器」中添加项目:
    • 项目路径:/www/wwwroot/stock.yourdomain.com
    • Python 版本:3.10(与本地开发一致)
    • 启动文件:/www/wwwroot/stock.yourdomain.com/manage.py
    • 启动命令:runserver 127.0.0.1:8001关键:必须绑定 127.0.0.1,不可用 0.0.0.0

5.2 Nginx 反向代理配置(绕过端口暴露)

在宝塔站点设置 → 「配置文件」中,在location /块内添加:

location / { proxy_pass http://127.0.0.1:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }

同时注释掉原有的try_files行。保存后重启 Nginx。

5.3 静态文件收集与权限修复

Django 静态文件(JS/CSS)需集中到staticfiles/目录供 Nginx 直接服务:

# 在宝塔终端中执行(进入项目目录) cd /www/wwwroot/stock.yourdomain.com source /www/server/python/bin/activate # 激活宝塔 Python 环境 python manage.py collectstatic --noinput

然后在宝塔文件管理中,将staticfiles/目录权限设为755,内部文件设为644

排错关键:若访问首页空白,打开浏览器开发者工具 → Network 标签,查看main.jsecharts.min.js是否 404。若是,说明 Nginx 未正确代理静态资源,需在站点配置中额外添加:

location /static/ { alias /www/wwwroot/stock.yourdomain.com/staticfiles/; }

并确保 Djangosettings.pySTATIC_URL = '/static/'STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

5.4 生产环境模型加载路径修正

Django 在宝塔中运行时,BASE_DIR指向/www/wwwroot/stock.yourdomain.com,因此模型路径os.path.join(settings.BASE_DIR, 'models', 'lstm_stock.h5')必须存在。上传代码时,确保models/文件夹及其.h5文件一同上传,并在宝塔文件管理中确认路径为/www/wwwroot/stock.yourdomain.com/models/lstm_stock.h5,权限为644

终极验证命令:在宝塔终端执行

cd /www/wwwroot/stock.yourdomain.com && source /www/server/python/bin/activate && python manage.py shell -c "from django.apps import apps; print(apps.get_app_config('stock_analysis').model)"

若输出<keras.engine.sequential.Sequential object ...>,则模型加载成功;若报错,按提示检查路径或 Keras 版本。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 3:55:17

WPS PDF实战手册:转Word、OCR、合并、转曲全攻略

先声明一下&#xff1a;整篇只聊正版WPS自带的能力&#xff0c;不碰破解、不聊灰色工具。原因很简单&#xff0c;没必要。WPS的PDF模块这些年迭代得比很多人想象中扎实&#xff0c;日常遇到的转Word、合并拆分、压缩、扫描件清洗、甚至印刷前的转曲需求&#xff0c;它都能正面接…

作者头像 李华
网站建设 2026/9/16 3:54:59

STM8S003多通道ADC采集实战:扫描模式、轮询与双基准校正

简介&#xff1a;面向STM8S003单片机学习者的多通道ADC采集示例工程&#xff0c;以“单片机轮询法”为主线&#xff0c;完整演示了ADC初始化、工作模式与参数配置、通道选择、启动转换、轮询等待状态标志以及读取转换结果的完整流程&#xff0c;适合嵌入式初学者、课程设计或需…

作者头像 李华
网站建设 2026/9/16 3:54:53

游戏下载站背后的安全暗面:破解生态与恶意代码投递链

1. 从“游戏下载站”到“安全风险观察样本”&#xff1a;为什么聊3DM和游民星空要扯上网络安全我是做网络安全这行的&#xff0c;平时除了盯流量、抓报文、分析告警之外&#xff0c;私下还有个爱好是研究互联网产品的“考古”。2025年了&#xff0c;再提起3DM和游民星空&#x…

作者头像 李华
网站建设 2026/9/16 3:54:44

Go语言实现最大子数组总值Ⅱ:前k大子区间和的高效解法

前几天我在 Go 技术群里看到有人发了一道题&#xff0c;标题写着“最大子数组总值Ⅱ”&#xff0c;底下跟着一串描述&#xff1a;给定长度 n 的整数数组 nums&#xff0c;还有一个整数 k&#xff0c;要挑出恰好 k 个互不相同的非空连续区间&#xff0c;允许重叠&#xff0c;但不…

作者头像 李华
网站建设 2026/9/16 3:53:43

有没有做家具特卖的网站对比评测

3家做家具特卖的网站建站方案对比评测,拒绝被坑 找建站公司最怕什么?怕报价虚高,怕做出来的站搜不到人,怕交钱后变脸。很多做家具特卖的老板,手里攥着预算,看着市面上“有没有做家具特卖的网站”这种长尾需求,心里直打鼓:到底该选哪家?怎么判断对方是技术派还是忽悠派?…

作者头像 李华
网站建设 2026/9/16 3:53:33

Wireshark解析TACACS+协议实战:抓包、解码与AAA故障定位

1. 项目概述&#xff1a;为什么TACACS抓包分析是网络工程师绕不开的硬功夫Wireshark实战续集121这个编号&#xff0c;不是随便起的——它意味着你已经啃下了TCP三次握手、HTTP状态码、DNS递归查询这些基础模块&#xff0c;现在真正站到了企业级网络运维的深水区。TACACS&#x…

作者头像 李华