Java量化系列(三十七) 1个接口搞定板块指标全景图,MACD KDJ可视化一步到位
本文介绍了一种高效的Java量化板块指标查询方案,通过一个聚合接口实现板块数据的全景查询与可视化。该方案设计了/findDetail接口,可一次性返回板块基础信息、历史数据、关联股票及四大技术指标(MACD/KDJ/BOLL/DMI),并直接输出可视化所需格式。核心逻辑包括日期校准、多维度数据查询排序、指标自动计算等,大幅提升板块分析效率。该方案避免了传统碎片化操作,实现"一键调用&qu
参考链接是: Java量化系列(三十七): 1个接口搞定板块指标全景图,MACD KDJ可视化一步到位
你是否还在为板块指标分析拆东补西?🤦♂️
想分析某板块强弱,得先查历史价格、再算MACD/KDJ,最后手动把数据导入Excel画图;好不容易凑齐数据,还得切换多个页面核对关联股票走势,一顿操作下来,行情都快过了。更麻烦的是,指标数据散落在不同表中,每次查询都要写一堆重复代码,效率低到离谱。
板块指标是量化策略判断板块趋势的核心依据,从价格走势到MACD金叉死叉、KDJ超买超卖,一套完整的指标体系才能支撑精准分析。但指标查询与可视化,真没必要“碎片化操作”。
今天带来Java量化系列第37篇实战——板块指标全景查询与可视化方案,1个接口聚合所有核心数据,自动计算四大指标,直接输出可视化所需格式,让板块分析从“耗时拼凑”变成“一键调用”。全程聚焦技术实现,无任何投资引导,纯干货可直接落地。
一、核心设计:1个接口承载全量需求🎯
本次方案的核心是/findDetail接口,通过板块编码和日期参数,一次性返回“基础信息+历史数据+关联股票+四大指标+K线数据”,避免多接口调用的冗余,为前端可视化提供一站式数据支撑。
1.1 接口定义:简洁高效,参数可控
接口采用GET请求,支持指定板块编码和日期,默认查询当前日期数据,适配复盘、实时分析等多场景:
@GetMapping("/findDetail")
@Operation(summary = "查询版块详细的信息",tags = {"特别推荐"})
public OutputResult<AiBkAllDataVo> findDetail(
@RequestParam("code") String code,
@RequestParam(value = "currDate",defaultValue = "") String currDate) {
// 核心业务逻辑调用,返回全量聚合数据
return OutputResult.buildSucc(aiUpBkStrategyBusiness.findDetail(code, currDate));
}
设计亮点:通过默认日期参数简化调用,标签标注“特别推荐”便于接口管理,关闭日志注解避免高频查询产生冗余日志,兼顾易用性与性能。
1.2 数据模型:AiBkAllDataVo 聚合全量信息
专门设计AiBkAllDataVo作为返回模型,像“数据集装箱”一样封装所有关联信息,结构清晰且适配可视化需求:
@Data
@Schema(name = "板块全部的数据")
public class AiBkAllDataVo implements Serializable {
@Schema(name = "板块数据")
private StockBkDo stockBkDo;
@Schema(name = "该板块最近的一条数据")
private BkHistoryDo lastBkHistoryDo;
@Schema(name = "该板块最近7条的涨跌记录")
private List<StockBkZdHistoryDo> stockBkZdHistoryDoList;
@Schema(name = "该板块最近7条的金额记录")
private List<StockBkMoneyHistoryDo> stockBkMoneyHistoryDoList;
@Schema(name = "该板块最近7条的历史记录信息")
private List<BkHistoryDo> bkHistoryDoList;
@Schema(name = "该板块所关联的股票系列")
private List<StockHistoryVo> stockList;
@Schema(name = "该板块在东方财富上的地址")
private String webUrl;
@Schema(name = "该板块对应的K线数据,要形成K线图")
private StatKDataVo statKDataVo;
// 四大技术指标(适配折线图可视化)
@Schema(name = "macd的指标信息")
private LineVo macdLineVo;
@Schema(name = "kdj的指标信息")
private LineVo kdjLineVo;
@Schema(name = "boll的指标信息")
private LineVo bollLineVo;
@Schema(name = "dmi的指标信息")
private LineVo dmiLineVo;
}
模型设计逻辑:从“基础信息-历史数据-关联标的-技术指标”层层递进,既满足后端数据聚合需求,又能让前端直接按字段渲染页面,无需二次数据处理。
二、核心逻辑:全量数据聚合与指标计算⚙️
核心业务方法findDetail承担“数据查询-排序-关联-指标计算”全流程,我们按步骤拆解,关键代码附详细注释,新手也能看懂。
2.1 数据范围校准:精准定位交易日
首先处理日期参数,自动过滤非交易日,确保查询数据的准确性,避免因节假日导致的数据断层:
@Override
public AiBkAllDataVo findDetail(String code, String currDate) {
Date date = new Date();
// 若传入日期则解析,否则用当前日期
if (StrUtil.isNotBlank(currDate)) {
date = DateUtil.parse(currDate);
}
// 校准日期:取上一个交易日(避免非交易日查询)
date = dateHelper.getBeforeLastWorking(date);
// 设定查询范围:前后3个交易日(共7天数据,适配短期趋势分析)
Date startDate =
DateUtil.beginOfDay(
dateHelper.getBeforeWorkingDateByDayRemoveToday(date,3));
Date endDate =
DateUtil.beginOfDay(
dateHelper.getAfterWorkingDateByDayRemoveToday(date,3));
AiBkAllDataVo aiBkAllDataVo = new AiBkAllDataVo();
// 查询板块基础信息,无信息则直接返回空对象
StockBkDo stockBkDo =
stockBkDomainService.getByCodeOrName(code);
aiBkAllDataVo.setStockBkDo(stockBkDo);
if (stockBkDo == null) {
return aiBkAllDataVo;
}
// 后续数据查询逻辑...
}
2.2 多维度数据查询与排序
按校准后的日期范围,分别查询板块价格历史、涨跌记录、资金流向,同时关联板块下的股票数据,按需排序后封装:
// 1. 查询板块近期价格历史(7天)并按日期排序
BkHistoryQueryParam bkHistoryQueryParam =
new BkHistoryQueryParam();
bkHistoryQueryParam.setStartDate(startDate);
bkHistoryQueryParam.setEndDate(endDate);
bkHistoryQueryParam.setCode(code);
List<BkHistoryDo> bkHistoryDoList =
bkHistoryDomainService.listByCondition(bkHistoryQueryParam);
if (CollUtil.isNotEmpty(bkHistoryDoList)) {
bkHistoryDoList = bkHistoryDoList.stream()
.sorted(Comparator.comparing(BkHistoryDo::getCurrDate))
.collect(Collectors.toList());
}
aiBkAllDataVo.setBkHistoryDoList(bkHistoryDoList);
// 2. 查询板块近期涨跌记录(7天)并排序
StockBkZdHistoryQueryParam zdQueryParam =
new StockBkZdHistoryQueryParam();
zdQueryParam.setStartDate(startDate);
zdQueryParam.setEndDate(endDate);
zdQueryParam.setCode(code);
List<StockBkZdHistoryDo> zdList =
stockBkZdHistoryDomainService.listByCondition(zdQueryParam);
if (CollUtil.isNotEmpty(zdList)) {
zdList = zdList.stream()
.sorted(Comparator.comparing(StockBkZdHistoryDo::getCurrDate))
.collect(Collectors.toList());
}
aiBkAllDataVo.setStockBkZdHistoryDoList(zdList);
// 3. 查询板块近期资金流向记录
StockBKMoneyQueryParam moneyQueryParam =
new StockBKMoneyQueryParam();
moneyQueryParam.setStartDate(startDate);
moneyQueryParam.setEndDate(endDate);
moneyQueryParam.setCode(code);
List<StockBkMoneyHistoryDo> moneyList =
stockBkMoneyHistoryDomainService.listByCondition(moneyQueryParam);
aiBkAllDataVo.setStockBkMoneyHistoryDoList(moneyList);
// 4. 查询板块关联股票Top20(按涨跌幅倒序,取前20条)
StockBkCodeQueryParam bkStockParam =
new StockBkCodeQueryParam();
bkStockParam.setBkCode(code);
List<StockBkStockDo> bkStockList =
stockBkStockDomainService.listByCondition(bkStockParam);
List<StockHistoryVo> stockVoList =
new ArrayList<>();
if (CollUtil.isNotEmpty(bkStockList)) {
StockHistoryQueryParam stockQueryParam =
new StockHistoryQueryParam();
stockQueryParam.setChooseDate(
dateHelper.getStockHistoryWebDate(new Date()));
// 提取关联股票编码列表
List<String> stockCodes = bkStockList.stream()
.map(StockBkStockDo::getStockCode)
.collect(Collectors.toList());
stockQueryParam.setCodes(stockCodes);
List<StockHistoryDo> stockDoList =
stockHistoryDomainService.listByCondition(stockQueryParam);
// DO转VO,填充详情页地址
stockVoList =
stockHistoryAssembler.doToVoList(stockDoList);
ReplayUrlInfo urlInfo = (ReplayUrlInfo)
redisUtil.hGet(Const.REAL_REPLAY,
ReplayUrlType.STOCK_PAGE.getCode());
stockVoList.forEach(n -> {
n.setWebUrl(urlInfo.getNoEndUrlPrefix() +"/stockDetail.html?code="+n.getCode()
+"&currDate="+DateUtil.format(new Date(),Const.STOCK_DATE_FORMAT));
});
// 按涨跌幅倒序,取前20条
stockVoList.sort(
Comparator.comparing(StockHistoryVo::getAmplitudeProportion)
.reversed());
stockVoList = stockVoList.subList(0,Math.min(20,stockVoList.size()));
}
aiBkAllDataVo.setStockList(stockVoList);
// 5. 填充最新行情快照和外部跳转地址
aiBkAllDataVo.setLastBkHistoryDo(
bkHistoryDomainService.getLastByCode(code));
ReplayUrlInfo urlInfo = (ReplayUrlInfo)
redisUtil.hGet(Const.REAL_REPLAY, ReplayUrlType.STOCK_PAGE.getCode());
aiBkAllDataVo.setWebUrl(urlInfo.getNoEndUrlPrefix() +"/bkDetail.html?code="+code
+"&currDate="+DateUtil.format(new Date(),Const.STOCK_DATE_FORMAT));
2.3 指标计算与K线数据封装:可视化核心
通过findBkKDataList方法计算四大技术指标(MACD/KDJ/BOLL/DMI),同时封装K线数据,直接适配前端可视化组件(如ECharts):
public StatKDataVo findBkKDataList
(AiBkAllDataVo aiBkAllDataVo,
String bkCode) {
BkHistoryQueryParam bkQueryParam =
new BkHistoryQueryParam();
bkQueryParam.setCode(bkCode);
// 查询近80个交易日数据(足够支撑指标计算,取后40个用于展示)
bkQueryParam.setStartDate(
dateHelper.getBeforeWorkingDateByDayRemoveToday
(new Date(),80));
bkQueryParam.setEndDate(new Date());
List<BkHistoryDo> data =
bkHistoryDomainService.listByCondition(bkQueryParam);
StatKDataVo statKDataVo = new StatKDataVo();
if (CollUtil.isEmpty(data)) {
return statKDataVo;
}
// 按日期排序,保证指标计算顺序正确
data =
data.stream().sorted(
Comparator.comparing(BkHistoryDo::getCurrDate))
.collect(Collectors.toList());
// 取后40条数据用于K线展示(避免数据过多导致图表卡顿)
List<BkHistoryDo> showDataList =
data.subList(Math.max(0,data.size()-40),data.size());
// 封装K线数据:日期、开盘价、最高价、最低价、收盘价、涨跌幅、干支日期
List<Object[]> kdata = new ArrayList<>();
GanZhiUtils ganZhiUtils = GanZhiUtils.getInstance();
for (BkHistoryDo bkHistoryDo : showDataList) {
List<Object> list = new ArrayList<>();
list.add(DateUtil.format(bkHistoryDo.getCurrDate(),
Const.SIMPLE_MD_DATE_FORMAT));
list.add(BigDecimalUtil.toDouble(bkHistoryDo.getOpeningPrice(),2));
list.add(BigDecimalUtil.toDouble(bkHistoryDo.getHighestPrice(),2));
list.add(BigDecimalUtil.toDouble(bkHistoryDo.getLowestPrice(),2));
list.add(BigDecimalUtil.toDouble(bkHistoryDo.getClosingPrice(),2));
list.add(BigDecimalUtil.toDouble(bkHistoryDo.getAmplitudeProportion(),2));
list.add(ganZhiUtils.convert(bkHistoryDo.getCurrDate())); // 干支日期(扩展字段)
kdata.add(list.toArray());
}
statKDataVo.setCode(bkCode);
statKDataVo.setName(data.get(0).getName());
statKDataVo.setKdata(kdata);
// 计算四大技术指标(复用股票指标计算逻辑,仅切换数据来源表)
aiBkAllDataVo.setMacdLineVo(getLineVoByHistory(data, ZbType.MACD));
aiBkAllDataVo.setKdjLineVo(getLineVoByHistory(data, ZbType.KDJ));
aiBkAllDataVo.setBollLineVo(getLineVoByHistory(data, ZbType.BOLL));
aiBkAllDataVo.setDmiLineVo(getLineVoByHistory(data, ZbType.DMI));
return statKDataVo;
}
关键技巧:指标计算逻辑完全复用股票指标代码,仅将数据来源从StockHistory表切换为BkHistory表,极大降低代码冗余,符合“复用性”设计原则。
三、可视化落地:前端对接指南📈
后端返回的数据格式可直接对接ECharts等可视化组件,无需额外转换,可以通过 AI 进行自行实现。
小亥为了感谢家人们的阅读和推广,将目前正在使用的 页面开放出来。
https://www.yueshushu.top/stockPage/bkDetailForm.html?sign=stock

可以查看其详细的信息:

四、避坑指南:5个实战关键提醒⚠️
这套方案看似简单,实则藏着不少细节坑,分享实战中踩过的雷,帮你少走弯路:
坑1:日期校准遗漏非交易日,导致数据缺失
✅ 原因:直接用传入日期查询,未过滤周末、节假日,导致查询结果为空或数据断层。
✅ 解决:必须通过dateHelper工具类校准日期,确保查询范围均为交易日。
坑2:指标计算数据量不足,结果失真
✅ 原因:MACD/KDJ等指标需要足够历史数据支撑(至少20个交易日),数据量不足会导致计算结果异常。
✅ 解决:查询指标数据时,至少获取80个交易日数据,既满足计算需求,又能截取近期数据展示。
坑3:关联股票查询过多,接口响应缓慢
✅ 原因:板块关联股票可能上百只,全量查询会占用大量资源,导致接口超时。
✅ 解决:按涨跌幅排序后取前20条,既聚焦核心标的,又提升接口响应速度(控制在200ms内)。
坑4:K线数据过多,前端图表卡顿
✅ 原因:返回全部历史数据用于绘图,导致前端渲染压力大,图表加载缓慢。
✅ 解决:取后40条数据用于展示,平衡趋势完整性与渲染性能。
坑5:指标计算逻辑重复开发,维护成本高
✅ 原因:单独为板块编写指标计算代码,与股票指标逻辑冗余,后续修改需同步改动两处。
✅ 解决:复用股票指标计算逻辑,仅切换数据来源表,通过抽象方法统一封装,降低维护成本。
五、福利领取:完整代码包免费送🎁
为了帮大家快速落地,我整理了本次实战的完整可运行代码包,包含:
- • ① 接口、模型、业务逻辑完整代码;
- • ② 四大指标计算工具类(复用版);
- • ③ 前端ECharts可视化对接示例代码;
- • ④ 日期校准工具类与避坑指南文档。
私信回复【板块指标可视化】,即可免费领取!所有代码均可直接导入项目运行,无需修改,帮你节省1-2天开发时间。
下期预告✨
本次我们搞定了板块指标的查询与可视化,下期将聚焦股票分钟级数据获取与存储——教你用Java实现分钟级数据的实时抓取、增量同步与高效存储,为高频策略分析打下基础,敬请期待!
结尾互动
你在板块指标可视化中,还遇到过哪些适配难题?需要我补充某类指标(如RSI)的计算逻辑吗?欢迎在评论区留言讨论!
整理了本次实战的完整可运行代码包,包含:
- • ① 接口、模型、业务逻辑完整代码;
- • ② 四大指标计算工具类(复用版);
- • ③ 前端ECharts可视化对接示例代码;
- • ④ 日期校准工具类与避坑指南文档。
私信回复【板块指标可视化】,即可免费领取!所有代码均可直接导入项目运行,无需修改,帮你节省1-2天开发时间。
下期预告✨
本次我们搞定了板块指标的查询与可视化,下期将聚焦股票分钟级数据获取与存储——教你用Java实现分钟级数据的实时抓取、增量同步与高效存储,为高频策略分析打下基础,敬请期待!
结尾互动
你在板块指标可视化中,还遇到过哪些适配难题?需要我补充某类指标(如RSI)的计算逻辑吗?欢迎在评论区留言讨论!
如果觉得这篇文章对你有帮助,别忘了点赞+在看+转发,让更多量化开发者告别“碎片化分析”的痛苦~ 关注我,持续解锁Java量化实战干货!🚀
更多推荐


所有评论(0)