From bf36f084b481590f0d094fcd994e320a89b0f942 Mon Sep 17 00:00:00 2001 From: majun Date: Tue, 18 Nov 2025 22:23:26 +0800 Subject: [PATCH] =?UTF-8?q?11.18=2022:22=20=E5=AE=8C=E6=88=90MACD=E6=8C=87?= =?UTF-8?q?=E6=A0=87=E7=9A=84=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/deepchart/controller/IndexController.java | 10 +- src/main/java/com/deepchart/entity/MACDData.java | 22 ++ .../java/com/deepchart/service/IndexService.java | 8 + .../deepchart/service/impl/IndexServiceImpl.java | 14 ++ src/main/java/com/deepchart/utils/MACDUtil.java | 227 +++++++++++++++++++++ src/main/java/com/deepchart/utils/Result.java | 24 +++ .../java/com/deepchart/utils/StockDataUtil.java | 16 +- 7 files changed, 311 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/deepchart/entity/MACDData.java create mode 100644 src/main/java/com/deepchart/utils/MACDUtil.java create mode 100644 src/main/java/com/deepchart/utils/Result.java diff --git a/src/main/java/com/deepchart/controller/IndexController.java b/src/main/java/com/deepchart/controller/IndexController.java index 167d969..502422e 100644 --- a/src/main/java/com/deepchart/controller/IndexController.java +++ b/src/main/java/com/deepchart/controller/IndexController.java @@ -1,13 +1,17 @@ package com.deepchart.controller; +import com.deepchart.entity.StockDailyData; import com.deepchart.entity.StockInfo; import com.deepchart.service.IndexService; +import com.deepchart.utils.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequestMapping(value = "/api/index", produces = "application/json") public class IndexController { @@ -16,7 +20,9 @@ public class IndexController { private IndexService indexService; @PostMapping("/macd") - public String macd(@RequestBody StockInfo stock) { - return ""; + public Result macd(@RequestBody StockInfo stock) { + List list = indexService.getStockData(stock); + String result = indexService.macd(list); + return Result.success("success", result); } } diff --git a/src/main/java/com/deepchart/entity/MACDData.java b/src/main/java/com/deepchart/entity/MACDData.java new file mode 100644 index 0000000..bb0c729 --- /dev/null +++ b/src/main/java/com/deepchart/entity/MACDData.java @@ -0,0 +1,22 @@ +package com.deepchart.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDate; + +/** + * MACD指标计算结果实体类 + */ +@Data +@AllArgsConstructor +public class MACDData { + private LocalDate date; // 交易日日期 + private double dif; // 差离值 + private double dea; // 信号线 + private double macdBar; // MACD柱状图 + + // 判断MACD柱颜色(红/绿) + public boolean isRedBar() { return macdBar > 0; } + public boolean isGreenBar() { return macdBar < 0; } +} \ No newline at end of file diff --git a/src/main/java/com/deepchart/service/IndexService.java b/src/main/java/com/deepchart/service/IndexService.java index 39974bc..5a84fd2 100644 --- a/src/main/java/com/deepchart/service/IndexService.java +++ b/src/main/java/com/deepchart/service/IndexService.java @@ -1,4 +1,12 @@ package com.deepchart.service; +import com.deepchart.entity.StockDailyData; +import com.deepchart.entity.StockInfo; + +import java.util.List; + public interface IndexService { + List getStockData(StockInfo stock); + + String macd(List list); } diff --git a/src/main/java/com/deepchart/service/impl/IndexServiceImpl.java b/src/main/java/com/deepchart/service/impl/IndexServiceImpl.java index be4f654..28da9f5 100644 --- a/src/main/java/com/deepchart/service/impl/IndexServiceImpl.java +++ b/src/main/java/com/deepchart/service/impl/IndexServiceImpl.java @@ -1,14 +1,28 @@ package com.deepchart.service.impl; +import com.deepchart.entity.StockDailyData; +import com.deepchart.entity.StockInfo; import com.deepchart.service.IndexService; +import com.deepchart.utils.MACDUtil; import com.deepchart.utils.StockDataUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; + @Service public class IndexServiceImpl implements IndexService { @Autowired private StockDataUtil stockDataUtil; + @Override + public List getStockData(StockInfo stock) { + return stockDataUtil.getStockData(stock); + } + + @Override + public String macd(List list) { + return MACDUtil.analyzeStock(list); + } } diff --git a/src/main/java/com/deepchart/utils/MACDUtil.java b/src/main/java/com/deepchart/utils/MACDUtil.java new file mode 100644 index 0000000..c6cfd5c --- /dev/null +++ b/src/main/java/com/deepchart/utils/MACDUtil.java @@ -0,0 +1,227 @@ +package com.deepchart.utils; + +import com.deepchart.entity.MACDData; +import com.deepchart.entity.StockDailyData; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +/** + * MACD指标分析工具类 + * 实现五大核心分析方法 + */ +public class MACDUtil { + + public static String analyzeStock(List prices) { + StringBuilder report = new StringBuilder(); + + List macdData = calculateMACD(prices); + + // 1. 交叉信号分析 + report.append("===== 交叉信号分析 =====\n"); + analyzeCrossovers(macdData, report); + + // 2. 柱状图动能分析 + report.append("\n===== 柱状图动能分析 =====\n"); + analyzeBarMomentum(macdData, report); + + // 3. 零轴趋势判断 + report.append("\n===== 零轴趋势判断 =====\n"); + analyzeZeroAxisTrend(macdData, report); + + // 4. 背离现象检测 + report.append("\n===== 背离现象分析 =====\n"); + analyzeDivergence(prices, macdData, report); + + // 5. 参数调整建议 +// report.append("\n===== 参数调整建议 =====\n"); +// report.append(" 当前参数:EMA(12,26,9)\n"); +// report.append("• 短线交易可调整为EMA(6,13,5)提升灵敏度\n"); +// report.append("• 长线投资可调整为EMA(24,52,9)过滤噪音\n"); + + return report.toString(); + } + + // 核心MACD计算逻辑 + private static List calculateMACD(List stockData) { + List result = new ArrayList<>(); + double ema12; + double ema26; + double prevEma12 = 0.0; + double prevEma26 = 0.0; + double prevDea = 0.0; + boolean firstDay = true; + + for (StockDailyData daily : stockData) { + double close = daily.getClosePrice(); + LocalDate date = daily.getDate(); + + // 首日初始化 + if (firstDay) { + firstDay = false; + MACDData data = new MACDData(date, 0.0, 0.0, 0.0); + result.add(data); + continue; + } + + // 计算EMA(12)和EMA(26) + ema12 = calculateEMA(close, prevEma12, 12); + ema26 = calculateEMA(close, prevEma26, 26); + + // 计算DIF + double dif = ema12 - ema26; + + // 计算DEA(DIF的9日EMA) + double dea = calculateEMA(dif, prevDea, 9); + + // 计算MACD柱状图 + double macdBar = 2 * (dif - dea); + + // 保存当前值供下个交易日使用 + prevEma12 = ema12; + prevEma26 = ema26; + prevDea = dea; + + result.add(new MACDData(date, dif, dea, macdBar)); + } + return result; + } + + // 指数移动平均(EMA)计算公式 + private static double calculateEMA(double currentPrice, double prevEMA, int period) { + return currentPrice * (2.0 / (period + 1)) + prevEMA * ((period - 1.0) / (period + 1)); + } + + // 金叉/死叉检测 + private static void analyzeCrossovers(List macdData, StringBuilder report) { + int goldenCrossCount = 0; + int deathCrossCount = 0; + boolean aboveZeroGolden = false; + boolean belowZeroDeath = false; + + for (int i = 1; i < macdData.size(); i++) { + MACDData prev = macdData.get(i-1); + MACDData curr = macdData.get(i); + + // 金叉:DIF上穿DEA + if (prev.getDif() < prev.getDea() && curr.getDif() > curr.getDea()) { + goldenCrossCount++; + if (curr.getDif() > 0) { + aboveZeroGolden = true; + report.append(String.format("• %tF:强势金叉信号(零轴上方),买入机会\n", curr.getDate())); + } else { + report.append(String.format("• %tF:弱势金叉信号(零轴下方),谨慎参与\n", curr.getDate())); + } + } + // 死叉:DIF下穿DEA + else if (prev.getDif() > prev.getDea() && curr.getDif() < curr.getDea()) { + deathCrossCount++; + if (curr.getDif() < 0) { + belowZeroDeath = true; + report.append(String.format("• %tF:强势死叉信号(零轴下方),卖出警示\n", curr.getDate())); + } else { + report.append(String.format("• %tF:弱势死叉信号(零轴上方),部分止盈\n", curr.getDate())); + } + } + } + + report.append("\n 交叉信号统计:\n"); + report.append("• 近期出现金叉次数:").append(goldenCrossCount).append("次\n"); + report.append("• 近期出现死叉次数:").append(deathCrossCount).append("次\n"); + if (aboveZeroGolden) report.append("▶ 存在零轴上方的强势金叉,多头动能强劲\n"); + if (belowZeroDeath) report.append("▶ 存在零轴下方的强势死叉,空头风险显著\n"); + } + + // 柱状图动能分析 + private static void analyzeBarMomentum(List macdData, StringBuilder report) { + if (macdData.size() < 5) return; + + // 获取最近5个交易日的柱状图数据 + MACDData curr = macdData.get(macdData.size() - 1); + MACDData prev1 = macdData.get(macdData.size() - 2); + MACDData prev2 = macdData.get(macdData.size() - 3); + + report.append(" 最新交易日数据:\n"); + report.append(String.format("• MACD柱状图数值:%.4f (%s)\n", + curr.getMacdBar(), curr.isRedBar() ? "红柱" : "绿柱")); + + // 红柱分析(零轴上方) + if (curr.isRedBar()) { + report.append("▶ 当前处于多头市场(红柱区域)\n"); + if (curr.getMacdBar() > prev1.getMacdBar() && prev1.getMacdBar() > prev2.getMacdBar()) { + report.append("▶ 红柱连续放大(3日),上涨动能加速\n"); + } else if (curr.getMacdBar() < prev1.getMacdBar()) { + report.append("▶ 红柱开始缩短,警惕上涨动能衰减\n"); + } + } + // 绿柱分析(零轴下方) + else { + report.append("▶ 当前处于空头市场(绿柱区域)\n"); + if (curr.getMacdBar() < prev1.getMacdBar() && prev1.getMacdBar() < prev2.getMacdBar()) { + report.append("▶ 绿柱连续放大(3日),下跌动能加速\n"); + } else if (curr.getMacdBar() > prev1.getMacdBar()) { + report.append("▶ 绿柱开始缩短,空头力量减弱\n"); + } + } + } + + // 零轴趋势分析 + private static void analyzeZeroAxisTrend(List macdData, StringBuilder report) { + int aboveZeroDays = 0; + int belowZeroDays = 0; + + for (MACDData data : macdData) { + if (data.getDif() > 0) aboveZeroDays++; + else belowZeroDays++; + } + + double aboveRatio = 100.0 * aboveZeroDays / macdData.size(); + report.append(String.format(" 历史交易日统计(共%d天):\n", macdData.size())); + report.append(String.format("• DIF在零轴上方天数:%d天 (占比%.1f%%)\n", aboveZeroDays, aboveRatio)); + report.append(String.format("• DIF在零轴下方天数:%d天 (占比%.1f%%)\n", belowZeroDays, 100 - aboveRatio)); + + MACDData last = macdData.get(macdData.size() - 1); + if (last.getDif() > 0 && last.getDea() > 0) { + report.append("▶ 当前趋势:多头市场(DIF与DEA均在零轴上方)\n"); + } else if (last.getDif() < 0 && last.getDea() < 0) { + report.append("▶ 当前趋势:空头市场(DIF与DEA均在零轴下方)\n"); + } else { + report.append("▶ 当前趋势:震荡行情(多空分歧明显)\n"); + } + } + + // 背离分析 + private static void analyzeDivergence(List prices, List macdData, StringBuilder report) { + boolean hasTopDivergence = false; + boolean hasBottomDivergence = false; + + // 寻找最近30个交易日内的背离 + int lookback = Math.min(30, prices.size() - 1); + for (int i = prices.size() - 2; i > prices.size() - lookback; i--) { + // 顶背离检测(股价新高但MACD低点) + if (prices.get(i).getClosePrice() > prices.get(i+1).getClosePrice() && + prices.get(i).getClosePrice() > prices.get(i-1).getClosePrice() && + macdData.get(i).getDif() < macdData.get(i+1).getDif() && + macdData.get(i).getDif() < macdData.get(i-1).getDif()) { + + hasTopDivergence = true; + report.append(String.format("• %tF:检测到顶背离(股价新高但MACD走弱),警示下跌风险\n", + prices.get(i).getDate())); + } + // 底背离检测(股价新低但MACD高点) + else if (prices.get(i).getClosePrice() < prices.get(i+1).getClosePrice() && + prices.get(i).getClosePrice() < prices.get(i-1).getClosePrice() && + macdData.get(i).getDif() > macdData.get(i+1).getDif() && + macdData.get(i).getDif() > macdData.get(i-1).getDif()) { + + hasBottomDivergence = true; + report.append(String.format("• %tF:检测到底背离(股价新低但MACD走强),预示反弹机会\n", + prices.get(i).getDate())); + } + } + + if (!hasTopDivergence) report.append("• 近期未检测到显著顶背离现象\n"); + if (!hasBottomDivergence) report.append("• 近期未检测到显著底背离现象\n"); + } +} \ No newline at end of file diff --git a/src/main/java/com/deepchart/utils/Result.java b/src/main/java/com/deepchart/utils/Result.java new file mode 100644 index 0000000..3b5f231 --- /dev/null +++ b/src/main/java/com/deepchart/utils/Result.java @@ -0,0 +1,24 @@ +package com.deepchart.utils; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 请求响应返回结果工具类 + * @param + */ +@Data +@AllArgsConstructor +public class Result { + private Integer code; //状态码 + private String msg; //返回信息 + private T data; //返回数据 + + public static Result success(String message, T data) { + return new Result<>(200, message, data); + } + + public static Result error(Integer resultCode, String message) { + return new Result<>(resultCode, message, null); + } +} \ No newline at end of file diff --git a/src/main/java/com/deepchart/utils/StockDataUtil.java b/src/main/java/com/deepchart/utils/StockDataUtil.java index 8b4ba0e..ce1f77a 100644 --- a/src/main/java/com/deepchart/utils/StockDataUtil.java +++ b/src/main/java/com/deepchart/utils/StockDataUtil.java @@ -29,7 +29,7 @@ public class StockDataUtil { public List getStockData(StockInfo stock) { - List> kLine20 = List.of(); + List> kLine20 = List.of(); try { // 1. 构建请求URL @@ -72,7 +72,7 @@ public class StockDataUtil { Map jsonResponse = objectMapper.readValue(response.toString(), Map.class); Map data = (Map) jsonResponse.get("data"); Map brain = (Map) data.get("Brain"); - kLine20 = (List>) brain.get("KLine20"); + kLine20 = (List>) brain.get("KLine20"); } } @@ -85,13 +85,13 @@ public class StockDataUtil { List list = new ArrayList<>(); - for (List v : kLine20) { + for (List v : kLine20) { StockDailyData s = new StockDailyData(); - s.setDate(LocalDate.parse(v.get(0), DateTimeFormatter.ofPattern("yyyy/MM/dd"))); - s.setOpenPrice(Double.parseDouble(v.get(1))); - s.setClosePrice(Double.parseDouble(v.get(2))); - s.setLowPrice(Double.parseDouble(v.get(3))); - s.setHighPrice(Double.parseDouble(v.get(4))); + s.setDate(LocalDate.parse((String) v.get(0), DateTimeFormatter.ofPattern("yyyy/MM/dd"))); + s.setOpenPrice((double)v.get(1)); + s.setClosePrice((double)v.get(2)); + s.setLowPrice((double)v.get(3)); + s.setHighPrice((double)v.get(4)); list.add(s); }