You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

156 lines
5.4 KiB

2 weeks ago
  1. package com.deepchart.utils;
  2. import com.deepchart.entity.StockDailyData;
  3. import java.time.LocalDate;
  4. import java.util.*;
  5. public class MATools {
  6. public static class MAAnalysisResult {
  7. private String trend; // 趋势方向:上升 / 下降 / 震荡
  8. private String crossSignal; // 金叉 / 死叉 / 无信号
  9. private String maArrangement; // 多头排列 / 空头排列 / 均线粘合
  10. private double shortMA; // 短期均线值(如5日)
  11. private double midMA; // 中期均线值(如20日)
  12. private Double longMA; // 长期均线值(如30日,可能为 null)
  13. // Getters and Setters
  14. public String getTrend() { return trend; }
  15. public void setTrend(String trend) { this.trend = trend; }
  16. public String getCrossSignal() { return crossSignal; }
  17. public void setCrossSignal(String crossSignal) { this.crossSignal = crossSignal; }
  18. public String getMaArrangement() { return maArrangement; }
  19. public void setMaArrangement(String maArrangement) { this.maArrangement = maArrangement; }
  20. public double getShortMA() { return shortMA; }
  21. public void setShortMA(double shortMA) { this.shortMA = shortMA; }
  22. public double getMidMA() { return midMA; }
  23. public void setMidMA(double midMA) { this.midMA = midMA; }
  24. public Double getLongMA() { return longMA; }
  25. public void setLongMA(Double longMA) { this.longMA = longMA; }
  26. @Override
  27. public String toString() {
  28. return "MAAnalysisResult{" +
  29. "trend='" + trend + '\'' +
  30. ", crossSignal='" + crossSignal + '\'' +
  31. ", maArrangement='" + maArrangement + '\'' +
  32. ", shortMA=" + shortMA +
  33. ", midMA=" + midMA +
  34. ", longMA=" + longMA +
  35. '}';
  36. }
  37. }
  38. /**
  39. * 对股票日线数据进行MA指标分析
  40. * @param stockDataList 至少包含20个交易日的StockDailyData列表按时间升序排列
  41. * @return MA分析结果
  42. */
  43. public static MAAnalysisResult analyze(List<StockDailyData> stockDataList) {
  44. if (stockDataList == null || stockDataList.size() < 20) {
  45. throw new IllegalArgumentException("数据不足,至少需要20个交易日的收盘价");
  46. }
  47. // 提取收盘价(确保按时间顺序,最新在最后)
  48. List<Double> closePrices = new ArrayList<>();
  49. for (StockDailyData data : stockDataList) {
  50. closePrices.add(data.getClosePrice());
  51. }
  52. int n = closePrices.size();
  53. // 定义周期
  54. int shortPeriod = 5;
  55. int midPeriod = 20;
  56. int longPeriod = 30; // 因为只有40天数据,60日无法计算,改用30日
  57. // 计算MA(取最近一个值)
  58. double shortMA = calculateMA(closePrices, shortPeriod);
  59. double midMA = calculateMA(closePrices, midPeriod);
  60. Double longMA = n >= longPeriod ? calculateMA(closePrices, longPeriod) : null;
  61. MAAnalysisResult result = new MAAnalysisResult();
  62. result.setShortMA(shortMA);
  63. result.setMidMA(midMA);
  64. result.setLongMA(longMA);
  65. // 1. 趋势判断(基于短中长期均线相对位置)
  66. String trend = "震荡趋势";
  67. if (shortMA > midMA && (longMA == null || midMA > longMA)) {
  68. trend = "上升趋势";
  69. } else if (shortMA < midMA && (longMA == null || midMA < longMA)) {
  70. trend = "下降趋势";
  71. }
  72. result.setTrend(trend);
  73. // 2. 金叉/死叉判断(5日 vs 20日)
  74. String crossSignal = "无信号";
  75. // 需要前一日数据才能判断交叉,这里简化:仅用当前值判断是否刚发生交叉(实际应比较前后两日)
  76. // 更严谨的做法是保留前一天的MA值,但此处假设调用方只关心当前状态下的潜在信号
  77. // 我们采用“当前短线上穿中线”作为金叉近似判断(需注意这是简化版)
  78. if (shortMA > midMA) {
  79. // 检查昨日是否 short <= mid(需要倒数第2个MA值)
  80. if (n >= Math.max(shortPeriod, midPeriod) + 1) {
  81. double prevShortMA = calculateMA(closePrices.subList(0, n - 1), shortPeriod);
  82. double prevMidMA = calculateMA(closePrices.subList(0, n - 1), midPeriod);
  83. if (prevShortMA <= prevMidMA) {
  84. crossSignal = "金叉信号";
  85. }
  86. }
  87. } else if (shortMA < midMA) {
  88. if (n >= Math.max(shortPeriod, midPeriod) + 1) {
  89. double prevShortMA = calculateMA(closePrices.subList(0, n - 1), shortPeriod);
  90. double prevMidMA = calculateMA(closePrices.subList(0, n - 1), midPeriod);
  91. if (prevShortMA >= prevMidMA) {
  92. crossSignal = "死叉信号";
  93. }
  94. }
  95. }
  96. result.setCrossSignal(crossSignal);
  97. // 3. 均线排列
  98. String arrangement = "均线粘合";
  99. if (longMA != null) {
  100. if (shortMA > midMA && midMA > longMA) {
  101. arrangement = "多头排列";
  102. } else if (shortMA < midMA && midMA < longMA) {
  103. arrangement = "空头排列";
  104. }
  105. } else {
  106. // 无长期均线时,仅用短中判断
  107. if (Math.abs(shortMA - midMA) / midMA < 0.01) { // 差距小于1%
  108. arrangement = "均线粘合";
  109. } else if (shortMA > midMA) {
  110. arrangement = "短期强于中期";
  111. } else {
  112. arrangement = "短期弱于中期";
  113. }
  114. }
  115. result.setMaArrangement(arrangement);
  116. return result;
  117. }
  118. /**
  119. * 计算最近一期的简单移动平均值SMA
  120. * @param prices 收盘价列表时间升序最新在末尾
  121. * @param period 周期
  122. * @return 最新一期的MA值
  123. */
  124. private static double calculateMA(List<Double> prices, int period) {
  125. if (prices.size() < period) {
  126. throw new IllegalArgumentException("价格数据长度不足,无法计算" + period + "日均线");
  127. }
  128. double sum = 0.0;
  129. int n = prices.size();
  130. for (int i = n - period; i < n; i++) {
  131. sum += prices.get(i);
  132. }
  133. return sum / period;
  134. }
  135. }