我用 stock-sdk 构建了一个个人专属的 A 股行情仪表盘

这是个啥

背景故事很简单:作为一个日常关注行情的“韭菜”,我有一个不太高效的习惯——同时打开无数个看盘软件和网页,在混乱的窗口切换中迷失自我,最终收获的往往只有焦虑,外加浏览器那令人窒息的标签页堆叠。为了彻底治愈这种低效,我决定动手打造一个专属工具:在一个页面内集成所有高频功能,涵盖实时行情、板块动态、分时走势、K 线分析、资金流向以及筛选器

这就诞生了 stock-dashboard:一个完全基于 React + TypeScript + Vite 技术栈的前端大屏。所有数据直接由 stock-sdk 驱动,这意味着项目完全摒弃了后端服务,不需要运行任何 Python 定时任务,也不依赖什么“神秘朋友的高端服务器”。纯前端直连数据源,所见即所得,一切都安排得井井有条。

直接上在线演示链接:stock-dashboard (友情提示:摸鱼期间请谨慎使用,建议配合小窗口模式)。
stock-overview

核心解密:数据层架构设计

为了保持代码整洁,我将所有针对 stock-sdk 的调用逻辑都封装在了 src/services/sdk.ts 中。

这里主要实施了三个既实用又不矫情的工程化策略:

  1. 全局单例与自动重试机制
    通过 new StockSDK({ timeout, retry }) 初始化实例。面对网络波动或接口偶尔抽风的情况,SDK 内置的自动重试机制(支持最大 3 次重试及指数退避算法)能完美兜底。

  2. 智能内存缓存(TTL 策略)
    对于行业或概念列表这类变动频率极低的数据(毕竟它们不会在几秒内发生剧变),直接上缓存减少无效请求;而对于实时行情,则设置了 2~3 秒的生存期(TTL),既保证了数据的时效性,又避免了无意义的高频请求轰炸接口。

  3. 分层隔离:页面仅对接服务层
    翻阅 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-sdksdk.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:一图看懂资金流向

stock-heatmap

实现文件位于 src/pages/Heatmap/Heatmap.tsx,底层依赖 ECharts 的矩形树图(Treemap)。

根据观察视角的不同,数据源也各异:

  • 行业视角:直接用 getIndustryList(),因为返回的数据中已经包含了涨跌幅、换手率及领涨股信息。
  • 概念视角:同理,调用 getConceptList()
  • 自选视角:获取所有自选代码 getAllWatchlistCodes() 后,通过 getAllQuotesByCodes(codes.slice(0, topK)) 批量拉取。

至于“全市场个股”热力图(代码预留了接口,暂未开启),实现逻辑也不复杂:

  1. 通过 getIndustryConstituents(industryCode) 获取特定板块成分股。
  2. getAllQuotesByCodes(stockCodes) 把行情数据补齐。
  3. 最后组装数据喂给 Treemap 组件。

热力图最大的魅力在于:告别枯燥的数字列表,红绿相间的色块让你瞬间洞察市场强弱结构。


4) 龙虎榜 Rankings:观察市场风向标

stock-leaderboard

页面路径: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)
  • 板块走势:拉取 getIndustryKlinegetConceptKline
  • 盘口快照:通过 getIndustrySpotgetConceptSpot 获取。

为了保证流畅度,板块 K 线图目前只截取了最近 60 根数据,防止缩放图表时浏览器渲染压力过大。


6) 自选监控 Watchlist:只看我在意的

核心页面:src/pages/Watchlist/Watchlist.tsx。所有的增删改查逻辑都封装在 src/services/storage.ts 中。

行情刷新主要依赖:

  • getAllQuotesByCodes(normalizedActiveCodes)

特别提一下这里的细节处理:在请求前我会先通过 normalizeStockCode(位于 src/utils/format.ts)对代码进行标准化格式化,有效防止了 SZ000001sz000001000001 这种“一码多式”造成的去重失败或数据请求异常。


7) 个股深度分析 StockDetail:全维数据一览无余

stock-detail

页面位置:src/pages/StockDetail/StockDetail.tsx。这是整个项目中承载信息量最大的页面,因为它聚合了极高密度的信息。

它聚合了多维度的 API 数据:

  • 实时报价:getFullQuotes([code])
  • 当日分时图(1分钟级):getTodayTimeline(code)
  • 分钟级 K 线(5/15/30/60):getMinuteKline(code, { period })
  • 历史 K 线(日/周/月)及复权:getKlineWithIndicators(code, { period, adjust: 'qfq', indicators })
  • 资金流向监测:getFundFlow([code])
  • 盘口大单监控:getPanelLargeOrder([code])

我个人非常推崇 getKlineWithIndicators 这个接口:只需传入你想要的指标参数(如 MA, MACD, KDJ, RSI, BOLL等),SDK 就能把计算好的指标数据连同 K 线一起返回。前端只需负责绘图,彻底告别了在前端手写复杂技术指标计算逻辑的噩梦(少写代码 = 少出 Bug = 长命百岁)。

在这里,轮询策略也做了精细化分层:

  • 基础行情:2 秒/次
  • 分时图:3 秒/次
  • 资金流向:10 秒/次

8) 策略扫描器 Scanner:量化交易的初体验

页面:src/pages/Scanner/Scanner.tsx

扫描逻辑简述如下:

  1. 确定股票池
    • 既可以是你的“自选股列表”。
    • 也可以是某个板块的成分股,例如调用 getIndustryConstituents('BK0475')
  2. 批量分析
    • 遍历每只股票,调用 getKlineWithIndicators 获取带指标的 K 线数据。
  3. 信号匹配
    • 前端逻辑判断最近两根 K 线是否满足预设形态(如均线金叉、MACD 金叉、RSI 超买超卖等)。

虽然这个功能带有一定的“心里安慰”属性,但它确确实实把模糊的“看涨感觉”转化为了可执行的“触发条件”。


9) 个性化设置 Settings:打造顺手的工具

stock-settings

页面:src/pages/Settings/Settings.tsx

这个页面并没有调用任何 stock-sdk 接口,它的使命是将你的使用偏好(刷新频率、红涨绿跌配色、各类指标的默认参数等)持久化保存到 localStorage。这样,无论何时打开页面,它都还是那个你最熟悉的样子。


重头戏:一日持股策略(尾盘选股)——前端实现的全市场扫描

stock-last

该功能位于 src/pages/EndOfDayPicker/EndOfDayPicker.tsx。我在这个页面实现了一套经典的“三步走”选股漏斗,其核心动力源自强大的 getAllAShareQuotes 接口。

第一阶段:全量 A 股行情抓取

// 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<FullQuote[]>
  • 参数 batchSize 控制单次批大小(默认 500),concurrency 控制并发数(默认 7)。

我采取了相对稳健的策略(并发设为 5),兼顾了浏览器的性能负载和网络稳定性。配合 onProgress 回调,用户能看到实时的进度条反馈,体验流畅不卡顿,不会误以为网页卡死。

第二阶段:基础指标粗筛

拿到全市场 5000+ 只股票的 FullQuote 数据后,我们先进行一轮粗筛(字段直接取自 FullQuote):

  • 流通市值 (circulatingMarketCap)
  • 量比 (volumeRatio)
  • 涨跌幅 (changePercent)
  • 换手率 (turnoverRate)
  • ST/风险股过滤

这一步逻辑封装在 filterStocksBasic() 中,通常能把目标池从 5000+ 缩减到几百甚至几十只,如果不筛这一刀,后续拉取分时数据会直接把浏览器送走。

第三阶段:分时图形态精选

对于粗筛剩下的候选股,我们再进行更细致的分时图分析:

  • 调用 getTodayTimeline(fullCode) 拉取分时数据(注意拼接 sh/sz/bj 前缀)。
  • 计算核心强度指标:timelineAboveAvgRatio(即:现价高于均价的时间占比,由 priceavgPrice 对比得出)。

为了防止浏览器崩溃,filterWithTimeline() 中手动控制了分时数据请求的并发量(batchSize = 5)。
最终结果按 timelineAboveAvgRatio 降序排列,并在列表中展示迷你的分时走势图。这样一来,尾盘选股的效率直接起飞。


写在最后:谁需要这个工具?

如果你渴望拥有一个“既能看盘、又能筛股、还能顺便管理自选”的轻量级看板,同时极其排斥维护后端服务或编写复杂的 Python 脚本,那么这个纯前端方案绝对是你的不二之选。核心思路就是利用 stock-sdk 将强大的数据能力引入前端,剩下的就是单纯的 UI 组装与逻辑编排

本地启动非常简单:

yarn install
yarn dev

最后不得不俗套地提醒一句:页面底部的 disclaimer “仅供学习参考,不构成投资建议”并非摆设。代码虽可自信敲,投资仍需谨慎行。


传送门

posted @ 2026-01-15 19:06  程序猿的程  阅读(39)  评论(0)    收藏  举报