我用 stock-sdk 做了个 A 股股票看板

先把"病因"交代清楚:我看盘的时候特别容易把自己搞累------同时开好几个 APP、网页、行情工具,来回切得飞起,信息没吸收多少,焦虑倒是堆满了(浏览器标签也堆满了)。所以我干脆换个思路:只要一个页面,把常用的行情、板块、分时、K 线、资金面、筛选工具都塞进去,减少"切来切去的成本",让自己专注在该看的东西上。

于是就有了 stock-dashboard:一个 React + TypeScript + Vite 的前端看板。数据源全部走 stock-sdk。重点是:没有后端、没有定时脚本、也没有"我朋友的服务器" ------就是纯前端直接拉数据,然后在 UI 里把它们排兵布阵。

体验地址放这:stock-dashboard 👉 https://chengzuopeng.github.io/stock-dashboard/ (建议小窗挂着,摸鱼更丝滑)。


先把底层说清楚:数据是怎么接进来的?

项目里把 stock-sdk 的调用集中到 src/services/sdk.ts,让页面层尽量"只消费服务,不直连 SDK"。整体做法偏工程化,但没有太多花活:

  1. SDK 单例 + 自带重试

    统一用 new StockSDK({ timeout, retry }) 创建实例。网络抖动、偶发超时之类的事,交给 SDK 的重试机制处理(最多 3 次,指数退避那一套)。

  2. 内存缓存(TTL)

    行业/概念列表这种数据短时间内变化不大,缓存一下能省不少请求;实时行情则给一个更短的 TTL(2~3 秒),在"新鲜度"和"别把接口当压测"之间取个平衡。

  3. 页面只认封装后的 service

    src/pages/** 里基本看不到 new StockSDK(),页面只调用 getFullQuotes / getTodayTimeline / getKlineWithIndicators ... 这些方法,同时类型直接从 stock-sdk 导入,减少"字符串拼错、字段写错"的机会。

下面两段是核心"骨架",后面的功能基本都是围绕这套服务层扩展的:

ts 复制代码
// 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);
}
ts 复制代码
// 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)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))

轮询用 usePollingsrc/hooks/usePolling.ts),默认 5 秒刷新一次;页面不可见会自动暂停,避免你切到别的标签页它还在后台狂刷。

(顺带一提:Dashboard 里的"榜单"区域目前更多是板块数据的延伸;如果要做全市场个股榜,路线其实就是 getAllAShareQuotes------后面"一日持股法"已经把关键能力跑通了。)


3) 热力图 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:卷不动就看谁在卷

页面在 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 用得更完整(行业/概念分别走不同分支):

  • 详情信息:直接从 getIndustryList() / getConceptList() 里 find(少一次请求)
  • 成分股:getIndustryConstituents(code) / getConceptConstituents(code)
  • 板块 K 线:getIndustryKline(code, { period }) / getConceptKline(code, { period })
  • 板块 Spot 指标:getIndustrySpot(code) / getConceptSpot(code)

板块 K 线我只取最近 60 根(slice(-60)),否则拖 dataZoom 的时候浏览器会明显开始"喘气"。


6) 自选 Watchlist:我盯的不是票,是"我自己的偏好"

页面在 src/pages/Watchlist/Watchlist.tsx,自选的分组/增删改在 src/services/storage.ts 处理。

行情拉取走:

  • getAllQuotesByCodes(normalizedActiveCodes)

这里我把代码做了 normalizeStockCodesrc/utils/format.ts),避免 SZ000001sz000001000001 这种"同一个人换不同马甲"造成重复或取不到数据。


7) 个股详情 StockDetail:该看的给你,不该看的也顺手给你

页面 src/pages/StockDetail/StockDetail.tsx 是整个项目里信息密度最高的一页之一,数据来源大概分几组:

  • 实时行情: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/BOLL/KDJ/RSI,SDK 直接把指标结果算好返回,前端专心画图就行------少写一堆计算逻辑,也少养一堆 bug。

同样配了轮询策略:

  • 行情 2 秒刷新
  • 分时 3 秒刷新
  • 资金 10 秒刷新

8) 信号扫描 Scanner:给自己一点"量化的幻觉"

页面在 src/pages/Scanner/Scanner.tsx,流程是:

  1. 先选股票池
    • 自选:本地读代码
    • 行业/概念:getIndustryConstituents('BK0475') / getConceptConstituents('BK0891') 抽取成分股
  2. 对每只股票调用:
    • getKlineWithIndicators(code, { indicators: { ma/macd/rsi/boll } })
  3. 前端用最近两根 K 线判断信号(比如 MA 金叉、MACD 金叉、RSI 超买超卖...)

这个功能的"心理收益"大于"实盘收益",但它至少能把"我觉得它要动了"变成"它确实触发了某个条件"(哪怕条件是我写的)。


9) 设置 Settings:不碰行情,只负责体验

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

这页不调用 stock-sdk ,只把刷新频率、涨跌配色、指标默认参数等偏好写到 localStorage(src/services/storage.ts),保证下次打开不是"出厂状态"。


重头戏:一日持股法(尾盘选股)= 前端全市场扫描的重武器

页面在 src/pages/EndOfDayPicker/EndOfDayPicker.tsx。我做的是一个"三段式"流程,核心能力就是 getAllAShareQuotes(全市场 5000+ 一把抓)。

第一步:全量拉取 A 股行情

ts 复制代码
// src/pages/EndOfDayPicker/EndOfDayPicker.tsx
const quotes = await getAllAShareQuotes({
  batchSize: 500,
  concurrency: 5,
  onProgress: (completed, total) => setLoadingProgress({ completed, total, stage: '获取行情数据' }),
});

对应 stock-sdk 的签名是:

  • sdk.getAllAShareQuotes(options?: GetAllAShareQuotesOptions): Promise<FullQuote[]>
  • GetAllAShareQuotesOptions 支持:
    • batchSize:单次请求股票数量(默认 500)
    • concurrency:最大并发数(默认 7)
    • onProgress:进度回调

我这里把并发设为 5,属于"别太激进,浏览器和网速都有极限"的保守设置;配合 onProgress,页面能实时显示进度条,不至于让人怀疑是不是卡死了。

第二步:用基础条件先把范围收紧

FullQuote[] 拿到手后,先用这些字段做一轮基础过滤(直接来自 FullQuote):

  • 流通市值 circulatingMarketCap
  • 量比 volumeRatio
  • 涨跌幅 changePercent
  • 换手率 turnoverRate
  • 以及过滤 ST/*ST

这一步在 filterStocksBasic(),目的是先把 5000+ 压到几十/几百只,避免后面拉分时把自己拖进"请求风暴"。

第三步:对候选股拉分时,算"强度比例"

对筛出来的候选,分批去拉分时:

  • getTodayTimeline(fullCode)(注意这里需要拼 sh/sz/bj 前缀)

然后用分时里的 priceavgPrice 算一个简单但直观的强度指标:

  • price >= avgPrice 的点数 / 总点数 = timelineAboveAvgRatio

分时请求在 filterWithTimeline() 里按 batchSize = 5 控制并发(这是我自己在页面里控制的批大小,不是 SDK 的参数),防止对几百只股票同时开火,最后浏览器先躺平。

最终按 timelineAboveAvgRatio 排序,列表里每只股票还带一张迷你分时图,用来做"尾盘快速过一遍候选"的效率工具。


收个尾:这套思路适合谁?

如果你想要的是一个"能看行情、能看板块、能看分时/ K 线、还能顺手管理自选"的轻量看板,同时你又不想为了它额外维护后端,那么这个项目的路线挺适合:stock-sdk 把数据能力带进前端,然后在 UI 里做组合、筛选和展示

本地启动也很简单:

bash 复制代码
yarn install
yarn dev

最后提醒一句:页面底部那句"仅供学习参考,不构成投资建议"不是装饰品------写代码可以自信,交易还是得保持敬畏。


相关链接

相关推荐
崔庆才丨静觅17 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606117 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了17 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅17 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅18 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅18 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment18 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅19 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊19 小时前
jwt介绍
前端
爱敲代码的小鱼19 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax