这是个啥
背景故事很简单:作为一个日常关注行情的“韭菜”,我有一个不太高效的习惯——同时打开无数个看盘软件和网页,在混乱的窗口切换中迷失自我,最终收获的往往只有焦虑,外加浏览器那令人窒息的标签页堆叠。为了彻底治愈这种低效,我决定动手打造一个专属工具:在一个页面内集成所有高频功能,涵盖实时行情、板块动态、分时走势、K 线分析、资金流向以及筛选器。
这就诞生了 stock-dashboard:一个完全基于 React + TypeScript + Vite 技术栈的前端大屏。所有数据直接由 stock-sdk 驱动,这意味着项目完全摒弃了后端服务,不需要运行任何 Python 定时任务,也不依赖什么“神秘朋友的高端服务器”。纯前端直连数据源,所见即所得,一切都安排得井井有条。
直接上在线演示链接:stock-dashboard (友情提示:摸鱼期间请谨慎使用,建议配合小窗口模式)。
核心解密:数据层架构设计
为了保持代码整洁,我将所有针对 stock-sdk 的调用逻辑都封装在了 src/services/sdk.ts 中。
这里主要实施了三个既实用又不矫情的工程化策略:
- 全局单例与自动重试机制
通过 new StockSDK({ timeout, retry }) 初始化实例。面对网络波动或接口偶尔抽风的情况,SDK 内置的自动重试机制(支持最大 3 次重试及指数退避算法)能完美兜底。
- 智能内存缓存(TTL 策略)
对于行业或概念列表这类变动频率极低的数据(毕竟它们不会在几秒内发生剧变),直接上缓存减少无效请求;而对于实时行情,则设置了 2~3 秒的生存期(TTL),既保证了数据的时效性,又避免了无意义的高频请求轰炸接口。
- 分层隔离:页面仅对接服务层
翻阅 src/pages/** 下的代码,你几乎找不到 new StockSDK() 的身影。UI 层只负责调用诸如 getFullQuotes / getTodayTimeline / getKlineWithIndicators 等经过二次封装的业务方法,而类型定义则直接复用 stock-sdk 的导出。
顺便展示两段核心代码骨架,后续的所有功能模块皆构建于此基础之上:- // src/services/sdk.ts
- export const sdk = new StockSDK({ timeout: 30000, retry: { maxRetries: 3, baseDelay: 1000, maxDelay: 10000, backoffMultiplier: 2 } });
- export async function getFullQuotes(codes: string[], useCache = true) {
- const key = getCacheKey('getFullQuotes', codes);
- if (useCache) {
- return withCache(key, DEFAULT_TTL.quotes, () => sdk.getFullQuotes(codes));
- }
- return sdk.getFullQuotes(codes);
- }
复制代码- // src/services/sdk.ts
- export async function getAllAShareQuotes(options?: { batchSize?: number; concurrency?: number; onProgress?: (completed: number, total: number) => void }) {
- return sdk.getAllAShareQuotes(options);
- }
复制代码 功能拆解:各模块如何玩转 stock-sdk 数据?
路由配置位于 src/router/index.tsx,而各个功能页面则模块化地分布在 src/pages/* 目录下。接下也就是大家最关心的——按“用户交互路径”来逐一复盘。
1) 全局搜索:告别手动翻代码的痛苦
搜索栏组件位于 src/components/layout/Header.tsx,其背后的魔法仅需一行代码:
- search(keyword) 映射到 stock-sdk 的 sdk.search(keyword)
为了优化体验,我添加了 300ms 的输入防抖处理。搜索结果完美支持个股与板块的混合查询,点击即达:
- 行业板块跳转至:/boards/industry/:code
- 概念板块跳转至:/boards/concept/:code
- 个股详情跳转至:/s/:code
顺手还利用 localStorage 实现了一个简单的历史记录功能(src/services/storage.ts),毕竟很多时候,我们寻找的不是新标的,而是昨天没看完的那个它。
2) 仪表盘 Dashboard:行情概览与自选速览
对应页面文件:src/pages/Dashboard/Dashboard.tsx。
数据获取逻辑非常直白粗暴:
- 指数行情:调用 getFullQuotes(MAIN_INDICES) 一次性获取上证、深成指、科创 50 等关键指数。
- 板块概况:并行调用 getIndustryList() 和 getConceptList()。
- 自选股预览:先从存储服务 src/services/storage.ts 读取自选列表,再通过 getFullQuotes(watchlistCodes.slice(0, 50)) 批量获取前 50 只行情的快照。
为了保证数据的鲜活度,配合 usePolling Hook(src/hooks/usePolling.ts)实现了每 5 秒自动轮询。贴心的是,当页面处于后台不可见状态时,轮询会自动挂起,绝不浪费你的浏览器资源。
额外提一句:目前 Dashboard 上的“榜单”主要展示板块数据。如果想做全市场的个股排名,技术路径完全可以参考后面提到的“一日持股法”,也就是直接利用 getAllAShareQuotes 接口。
3) 市场热力图 Heatmap:一图看懂资金流向
实现文件位于 src/pages/Heatmap/Heatmap.tsx,底层依赖 ECharts 的矩形树图(Treemap)。
根据观察视角的不同,数据源也各异:
- 行业视角:直接用 getIndustryList(),因为返回的数据中已经包含了涨跌幅、换手率及领涨股信息。
- 概念视角:同理,调用 getConceptList()。
- 自选视角:获取所有自选代码 getAllWatchlistCodes() 后,通过 getAllQuotesByCodes(codes.slice(0, topK)) 批量拉取。
至于“全市场个股”热力图(代码预留了接口,暂未开启),实现逻辑也不复杂:
- 通过 getIndustryConstituents(industryCode) 获取特定板块成分股。
- 用 getAllQuotesByCodes(stockCodes) 把行情数据补齐。
- 最后组装数据喂给 Treemap 组件。
热力图最大的魅力在于:告别枯燥的数字列表,红绿相间的色块让你瞬间洞察市场强弱结构。
4) 龙虎榜 Rankings:观察市场风向标
页面路径:src/pages/Rankings/Rankings.tsx。
实现方式属于“简单粗暴且有效”:
- 并行获取 getIndustryList() 和 getConceptList()。
- 前端直接根据 changePercent(涨跌幅)或 turnoverRate(换手率)进行排序,截取 Top 50。
目前的榜单本质上是“板块排行榜”。如果未来要扩展到全市场个股排行,技术方案与后文的“选股器”一致。
5) 板块透视:追踪领涨先锋
板块列表页位于 src/pages/Boards/Boards.tsx:
- getIndustryList() 与 getConceptList() 一把梭。
- 所谓的 Tab 切换,仅仅是前端对不同数据源数组的渲染切换。
- 当然也支持按板块名称或领涨股进行检索。
详情页见 src/pages/Boards/BoardDetail.tsx,这里展示了 API 的组合拳能力(按行业/概念分流):
- 基础信息:直接复用列表数据,减少一次网络请求。
- 成分股列表:调用 getIndustryConstituents(code) 或 getConceptConstituents(code)。
- 板块走势:拉取 getIndustryKline 或 getConceptKline。
- 盘口快照:通过 getIndustrySpot 或 getConceptSpot 获取。
为了保证流畅度,板块 K 线图目前只截取了最近 60 根数据,防止缩放图表时浏览器渲染压力过大。
6) 自选监控 Watchlist:只看我在意的
核心页面:src/pages/Watchlist/Watchlist.tsx。所有的增删改查逻辑都封装在 src/services/storage.ts 中。
行情刷新主要依赖:
- getAllQuotesByCodes(normalizedActiveCodes)
特别提一下这里的细节处理:在请求前我会先通过 normalizeStockCode(位于 src/utils/format.ts)对代码进行标准化格式化,有效防止了 SZ000001、sz000001 和 000001 这种“一码多式”造成的去重失败或数据请求异常。
7) 个股深度分析 StockDetail:全维数据一览无余
页面位置:src/pages/StockDetail/StockDetail.tsx。这是整个项目中承载信息量最大的页面,因为它聚合了极高密度的信息。
它聚合了多维度的 API 数据:
- 实时报价:getFullQuotes(
- )</li>
- <li>当日分时图(1分钟级):getTodayTimeline(code)</li>
- <li>分钟级 K 线(5/15/30/60):getMinuteKline(code, { period })</li>
- <li>历史 K 线(日/周/月)及复权:getKlineWithIndicators(code, { period, adjust: 'qfq', indicators })</li>
- <li>资金流向监测:getFundFlow([code])</li>
- <li>盘口大单监控:getPanelLargeOrder([code])</li>
- </ul>
- <p>我个人非常推崇 getKlineWithIndicators 这个接口:只需传入你想要的指标参数(如 MA, MACD, KDJ, RSI, BOLL等),SDK 就能把计算好的指标数据连同 K 线一起返回。前端只需负责绘图,彻底告别了在前端手写复杂技术指标计算逻辑的噩梦(少写代码 = 少出 Bug = 长命百岁)。</p>
- <p>在这里,轮询策略也做了精细化分层:</p>
- <ul>
- <li>基础行情:2 秒/次</li>
- <li>分时图:3 秒/次</li>
- <li>资金流向:10 秒/次</li>
- </ul>
- <h3 id="8-策略扫描器-scanner量化交易的初体验">8) 策略扫描器 Scanner:量化交易的初体验</h3>
- <p>页面:src/pages/Scanner/Scanner.tsx。</p>
- <p>扫描逻辑简述如下:</p>
- <ol>
- <li><strong>确定股票池</strong>:
- <ul>
- <li>既可以是你的“自选股列表”。</li>
- <li>也可以是某个板块的成分股,例如调用 getIndustryConstituents('BK0475')。</li>
- </ul>
- </li>
- <li><strong>批量分析</strong>:
- <ul>
- <li>遍历每只股票,调用 getKlineWithIndicators 获取带指标的 K 线数据。</li>
- </ul>
- </li>
- <li><strong>信号匹配</strong>:
- <ul>
- <li>前端逻辑判断最近两根 K 线是否满足预设形态(如均线金叉、MACD 金叉、RSI 超买超卖等)。</li>
- </ul>
- </li>
- </ol>
- <p>虽然这个功能带有一定的“心里安慰”属性,但它确确实实把模糊的“看涨感觉”转化为了可执行的“触发条件”。</p>
- <h3 id="9-个性化设置-settings打造顺手的工具">9) 个性化设置 Settings:打造顺手的工具</h3>
- <p><img alt="stock-settings" loading="lazy" data-src="https://img2024.cnblogs.com/blog/1265396/202601/1265396-20260115184611235-287105548.png" ></p>
- <p>页面:src/pages/Settings/Settings.tsx。</p>
- <p>这个页面并没有调用任何 stock-sdk 接口,它的使命是将你的使用偏好(刷新频率、红涨绿跌配色、各类指标的默认参数等)持久化保存到 localStorage。这样,无论何时打开页面,它都还是那个你最熟悉的样子。</p>
- <h2 id="重头戏一日持股策略尾盘选股前端实现的全市场扫描">重头戏:一日持股策略(尾盘选股)——前端实现的全市场扫描</h2>
- <p><img alt="stock-last" loading="lazy" data-src="https://img2024.cnblogs.com/blog/1265396/202601/1265396-20260115184618983-2095502387.png" ></p>
- <p>该功能位于 src/pages/EndOfDayPicker/EndOfDayPicker.tsx。我在这个页面实现了一套经典的“三步走”选股漏斗,其核心动力源自强大的 <strong>getAllAShareQuotes</strong> 接口。</p>
- <h3 id="第一阶段全量-a-股行情抓取">第一阶段:全量 A 股行情抓取</h3>
- [code]// src/pages/EndOfDayPicker/EndOfDayPicker.tsx
- const quotes = await getAllAShareQuotes({
- batchSize: 500,
- concurrency: 5,
- onProgress: (completed, total) => setLoadingProgress({ completed, total, stage: '数据加载中...' }),
- });
复制代码 这一步调用的是 SDK 的重磅接口:
- sdk.getAllAShareQuotes(options?: GetAllAShareQuotesOptions): Promise
- 参数 batchSize 控制单次批大小(默认 500),concurrency 控制并发数(默认 7)。
我采取了相对稳健的策略(并发设为 5),兼顾了浏览器的性能负载和网络稳定性。配合 onProgress 回调,用户能看到实时的进度条反馈,体验流畅不卡顿,不会误以为网页卡死。
第二阶段:基础指标粗筛
拿到全市场 5000+ 只股票的 FullQuote 数据后,我们先进行一轮粗筛(字段直接取自 FullQuote):
- 流通市值 (circulatingMarketCap)
- 量比 (volumeRatio)
- 涨跌幅 (changePercent)
- 换手率 (turnoverRate)
- ST/风险股过滤
这一步逻辑封装在 filterStocksBasic() 中,通常能把目标池从 5000+ 缩减到几百甚至几十只,如果不筛这一刀,后续拉取分时数据会直接把浏览器送走。
第三阶段:分时图形态精选
对于粗筛剩下的候选股,我们再进行更细致的分时图分析:
- 调用 getTodayTimeline(fullCode) 拉取分时数据(注意拼接 sh/sz/bj 前缀)。
- 计算核心强度指标:timelineAboveAvgRatio(即:现价高于均价的时间占比,由 price 和 avgPrice 对比得出)。
为了防止浏览器崩溃,filterWithTimeline() 中手动控制了分时数据请求的并发量(batchSize = 5)。
最终结果按 timelineAboveAvgRatio 降序排列,并在列表中展示迷你的分时走势图。这样一来,尾盘选股的效率直接起飞。
写在最后:谁需要这个工具?
如果你渴望拥有一个“既能看盘、又能筛股、还能顺便管理自选”的轻量级看板,同时极其排斥维护后端服务或编写复杂的 Python 脚本,那么这个纯前端方案绝对是你的不二之选。核心思路就是利用 stock-sdk 将强大的数据能力引入前端,剩下的就是单纯的 UI 组装与逻辑编排。
本地启动非常简单:最后不得不俗套地提醒一句:页面底部的 disclaimer “仅供学习参考,不构成投资建议”并非摆设。代码虽可自信敲,投资仍需谨慎行。
传送门
- 在线看板: https://chengzuopeng.github.io/stock-dashboard/
- SDK 文档: https://stock-sdk.linkdiary.cn/
- SDK 演练场: https://stock-sdk.linkdiary.cn/playground/
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
|