资讯动态

Django实战:从ORM模型到AJAX行情看板,构建股票交易管理系统

发布时间:2026/9/15 20:01:59 来源:尧图企业网站定制
简介基于Django框架实现的股票交易管理系统源码包完整演示了如何运用Python Web开发中的模型定义、模板渲染、视图控制、用户认证、表单处理以及异步请求等技术构建股票行情展示、交易下单、持仓管理和历史记录查询等核心业务功能。项目整体结构清晰适合计算机专业学生选取为课程设计或毕业设计参考也适合Django入门到进阶的开发者通过完整实战理解Web应用从数据层到表现层的协作方式。压缩包内共有1611个文件整体大小约21.84MB其中包含31个Python后端源码文件、167个HTML页面模板、162个CSS样式表、879个JavaScript脚本以及20个SQL数据库脚本还附带大量图片、字体、说明文档等资源属于前后端功能兼备的完整工程前端样式覆盖了Bootstrap、AdminLTE等常见框架数据库脚本便于快速初始化和还原演示数据。目前已有187人学习下载。仔细阅读源码可以掌握Django项目中的路由配置、ORM数据操作、用户登录会话管理、表单验证与异步交互技巧同时也能了解如何组织一个中型Web项目的文件结构为独立开发同类管理系统提供直接参考。1. 一个Django股票交易管理项目值得拆开看的部分在哪里股票交易系统这类业务最考验 Web 框架的不是下单速度而是能不能把行情、账户、订单、风控拆成清晰的对象模型。Django 的优势恰好在这里ORM 可以直接把交易关系落库内置 User 认证解决了“谁在下单”的问题模板系统再配合 AdminLTE 这类后台皮肤能在很短的时间内搭出一个可演示的股票交易管理台。这个项目里既有 Django 的 MTV 三层结构也有外部行情数据通过批处理脚本进入数据库的完整链路dailytosql.bat 负责把日行情清洗成 SQLdbtosql.bat 负责把 SQL 导入 MySQL前端再通过 AJAX 定时拉取最新价格。项目虽以课程设计常见但拆开之后能看到 Django 在真实业务场景里的完整姿势适合计算机专业学生作为毕业设计或课程设计也适合想看看 Django 怎么扛业务逻辑的开发者。2. Django 模型层股票、订单与行情实体是怎么设计的2.1 先分清哪些是业务实体哪些是辅助实体Django 项目拿到手第一个动作不是写视图而是跑python manage.py startapp trading先把应用建出来再把它注册进settings.py的INSTALLED_APPS。这一步对应热词里常说的“django 创建 app”指令很简单python manage.py startapp trading创建出来的trading/models.py是后续所有业务的根。股票交易系统里真正需要落库的实体我一般分成四类股票基本信息、用户账户、交易订单、行情快照。前三个直接参与业务交互第四个是外部脚本灌入的数据表。需要特别注意的是用户账户不要自己另建表优先复用 Django 自带的auth.User通过ForeignKey关联到订单表。这样注册、登录、会话、权限都有现成实现后面的视图层可以直接用request.user拿到当前用户这个设计比自建用户表省掉一大半安全问题。2.2 模型字段怎么选Decimal 与 Float 的区别必须明确下面是一份可运行的models.py参考覆盖股票、订单、行情快照三张表from django.db import models from django.conf import settings class Stock(models.Model): code models.CharField(max_length10, uniqueTrue, verbose_name股票代码) name models.CharField(max_length32, verbose_name股票名称) industry models.CharField(max_length32, blankTrue, verbose_name所属行业) close_price models.DecimalField(max_digits10, decimal_places2, default0, verbose_name最新价) updated_at models.DateTimeField(auto_nowTrue, verbose_name更新时间) def __str__(self): return f{self.code} {self.name} class TradeOrder(models.Model): SIDE_CHOICES ((BUY, 买入), (SELL, 卖出)) STATUS_CHOICES ((PENDING, 待成交), (DONE, 已成交), (CANCEL, 已撤单)) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, verbose_name下单用户) stock models.ForeignKey(Stock, on_deletemodels.PROTECT, verbose_name股票) side models.CharField(max_length4, choicesSIDE_CHOICES, verbose_name方向) shares models.PositiveIntegerField(verbose_name数量) price models.DecimalField(max_digits10, decimal_places2, verbose_name委托价) status models.CharField(max_length6, choicesSTATUS_CHOICES, defaultPENDING, verbose_name状态) created_at models.DateTimeField(auto_now_addTrue, verbose_name下单时间) class StockDailyPrice(models.Model): stock models.ForeignKey(Stock, on_deletemodels.CASCADE, verbose_name股票) trade_date models.DateField(verbose_name交易日期) open_price models.DecimalField(max_digits10, decimal_places2, verbose_name开盘价) high_price models.DecimalField(max_digits10, decimal_places2, verbose_name最高价) low_price models.DecimalField(max_digits10, decimal_places2, verbose_name最低价) close_price models.DecimalField(max_digits10, decimal_places2, verbose_name收盘价) volume models.BigIntegerField(default0, verbose_name成交量) class Meta: unique_together (stock, trade_date)价格字段用DecimalField而不是FloatField这是我强调的第一条规范。股票价格是金额浮点数在二进制存储里会有精度误差例如 19.99 可能存成 19.989999订单金额一旦算错整个系统就失去可信度。DecimalField在 Python 侧拿到的是Decimal对象写入 MySQL 后会落成DECIMAL类型四舍五入规则由数据库统一控制。on_delete参数也是容易踩坑的位置。TradeOrder指向Stock时用了PROTECT意思是某只股票如果已经存在关联订单就不允许直接删除股票记录避免行情数据因为外键断裂而失真。StockDailyPrice指向Stock用CASCADE删除股票时历史行情一起清掉适合做测试数据重置。字段定义完之后执行迁移命令python manage.py makemigrations trading python manage.py migratemakemigrations会比对模型与数据库差异生成迁移文件migrate把迁移应用到数据库。这个过程也是排查模型错误的最好时机很多字段长度或外键关联问题都会在迁移阶段暴露。若迁移文件已经生成但后来又改了模型不能用删文件的方式解决应该继续makemigrations生成新的迁移保持迁移链路完整。2.3 ORM 查询、删除对象与常见误用ORM 是 Django 对数据库操作的一层封装使用得当可以完全避开手写 SQL下面的表整理了高频操作与 SQL 的对应关系操作ORM 写法对应 SQL 语义查询全部Stock.objects.all()SELECT * FROM trading_stock;条件过滤Stock.objects.filter(industry科技)WHERE industry科技取单条Stock.objects.get(code000001)WHERE code000001 LIMIT 1;创建记录Stock.objects.create(...)INSERT INTO ...删除对象Stock.objects.filter(code000001).delete()DELETE FROM trading_stock WHERE code000001;关联查询TradeOrder.objects.select_related(stock)LEFT JOIN trading_stock ...热词里提到的“django 执行查询-删除对象”在股票系统里最常见的场景是清理测试数据或撤单。下面的写法演示了如何先查后删并正确处理级联关系from trading.models import Stock, TradeOrder # 查询所有待成交订单select_related 避免 N1 查询 pending_orders TradeOrder.objects.filter(statusPENDING).select_related(stock) for order in pending_orders: print(order.user.username, order.stock.code, order.side, order.shares) # 删除某只退市股票若有 PROTECT 外键关联会抛出 ProtectedError Stock.objects.filter(code000001).delete()select_related的作用是让 Django 在一次查询里通过JOIN把关联表数据一起取出来而不是每访问一条订单就再去查一次股票表。不使用它时循环 100 条订单会产生 101 条 SQL页面响应会肉眼可见地变慢。删除操作也要注意外键约束如果TradeOrder使用了PROTECT那么直接删除股票会抛出ProtectedError失败时先确认是否有未处理的关联订单。3. 视图、URL 路由与登录态交易行为如何被串联起来3.1 URL 路由设计与 reverse 解析模型只是数据底座真正让交易系统跑起来的是 URL 到视图函数的映射。Django 的路由系统在trading/urls.py中配置我习惯先给应用定义一个app_name方便在视图和模板里用reverse反向解析 URL。下面是典型的 URL 配置from django.urls import path from . import views app_name trading urlpatterns [ path(stocks/, views.stock_list, namestock_list), path(stocks/int:stock_id/, views.stock_detail, namestock_detail), path(stocks/int:stock_id/price/, views.price_feed, nameprice_feed), path(orders/new/, views.create_order, namecreate_order), path(orders/, views.my_orders, namemy_orders), ]int:stock_id是路径转换器限定该参数必须是整数这样请求/stocks/abc/时 Django 直接返回 404不会进入视图层。name参数是 URL 的别名业务代码里不要硬编码字符串路径而是用reverse(trading:stock_detail, args[stock.id])来解析。关于热词里反复出现的“django reverse resolve”reverse负责把视图名和参数解析成完整 URLresolve是它的反向操作用来根据 URL 解析出视图函数。在视图之间做重定向时reverse比硬编码路径更安全如果某天stock_detail的路径从/stocks/int:stock_id/改成/market/stock/int:stock_id/只要路由 name 不变业务代码里的 reverse 调用完全不用改。3.2 用户认证与交易下单视图交易系统必须知道“谁在下单”这一层直接用 Django 内置认证体系最稳妥。在项目级urls.py中挂上认证路由from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(auth/, include(django.contrib.auth.urls)), path(, include(trading.urls)), ]django.contrib.auth.urls会一次性提供 login、logout、password_change 等标准视图对应模板放到templates/registration/login.html即可。视图层需要登录才能访问的接口用login_required装饰器统一拦截。下面是一个订单创建的完整视图示例from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib import messages from django.urls import reverse from .models import Stock, TradeOrder from .forms import OrderForm login_required def create_order(request): if request.method POST: form OrderForm(request.POST) if form.is_valid(): order form.save(commitFalse) order.user request.user order.save() messages.success(request, 订单创建成功等待成交) return redirect(reverse(trading:my_orders)) else: form OrderForm() return render(request, trading/order_form.html, {form: form})login_required未登录时会自动跳转到登录页并且会在 URL 上带?next/orders/new/用户登录成功后会回跳到原始页面这个机制比在每个视图里手写request.user.is_authenticated判断要统一得多。form.save(commitFalse)是表单处理里的关键点表单从请求里拿到股票、方向、数量、价格但user字段没有暴露在表单中先在内存中构建TradeOrder实例再手动把当前登录用户赋值给order.user最后真正落库。这样避免用户通过伪造请求提交user_id来冒充他人下单。热词里提到的“django 重定向传递数据”推荐方案是使用消息框架而不是在 URL 拼接参数。上面的messages.success(request, 订单创建成功)会把消息写入会话在base.html模板中遍历输出{% if messages %} ul classlist-unstyled {% for message in messages %} li classalert alert-info{{ message }}/li {% endfor %} /ul {% endif %}URL 传参方式一旦刷新页面参数就会丢失而消息框架读取后即清除既保证了用户体验也避免敏感信息暴露在地址栏里。3.3 查询当前用户订单与权限控制用户下单之后需要查看自己的历史订单。这个视图要特别注意过滤条件绝对不能把全表订单返回给普通用户from django.contrib.auth.decorators import login_required from .models import TradeOrder login_required def my_orders(request): orders TradeOrder.objects.filter(userrequest.user).order_by(-created_at) return render(request, trading/order_list.html, {orders: orders}) login_required def cancel_order(request, order_id): order get_object_or_404(TradeOrder, pkorder_id, userrequest.user) if order.status PENDING: TradeOrder.objects.filter(pkorder.id).update(statusCANCEL) order.refresh_from_db() return redirect(trading:my_orders)get_object_or_404接受两个条件参数pkorder_id和userrequest.user这样当前用户试图撤销别人的订单时直接得到 404而不是看到订单详情。用update()更新状态也是一种更高效的做法它直接生成一条 UPDATE SQL不经过对象 save 流程并且会跳过模型的save()方法适合只改个别字段的场景。4. 模板、AdminLTE 与 AJAX把后台界面变成实时行情面板4.1 用 AdminLTE 搭出可复用的基础模板项目资源里出现AdminLTE.min.css、bootstrap.min.css和frozenui.css说明前端基于 AdminLTE 后台皮肤搭建。AdminLTE 是建立在 Bootstrap 之上的后台管理模板自带侧边栏、导航栏、卡片组件最适合股票这类信息密度高的管理界面。使用方式不是把静态文件散落到各个模板中而是建一个templates/base.html作为全局骨架。先处理静态文件目录在settings.py中配置# settings.py STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static]然后在base.html中引入 CSS{% load static %} !DOCTYPE html html langzh-cn head meta charsetutf-8 title{% block title %}股票交易管理系统{% endblock %}/title link relstylesheet href{% static adminlte/css/bootstrap.min.css %} link relstylesheet href{% static adminlte/css/AdminLTE.min.css %} link relstylesheet href{% static adminlte/css/skins/_all-skins.min.css %} {% block extra_css %}{% endblock %} /head body classhold-transition skin-blue sidebar-mini div classwrapper !-- 顶部与侧边栏略 -- div classcontent-wrapper {% block content %}{% endblock %} /div /div script src{% static adminlte/js/jquery.min.js %}/script {% block extra_js %}{% endblock %} /body /html{% static %}标签是 Django 模板系统解析静态文件路径的标准方式不能直接写死/static/adminlte/css/AdminLTE.min.css。设置了STATICFILES_DIRS后Django 可以在开发服务器上直接服务这些文件部署到生产环境时再由 Nginx 或 CDN 接管模板代码无需改动。4.2 AJAX 轮询接口JSON 视图如何写股票行情页面需要定时刷新最新价但不能整页刷新这里用 AJAX 轮询是最直观的方案。服务端先提供一个返回 JSON 的视图import json from django.http import JsonResponse from django.shortcuts import get_object_or_404 from .models import Stock def price_feed(request, stock_id): stock get_object_or_404(Stock, pkstock_id) data { code: stock.code, name: stock.name, price: str(stock.close_price), timestamp: stock.updated_at.strftime(%Y-%m-%d %H:%M:%S), } return JsonResponse(data)这里有两个细节值得关注。第一close_price是Decimal类型直接放进json.dumps会报错所以先用str()转成字符串再由前端parseFloat解析第二JsonResponse会自动设置Content-Type: application/json避免中文乱码问题同时还要注意settings.py里的DEFAULT_CHARSET保持为utf-8。前端页面通过 jQuery 定时拉取并更新 DOMfunction refreshPrice(stockId) { $.get(/trading/stocks/ stockId /price/, function (resp) { $(#stock-price).text(resp.price); $(#stock-time).text(resp.timestamp); }); } $(function () { refreshPrice(123); // 123 换成实际股票 id setInterval(function () { refreshPrice(123); }, 5000); });轮询间隔设置成 5 秒对课程设计足够但如果要面向真实行情5 秒一次对数据库压力不小。常见做法是后端用 Redis 缓存最近一次行情快照AJAX 请求先读缓存或者干脆换成 WebSocket 推送。这里的 AJAX 只是展示了 Django 视图如何同时输出 HTML 和 JSON为后续接入 celery 定时任务或异步推送留好接口。4.3 Django Admin 界面美化与订单审核Django Admin 是项目自带的弱后台虽然默认样式朴素但通过自定义ModelAdmin可以满足大部分后台管理需求。热词里提到“django admin 界面美化”除了改静态 CSS更核心的是把admin.py的可读性做出来from django.contrib import admin from .models import Stock, TradeOrder admin.register(Stock) class StockAdmin(admin.ModelAdmin): list_display (code, name, industry, close_price, updated_at) search_fields (code, name) list_filter (industry,) admin.register(TradeOrder) class TradeOrderAdmin(admin.ModelAdmin): list_display (id, user, stock, side, shares, price, status, created_at) list_filter (status, side) actions [mark_done] admin.action(description标记为已成交) def mark_done(self, request, queryset): queryset.update(statusDONE)list_display控制后台列表展示哪些字段search_fields让股票代码和名称支持搜索list_filter在右侧生成按状态和方向的筛选栏。actions自定义批量操作可以一秒把选中订单全部标记为已成交比逐个编辑效率更高。后台地址仍然用/admin/访问只是在原来的默认样式基础上拥有了更贴合交易业务的管理能力。5. dailytosql.bat 与 dbtosql.bat行情数据入库与 MySQL 配置5.1 两个批处理脚本的工作流程拆解项目资源里有两个批处理文件dailytosql.bat和dbtosql.bat。从命名上能看出它们的配合方式前一个负责把当日行情源文件转换成 SQL 插入语句后一个负责真正把 SQL 执行到数据库。这种“先出文件再入库”的阶段划分在数据量不大时非常实用因为你可以先打开生成的 SQL 检查数据是否正常再决定是否入库避免错误数据直接污染正式表。dailytosql.bat的常见骨架如下echo off chcp 65001 nul set INPUT_FILEday_data.txt set OUTPUT_FILEdaily.sql python gen_sql.py %INPUT_FILE% %OUTPUT_FILE% if exist %OUTPUT_FILE% ( echo 日行情 SQL 已生成%OUTPUT_FILE% ) else ( echo 生成失败请检查输入文件格式 )chcp 65001是为了让批处理窗口以 UTF-8 编码运行防止源文件中中文股票名称在生成 SQL 时变成乱码。gen_sql.py是真正干活的脚本它读取当日行情文本逐行转换成 INSERT 语句。下面是一段简化版的生成逻辑import sys INPUT_FILE sys.argv[1] OUTPUT_FILE sys.argv[2] lines [] with open(INPUT_FILE, r, encodingutf-8) as f: for line in f: code, name, close_price line.strip().split(|) sql fINSERT INTO trading_stockdailyprice (stock_id, trade_date, close_price) VALUES ((SELECT id FROM trading_stock WHERE code{code}), CURDATE(), {close_price}); lines.append(sql) with open(OUTPUT_FILE, w, encodingutf-8) as f: f.write(\n.join(lines))这段代码的要点在于行情表外键stock_id不是直接写死数值而是用子查询从股票主表里找出对应 id这样即使股票主表的自增 id 发生过变化导入时也能准确关联。trade_date使用了 MySQL 的CURDATE()如果脚本每天固定执行一次这个写法可以自动填充当前交易日。5.2 dbtosql.bat 入库与数据校验dbtosql.bat负责把上一步生成的daily.sql导入 MySQLecho off chcp 65001 nul set DB_USERroot set DB_PASS123456 set DB_NAMEstock_db mysql -u%DB_USER% -p%DB_PASS% %DB_NAME% daily.sql if %errorlevel% 0 ( echo 行情数据入库成功 ) else ( echo 入库失败请检查账号权限或 SQL 语法 )mysql命令通过重定向把daily.sql的内容交给 mysql 客户端执行errorlevel是 Windows 批处理里判断上一条命令是否成功的标准方式。入库建议分环境执行先在测试库跑一遍确认行数、最新日期、价格精度无误后再导入生产库。导入完成后可以在 Django 的 shell 里快速校验python manage.py shell -c from trading.models import StockDailyPrice; print(StockDailyPrice.objects.count()); print(StockDailyPrice.objects.order_by(-trade_date)[:3])这个命令会输出行情总记录数和最近三天的数据对象如果数量为 0基本可以判断是dailytosql.bat生成的 SQL 文件为空或者源文件路径写错。如果数量有但最新日期不是当天重点检查CURDATE()是否被表单里trade_date字段覆盖。5.3 MySQL 连接配置与常见坑Django 要连接 MySQL数据库配置在settings.py中改写DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: stock_db, USER: root, PASSWORD: 123456, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }ENGINE指定 MySQL 驱动Django 官方推荐的驱动是mysqlclient安装命令通常为pip install mysqlclient。安装时如果报缺少编译环境Windows 上可以直接安装对应的 whl 包Linux 上需要先安装libmysqlclient-dev。OPTIONS里的charset设为utf8mb4是为了兼容股票名称里的生僻字和 emoji 符号只设utf8在写入四字节字符时会报Incorrect string value错误。两个批处理脚本里最容易忽略的问题是路径依赖。双击执行时%~dp0指向脚本所在目录更稳妥的方式是在脚本第一行加上cd /d %~dp0确保在当前目录下寻找源文件和生成文件。MySQL 账号密码硬编码在 bat 里只是个临时方案更严谨的做法是把连接参数放进config.ini或环境变量避免脚本提交到仓库时泄露数据库口令。整个行情自动入库链路做到这里日线数据就能每天定时进入 Django 的 ORM 管理范围后续基于历史行情做均线计算、盈亏统计或图形展示都只需要在这套模型上继续扩展。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价