7 changed files with 311 additions and 10 deletions
-
10src/main/java/com/deepchart/controller/IndexController.java
-
22src/main/java/com/deepchart/entity/MACDData.java
-
8src/main/java/com/deepchart/service/IndexService.java
-
14src/main/java/com/deepchart/service/impl/IndexServiceImpl.java
-
227src/main/java/com/deepchart/utils/MACDUtil.java
-
24src/main/java/com/deepchart/utils/Result.java
-
16src/main/java/com/deepchart/utils/StockDataUtil.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; } |
||||
|
} |
||||
@ -1,4 +1,12 @@ |
|||||
package com.deepchart.service; |
package com.deepchart.service; |
||||
|
|
||||
|
import com.deepchart.entity.StockDailyData; |
||||
|
import com.deepchart.entity.StockInfo; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
public interface IndexService { |
public interface IndexService { |
||||
|
List<StockDailyData> getStockData(StockInfo stock); |
||||
|
|
||||
|
String macd(List<StockDailyData> list); |
||||
} |
} |
||||
@ -1,14 +1,28 @@ |
|||||
package com.deepchart.service.impl; |
package com.deepchart.service.impl; |
||||
|
|
||||
|
import com.deepchart.entity.StockDailyData; |
||||
|
import com.deepchart.entity.StockInfo; |
||||
import com.deepchart.service.IndexService; |
import com.deepchart.service.IndexService; |
||||
|
import com.deepchart.utils.MACDUtil; |
||||
import com.deepchart.utils.StockDataUtil; |
import com.deepchart.utils.StockDataUtil; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
@Service |
@Service |
||||
public class IndexServiceImpl implements IndexService { |
public class IndexServiceImpl implements IndexService { |
||||
|
|
||||
@Autowired |
@Autowired |
||||
private StockDataUtil stockDataUtil; |
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); |
||||
|
} |
||||
} |
} |
||||
@ -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; |
||||
|
|
||||
|
// 计算DEA(DIF的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"); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue