我用 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

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


相关链接

相关推荐
摘星编程1 小时前
React Native for OpenHarmony 实战:BackgroundImage 背景视图详解
javascript·react native·react.js
IT_陈寒2 小时前
5 个现代 JavaScript 特性让你彻底告别老旧写法,编码效率提升 50%
前端·人工智能·后端
仙俊红2 小时前
一次 Web 请求,服务器到底能看到什么?
服务器·前端·firefox
iFlow_AI2 小时前
使用iFlow CLI创建自定义Command:网页文章下载与翻译工具
前端·javascript·大模型·心流·iflow·iflowcli
帅次2 小时前
Web应用系统全面解析:从架构设计到测试部署的核心要点
前端·javascript·ajax·html5
前端 贾公子2 小时前
从0到1 使用netlify进行线上部署网站
前端
电商API&Tina2 小时前
合规电商数据采集 API|多平台实时数据抓取,告别爬虫封号风险
大数据·开发语言·前端·数据库·爬虫·python
梦6502 小时前
HTML5 零基础详解
前端·html·html5
zhengxianyi5152 小时前
ruoyi-vue-pro数据大屏优化——解决go-view同一个大屏报表在数据库中存储大量的图片的问题
前端·vue.js·前后端分离·数据大屏·ruoyi-vue-pro优化