最近在复盘自己的数据工作流时我经常被问到一个问题个人做股票分析到底要不要自己搭一套行情数据系统市面上的炒股软件、免费网页看盘工具一抓一大把为什么还要费劲自己动手如果你也有同样的疑问OpenStock 这个开源项目可以给你一个很好的答案。OpenStock 从名字就能看出来Open Stock主打一套开源的、个人可部署的股票数据分析与展示系统。它可以完成行情数据采集、数据存储、技术指标计算、自定义选股、可视化看板展示这几件核心事情。也就是说你不需要把数据局限在某个封闭软件里所有行情数据、财务数据、交易记录都可以沉淀在你自己控制的数据库中按自己的思路去分析、去计算、去展示。这篇文章我会基于我自己从零搭建 OpenStock 的完整过程把架构思路、数据采集、数据库设计、指标计算、Web 展示、定时任务这些环节一个个拆开讲清楚并且把实际操作中踩过的坑一并列出来适合想做量化入门、个人数据看板、股票数据研究的开发者参考。如果你是一个刚接触股票数据开发的初学者或者已经有一定 Python 基础但想尝试完整项目这篇文章能帮你省掉不少弯路。我不打算只给一份部署命令了事而是会把每一步“为什么这么设计”“为什么选这个方案”都解释清楚这样你搭完之后也能自己动手改、自己加功能而不是照抄完就丢一边。1. 项目整体设计与思路拆解1.1 OpenStock 到底解决什么问题先聊聊我在决定自己搭 OpenStock 之前遇到的痛点。用免费看盘软件数据都在对方服务器上你能看到的只是别人处理好的图表和指标想拿到原始分钟线、日线数据做二次分析基本不可能。用商业数据接口个人付费成本又偏高而且如果你只是想研究某几个策略这个钱花得并不划算。OpenStock 把这件事拆成了几个标准模块每个模块各管一摊数据采集模块从公开数据源抓取股票日线、分钟线、基础信息、财务数据做一个本地数据仓库。数据存储模块用 MySQL或者 SQLite 做轻量替代保存采集到的数据建好索引查询速度有保障。指标计算模块在本地数据基础上计算 MA、MACD、RSI、KDJ 等常用技术指标也可以自己写选股条件。可视化模块利用 Flask ECharts 搭建 Web 看板把 K 线、成交量、指标信号展示出来不用依赖外部平台。当时我之所以选定这套结构是因为它把“数据获取”和“策略研究”很好地解耦了。数据获取是脏活累活集中在一个模块里管理切换数据源或者补充字段时只用动一个地方。策略研究和展示是上层建筑数据到位后你可以用 Python 的 pandas 随便折腾也可以直接在 SQL 里做条件筛选灵活性比在封闭软件里点鼠标高太多了。1.2 为什么选择这套技术栈技术选型方面我认真对比过几种方案这里直接说结论。第一Python。不用多解释数据采集、清洗、计算生态太成熟了pandas、numpy、requests 这些库能帮你把开发效率拉满。对于这种个人项目我没有理由去选 Java 或者 Go 增加自己的工作量除非你有高并发的性能要求。第二MySQL。这里可能有人会问为什么不用 MongoDB 或者直接存 CSV说实话最开始我图省事直接把数据存成了 CSV 文件结果数据量到几万条后加载和筛选开始变慢而且并发读写时容易出问题。后来换到 MySQL按股票代码和日期建好联合索引查询性能完全是另一个档次。如果只是想先跑通流程SQLite 也可以但后面数据量涨上来迁移会比较折腾所以不如一开始就选 MySQL。第三Flask ECharts。Flask 足够轻量写几个 API 给前端用非常方便ECharts 的 K 线图组件成熟交互顺手拿来画蜡烛图、成交量图、MACD 副图都不需要自己造轮子。相比直接用 Jupyter Notebook 画图Web 看板的好处是可以随时打开、随时刷新不用每次重新跑代码。1.3 手动部署还是容器化部署OpenStock 支持两种部署方式。如果你熟悉 Docker直接跑 docker-compose 是最快的一条命令就能把数据库和应用一起拉起来。但我的建议是第一次搭建尽量手动部署不要图省事直接上容器。原因很简单只有手动部署一遍你才会知道配置项到底配的是什么、数据库表为什么这样建、数据初始化脚本在做什么。一旦这些底层逻辑搞清楚了后面出任何问题你能快速定位是代码问题还是环境问题。我在实际排障过程中发现大部分新手遇到的问题都不是 OpenStock 项目代码本身而是 Python 环境冲突、MySQL 字符集配置、crontab 环境变量这类基础问题。这些坑手动部署一遍全都心里有数了。2. 环境准备与数据采集核心实现2.1 五分钟准备基础环境在开始拉代码之前先把环境准备好。我的机器是 Ubuntu 20.04Python 版本 3.8MySQL 8.0。Windows 和 macOS 也能跑只是个别系统级依赖安装方式略有不同后面遇到问题我会单独说。按顺序执行这几步安装 Python 虚拟环境工具apt install python3-venv或者直接用python3 -m venv venv创建虚拟环境。激活虚拟环境source venv/bin/activateWindows 下是venv\Scripts\activate。拉取 OpenStock 代码git clone https://github.com/你的仓库地址/openstock.git cd openstock。安装依赖pip install -r requirements.txt。这里强烈建议使用虚拟环境。我见过太多人在全局 Python 环境里装了一堆包版本互相冲突最后整个系统环境都坏了。虚拟环境相当于给每个项目一个独立的小房间项目之间互不干扰。安装依赖的时候如果网络速度不理想可以换成国内镜像源pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple速度会快很多。2.2 数据源接入从公开接口到本地数据仓库数据是 OpenStock 的命根子没有数据后面的一切都是空中楼阁。OpenStock 在设计上抽象了一个数据源接口层底层对接的是公开的股票数据接口比如 AkShare 这类免费开源库。选它主要有几个考虑免费、无需申请复杂的 token、接口覆盖范围广A 股、港股、美股的基础行情数据基本都能拿到。在实际代码里数据采集模块会做一个“全量初始化 增量更新”的策略。全量初始化是什么意思就是第一次运行时把选定的股票池中每一只股票的历史日线数据从头拉一遍比如从上市首日到现在。这个过程比较慢因为要一只股票一只股票地请求还要注意接口限频。我当时初始化 300 只股票用单线程跑花了接近二十分钟后来加了time.sleep(0.5)来控制请求频率避免被封 IP。增量更新就快多了每天收盘后只需要拉取当天的数据插入到对应股票的记录里。这个逻辑可以写在定时任务里后面我会专门讲。2.3 数据表设计与索引优化有了数据之后怎么存就成了关键。OpenStock 数据库里最重要的表是日线行情表我把它命名为daily_kline核心字段包括stock_code股票代码比如000001平安银行。trade_date交易日期。open_price/high_price/low_price/close_price开高低收四个价格。volume成交量。amount成交额。这些字段对应股票数据的标准 OHLCV 结构。建表时我建议把(stock_code, trade_date)建成联合唯一索引这样既能保证同一只股票同一天只存一条记录不会有重复数据又能让按股票和时间范围查询的速度大幅提升。这里有一个我在实际操作中踩过的坑一开始我只对trade_date建了普通索引结果查询某只股票一年的数据时数据库扫描了全表慢得让人崩溃。后来改成联合索引后同样的查询直接从几百毫秒降到了个位数毫秒。如果你是初学者可能对索引不太敏感但等你数据量上了十万条就明白这个设计有多重要了。存储引擎方面默认 InnoDB 就行支持事务数据安全性更好。不建议为了省空间用 MyISAM一旦插入过程中途出错没有事务回滚很容易留下半截数据。3. 指标计算与选股逻辑实现3.1 技术指标是实时计算还是预计算行情数据到位后接下来就是算指标。OpenStock 里内置了 MA均线、MACD、RSI、KDJ 这些最常见的指标但这里有一个设计选择这些指标是每次打开页面时实时计算还是数据入库时一次性算好存下来两种方案我都试过。实时计算的好处是代码简单逻辑清晰但数据量大时响应速度跟不上而且每次刷新页面都要重复算一遍纯属浪费算力。预计算则是把指标结果存在单独的表中页面直接读结果快是快但如果改了指标参数比如均线从 5 日改成 10 日历史数据需要重算。最终 OpenStock 采用的是一种折中方案核心指标如 MA、MACD 在数据更新任务中预计算并存储而一些自定义的临时指标支持按需计算。这样既保证了日常使用的流畅度又保留了灵活性。预计算任务其实就是一个 Python 脚本读取日线数据用 pandas 计算指标再写回数据库。唯一要注意的是计算日期的边界条件比如新上市股票数据量不足计算 MA5 时前四天是拿不到值的代码里要跳过或用 None 填充。3.2 如何用 SQL 实现自己的选股策略指标只是工具真正有意思的是选股策略。OpenStock 没有把策略写死而是留了自定义接口你可以用纯 SQL 实现很多基础策略。举个例子比如我想找出“5 日均线上穿 20 日均线”的股票也就是常说的金叉用 SQL 可以这样表达从指标表里找到某一天的 MA5 和 MA20。比较今天和昨天的 MA5 与 MA20 大小关系昨天 MA5 小于 MA20今天 MA5 大于 MA20。写成 SQL 的逻辑就是用LAG函数取前一行数据或者用自连接。我第一次写这个查询时由于数据量不大直接用自连接搞定后来数据量涨上来发现自连接效率偏低改成了窗口函数LAG()代码更简洁性能也更好。这个经历也提醒我选股策略可以先用最简单的方式跑通遇到瓶颈再优化不要一开始就想着搞复杂架构。3.3 用 pandas 做更灵活的策略回测SQL 适合做条件筛选但如果你要做一个稍微复杂的策略回测比如“连续三根阳线后买入持有一周后卖出”纯 SQL 写起来有点痛苦。这种时候我的做法是把数据从数据库里读出来交给 pandas 处理。OpenStock 的代码结构里单独留了一个analyzer模块专门放这类分析代码。你可以自由地用 pandas 做滚动计算、分组统计、回测收益曲线最后把结果存回数据库或者直接在前端展示。我自己在实际使用中就是用 pandas 写了一个“放量突破 20 日新高”的回测脚本跑完数据发现胜率并不理想于是调整参数后再跑。这个反复试验的过程正是自己搭系统的价值所在——你可以深入到底层数据里而不是在别人封装好的黑盒里碰运气。4. 可视化看板搭建与定时更新4.1 用 Flask 写个轻量 API 层有了数据有了指标最后就是展示。OpenStock 的后端用了 Flask它是一个非常轻量的 Web 框架写几个 JSON 接口给前端用非常方便。我的 API 层设计大概是这样/api/stock/list返回股票基础列表。/api/stock/kline?code000001返回指定股票的 K 线数据包括日期、开高低收、成交量。/api/stock/indicator?code000001typeMACD返回指定股票的指标数据。这些接口的逻辑都不复杂核心就是从数据库里把数据查出来转成 JSON 返回。需要注意的一点是时间字段的序列化Python 的datetime对象不能直接转 JSON需要对它做格式化比如转成2024-01-15这样的字符串。我在第一次写接口时忘了处理结果前端拿到数据后怎么都解析不出来排查了半天才发现是返回的时间格式有问题。4.2 基于 ECharts 画 K 线和 MACD 副图前端展示部分OpenStock 选择了 ECharts。画 K 线图是 ECharts 最基础的功能把 K 线数据和成交量数据传进去设置好series类型就能出图。我实际使用中觉得最方便的是 ECharts 的 dataZoom 组件可以直接拖拽放大缩小时间范围复盘行情时体验比看静态图片好出不少。画 MACD 副图的思路也类似在同一个图表里加一个副网格绑定MACD的 DIF、DEA、柱状图数据。ECharts 支持一个图表多个 grid只需要把副图的位置设置好就行。这里有个小技巧MACD 柱状图的正负柱颜色要区分用visualMap组件可以很容易做到正值画红色负值画绿色和国内行情软件的习惯保持一致。第一次部署 OpenStock 的时候我发现前端页面数据迟迟不显示打开浏览器开发者工具才看到是接口返回的字段名和前端对不上。这是因为我在后端把open_price返回为open而前端期望的是open_price。这类前后端字段约定问题在做前后端分离的项目时非常常见一定要在设计 API 时把字段风格统一好省得后面来回改。4.3 定时任务让数据每天自动更新系统搭好之后手动更新数据显然不是长久之计。OpenStock 的定时更新任务我是用 Linux 的 crontab 实现的每天下午收盘后定时执行更新脚本。crontab 的配置大概是这样的0 16 * * 1-5 cd /path/to/openstock /usr/bin/python3 update_daily.py logs/update.log 21这条命令的含义是每周一到周五下午 16 点进入项目目录运行数据更新脚本并把日志输出到日志文件里。为什么定在 16 点因为 A 股 15 点收盘数据源一般需要一些时间才能更新完留一个小时缓冲比较稳妥。这里有一个我在实际运维中踩过的典型的坑crontab 里设置的 Python 路径必须是绝对路径。如果你直接用python3crontab 默认的 PATH 环境变量可能找不到这个命令任务会静默失败。而且脚本里如果用了虚拟环境也要把 Python 的路径指向虚拟环境的解释器而不是全局的 Python否则会报找不到依赖包的错误。还有一个建议日志一定要保留。刚开始时我不太在意日志直到有一次发现连续三天数据没有更新查了半天才发现是上游数据源接口变更脚本报错了。如果没有日志这种问题根本无从查起。后来我加了个简单的日志保留策略每天一个日志文件保留 30 天这样既方便排查问题也不会占太多磁盘空间。5. 常见问题与排查技巧实录5.1 这张表总结了我所有踩过的坑从零搭建 OpenStock 的过程中我前前后后遇到了不少问题我把其中有代表性的整理成一个表格方便你遇到相同情况时快速对照。问题现象根本原因解决办法数据一直采集不进来控制台无报错数据源接口限流请求过于频繁被拒绝在采集脚本中增加每次请求之间的休眠时间建议 0.5 秒以上MySQL 插入中文乱码数据库字符集没有设置为 utf8mb4建库时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ciK 线图页面上价格显示不完整接口返回的 JSON 中价格字段精度丢失在 ORM 映射时将 Decimal 类型转为 float或在前端统一做格式化定时任务没有执行crontab 使用了相对路径的 Python将 Python 解释器路径改为虚拟环境的绝对路径并确认脚本有执行权限数据库查询越来越慢索引缺失或查询条件未走索引用EXPLAIN分析 SQL 执行计划确认联合索引是否命中更新数据时出现重复记录重复执行了多次更新脚本且无去重逻辑给表加上联合唯一索引并使用INSERT ... ON DUPLICATE KEY UPDATE你会发现这些问题的根源大都不是复杂的高深理论而是细节处理不到位。所以遇到问题时第一反应不要是“重装系统”或“重选框架”先看日志、看报错信息、看 SQL 执行计划90% 的问题都能自己定位出来。5.2 数据源接口变更时的应急处理用免费数据源有一个避不开的问题接口随时可能调整甚至停用。我曾经遇到过数据源的字段名变了脚本里还按老的字段名解析结果采集到的数据全是空值但是脚本本身没有报错因为解析环节用了dict.get()而不是直接索引缺失字段时只返回了 None。这个经历告诉我不管用哪个数据源代码里一定要有“字段校验”这一层。比如请求返回后先检查关键字段是否存在于返回结果中如果缺失就打印告警日志并且跳过这条数据。这样接口一变你能第一时间从日志里发现异常而不是让脏数据悄悄入库等到发现时已经积压了一大堆。另外重要数据源尽量做好隔离。哪怕你只有一个数据源也建议在代码里封装一层“数据源适配器”接口。等将来想换数据源时只需要重写适配器上层逻辑完全不用动。OpenStock 的代码结构里保留了这个扩展点我强烈建议你不要把那层封装删掉。5.3 性能优化单线程采集如何提速刚开始初始化数据时我用单线程逐只股票采集300 只股票跑下来差不多二十分钟。这个速度虽然能忍但每次想扩大股票池就得重新忍受一次漫长的初始化。提速方案用concurrent.futures线程池开 5 个线程并发采集。速度上去很直观但风险也随之而来请求频率太高容易被数据源临时封禁 IP。我的折中方案是控制并发数在 3 到 5 之间并且对线程打一个随机的休眠时间比如 0.3 到 0.8 秒之间随机这样既不会触发限流又能把速度提升 3 倍左右。还有一个优化点是可以做断点续采。每只股票采集完成后在本地记录一个已完成的股票代码集合。下次初始化时跳过这些股票只补增量部分。这个改进对实际使用体验提升非常大特别是在网络不稳定、中途中断的场景下不用从头再来。写在最后的一点个人体会从零搭完 OpenStock对我来说最有价值的不是最终那个能看 K 线的网页而是把行情数据的全链路亲手走了一遍。以前用现成软件时我只关心“这个指标看起来怎么样”现在我会下意识地想“这个指标背后到底是怎么计算的”“数据从哪里来有没有脏数据”。这种对数据底层的掌控感是直接使用商业软件永远得不到的。如果你也想动手搭建一套自己的股票数据分析系统我的建议是别一上来就追求大而全先跑通最小的闭环。先把数据采下来、存进 MySQL、能在终端里查到结果这一步就值得庆祝了。然后再加上指标计算、可视化展示、定时更新一点一点把系统养大。不要怕遇到问题你踩的每一个坑都是对这套系统更深理解的机会。按照我上面的步骤操作你完全可以一步步把 OpenStock 跑起来并在跑通的基础上加上属于你自己的分析逻辑和策略想法。
