diff --git a/src/main/java/com/deepchart/utils/MATools.java b/src/main/java/com/deepchart/utils/MATools.java new file mode 100644 index 0000000..bea7e6f --- /dev/null +++ b/src/main/java/com/deepchart/utils/MATools.java @@ -0,0 +1,157 @@ +package com.deepchart.utils; + +import com.deepchart.entity.StockDailyData; + +import java.time.LocalDate; +import java.util.*; + +public class MATools { + + public static class MAAnalysisResult { + private String trend; // 趋势方向:上升 / 下降 / 震荡 + private String crossSignal; // 金叉 / 死叉 / 无信号 + private String maArrangement; // 多头排列 / 空头排列 / 均线粘合 + private double shortMA; // 短期均线值(如5日) + private double midMA; // 中期均线值(如20日) + private Double longMA; // 长期均线值(如30日,可能为 null) + + // Getters and Setters + public String getTrend() { return trend; } + public void setTrend(String trend) { this.trend = trend; } + + public String getCrossSignal() { return crossSignal; } + public void setCrossSignal(String crossSignal) { this.crossSignal = crossSignal; } + + public String getMaArrangement() { return maArrangement; } + public void setMaArrangement(String maArrangement) { this.maArrangement = maArrangement; } + + public double getShortMA() { return shortMA; } + public void setShortMA(double shortMA) { this.shortMA = shortMA; } + + public double getMidMA() { return midMA; } + public void setMidMA(double midMA) { this.midMA = midMA; } + + public Double getLongMA() { return longMA; } + public void setLongMA(Double longMA) { this.longMA = longMA; } + + @Override + public String toString() { + return "MAAnalysisResult{" + + "trend='" + trend + '\'' + + ", crossSignal='" + crossSignal + '\'' + + ", maArrangement='" + maArrangement + '\'' + + ", shortMA=" + shortMA + + ", midMA=" + midMA + + ", longMA=" + longMA + + '}'; + } + } + + /** + * 对股票日线数据进行MA指标分析 + * @param stockDataList 至少包含20个交易日的StockDailyData列表(按时间升序排列) + * @return MA分析结果 + */ + public static MAAnalysisResult analyze(List stockDataList) { + if (stockDataList == null || stockDataList.size() < 20) { + throw new IllegalArgumentException("数据不足,至少需要20个交易日的收盘价"); + } + + // 提取收盘价(确保按时间顺序,最新在最后) + List closePrices = new ArrayList<>(); + for (StockDailyData data : stockDataList) { + closePrices.add(data.getClosePrice()); + } + + int n = closePrices.size(); + + // 定义周期 + int shortPeriod = 5; + int midPeriod = 20; + int longPeriod = 30; // 因为只有40天数据,60日无法计算,改用30日 + + // 计算MA(取最近一个值) + double shortMA = calculateMA(closePrices, shortPeriod); + double midMA = calculateMA(closePrices, midPeriod); + Double longMA = n >= longPeriod ? calculateMA(closePrices, longPeriod) : null; + + MAAnalysisResult result = new MAAnalysisResult(); + result.setShortMA(shortMA); + result.setMidMA(midMA); + result.setLongMA(longMA); + + // 1. 趋势判断(基于短中长期均线相对位置) + String trend = "震荡趋势"; + if (shortMA > midMA && (longMA == null || midMA > longMA)) { + trend = "上升趋势"; + } else if (shortMA < midMA && (longMA == null || midMA < longMA)) { + trend = "下降趋势"; + } + result.setTrend(trend); + + // 2. 金叉/死叉判断(5日 vs 20日) + String crossSignal = "无信号"; + // 需要前一日数据才能判断交叉,这里简化:仅用当前值判断是否刚发生交叉(实际应比较前后两日) + // 更严谨的做法是保留前一天的MA值,但此处假设调用方只关心当前状态下的潜在信号 + // 我们采用“当前短线上穿中线”作为金叉近似判断(需注意这是简化版) + if (shortMA > midMA) { + // 检查昨日是否 short <= mid(需要倒数第2个MA值) + if (n >= Math.max(shortPeriod, midPeriod) + 1) { + double prevShortMA = calculateMA(closePrices.subList(0, n - 1), shortPeriod); + double prevMidMA = calculateMA(closePrices.subList(0, n - 1), midPeriod); + if (prevShortMA <= prevMidMA) { + crossSignal = "金叉信号"; + } + } + } else if (shortMA < midMA) { + if (n >= Math.max(shortPeriod, midPeriod) + 1) { + double prevShortMA = calculateMA(closePrices.subList(0, n - 1), shortPeriod); + double prevMidMA = calculateMA(closePrices.subList(0, n - 1), midPeriod); + if (prevShortMA >= prevMidMA) { + crossSignal = "死叉信号"; + } + } + } + result.setCrossSignal(crossSignal); + + // 3. 均线排列 + String arrangement = "均线粘合"; + if (longMA != null) { + if (shortMA > midMA && midMA > longMA) { + arrangement = "多头排列"; + } else if (shortMA < midMA && midMA < longMA) { + arrangement = "空头排列"; + } + } else { + // 无长期均线时,仅用短中判断 + if (Math.abs(shortMA - midMA) / midMA < 0.01) { // 差距小于1% + arrangement = "均线粘合"; + } else if (shortMA > midMA) { + arrangement = "短期强于中期"; + } else { + arrangement = "短期弱于中期"; + } + } + result.setMaArrangement(arrangement); + + return result; + } + + /** + * 计算最近一期的简单移动平均值(SMA) + * @param prices 收盘价列表(时间升序,最新在末尾) + * @param period 周期 + * @return 最新一期的MA值 + */ + private static double calculateMA(List prices, int period) { + if (prices.size() < period) { + throw new IllegalArgumentException("价格数据长度不足,无法计算" + period + "日均线"); + } + double sum = 0.0; + int n = prices.size(); + for (int i = n - period; i < n; i++) { + sum += prices.get(i); + } + return sum / period; + } +} \ No newline at end of file