From 48aa7dafbc128b886225e6d13160c9ec119e8731 Mon Sep 17 00:00:00 2001 From: liruiqiang <3151805288@qq.com> Date: Wed, 19 Nov 2025 15:47:23 +0800 Subject: [PATCH] =?UTF-8?q?EXPMA=E6=8C=87=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/deepchart/controller/IndexController.java | 7 + .../java/com/deepchart/service/IndexService.java | 2 + .../deepchart/service/impl/IndexServiceImpl.java | 10 +- src/main/java/com/deepchart/utils/EXPMAUtil.java | 156 +++++++++++++++++++++ 4 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/deepchart/utils/EXPMAUtil.java diff --git a/src/main/java/com/deepchart/controller/IndexController.java b/src/main/java/com/deepchart/controller/IndexController.java index 463bb42..ebfb05c 100644 --- a/src/main/java/com/deepchart/controller/IndexController.java +++ b/src/main/java/com/deepchart/controller/IndexController.java @@ -39,4 +39,11 @@ public class IndexController { String result = indexService.cci(list); return Result.success("success", result); } + + @PostMapping("/expma") + public Result expma(@RequestBody StockInfo stock) { + List list = indexService.getStockData(stock); + String result = indexService.expma(list); + return Result.success("success", result); + } } diff --git a/src/main/java/com/deepchart/service/IndexService.java b/src/main/java/com/deepchart/service/IndexService.java index 7e92945..3dc69a8 100644 --- a/src/main/java/com/deepchart/service/IndexService.java +++ b/src/main/java/com/deepchart/service/IndexService.java @@ -13,4 +13,6 @@ public interface IndexService { String ma(List list); String cci(List list); + + String expma(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 a2c495f..cf8b947 100644 --- a/src/main/java/com/deepchart/service/impl/IndexServiceImpl.java +++ b/src/main/java/com/deepchart/service/impl/IndexServiceImpl.java @@ -3,10 +3,7 @@ package com.deepchart.service.impl; import com.deepchart.entity.StockDailyData; import com.deepchart.entity.StockInfo; import com.deepchart.service.IndexService; -import com.deepchart.utils.CCIUtil; -import com.deepchart.utils.MACDUtil; -import com.deepchart.utils.MAUtil; -import com.deepchart.utils.StockDataUtil; +import com.deepchart.utils.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -37,4 +34,9 @@ public class IndexServiceImpl implements IndexService { public String cci(List list) { return CCIUtil.analyzeCCI(list); } + + @Override + public String expma(List list) { + return EXPMAUtil.analyzeEXPMA(list,12,40); + } } diff --git a/src/main/java/com/deepchart/utils/EXPMAUtil.java b/src/main/java/com/deepchart/utils/EXPMAUtil.java new file mode 100644 index 0000000..4d36bcf --- /dev/null +++ b/src/main/java/com/deepchart/utils/EXPMAUtil.java @@ -0,0 +1,156 @@ +package com.deepchart.utils; + +import com.deepchart.entity.StockDailyData; + +import java.time.LocalDate; +import java.util.*; + +public class EXPMAUtil { + + public static String analyzeEXPMA(List stockData, int shortPeriod, int longPeriod) { + if (stockData == null || stockData.size() < Math.max(shortPeriod, longPeriod)) { + return "数据不足,无法进行 EXPMA 分析。请至少提供 " + Math.max(shortPeriod, longPeriod) + " 天数据。"; + } + + // 1. 计算 EXPMA + List expmaShort = calculateSingleExpma(stockData, shortPeriod); + List expmaLong = calculateSingleExpma(stockData, longPeriod); + + int n = stockData.size(); + LocalDate latestDate = stockData.get(n - 1).getDate(); + double latestPrice = stockData.get(n - 1).getClosePrice(); + double currentShort = expmaShort.get(n - 1); + double currentLong = expmaLong.get(n - 1); + + StringBuilder analysis = new StringBuilder(); + analysis.append("📊 EXPMA 趋势分析(截至 ").append(latestDate).append(")\n"); + analysis.append(" • 短期EXPMA(").append(shortPeriod).append("): ").append(String.format("%.2f", currentShort)).append("\n"); + analysis.append(" • 长期EXPMA(").append(longPeriod).append("): ").append(String.format("%.2f", currentLong)).append("\n"); + analysis.append(" • 当前收盘价: ").append(String.format("%.2f", latestPrice)).append("\n\n"); + + // 2. 判断多头/空头排列 + boolean isBullish = currentShort > currentLong; + boolean shortUp = n >= 2 && expmaShort.get(n - 1) > expmaShort.get(n - 2); + boolean longUp = n >= 2 && expmaLong.get(n - 1) > expmaLong.get(n - 2); + + if (isBullish && shortUp && longUp) { + analysis.append("📈 趋势状态:【多头排列】\n"); + analysis.append(" → 市场处于上升趋势,短期均线在长期均线上方且双双上行。\n"); + if (latestPrice >= currentShort) { + analysis.append(" → 股价站稳短期EXPMA之上,强势格局。\n"); + } else { + analysis.append(" → 股价回踩短期EXPMA,若不破可视为加仓机会。\n"); + } + } else if (!isBullish && !shortUp && !longUp) { + analysis.append("📉 趋势状态:【空头排列】\n"); + analysis.append(" → 市场处于下降趋势,短期均线在长期均线下方且双双下行。\n"); + if (latestPrice <= currentShort) { + analysis.append(" → 股价受压于短期EXPMA,弱势明显,建议观望。\n"); + } else { + analysis.append(" → 股价反弹至短期EXPMA附近,可能是做空或减仓时机。\n"); + } + } else { + analysis.append("🔄 趋势状态:【震荡整理】\n"); + analysis.append(" → 均线缠绕或方向不一致,市场缺乏明确趋势。\n"); + } + + // 3. 检测最近是否发生金叉/死叉(过去5日内) + boolean goldenCross = false; + boolean deathCross = false; + int crossDayIndex = -1; + String crossType = ""; + + // 从后往前找最近一次交叉(最多看最近10天) + int lookback = Math.min(10, n - 1); + for (int i = n - 1; i >= lookback; i--) { + double shortToday = expmaShort.get(i); + double longToday = expmaLong.get(i); + double shortYesterday = expmaShort.get(i - 1); + double longYesterday = expmaLong.get(i - 1); + + // 金叉:昨日 short <= long,今日 short > long + if (shortYesterday <= longYesterday && shortToday > longToday) { + goldenCross = true; + crossDayIndex = i; + crossType = "黄金交叉"; + break; + } + // 死叉:昨日 short >= long,今日 short < long + if (shortYesterday >= longYesterday && shortToday < longToday) { + deathCross = true; + crossDayIndex = i; + crossType = "死亡交叉"; + break; + } + } + + if (goldenCross || deathCross) { + LocalDate crossDate = stockData.get(crossDayIndex).getDate(); + analysis.append("\n🔔 近期信号:") + .append(crossType) + .append("(").append(crossDate).append(")\n"); + if (goldenCross) { + analysis.append(" → 短期动能转强,可视为潜在买入信号。\n"); + if (currentLong > expmaLong.get(Math.max(0, crossDayIndex - 5))) { + analysis.append(" → 且长期EXPMA已走平或向上,信号可靠性较高。\n"); + } + } else { + analysis.append(" → 短期动能转弱,可视为潜在卖出信号。\n"); + } + } + + // 4. 支撑/阻力提示 + if (isBullish) { + double support = currentShort; + if (Math.abs(latestPrice - support) / support < 0.01) { // 价格接近短期EXPMA(1%内) + analysis.append("\n🛡️ 支撑观察:当前股价接近短期EXPMA(") + .append(String.format("%.2f", support)) + .append("),若在此企稳,可能延续升势。\n"); + } + } else if (!isBullish) { + double resistance = currentShort; + if (Math.abs(latestPrice - resistance) / resistance < 0.01) { + analysis.append("\n⚠️ 阻力观察:当前股价接近短期EXPMA(") + .append(String.format("%.2f", resistance)) + .append("),若遇阻回落,可能延续跌势。\n"); + } + } + + // 5. 总结建议 + analysis.append("\n📌 操作建议:\n"); + if (isBullish && shortUp) { + analysis.append(" • 多头趋势中,持股待涨;回调至短期EXPMA附近可考虑低吸。\n"); + } else if (!isBullish && !shortUp) { + analysis.append(" • 空头趋势中,避免抄底;反弹至短期EXPMA附近可考虑离场。\n"); + } else { + analysis.append(" • 市场方向不明,建议结合成交量或震荡指标(如KDJ)辅助决策。\n"); + } + analysis.append("\n❗ 注意:EXPMA为趋势跟踪指标,在横盘震荡市中易产生假信号,请勿单独依赖。"); + + return analysis.toString(); + } + + // 辅助方法:计算单周期 EXPMA + private static List calculateSingleExpma(List data, int period) { + List expma = new ArrayList<>(data.size()); + double alpha = 2.0 / (period + 1); + double value = 0.0; + + for (int i = 0; i < data.size(); i++) { + double price = data.get(i).getClosePrice(); + if (i == 0) { + // 初始值用 SMA(period) + double sum = 0; + int count = Math.min(period, data.size()); + for (int j = 0; j < count; j++) { + sum += data.get(j).getClosePrice(); + } + value = sum / count; + } else { + value = alpha * price + (1 - alpha) * value; + } + expma.add(value); + } + return expma; + } +}