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.

107 lines
3.9 KiB

  1. package com.deepchart.utils;
  2. import com.deepchart.entity.KDJData;
  3. import com.deepchart.entity.StockDailyData;
  4. import java.util.*;
  5. /**
  6. * KD指标分析工具类
  7. */
  8. public class KDUtil {
  9. private static final int N = 9; // 默认周期长度
  10. private static final double SMOOTH_K = 1.0 / 3; // K 平滑系数
  11. private static final double SMOOTH_D = 1.0 / 3; // D 平滑系数
  12. private static List<KDJData> calculateKD(List<StockDailyData> dataList) {
  13. if (dataList == null || dataList.size() < N) {
  14. throw new IllegalArgumentException("数据不足,至少需要" + N + "条记录");
  15. }
  16. List<KDJData> kdList = new ArrayList<>();
  17. Deque<Double> highs = new ArrayDeque<>(N);
  18. Deque<Double> lows = new ArrayDeque<>(N);
  19. Double prevK = null;
  20. Double prevD = null;
  21. for (int i = 0; i < dataList.size(); i++) {
  22. StockDailyData data = dataList.get(i);
  23. highs.offerLast(data.getHighPrice());
  24. lows.offerLast(data.getLowPrice());
  25. if (highs.size() > N) {
  26. highs.pollFirst();
  27. lows.pollFirst();
  28. }
  29. if (i >= N - 1) {
  30. double hhv = Collections.max(highs);
  31. double llv = Collections.min(lows);
  32. double rsv = ((data.getClosePrice() - llv) / (hhv - llv)) * 100;
  33. double kValue = (prevK == null ? rsv : prevK * (1 - SMOOTH_K) + rsv * SMOOTH_K);
  34. double dValue = (prevD == null ? kValue : prevD * (1 - SMOOTH_D) + kValue * SMOOTH_D);
  35. kdList.add(new KDJData(data.getDate(), rsv, kValue, dValue, 0));
  36. prevK = kValue;
  37. prevD = dValue;
  38. } else {
  39. kdList.add(new KDJData(data.getDate(), 0, 0, 0, 0)); // 占位符
  40. }
  41. }
  42. return kdList;
  43. }
  44. public static String generateReport(List<StockDailyData> stockDataList) {
  45. List<KDJData> kdList = calculateKD(stockDataList);
  46. StringBuilder report = new StringBuilder();
  47. report.append("📈 股票KD指标分析报告\n");
  48. report.append("=======================\n");
  49. if (kdList.isEmpty()) {
  50. report.append("❌ 数据不足,无法生成分析。\n");
  51. return report.toString();
  52. }
  53. KDJData latest = kdList.get(kdList.size() - 1);
  54. KDJData previous = kdList.size() >= 2 ? kdList.get(kdList.size() - 2) : null;
  55. double k = latest.getK();
  56. double d = latest.getD();
  57. // 判断超买或超卖
  58. if (k > 80 && d > 80) {
  59. report.append("🔴 当前处于【超买】状态,注意回调风险。\n");
  60. } else if (k < 20 && d < 20) {
  61. report.append("🟢 当前处于【超卖】状态,关注反弹机会。\n");
  62. } else {
  63. report.append("🟡 当前处于中性区域,暂无明显买卖信号。\n");
  64. }
  65. // 判断金叉/死叉
  66. if (previous != null) {
  67. boolean isGoldenCross = previous.getK() <= previous.getD() && k > d;
  68. boolean isDeathCross = previous.getK() >= previous.getD() && k < d;
  69. if (isGoldenCross && d < 30) {
  70. report.append("✨ 出现金叉(K线上穿D线),特别是在低位,是潜在买入信号。\n");
  71. } else if (isDeathCross && d > 70) {
  72. report.append("⚠️ 出现死叉(K线下穿D线),尤其是在高位,建议谨慎卖出。\n");
  73. }
  74. }
  75. // 多空趋势判断
  76. if (k > 50 && d > 50 && k > d) {
  77. report.append("⬆️ 当前为多头趋势,可考虑持股观望。\n");
  78. } else if (k < 50 && d < 50 && k < d) {
  79. report.append("⬇️ 当前为空头趋势,宜控制仓位。\n");
  80. } else {
  81. report.append("🔄 当前为震荡行情,短线操作更适宜。\n");
  82. }
  83. return report.toString();
  84. }
  85. }