ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

OpenStock:自托管股票行情跟踪与持仓分析系统搭建指南

OpenStock:自托管股票行情跟踪与持仓分析系统搭建指南 今天想和大家聊一个我折腾了大半个月的项目OpenStock。这是一个完全开源、自托管的个人股票行情跟踪与持仓分析系统。说白了它就是一套能把行情数据抓下来、存到你自己的数据库里然后通过网页看板去查看价格走势、分析你自己的持仓收益、计算盈亏指标的小工具。适合谁用我觉得主要是三类人一是受够了各种炒股App里花里胡哨广告和数据混乱的普通投资者二是喜欢自己折腾、想攒一个属于自己数据仓库的Python开发者三是对量化交易或者个人数据分析感兴趣但还没找到合适练手项目的人。如果你只想打开某App看一眼价格这个项目对你没什么用。但如果你想把自己的投资数据彻底握在手心同时还想顺带练练数据库、API、定时任务、数据可视化这一整套技术栈那OpenStock是个非常合适的机会。我当初决定自建这套系统的原因很简单市面上的行情工具要么数据口径不透明要么有各种推送干扰要么想导出自己的交易记录还得手动复制粘贴。我就想要一个能自动抓取行情、能记录我每一笔买点卖点、能一眼看出我仓位盈亏情况的网页而且所有数据都只存在我自己的电脑或服务器里。OpenStock就是朝这个方向去的一个完整实践。它不是什么能自动驾驶帮你炒股的黑科技它更像一个“个人理财数据底座”把数据抓取、存储、分析、展示这条链路全部打通。接下来我会从整个项目的核心设计、技术选型、搭建步骤、常见坑点这几个维度把这次实操经验完整记录下来希望能帮准备入手的你少走弯路。1. 为什么自建OpenStock把数据主动权拿回自己手里如果只是“看个价格”那任何免费App都够了。真正让人不舒服的点在于你想看自己某只股票从买入到现在的真实收益变化App只给你一个莫名其妙的“持有收益”你想把它和另一只股票做对比又发现不同App的数据源还不一致你想统计自己这半年来的交易纪律执行得怎么样结果根本没有这种功能。OpenStock的思路其实很朴素行情数据我自己抓交易记录我自己存分析逻辑我自己写页面展示我自己的数据库怎么组织都行。它放弃了“实时极速行情”这种花架子换取的是数据透明度、可控性和完全的自由度。从技术角度看这个项目也提供了一个很典型的数据管道实践场景外部数据源——定时任务抓取——结构化存储——接口服务——前端可视化。这套链路在真实的行业项目里非常常见区别只是数据可能从行情变成订单、用户行为、传感器日志等等。所以搭建OpenStock的过程无论你是投资爱好者还是想练手的开发者都能从中获得不少能复用的工程经验。1.1 自建系统的核心能力拆解我最初的设想是把OpenStock做成一个“轻量但完整”的个人投资数据中心而不是一个功能堆砌的玩具。最终我圈定了六个核心能力也是我认为一个自建行情跟踪系统最必要的底座行情数据定时抓取与存储日K、周K、月K以及盘后快照数据能自动更新到本地数据库不依赖任何外部App。自选股管理可以维护一个自己关注股票的列表系统会自动同步这些股票的最新行情与历史K线。交易记录管理手动录入每笔买入、卖出操作包括成交价格、数量、手续费系统根据成交价和行情数据自动计算浮动盈亏。持仓盈亏看板用图表展示每只股票的收益情况、资产占比、相对沪深300的超额收益等核心分析结果。数据导出能力所有数据库表都可以随时导出为 CSV方便进一步做自己的量化分析。本地优先隐私可控所有数据只落在你自己的数据库里和外部服务只有“抓行情”这一个出口。这套能力拆解下来你会发现它本质就是一个“数据采集 账单管理 简单分析”的组合。它不需要复杂的实时推送也不需要券商的交易接口只需要在每天收盘后定时把数据同步一次然后基于你自己的交易记录算出各类收益指标。这就让整个项目在技术实现上变得非常可控单机跑完全没有压力。1.2 OpenStock相对于现成App的优势和劣势我身边总有朋友问既然手机里已经有股票软件了为什么还要自己搭一套我的回答是现成App解决的是“即开即用”的需求而OpenStock解决的是“数据二次加工”的需求。它永远不会取代你主力看盘工具但它可以作为你后续做个人决策分析的基础设施。数据口径在自己手里计算方法可以随时改动即便未来你换了券商历史记录也一点不会丢。当然劣势也很明显第一数据实时性差只能做到收盘后或盘中低频更新不可能达到毫秒级第二需要自己维护服务器或者至少一台常开的电脑有电力、网络等运维成本第三行情数据源通常有调用频率限制如果你很贪心、维护了几千只股票的自动更新需要额外处理限流和分批抓取的问题。这些限制我在后面会遇到并逐一说明。2. 整体设计思路与技术选型解析很多人搭建这类系统的第一反应是“先写前端”问我要用什么框架。我的建议永远是先设计数据层再把接口定了最后才是页面。OpenStock整个项目的数据流是单向的数据源脚本负责抓取行情写入数据库后端接口负责从数据库读数据组装成前端需要的 JSON前端页面负责把 JSON 渲染成图表和表格。这个单向数据流的好处是出了任何问题你只要按着“抓取层—存储层—服务层—展示层”一层层排查思路会非常清晰。2.1 技术栈一图流与选型理由先说最终确定的技术栈然后解释为什么这么选。后端用 Python FastAPI数据库用 PostgreSQL缓存用 Redis定时任务框架用 APScheduler前端用 Vue 3 ECharts部署直接走 Docker Compose。这套组合现在看不算新颖但它比较均衡Python生态的数据库驱动和数据处理的库非常成熟FastAPI天然自带OpenAPI文档调试接口很方便PostgreSQL在金融数据这种需要强一致性的场景下比很多NoSQL数据库靠谱得多Redis则用来缓存热点行情数据避免前端频繁刷新时直接把压力打到数据库上。有人可能会问为什么不用 Flask 或者 DjangoFlask 的话路由、校验、序列化这些都要自己拼写多了容易乱Django 则是大而全对一个小型自托管项目来说有点重。FastAPI 正好卡在中间轻量的同时自带请求校验和自动文档写接口的效率非常高。还有一个细节FastAPI 天然支持异步以后如果想加 WebSocket 推送行情也不用推翻重写。2.2 数据库表设计为股票数据单独建模数据库是整个OpenStock最需要花心思的地方。我最终建立了五张核心表分别是股票基础信息表、每日行情表、财务指标表、自选股表、交易记录表。这里特别说一下每日行情表和交易记录表的设计思路。每日行情表保留了最核心的几个字段股票代码、交易日期、开盘价、最高价、最低价、收盘价、成交量、成交额、复权因子。为什么把复权因子单独拿出来因为股票除权除息会影响价格连续性做收益率计算的时候必须用复权后的价格。如果直接把后复权价格当原始价格存那后续算出来的每股成本、盈亏都是不对的。所以在抓取阶段我会把原始价格和复权因子都存下来计算时统一换算。交易记录表则记录了每笔真实操作的细节股票代码、买卖方向、成交时间、成交价格、成交数量、手续费、备注。很多人会忽略备注这个字段但实际用下来备注非常有用。比如你卖出某只股票是因为止损还是止盈基金定投那笔钱来自哪个银行卡这些信息在事后复盘时价值巨大。2.3 为什么用定时任务而不是实时推送很多没做过类似系统的朋友对“行情数据从哪来”有误解以为需要连交易所的实时推送。其实个人项目完全没必要。OpenStock的策略是使用每日定时任务在交易日收盘后或者盘中按固定间隔去抓取行情。这么做的原因有两个一是实时行情数据源通常需要付费或者有严格权限个人很难拿到稳定授权二是个人投资决策根本不需要毫秒级行情分钟级甚至日级别的数据足够完成绝大多数回测和收益评估。具体实现上我用APScheduler而不是系统Cron是因为它能让定时任务和Web应用在同一个进程里维护部署起来最简单。如果我设置每天15点30分执行一次日K同步任务当天是周末或者节假日就跳过避免抓到空数据。再把同步任务放在Docker容器里就算服务器重启了容器也能自动恢复并继续执行任务。3. 手把手搭建OpenStock从零到可用的完整流程接下来进入整个项目最核心的部分也就是完整搭建过程。我假设你现在的环境是一个干净的Linux服务器或者安装了Docker Desktop的Windows/Mac电脑不需要提前装好Python和数据库因为最后全部走Docker方式运行。如果你不想用Docker手动在宿主机上装依赖也可以但步骤会多不少这里我优先推荐Docker Compose的路线。3.1 搭建前的环境准备在开始之前需要确认三件事第一你的电脑或服务器上已经安装好了Docker和Docker Compose插件第二能正常访问Docker Hub或者你已经配置好了可用的镜像加速第三预留至少2GB内存和10GB磁盘空间。OpenStock本身的代码量不大占地方的主要是PostgreSQL里的行情数据。如果你打算长期同步上证、深证、创业板的几千只股票日K表的数据量会增长非常快所以磁盘建议给得宽裕一些。检查环境是否就绪直接在终端里跑两个命令docker version docker compose version如果都能正常输出版本号说明环境没有问题。接着创建一个项目目录比如叫 openstockmkdir ~/openstock cd ~/openstock然后从代码仓库把项目克隆下来。如果项目在你自己的Git仓库里就直接克隆你自己的地址。克隆完成之后目录下应该会有一个 docker-compose.yml 文件和一个文件夹里面放着后端、前端、数据库脚本等代码。3.2 用Docker Compose一键启动整套服务OpenStock使用Docker Compose来编排四个服务openstock-dbPostgreSQL数据库、openstock-redisRedis缓存、openstock-backendFastAPI后端、openstock-frontendNginx托管的前端静态页面。在项目根目录执行一句docker compose up -d第一次启动会去Docker Hub拉取镜像耗时取决于网络情况耐心等一下。拉取完成之后各个容器会自动启动。通过 docker compose ps 查看状态正常情况下应该是四个服务都是 Up 状态。如果发现 openstock-db 反复重启大概率是数据库数据目录的权限问题需要检查宿主机上挂载的 volume 目录是否可写。启动完成之后打开浏览器访问 http://localhost:8080如果一切正常就能看到OpenStock的登录页面。首次使用需要注册一个管理员账号注册信息会存到PostgreSQL里。这一步比较简单但要注意用户名不要用中文因为用户表默认只适配了字母和数字。3.3 数据库初始化和默认配置修改虽然 docker compose up 能一键拉起所有服务但如果你直接使用默认配置很快会遇到数据抓不下来或者时区不对等问题。这里有几个必须改的配置项我踩过坑逐一说明。第一是时区。数据库容器默认可能是UTC时间行情数据的交易日期必须和国内日历保持一致否则周五晚上同步的数据会算到周六去。解决方法是修改数据库环境变量里的 TZ 为 Asia/Shanghai同时在后端应用的配置文件中把定时任务时区也设置成 Asia/Shanghai。两边时区不一致会导致定时任务在预想不到的时间点触发而且K线日期错乱排查起来非常隐蔽。第二是数据源参数。OpenStock默认从一个免费行情接口拉数据不同数据源的字段格式略有不同。你需要在一个叫 config.yaml 的文件里配置数据源名称、请求间隔、需要同步的股票池列表。比如我只关心沪深300的成分股那就把股票池配置成沪深300的代码列表别一次性把所有A股都放进去不然请求频率很容易被限流。第三是Redis缓存策略。默认配置下行情列表接口会缓存5分钟这是为了给数据库减负。如果你的使用频率很低也没有必要改但如果你希望前端数据刷新得更勤快可以把 CACHE_TTL 从默认的300秒调低到60秒。这个参数影响的是行情接口的响应速度不影响定时抓取任务本身。3.4 首次同步行情数据的完整操作数据库和配置都准备完成之后就可以手工触发一次行情同步验证整条数据链路是否通畅。在容器里执行docker compose exec openstock-backend python -m app.jobs.run_once --sync daily_kline这条命令会在后台任务模块中执行一次日K线同步。首次同步会逐条抓取股票池中所有股票的日K数据耗时取决于股票数量。比如同步100只股票大约需要两三分钟如果是1800只可能需要二十多分钟。执行过程中你可以观察日志输出正常情况会看到类似“fetched 250 rows for 000001.SZ”的日志说明数据写入成功。同步完成之后进入数据库看看实际表结构和数据条数docker compose exec openstock-db psql -U openstock -d openstock然后执行SELECT COUNT(*) FROM daily_kline; SELECT stock_code, trade_date, close_price FROM daily_kline ORDER BY trade_date DESC LIMIT 10;如果能看到最近几个交易日的完整行情记录说明数据链路已经打通了。第一次跑通这个查询的时候我确实觉得花时间折腾是值得的因为这意味着后续所有分析功能都有了数据基础。3.5 添加自选股和记录交易数据同步正常之后系统的基础功能已经能用了。接下来要做的是录入自己的关注列表和历史交易记录。OpenStock提供了两种方式一种是在前端页面上点击“添加自选股”按钮填写股票代码后系统自动去数据库里查匹配记录并加入我的自选列表另一种是把交易记录通过CSV文件批量导入。CSV导入的逻辑是你把券商导出的成交记录整理成固定格式包含股票代码、方向、日期、价格、数量、手续费然后上传到系统里。系统会逐行校验字段是否合法比如方向只能是BUY或者SELL价格和数量必须大于0校验通过后写入交易记录表。这个功能我强烈建议你们好好用起来因为一旦连续记录一个月你就能看到持仓盈亏看板里那些统计数字的动态变化比对着Excel表格计算舒服太多了。录完之后打开前端“持仓分析”页面就能看到每只股票的成本价、最新价、浮动盈亏、收益率以及整个组合的资产分布饼图。这些指标全部由后端根据行情表和交易记录表实时计算不是前端本地算出来的所以哪怕你换一台电脑、重新登录看到的结果也是一致的。3.6 定时任务与长期运行的配置方式本地手工同步一次验证功能可以但真正长期跑的时候不可能每天都手动进终端敲命令。OpenStock已经把定时任务固化在代码里你只需要确保backend容器一直在运行定时任务就会按配置的时间自动触发。默认配置是每个交易日下午15点40分开始同步日K17点整开始计算技术指标和收益指标周末不执行。如果想让定时任务跑得更加可靠我建议设置一个健康检查接口比如OpenStock提供了 /healthz 路径返回的内容包含数据库连接状态和最近一次同步时间。你可以把它挂给外部监控或者写一个简单的shell脚本定期访问如果连续几次没有返回200就发一封告警邮件。长期自托管的服务不在于功能多花哨而在于你能不能及时知道它什么时候挂了。4. 常见问题与排查技巧实录任何自部署项目出问题都是正常现象。我把自己在搭建和使用OpenStock过程中遇到的高频问题和排查思路整理成一份速查表希望能帮你节省一些排查时间。有些问题光看报错第一时间猜不到原因要结合日志和数据库状态一起判断。4.1 数据库连接失败或容器反复重启这是Docker部署最常见的坑。如果你看到 backend 容器一直处于 Restarting 状态先查 backend 日志docker compose logs openstock-backend如果是“database connection failed”之类错误先确认数据库容器是否正常docker compose ps有时候数据库容器虽然显示 Up但实际上没有完成初始化因为PostgreSQL第一次启动初始化会比较慢而backend容器启动得早连接失败之后没有自动重试机制就卡住了。解决办法是把backend容器的重启策略改成 depends_on: condition: service_healthy并且在healthcheck里增加对PostgreSQL就绪状态的检查。如果已经处于卡死状态直接重启backend容器通常就能恢复docker compose restart openstock-backend另一个常见原因是宿主机上的5432端口被本地PostgreSQL占用了。如果你电脑上已经装过PostgreSQL再跑Docker容器就会出现端口映射失败。解决方案是把docker-compose.yml里的端口映射改成前面一个宿主端口比如“5433:5432”。4.2 同步行情时报错频率被限制免费行情接口通常有每秒钟最多请求多少次这样的限制。OpenStock在抓取逻辑里已经做了简单的间隔控制但如果你手动把股票池扩大了很多倍或者同时运行了多个同步任务就可能触发数据源的限流。报错信息一般会包含HTTP 429或者“Too Many Requests”的关键词。排查方法是先看日志里连续出现HTTP 429的时间段确认是不是自己并发设置的请求间隔太短。在config.yaml里调整 REQUEST_INTERVAL 参数比如从默认的0.5秒改成2秒再重新尝试。如果这样还不行可能是IP已经被临时封禁等半小时再试。为了减少限流影响我后来把股票池切成了几个批次每个批次在不同的时间点错峰同步不再用一把梭的方式抓取全部股票。4.3 K线日期出现缺失或者时区错乱这是一个非常隐蔽的问题。症状是你的自选股列表里某只股票某一天的K线缺失或者最新交易日数据一直不更新。大概率不是数据源的问题而是数据库时区和定时任务时区不对齐导致的。你可以先执行一条SQL检查数据库中最新交易日SELECT MAX(trade_date) FROM daily_kline WHERE stock_code 000001.SZ;如果最新日期不是最近的交易日而是前一天甚至更早基本可以判定是同步任务没在预期时间触发。这时候去backend的日志里看上次任务执行时间确认定时任务是否按Asia/Shanghai时区触发。如果日志显示任务在UTC时间跑过了但你数据库里没有新数据还可以检查一下数据表里trade_date字段的类型和写入逻辑。总之时区问题要从“任务触发时间—接口请求参数—存储日期”三个环节逐一排查。4.4 前端页面图表加载不出来前端图表加载不出来的原因通常是接口返回的数据格式不对再就是网络代理问题。打开浏览器的开发者工具切到Network面板看看行情接口的返回内容是不是正常的JSON。如果接口返回了500去后端日志里看具体报错。我碰到过一次是因为数据库的某个字段在同步时写入了NULL而计算收益率的SQL没有对NULL做判断导致接口直接抛异常。后来在计算逻辑里统一加了 COALESCE 默认值处理问题就解决了。如果接口返回数据正常但图表还是空白大概率是前端ECharts在拿到数据时字段名对不上。比如后端返回的日期字段叫 trade_date但前端图表配置里读的是 date对不上就什么都画不出来。这种问题不用动后端改前端的数据映射即可。5. 长期使用下来的性能优化与避坑心得系统能跑起来是一回事跑得顺畅、数据准确是另一回事。OpenStock用到后面我逐渐遇到了一些数据量上来之后才会暴露的问题也摸索出一些优化思路这里分享几条最实用的经验。5.1 行情数据量上来之后数据库必须建索引和做分区日K线看起来一只股票一年只有两百多条数据几百只股票也就是几万条听上去不多。但如果你跑上两三年再加上分钟级数据或周K、月K等不同周期数据量会迅速膨胀。当某张表超过百万行之后不带索引的查询会明显变慢。务必在 daily_kline 表的 stock_code 和 trade_date 这两个字段上建联合索引CREATE INDEX idx_daily_kline_stock_date ON daily_kline(stock_code, trade_date);这样按股票代码查历史K线时查询效率会有质的提升。如果你打算长期存储多年数据还可以考虑按年份做表分区但考虑到个人项目的数据量联合索引在绝大多数情况下已经足够了。另外行情数据同步任务不要在交易时段内频繁执行尽量集中在一个时间点避免MySQL或者PostgreSQL出现大量锁操作。5.2 数据备份比功能更重要别忘了自动化自托管系统最大的风险不是宕机而是磁盘损坏导致数据丢失。OpenStock的数据全部在PostgreSQL里所以备份方案其实很简单。我在宿主机上写了一个cron脚本每天凌晨对数据库执行 pg_dump然后把备份文件同步到另一块外置磁盘或者对象存储里。恢复的时候只需要在Docker里执行 pg_restore 就能把数据恢复到新容器中。具体备份命令大致是这个样子的docker compose exec -T openstock-db pg_dump -U openstock -d openstock -F c -f /tmp/openstock.dump docker cp openstock-backend_1:/tmp/openstock.dump ./backup/openstock_$(date %Y%m%d).dump备份策略是保留最近30天的备份文件更早的自动清理。这个习惯我强烈建议从一开始就养成不要等到数据出问题了再后悔。5.3 关于数据源选择和频繁请求的一些实在建议免费数据源虽然有便利性但稳定性确实无法保证。我个人体会是至少要配置两个数据源一个主一个备在主数据源连续失败时自动切换。更奢侈一点的做法是每天只同步自选股和持仓股的数据不追求同步全市场数据这样既降低数据源请求压力也减少存储成本因为个人投资关注范围其实非常有限。还有一点想提醒的是不要把OpenStock里的数据当成交易决策的唯一依据。它的定位是个人记录和统计工具数据可能存在延迟或缺失任何基于行情数据的买卖决策都应该回到券商平台或交易所官方渠道确认。我这个项目从来不给用户推送任何买卖建议它只负责把事实数据整理清楚至于怎么判断那是每个人自己的功课。在我看来这才是工具该有的样子。5.4 后续还能怎么扩展OpenStock目前的版本足够日常使用了但如果你有兴趣有几个方向可以继续玩接入分钟级K线实现盘中刷新增加技术指标计算模块比如MA、MACD、RSI都存到数据库里把计算好的收益指标通过Webhook推送到手机通知把交易记录导出功能做成类似券商对账单的PDF甚至可以把数据库里的数据接进Jupyter Notebook做自己的量化因子回测。底子已经打好了剩下的空间完全在你自己的想象力。最后分享一个我自己用下来的小习惯每周五收盘之后我会打开OpenStock的持仓看板把本周的浮动盈亏、最大回撤和交易次数截图存档月底再汇总放在同一个文件夹里。几个月的截图放在一起很多交易行为和心态变化会看得特别清晰。一个工具好不好用不在于功能多丰富而在于它有没有让你更了解自己的行为。OpenStock带给我的就是这种对自己投资行为逐渐明确的感觉。
返回列表