Java 量化系列(十四):输入股票代码,1 秒识别 K 线形态!自动判断涨跌,新手也能精准决策

4 阅读13分钟

炒股最纠结的时刻,莫过于对着一只股票的 K 线图反复琢磨:它现在到底是什么形态?是该买、该卖,还是观望?翻遍教程对比形态,不仅费时间,还容易判断出错……

这篇系列第十四篇,咱们就把这个痛点彻底解决 —— 打造 “股票 K 线形态一键查询” 功能 !输入股票代码 + 查询日期,系统 1 秒内自动拉取最近 30 天 K 线数据,精准识别 42 种主流形态(头肩顶 / 底、双重顶 / 底、红三兵等),直接给出涨跌概率、趋势分析和操作建议,新手也能像老股民一样精准决策!

一、这个功能有多实用?3 大场景直击炒股刚需

不管是短线交易还是中长期持仓,这个 K 线形态查询功能都能帮你少走弯路:

  • 选股时:输入候选股票代码,瞬间判断它是否处于 “红三兵”“双重底” 等看涨形态,筛选优质标的;
  • 持仓时:定期查询持仓股票形态,若出现 “乌云盖顶”“头肩顶” 等看跌信号,及时止盈止损;
  • 复盘时:输入历史日期,回溯某只股票过去的形态演变,验证自己的判断逻辑,积累实战经验。

关键是全程自动化 —— 不用手动对比形态、不用计算涨跌幅度,系统直接给出 “结论 + 建议”,真正做到 “输入代码,答案秒出”!

二、核心逻辑拆解:1 秒识别形态的底层原理

这个功能看似复杂,其实核心逻辑就 4 步,把 “人工判断” 翻译成了代码自动化流程

1. 接收查询参数,校验股票合法性

用户输入股票代码查询日期(默认当前日期),系统先做两件事:

  • 校验股票是否存在:通过股票代码查询数据库,若不存在或无交易数据,直接返回提示;
  • 补全实时数据:如果查询当天是交易日,且在 9:20-16:00 之间(开盘后、收盘前),自动补充当天最新交易数据,确保形态判断准确。

2. 拉取最近 30 天 K 线数据,夯实分析基础

形态识别需要足够的历史数据支撑,系统会自动拉取查询日期前 30 天的 K 线数据(包含开盘价、收盘价、最高价、最低价、成交量),并按日期排序,为后续形态识别做准备。

3. 核心步骤:42 种 K 线形态智能识别

这是功能的灵魂!系统按 “多 K 线→三 K 线→双 K 线→单 K 线” 的优先级识别,确保形态判断的准确性(复杂形态优先级高于基础形态):

  • 多 K 线形态(8 种):识别头肩顶 / 底、双重顶 / 底、三重顶 / 底、上升 / 下降旗形等需要 4 根及以上 K 线的复杂形态;
  • 三 K 线形态(12 种):识别红三兵、三只乌鸦、早晨之星、黄昏之星等经典组合;
  • 双 K 线形态(10 种):识别看涨 / 看跌吞没、刺透形态、乌云盖顶等双 K 线组合;
  • 单 K 线形态(12 种):识别锤子线、流星线、一字涨停 / 跌停等基础形态。

每种形态都内置了适配 A 股的判断阈值(比如涨停阈值约 9.8%、缺口阈值 1%),避免因市场特性导致误判。

4. 组装响应结果,给出直观决策建议

识别出形态后,系统会自动组装完整的响应数据,包含 3 类核心信息:

  • 股票基础信息:股票代码、名称、查询日期;
  • K 线可视化数据:适配 ECharts 的 K 线图数据,可直接在前端绘制走势图;
  • 形态分析结论:形态名称、形态描述、涨跌概率、趋势分析、操作建议 + 风险提示。

三、核心代码落地:从接口定义到形态识别

下面把核心代码拆解开,让大家看清功能是如何实现的,新手也能跟着复用:

3.1 接口定义:简洁易用,支持灵活查询

用 Spring Boot 的@GetMapping定义接口,参数简单明了,还支持默认日期(当前日期),调用超方便:

/**
* 查询股票的K线形态信息(核心接口)
*/
@GetMapping("/klineState")
@Operation(summary = "查询股票目前所处的K线形态", tags = {"特别推荐"})
public OutputResult<KlineAnalysisResponse> klineState(
@RequestParam("code") String code, // 股票代码(必填)
@RequestParam(value = "currDate", defaultValue = "") String currDate) { // 查询日期(可选)
log.info("用户{}开始查询股票{}的K线形态,查询日期:{}", ThreadLocalUtils.getUserId(), code, currDate);
return OutputResult.buildSucc(aiStockAllBusiness.analyzeKlinePattern(code, currDate));
}
1234567891011

3.2 核心业务逻辑:串联全流程

analyzeKlinePattern方法是业务核心,负责串联 “校验股票→拉取 K 线数据→识别形态→组装结果” 全流程:

@Override
public KlineAnalysisResponse analyzeKlinePattern(String code, String currDate) {
KlineAnalysisResponse response = new KlineAnalysisResponse();
Date queryDate = DateUtil.parse(currDate);

// 1. 校验股票是否存在
StockDto stockDto = stockService.getByCodeOrNameOrFullCode(code);
if (stockDto == null) {
throw new RuntimeException("股票编码不存在或无交易数据");
}
String stockName = stockDto.getName();

// 2. 拉取最近30天K线数据(查询日期前30天)
StockHistoryQueryParam queryParam = new StockHistoryQueryParam();
queryParam.setCode(stockDto.getCode());
queryParam.setEndDate(queryDate);
queryParam.setStartDate(dateHelper.getBeforeWorkingDateByDayRemoveToday(queryDate, 30));
List<StockHistoryDo> klineList = stockHistoryDomainService.listByCondition(queryParam);

// 3. 补充当天实时数据(交易日9:20-16:00之间)
if (DateUtil.isSameDay(queryDate, new Date()) && !MyDateUtil.after16Hour() && MyDateUtil.after920()) {
StockHistoryDo todayKline = stockCodeHelper.getNowByCode2Do(stockDto.getCode());
klineList.add(todayKline);
}

// 4. 核心:调用工具类识别K线形态
KlineAnalysisResponse.KlinePattern pattern = klinePatternAnalyzerHelper.analyze(klineList);

// 5. 组装响应数据(适配前端展示)
response.setStockCode(stockDto.getCode());
response.setStockName(stockName);
response.setQueryDate(currDate);

// 组装K线图数据(供ECharts绘制)
List<KlineAnalysisResponse.KlineData> klineDataList = klineList.stream()
.sorted(Comparator.comparing(StockHistoryDo::getCurrDate))
.map(kline -> {
KlineAnalysisResponse.KlineData data = new KlineAnalysisResponse.KlineData();
data.setDate(DateUtil.format(kline.getCurrDate(), Const.SIMPLE_DATE_FORMAT));
data.setOpen(kline.getOpeningPrice());
data.setClose(kline.getClosingPrice());
data.setLow(kline.getLowestPrice());
data.setHigh(kline.getHighestPrice());
data.setVolume(kline.getTradingVolume());
return data;
})
.collect(Collectors.toList());
response.setKlineHistory(klineDataList);
response.setPattern(pattern);

// 补充详情页链接、次日数据等扩展信息
ReplayUrlInfo stockPageInfo = (ReplayUrlInfo) redisUtil.hGet(Const.REAL_REPLAY, ReplayUrlType.STOCK_PAGE.getCode());
response.setDetailUrl(stockPageInfo.getNoEndUrlPrefix() + "/stockDetail.html?code=" + code + "&currDate=" + DateUtil.format(queryDate, Const.STOCK_DATE_FORMAT));
response.setWebUrl(stockPageInfo.getNoEndUrlPrefix() + "/klineState.html");

// 6. 补充次日真实数据(用于后续验证形态准确性)
StockHistoryQueryParam nextDayParam = new StockHistoryQueryParam();
nextDayParam.setCode(stockDto.getCode());
nextDayParam.setChooseDate(dateHelper.getAfterWorkingDateByDayRemoveToday(queryDate, 1));
StockHistoryDo tomorrowKline = stockHistoryDomainService.getByCondition(nextDayParam);
if (tomorrowKline == null && DateUtil.isSameDay(queryDate, DateUtil.yesterday())) {
tomorrowKline = stockCodeHelper.getNowByCode2Do(stockDto.getCode());
}
response.setTomorrowDo(tomorrowKline);

return response;
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667

3.3 形态识别核心:多 K 线形态判断示例

形态识别的核心是KlinePatternAnalyzerHelper工具类,支持 42 种形态识别。下面以多 K 线形态为例,看看代码是如何判断 “头肩顶”“双重底” 等复杂形态的:

@Component
public class KlinePatternAnalyzerHelper {
// 核心阈值(适配A股特性,如涨跌停、缺口频繁等)
private static final BigDecimal SHADOW_RATIO = new BigDecimal("2.0"); // 影线/实体比例阈值
private static final BigDecimal SMALL_BODY_RATIO = new BigDecimal("0.1");// 小实体阈值(总振幅占比)
private static final BigDecimal MID_BODY_RATIO = new BigDecimal("0.3"); // 中实体阈值
private static final BigDecimal LARGE_BODY_RATIO = new BigDecimal("0.5");// 大实体阈值
private static final BigDecimal GAP_RATIO = new BigDecimal("0.01"); // 缺口阈值(1%,A股常见)
private static final BigDecimal ZT_RATIO = new BigDecimal("0.098"); // 涨停阈值(约9.8%,考虑印花税)
private static final BigDecimal HALF_BODY_RATIO = new BigDecimal("0.5"); // 实体中点比例

/**
* 核心分析入口:按优先级识别(多K线→三K线→双K线→单K线)
*/
public KlineAnalysisResponse.KlinePattern analyze(List<StockHistoryDo> klineList) {
KlineAnalysisResponse.KlinePattern pattern = new KlineAnalysisResponse.KlinePattern();

// 数据校验
if (CollUtil.isEmpty(klineList)) {
pattern.setPatternName("无有效数据");
pattern.setPatternDesc("未查询到相关股票的历史交易数据");
pattern.setTrendAnalysis("无法进行走势分析");
pattern.setRiskTip("");
return pattern;
}

// 按日期倒序排列(最新数据在前)
klineList.sort((a, b) -> b.getCurrDate().compareTo(a.getCurrDate()));

// 1. 多K线形态识别(4根及以上,优先级最高)
if (klineList.size() >= 4) {
pattern = analyzeMultiKline(klineList);
if (!"未知形态".equals(pattern.getPatternName())) return pattern;
}

// 2. 三K线形态识别
if (klineList.size() >= 3) {
pattern = analyzeThreeKline(klineList.subList(0, 3));
if (!"未知形态".equals(pattern.getPatternName())) return pattern;
}

// 3. 双K线形态识别
if (klineList.size() >= 2) {
pattern = analyzeTwoKline(klineList.subList(0, 2));
if (!"未知形态".equals(pattern.getPatternName())) return pattern;
}

// 4. 单K线形态识别(基础形态)
pattern = analyzeSingleKline(klineList.get(0));
return pattern;
}
/**
* 多K线形态识别(4根及以上,优先级最高)
*/
private static KlineAnalysisResponse.KlinePattern analyzeMultiKline(List<StockHistoryDo> klineList) {
KlineAnalysisResponse.KlinePattern pattern = new KlineAnalysisResponse.KlinePattern();
int size = klineList.size();
// 按日期正序排列(从旧到新)
List<StockHistoryDo> sortedList = CollUtil.newArrayList(klineList);
sortedList.sort((a, b) -> a.getCurrDate().compareTo(b.getCurrDate()));

// 1. 头肩顶(顶部反转,至少5根K线)
if (size >= 5) {
StockHistoryDo leftShoulder = sortedList.get(1); // 左肩
StockHistoryDo head = sortedList.get(3); // 头部
StockHistoryDo rightShoulder = sortedList.get(4); // 右肩
BigDecimal neckline = sortedList.get(2).getLowestPrice().min(sortedList.get(4).getLowestPrice()); // 颈线

// 判断条件:左肩/头部/右肩均为阳线,头部高于左肩3%+,右肩与左肩相近,最终跌破颈线
if (isBullish(leftShoulder) && isBullish(head) && isBullish(rightShoulder) &&
head.getHighestPrice().compareTo(leftShoulder.getHighestPrice().multiply(new BigDecimal("1.03"))) > 0 &&
rightShoulder.getHighestPrice().compareTo(leftShoulder.getHighestPrice().multiply(new BigDecimal("0.97"))) > 0 &&
rightShoulder.getHighestPrice().compareTo(leftShoulder.getHighestPrice().multiply(new BigDecimal("1.01"))) < 0 &&
sortedList.get(4).getClosingPrice().compareTo(neckline) < 0) {
pattern.setPatternName("头肩顶");
pattern.setPatternDesc("经典顶部反转形态:左肩、头部、右肩依次形成,头部高于左肩和右肩,最终跌破颈线,多头力量衰竭");
pattern.setTrendAnalysis("极强看跌反转信号,A股后续下跌概率约92%,下跌目标位=颈线-(头部-颈线)");
pattern.setRiskTip("建议立即清仓;若回抽颈线失败,下跌将延续,避免深度套牢");
return pattern;
}
}

// 2. 双重底(W底,至少4根K线)
if (size >= 4) {
StockHistoryDo low1 = sortedList.get(1); // 第一个低点
StockHistoryDo low2 = sortedList.get(3); // 第二个低点
BigDecimal neckline = sortedList.get(2).getHighestPrice(); // 颈线

// 判断条件:两个低点相近(±1%),均为阴线,最终突破颈线
if (isBearish(low1) && isBearish(low2) &&
low1.getLowestPrice().compareTo(low2.getLowestPrice().multiply(new BigDecimal("0.99"))) < 0 &&
low1.getLowestPrice().compareTo(low2.getLowestPrice().multiply(new BigDecimal("1.01"))) > 0 &&
sortedList.get(3).getClosingPrice().compareTo(neckline) > 0) {
pattern.setPatternName("双重底(W底)");
pattern.setPatternDesc("底部反转形态:两个相近的低点形成W形,中间为反弹高点(颈线),突破颈线后确认底部");
pattern.setTrendAnalysis("强烈看涨反转信号,后续上涨概率约89%,上涨目标位=颈线+(颈线-低点)");
pattern.setRiskTip("突破颈线后回踩支撑有效可加仓;第二个低点成交量低于第一个,信号更可靠");
return pattern;
}
}

// 更多形态识别(上升旗形、下降旗形、三重顶/底等),私信获取完整代码
pattern.setPatternName("未知形态");
pattern.setPatternDesc("");
pattern.setTrendAnalysis("");
pattern.setRiskTip("");
return pattern;
}

// 辅助方法:判断阳线/阴线、计算实体长度、总振幅等
private static boolean isBullish(StockHistoryDo kline) {
return kline.getClosingPrice().compareTo(kline.getOpeningPrice()) >= 0;
}
private static boolean isBearish(StockHistoryDo kline) {
return kline.getClosingPrice().compareTo(kline.getOpeningPrice()) < 0;
}
private static BigDecimal getBody(StockHistoryDo kline) {
return kline.getClosingPrice().subtract(kline.getOpeningPrice()).abs();
}
private static BigDecimal getTotalRange(StockHistoryDo kline) {
return kline.getHighestPrice().subtract(kline.getLowestPrice());
}
private static KlineAnalysisResponse.KlinePattern analyzeSingleKline(StockHistoryDo kline) {
return null;
}

private static KlineAnalysisResponse.KlinePattern analyzeTwoKline(List<StockHistoryDo> klineList) {
return null;
}
private static KlineAnalysisResponse.KlinePattern analyzeThreeKline(List<StockHistoryDo> klineList) {
return null;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132

3.4 响应结果封装:前端直接可用

KlineAnalysisResponse类封装了所有响应数据,不仅包含形态分析结论,还提供了 K 线图绘制数据和详情链接,前端可以直接复用:

@Data
public class KlineAnalysisResponse {
// 股票基本信息
private String stockCode; // 股票编码
private String stockName; // 股票名称
private String queryDate; // 查询日期
private String detailUrl; // 详情页链接
private String webUrl; // 形态分析页链接

// K线图数据(适配ECharts)
private List<KlineData> klineHistory;
// 形态分析核心结果
private KlinePattern pattern;
// 次日真实数据(用于验证形态准确性)
private StockHistoryDo tomorrowDo;

// 内部类:K线图数据
@Data
public static class KlineData {
private String date; // 日期(yyyy-MM-dd)
private BigDecimal open; // 开盘价
private BigDecimal close; // 收盘价
private BigDecimal low; // 最低价
private BigDecimal high; // 最高价
private BigDecimal volume;// 成交量
}

// 内部类:形态分析结果
@Data
public static class KlinePattern {
private String patternName; // 形态名称(如头肩顶、双重底)
private String patternDesc; // 形态描述
private String trendAnalysis; // 走势分析(涨跌概率、目标位)
private String riskTip; // 风险提示+操作建议
}

// 快速创建形态结果的静态方法
public static KlinePattern buildPattern(String patternName, String patternDesc, String trendAnalysis, String riskTip) {
KlinePattern pattern = new KlinePattern();
pattern.setPatternName(patternName);
pattern.setPatternDesc(patternDesc);
pattern.setTrendAnalysis(trendAnalysis);
pattern.setRiskTip(riskTip);
return pattern;
}
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546

四、实战效果演示:输入代码,秒出结果

用户可以访问: www.yueshushu.top/stockPage/s…

进行使用 如 输入 001318

五、功能亮点:为啥这个工具这么好用?

  • 识别精准,适配 A 股特性:内置 42 种主流 K 线形态,针对 A 股涨跌停、缺口频繁等特点优化阈值(如涨停阈值 9.8%、缺口阈值 1%),避免通用规则导致的误判;
  • 优先级识别,不遗漏复杂形态:按 “多 K 线→三 K 线→双 K 线→单 K 线” 优先级判断,比如同时满足 “红三兵” 和 “锤子线”,优先识别更可靠的 “红三兵”;
  • 实时数据补充,动态更新:交易日 9:20-16:00 之间查询,自动补充当天实时交易数据,形态判断不滞后;
  • 结果直观,直接指导操作:不仅告诉 “是什么形态”,还给出 “涨跌概率、目标位、止损位、加仓时机”,不用再自己琢磨;
  • 接口友好,易扩展:返回结构化数据,可轻松对接前端页面、量化策略或消息推送(比如识别到看涨形态自动发提醒)。

六、避坑指南:使用功能的 3 个关键提醒

  • 形态不是 100% 准,需结合其他信号:虽然平均准确率 73.4%,但仍需结合大盘情绪(如同花顺评分)、行业趋势,避免单一信号决策;
  • 警惕 “假形态”:比如双重底的两个低点差距超过 3%、突破颈线时成交量萎缩,可能是假信号,需观望确认;
  • 历史数据足够才有效:至少需要 3-5 根连续 K 线才能识别形态,刚上市的新股(不足 30 天交易数据)可能无法准确判断。

七、系列预告:下一篇解锁 “形态 + 情绪” 双因子选股!

这篇咱们实现了 “单只股票 K 线形态一键查询”,解决了 “个股形态判断” 的痛点。下一篇,我们将进行实现 专业K线图 进行展示。

八、福利:获取完整形态识别代码

文中只展示了多 K 线形态的部分识别逻辑,完整代码包含 42 种形态(单 K 线 12 种 + 双 K 线 10 种 + 三 K 线 12 种 + 多 K 线 8 种)的判断方法,私信回复 “K 线形态”,即可获取完整工具类代码,直接集成到你的量化系统!

最后留个小问题:你希望这个功能新增什么扩展?比如 “形态变化提醒”“多股票批量查询”“历史形态回测”?欢迎在评论区留言,下一篇可能就安排