
简介一份基于Django框架的股票市值管理系统源码面向希望学习Python Web开发、了解证券账户与资产跟踪实战的开发者。系统围绕港股和美股的投资场景涵盖交易记录、分红记录、新股信息等数据模型通过模型-视图-模板-URL的完整链路实现用户认证、持仓展示、表单录入与Ajax异步刷新等功能展示了从数据库设计到前端交互的典型Django项目结构。压缩包内有2030个文件主要为Python源码、HTML模板、JavaScript脚本以及CSS/SCSS/SVG等前端资源其中SVG图标与样式文件占比较高可支撑后台管理界面和图表展示整体包体仅8.95MB便于下载与部署。已有217人学习浏览适合具备初步Python基础、希望进阶Django全栈开发或构建金融类管理系统的开发者参考可直接分析其模型定义、URL路由与视图函数组织方式并复用其成熟目录结构。 前阵子整理自己的项目集合翻出来一个之前用Django做的股票市值管理系统源码包顺手在几个开源社区分享了一下没想到私信问细节的人还挺多。索性把整个项目的来龙去脉、设计思路和落地过程完整写一篇。如果你正好也在用Django或者正打算写一个和股票、基金、资产记账相关的Web系统这篇应该能给你省不少弯路。这个系统的定位很明确个人投资者自用的市值管理工具。它不炒概念、不做量化选股、不搞自动交易核心就干三件事——记录你持有什么、当前值多少钱、整体盈亏怎么样。听起来简单但真正用Django从零把这几件事做扎实涉及数据建模、行情接口接入、定时任务、前端展示和后期部署每一步都有值得细说的地方。1. 为什么一门心思选Django做这个管理系统1.1 Django在项目管理类工具里的天然优势先说结论选Django不是因为它新潮而是这类单人多角色、重数据录入、重信息展示的系统Django几乎是Python生态里最省力的选择。我用过Flask写小工具也用过Spring Boot写企业服务最后落到Django上核心原因是它的ORM和Admin后台太适合这种业务了。市值管理系统最绕不开的就是表和表之间的关系——用户、股票、持仓、行情、交易记录这些实体互相之间既有外键关联又有聚合计算。Django的ORM能让我用Python对象直接描述数据关系不用反复写SQL开发效率至少提升一倍。还有个容易被忽略的点Django自带的Admin后台在项目初期就是一套完整的数据管理界面。我开发头两天没有任何前端代码全靠Admin在往数据库里录真实的股票代码和价格数据来验证逻辑。这个优势在项目原型期特别明显很多Flask项目到这个阶段就得自己拼一套后台费时费力。1.2 与其他方案的对比为什么不是Flask或Spring Boot我不是无脑Django吹选型的时候确实做过横向对比。Flask轻量灵活适合做一个API服务或者极小的工具页但一旦涉及用户认证、Admin后台、ORM迁移、表单校验这些Web应用标配Flask需要自己集成一堆第三方库。Flask-SQLAlchemy、Flask-Login、Flask-Migrate每个库都有自己的用法和理解成本串起来反而没有Django那种开箱即用的顺畅感。Spring Boot在Java生态里很强但对我来说太重了。写一个个人用的市值管理工具Java整个工具链的复杂度远超所需而且Python在处理行情数据、调用数据源接口方面有天然的库生态优势。我想在项目里快速试一个数据可视化方案Python这边的选择比Java灵活得多。所以最终的结论很直接个人项目、快速迭代、数据模型复杂、需要后台管理Django就是最均衡的选择。2. 核心数据模型怎么设计持仓、行情和市值快照2.1 用户、股票与持仓三张基础表的建模整个系统的数据模型我踩过几次坑之后沉淀下来的核心是四张表用户表、股票基本信息表、持仓表和市值快照表。用户表直接复用Django内置的auth.User这个没什么好说的。股票基本信息表记录的是股票代码、名称、所属交易所、行业分类、上市日期这些静态信息它们不是行情数据不需要频繁变动但几乎每项业务都会用到。持仓表是整个系统的心脏字段设计上必须考虑一个现实问题同一只股票多次买入。很多人第一版设计会把持仓表和交易记录表合并导致修改一笔历史交易就要重新计算全部持仓。我最终的设计是把买入记录作为明细表通过聚合生成当前持仓视图。这样记录历史流水和查看当前持仓是两个独立需求互不干扰。核心字段大致是这些user外键指向用户stock外键指向股票基本信息表shares持仓数量用PositiveIntegerFieldcost_price成本价用DecimalField(max_digits10, decimal_places3)这个细节后面会详说bought_at买入时间2.2 市值快照表每日期末数据的价值市值快照表可能是一开始最容易被忽略的设计但恰恰是这个系统最有价值的部分。它的作用是每天收盘后把每个用户持有的每只股票的市值、当日收盘价、当日盈亏等数据存成一条记录。这样做的最大好处是历史可回溯——你想看上周三那天账户总市值是多少不用重新拉一遍行情直接查快照表就行。还有一个隐藏价值是审计和报表如果账户是多人共同使用或者你需要展示月度收益曲线快照表可以按日期聚合统计每天的持仓总市值、当日盈亏、累计收益率这些数据全部是带时间戳的历史真相而不是临时计算的瞬时值。如果不用快照表每次算历史数据都要重新抓行情这有两个问题一是行情接口通常有频率限制二是当时的价格未必能拿到精确的复权结果。对于个人工具来说冗余存储换取可靠性和速度是值得的。2.3 自选股和分组提升使用体验的小设计除了核心持仓管理我还加了自选股功能。自选股表和持仓表不同它只是用户关注但未买入的股票和持仓数量、成本价没有任何关系本质上是一个收藏夹。自选股表关联用户和股票基本信息表再加一个添加时间就够用了。不需要多复杂但它让系统的使用场景从记账扩展到了盯盘。在自选股之上我做了分组功能用户可以把自选股归入长期关注短线观察基金重仓等自定义分组。这个功能加得很轻只在自选股表里加一个group_name字符串字段没有单独建组表。为什么这么做从我的经验看个人用户的自选股数量很少超过100只分组只是一个标签需求不是正经的多对多关系如果为这个建一堆关联表反而让模型复杂化。简单字段解决80%的需求剩下20%等真有规模了再优化。3. 行情数据拉取与市值计算的实现细节3.1 用akshare拉取行情按需刷新和批量更新系统本身不产生行情数据必须从外部数据源拿。我调研了几个方案最终选定akshare作为主数据源因为它是开源免费的 Python 库支持 A 股、港股、美股接口覆盖实时行情、历史行情、财务数据等对个人项目非常友好。实时行情的获取逻辑我设计了两层页面按需刷新用户打开市值总览页时前端通过Ajax请求后端接口后端调用akshare获取当前价格返回给前端渲染。这个频率很低而且只在用户主动操作时触发对数据源压力很小。定时批量更新每天收盘后用Celery定时任务把所有持仓股票和自选股的当日行情拉取一遍写入属于自己的缓存表。因为大批量请求行情接口很容易触发频率限制所以批量更新放在盘后执行错峰拉取然后通过市值快照表记录结果。import akshare as ak def fetch_realtime_price(stock_code: str): 获取单只股票的实时行情 df ak.stock_bid_ask_em(symbolstock_code) return float(df.loc[最新, value])一个很关键的细节股票代码必须规范处理。A股代码是6位数字但akshare接口里沪市和深市的代码会出现不同前缀比如sh600000和sz000001。我的处理方式是统一从股票基本信息表里读取代码和交易所字段在调用接口时拼接前缀不依赖用户输入格式。3.2 市值计算的核心逻辑与后端接口市值计算本身不复杂但有一个地方非常容易出错持仓成本、市价和浮盈的数据类型。这里必须用Decimal而不是Float。Float在计算机中是二进制浮点数无法精确表示像0.1这样的十进制小数累计多笔交易后误差会悄悄变大。我刚开始用Float存成本后来发现持仓浮盈对不上账排查了很久才定位到是这个精度问题。改用Decimal之后金额计算再也没有出过差异。后端视图的接口设计上我遵循一次请求返回完整页面数据的思想而不是拆成十几个小接口让前端拼装。这个接口大概长这样def get_portfolio_summary(user): 计算用户所有持仓的市值汇总 positions Position.objects.filter(useruser).select_related(stock) total_market_value Decimal(0.00) total_cost Decimal(0.00) items [] for pos in positions: current_price get_latest_price(pos.stock.code) market_value Decimal(str(current_price)) * pos.shares profit market_value - (pos.cost_price * pos.shares) profit_ratio profit / (pos.cost_price * pos.shares) total_market_value market_value total_cost pos.cost_price * pos.shares items.append({ name: pos.stock.name, code: pos.stock.code, shares: pos.shares, cost_price: pos.cost_price, current_price: current_price, market_value: market_value, profit: profit, profit_ratio: profit_ratio, }) return { items: items, total_market_value: total_market_value, total_cost: total_cost, total_profit: total_market_value - total_cost, total_profit_ratio: (total_market_value - total_cost) / total_cost, }注意代码里的select_related(stock)这个优化极其重要。如果不加Django ORM会在循环里逐条查询股票表信息持仓一多就会产生N1次查询页面响应时间会明显变慢。3.3 每日收盘后任务用Celery定时生成持仓报表盘后数据更新我用了django-celery-beat。Celery的接入不算复杂但有几个需要提前规划的细节。第一是任务的幂等性。每天跑一次更新任务如果某一天数据源临时不可用或者任务执行到一半失败重跑时不能产生重复记录。我的处理方式是快照表对user stock date建立唯一约束当天每只股票只有一条快照记录。第二是时间窗口。我的任务设置在每天15:30执行因为A股15:00收盘预留30分钟给行情数据源更新当日数据。如果执行时间太早可能拿不到准确的收盘价格。app.task def generate_daily_snapshot(): today timezone.localdate() users User.objects.all() for user in users: positions Position.objects.filter(useruser) daily_total_value Decimal(0.00) for pos in positions: df ak.stock_zh_a_hist( symbolpos.stock.code, perioddaily, start_datetoday.strftime(%Y%m%d), end_datetoday.strftime(%Y%m%d), ) if df.empty: continue close_price Decimal(str(df.iloc[-1][收盘])) MarketSnapshot.objects.update_or_create( useruser, stockpos.stock, snapshot_datetoday, defaults{ close_price: close_price, shares: pos.shares, market_value: close_price * pos.shares, }, ) daily_total_value close_price * pos.shares UserDailySummary.objects.update_or_create( useruser, snapshot_datetoday, defaults{total_market_value: daily_total_value}, )4. 前端页面和交互从自选股列表到趋势图表4.1 页面结构模板继承与组件化思路前端部分我并没有引入复杂的前后端分离架构而是用Django模板系统加少量JavaScript。这种选择完全符合项目定位——数据管理工具不是高并发C端产品。直接用模板渲染后端把上下文数据传给前端服务端渲染首屏SEO无所谓但胜在结构简单、部署方便、不需要额外搭建Node服务。模板继承方面我设计了一个基础模板base.html里面包含侧边导航栏和页面主区域。子模板只需要继承并实现content块。这样在新增页面时不用重复写导航整个项目的前端代码量控制得很小。4.2 用ECharts做市值趋势图市值趋势图是系统里最有科技感的部分前端用了ECharts。它支持从后端传入JSON数据非常方便。数据来源就是上一节说的UserDailySummary表。我写了一个视图专门返回近30天的市值趋势数据def portfolio_trend(request): summaries ( UserDailySummary.objects .filter(userrequest.user) .order_by(-snapshot_date)[:30] ) trend_data { dates: [s.snapshot_date.strftime(%m-%d) for s in summaries][::-1], values: [float(s.total_market_value) for s in summaries][::-1], } return JsonResponse(trend_data)前端用ECharts折线图展示fetch(/portfolio/trend/) .then(response response.json()) .then(data { const chart echarts.init(document.getElementById(trendChart)); chart.setOption({ xAxis: { type: category, data: data.dates }, yAxis: { type: value }, series: [{ type: line, data: data.values, areaStyle: {} }] }); });这个图最需要注意的地方是数据倒序。数据库里最新日期排在最前画图时要从旧到新显示所以我在Python里取了[:30]后又做了[::-1]反转前端拿到的才是时间正序。4.3 Ajax刷新实时价格的前端细节总览页面上的实时价格如果每次打开都重新渲染整个页面体验会比较生硬。我用了Ajax定时刷新每10秒请求一次后端接口只更新价格和市值这几个数字不让整个页面闪烁。实现上每个股票代码对应的DOM元素加了一个style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />