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; } }