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