Browse Source

11.18 22:22

完成MACD指标的插件
majun
majun 2 weeks ago
parent
commit
bf36f084b4
  1. 10
      src/main/java/com/deepchart/controller/IndexController.java
  2. 22
      src/main/java/com/deepchart/entity/MACDData.java
  3. 8
      src/main/java/com/deepchart/service/IndexService.java
  4. 14
      src/main/java/com/deepchart/service/impl/IndexServiceImpl.java
  5. 227
      src/main/java/com/deepchart/utils/MACDUtil.java
  6. 24
      src/main/java/com/deepchart/utils/Result.java
  7. 16
      src/main/java/com/deepchart/utils/StockDataUtil.java

10
src/main/java/com/deepchart/controller/IndexController.java

@ -1,13 +1,17 @@
package com.deepchart.controller;
import com.deepchart.entity.StockDailyData;
import com.deepchart.entity.StockInfo;
import com.deepchart.service.IndexService;
import com.deepchart.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping(value = "/api/index", produces = "application/json")
public class IndexController {
@ -16,7 +20,9 @@ public class IndexController {
private IndexService indexService;
@PostMapping("/macd")
public String macd(@RequestBody StockInfo stock) {
return "";
public Result macd(@RequestBody StockInfo stock) {
List<StockDailyData> list = indexService.getStockData(stock);
String result = indexService.macd(list);
return Result.success("success", result);
}
}

22
src/main/java/com/deepchart/entity/MACDData.java

@ -0,0 +1,22 @@
package com.deepchart.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.time.LocalDate;
/**
* MACD指标计算结果实体类
*/
@Data
@AllArgsConstructor
public class MACDData {
private LocalDate date; // 交易日日期
private double dif; // 差离值
private double dea; // 信号线
private double macdBar; // MACD柱状图
// 判断MACD柱颜色/绿
public boolean isRedBar() { return macdBar > 0; }
public boolean isGreenBar() { return macdBar < 0; }
}

8
src/main/java/com/deepchart/service/IndexService.java

@ -1,4 +1,12 @@
package com.deepchart.service;
import com.deepchart.entity.StockDailyData;
import com.deepchart.entity.StockInfo;
import java.util.List;
public interface IndexService {
List<StockDailyData> getStockData(StockInfo stock);
String macd(List<StockDailyData> list);
}

14
src/main/java/com/deepchart/service/impl/IndexServiceImpl.java

@ -1,14 +1,28 @@
package com.deepchart.service.impl;
import com.deepchart.entity.StockDailyData;
import com.deepchart.entity.StockInfo;
import com.deepchart.service.IndexService;
import com.deepchart.utils.MACDUtil;
import com.deepchart.utils.StockDataUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class IndexServiceImpl implements IndexService {
@Autowired
private StockDataUtil stockDataUtil;
@Override
public List<StockDailyData> getStockData(StockInfo stock) {
return stockDataUtil.getStockData(stock);
}
@Override
public String macd(List<StockDailyData> list) {
return MACDUtil.analyzeStock(list);
}
}

227
src/main/java/com/deepchart/utils/MACDUtil.java

@ -0,0 +1,227 @@
package com.deepchart.utils;
import com.deepchart.entity.MACDData;
import com.deepchart.entity.StockDailyData;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
/**
* MACD指标分析工具类
* 实现五大核心分析方法
*/
public class MACDUtil {
public static String analyzeStock(List<StockDailyData> prices) {
StringBuilder report = new StringBuilder();
List<MACDData> macdData = calculateMACD(prices);
// 1. 交叉信号分析
report.append("===== 交叉信号分析 =====\n");
analyzeCrossovers(macdData, report);
// 2. 柱状图动能分析
report.append("\n===== 柱状图动能分析 =====\n");
analyzeBarMomentum(macdData, report);
// 3. 零轴趋势判断
report.append("\n===== 零轴趋势判断 =====\n");
analyzeZeroAxisTrend(macdData, report);
// 4. 背离现象检测
report.append("\n===== 背离现象分析 =====\n");
analyzeDivergence(prices, macdData, report);
// 5. 参数调整建议
// report.append("\n===== 参数调整建议 =====\n");
// report.append(" 当前参数:EMA(12,26,9)\n");
// report.append("• 短线交易可调整为EMA(6,13,5)提升灵敏度\n");
// report.append("• 长线投资可调整为EMA(24,52,9)过滤噪音\n");
return report.toString();
}
// 核心MACD计算逻辑
private static List<MACDData> calculateMACD(List<StockDailyData> stockData) {
List<MACDData> result = new ArrayList<>();
double ema12;
double ema26;
double prevEma12 = 0.0;
double prevEma26 = 0.0;
double prevDea = 0.0;
boolean firstDay = true;
for (StockDailyData daily : stockData) {
double close = daily.getClosePrice();
LocalDate date = daily.getDate();
// 首日初始化
if (firstDay) {
firstDay = false;
MACDData data = new MACDData(date, 0.0, 0.0, 0.0);
result.add(data);
continue;
}
// 计算EMA(12)和EMA(26)
ema12 = calculateEMA(close, prevEma12, 12);
ema26 = calculateEMA(close, prevEma26, 26);
// 计算DIF
double dif = ema12 - ema26;
// 计算DEADIF的9日EMA
double dea = calculateEMA(dif, prevDea, 9);
// 计算MACD柱状图
double macdBar = 2 * (dif - dea);
// 保存当前值供下个交易日使用
prevEma12 = ema12;
prevEma26 = ema26;
prevDea = dea;
result.add(new MACDData(date, dif, dea, macdBar));
}
return result;
}
// 指数移动平均(EMA)计算公式
private static double calculateEMA(double currentPrice, double prevEMA, int period) {
return currentPrice * (2.0 / (period + 1)) + prevEMA * ((period - 1.0) / (period + 1));
}
// 金叉/死叉检测
private static void analyzeCrossovers(List<MACDData> macdData, StringBuilder report) {
int goldenCrossCount = 0;
int deathCrossCount = 0;
boolean aboveZeroGolden = false;
boolean belowZeroDeath = false;
for (int i = 1; i < macdData.size(); i++) {
MACDData prev = macdData.get(i-1);
MACDData curr = macdData.get(i);
// 金叉DIF上穿DEA
if (prev.getDif() < prev.getDea() && curr.getDif() > curr.getDea()) {
goldenCrossCount++;
if (curr.getDif() > 0) {
aboveZeroGolden = true;
report.append(String.format("• %tF:强势金叉信号(零轴上方),买入机会\n", curr.getDate()));
} else {
report.append(String.format("• %tF:弱势金叉信号(零轴下方),谨慎参与\n", curr.getDate()));
}
}
// 死叉DIF下穿DEA
else if (prev.getDif() > prev.getDea() && curr.getDif() < curr.getDea()) {
deathCrossCount++;
if (curr.getDif() < 0) {
belowZeroDeath = true;
report.append(String.format("• %tF:强势死叉信号(零轴下方),卖出警示\n", curr.getDate()));
} else {
report.append(String.format("• %tF:弱势死叉信号(零轴上方),部分止盈\n", curr.getDate()));
}
}
}
report.append("\n 交叉信号统计:\n");
report.append("• 近期出现金叉次数:").append(goldenCrossCount).append("次\n");
report.append("• 近期出现死叉次数:").append(deathCrossCount).append("次\n");
if (aboveZeroGolden) report.append("▶ 存在零轴上方的强势金叉,多头动能强劲\n");
if (belowZeroDeath) report.append("▶ 存在零轴下方的强势死叉,空头风险显著\n");
}
// 柱状图动能分析
private static void analyzeBarMomentum(List<MACDData> macdData, StringBuilder report) {
if (macdData.size() < 5) return;
// 获取最近5个交易日的柱状图数据
MACDData curr = macdData.get(macdData.size() - 1);
MACDData prev1 = macdData.get(macdData.size() - 2);
MACDData prev2 = macdData.get(macdData.size() - 3);
report.append(" 最新交易日数据:\n");
report.append(String.format("• MACD柱状图数值:%.4f (%s)\n",
curr.getMacdBar(), curr.isRedBar() ? "红柱" : "绿柱"));
// 红柱分析零轴上方
if (curr.isRedBar()) {
report.append("▶ 当前处于多头市场(红柱区域)\n");
if (curr.getMacdBar() > prev1.getMacdBar() && prev1.getMacdBar() > prev2.getMacdBar()) {
report.append("▶ 红柱连续放大(3日),上涨动能加速\n");
} else if (curr.getMacdBar() < prev1.getMacdBar()) {
report.append("▶ 红柱开始缩短,警惕上涨动能衰减\n");
}
}
// 绿柱分析零轴下方
else {
report.append("▶ 当前处于空头市场(绿柱区域)\n");
if (curr.getMacdBar() < prev1.getMacdBar() && prev1.getMacdBar() < prev2.getMacdBar()) {
report.append("▶ 绿柱连续放大(3日),下跌动能加速\n");
} else if (curr.getMacdBar() > prev1.getMacdBar()) {
report.append("▶ 绿柱开始缩短,空头力量减弱\n");
}
}
}
// 零轴趋势分析
private static void analyzeZeroAxisTrend(List<MACDData> macdData, StringBuilder report) {
int aboveZeroDays = 0;
int belowZeroDays = 0;
for (MACDData data : macdData) {
if (data.getDif() > 0) aboveZeroDays++;
else belowZeroDays++;
}
double aboveRatio = 100.0 * aboveZeroDays / macdData.size();
report.append(String.format(" 历史交易日统计(共%d天):\n", macdData.size()));
report.append(String.format("• DIF在零轴上方天数:%d天 (占比%.1f%%)\n", aboveZeroDays, aboveRatio));
report.append(String.format("• DIF在零轴下方天数:%d天 (占比%.1f%%)\n", belowZeroDays, 100 - aboveRatio));
MACDData last = macdData.get(macdData.size() - 1);
if (last.getDif() > 0 && last.getDea() > 0) {
report.append("▶ 当前趋势:多头市场(DIF与DEA均在零轴上方)\n");
} else if (last.getDif() < 0 && last.getDea() < 0) {
report.append("▶ 当前趋势:空头市场(DIF与DEA均在零轴下方)\n");
} else {
report.append("▶ 当前趋势:震荡行情(多空分歧明显)\n");
}
}
// 背离分析
private static void analyzeDivergence(List<StockDailyData> prices, List<MACDData> macdData, StringBuilder report) {
boolean hasTopDivergence = false;
boolean hasBottomDivergence = false;
// 寻找最近30个交易日内的背离
int lookback = Math.min(30, prices.size() - 1);
for (int i = prices.size() - 2; i > prices.size() - lookback; i--) {
// 顶背离检测股价新高但MACD低点
if (prices.get(i).getClosePrice() > prices.get(i+1).getClosePrice() &&
prices.get(i).getClosePrice() > prices.get(i-1).getClosePrice() &&
macdData.get(i).getDif() < macdData.get(i+1).getDif() &&
macdData.get(i).getDif() < macdData.get(i-1).getDif()) {
hasTopDivergence = true;
report.append(String.format("• %tF:检测到顶背离(股价新高但MACD走弱),警示下跌风险\n",
prices.get(i).getDate()));
}
// 底背离检测股价新低但MACD高点
else if (prices.get(i).getClosePrice() < prices.get(i+1).getClosePrice() &&
prices.get(i).getClosePrice() < prices.get(i-1).getClosePrice() &&
macdData.get(i).getDif() > macdData.get(i+1).getDif() &&
macdData.get(i).getDif() > macdData.get(i-1).getDif()) {
hasBottomDivergence = true;
report.append(String.format("• %tF:检测到底背离(股价新低但MACD走强),预示反弹机会\n",
prices.get(i).getDate()));
}
}
if (!hasTopDivergence) report.append("• 近期未检测到显著顶背离现象\n");
if (!hasBottomDivergence) report.append("• 近期未检测到显著底背离现象\n");
}
}

24
src/main/java/com/deepchart/utils/Result.java

@ -0,0 +1,24 @@
package com.deepchart.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 请求响应返回结果工具类
* @param <T>
*/
@Data
@AllArgsConstructor
public class Result<T> {
private Integer code; //状态码
private String msg; //返回信息
private T data; //返回数据
public static <T> Result<T> success(String message, T data) {
return new Result<>(200, message, data);
}
public static <T> Result<T> error(Integer resultCode, String message) {
return new Result<>(resultCode, message, null);
}
}

16
src/main/java/com/deepchart/utils/StockDataUtil.java

@ -29,7 +29,7 @@ public class StockDataUtil {
public List<StockDailyData> getStockData(StockInfo stock) {
List<List<String>> kLine20 = List.of();
List<List<Object>> kLine20 = List.of();
try {
// 1. 构建请求URL
@ -72,7 +72,7 @@ public class StockDataUtil {
Map<String, Object> jsonResponse = objectMapper.readValue(response.toString(), Map.class);
Map<String, Object> data = (Map<String, Object>) jsonResponse.get("data");
Map<String, Object> brain = (Map<String, Object>) data.get("Brain");
kLine20 = (List<List<String>>) brain.get("KLine20");
kLine20 = (List<List<Object>>) brain.get("KLine20");
}
}
@ -85,13 +85,13 @@ public class StockDataUtil {
List<StockDailyData> list = new ArrayList<>();
for (List<String> v : kLine20) {
for (List<Object> v : kLine20) {
StockDailyData s = new StockDailyData();
s.setDate(LocalDate.parse(v.get(0), DateTimeFormatter.ofPattern("yyyy/MM/dd")));
s.setOpenPrice(Double.parseDouble(v.get(1)));
s.setClosePrice(Double.parseDouble(v.get(2)));
s.setLowPrice(Double.parseDouble(v.get(3)));
s.setHighPrice(Double.parseDouble(v.get(4)));
s.setDate(LocalDate.parse((String) v.get(0), DateTimeFormatter.ofPattern("yyyy/MM/dd")));
s.setOpenPrice((double)v.get(1));
s.setClosePrice((double)v.get(2));
s.setLowPrice((double)v.get(3));
s.setHighPrice((double)v.get(4));
list.add(s);
}

Loading…
Cancel
Save