参考链接是: 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量化实战干货!🚀

参考链接是: Java量化系列(三十七): 1个接口搞定板块指标全景图,MACD KDJ可视化一步到位

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐