Browse Source

MA指标与CCI指标

liruiqiang
liruiqiang 1 week ago
parent
commit
14cbc45601
  1. 14
      src/main/java/com/deepchart/controller/IndexController.java
  2. 4
      src/main/java/com/deepchart/service/IndexService.java
  3. 12
      src/main/java/com/deepchart/service/impl/IndexServiceImpl.java
  4. 118
      src/main/java/com/deepchart/utils/CCIUtil.java
  5. 157
      src/main/java/com/deepchart/utils/MATools.java
  6. 102
      src/main/java/com/deepchart/utils/MAUtil.java

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

@ -25,4 +25,18 @@ public class IndexController {
String result = indexService.macd(list); String result = indexService.macd(list);
return Result.success("success", result); return Result.success("success", result);
} }
@PostMapping("/ma")
public Result ma(@RequestBody StockInfo stock) {
List<StockDailyData> list = indexService.getStockData(stock);
String result = indexService.ma(list);
return Result.success("success", result);
}
@PostMapping("/cci")
public Result cci(@RequestBody StockInfo stock) {
List<StockDailyData> list = indexService.getStockData(stock);
String result = indexService.cci(list);
return Result.success("success", result);
}
} }

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

@ -9,4 +9,8 @@ public interface IndexService {
List<StockDailyData> getStockData(StockInfo stock); List<StockDailyData> getStockData(StockInfo stock);
String macd(List<StockDailyData> list); String macd(List<StockDailyData> list);
String ma(List<StockDailyData> list);
String cci(List<StockDailyData> list);
} }

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

@ -3,7 +3,9 @@ package com.deepchart.service.impl;
import com.deepchart.entity.StockDailyData; import com.deepchart.entity.StockDailyData;
import com.deepchart.entity.StockInfo; import com.deepchart.entity.StockInfo;
import com.deepchart.service.IndexService; import com.deepchart.service.IndexService;
import com.deepchart.utils.CCIUtil;
import com.deepchart.utils.MACDUtil; import com.deepchart.utils.MACDUtil;
import com.deepchart.utils.MAUtil;
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;
@ -25,4 +27,14 @@ public class IndexServiceImpl implements IndexService {
public String macd(List<StockDailyData> list) { public String macd(List<StockDailyData> list) {
return MACDUtil.analyzeStock(list); return MACDUtil.analyzeStock(list);
} }
@Override
public String ma(List<StockDailyData> list) {
return MAUtil.analyzeMA(list);
}
@Override
public String cci(List<StockDailyData> list) {
return CCIUtil.analyzeCCI(list);
}
} }

118
src/main/java/com/deepchart/utils/CCIUtil.java

@ -0,0 +1,118 @@
package com.deepchart.utils;
import com.deepchart.entity.StockDailyData;
import java.util.ArrayList;
import java.util.List;
public class CCIUtil {
private static final int CCI_PERIOD = 14; // CCI默认周期
public static String analyzeCCI(List<StockDailyData> stockDataList) {
if (stockDataList == null || stockDataList.size() < CCI_PERIOD) {
return "CCI分析失败:数据不足,至少需要" + CCI_PERIOD + "个交易日";
}
// 1. 计算CCI指标值
double currentCCI = calculateCCI(stockDataList);
double prevCCI = calculateCCI(stockDataList.subList(0, stockDataList.size() - 1));
// 2. 计算20日均线用于判断价格位置
double twentyDayMA = calculateMA(stockDataList, 20);
double currentClose = stockDataList.get(stockDataList.size() - 1).getClosePrice();
String pricePosition = (currentClose > twentyDayMA) ? "20日均线之上" : "20日均线之下";
// 3. 判断信号类型
String signalType = "无信号";
String signalDescription = "CCI值未触发标准信号";
if (prevCCI <= -100 && currentCCI > -100) {
signalType = "超卖反弹买入信号";
signalDescription = "CCI从超卖区上穿-100线,进入常态区间";
} else if (prevCCI >= 100 && currentCCI < 100) {
signalType = "超买回调卖出信号";
signalDescription = "CCI从超买区下穿+100线,进入常态区间";
} else if (currentCCI > 100) {
signalType = "强势上涨信号";
signalDescription = "CCI持续在+100上方,处于强势上涨趋势";
} else if (currentCCI < -100) {
signalType = "强势下跌信号";
signalDescription = "CCI持续在-100下方,处于强势下跌趋势";
}
// 4. 生成分析报告
return generateReport(currentCCI, signalType, signalDescription, pricePosition, currentClose, twentyDayMA);
}
private static double calculateCCI(List<StockDailyData> stockDataList) {
int n = stockDataList.size();
List<Double> typicalPrices = new ArrayList<>();
// 计算典型价格 (H+L+C)/3
for (StockDailyData data : stockDataList) {
double typicalPrice = (data.getHighPrice() + data.getLowPrice() + data.getClosePrice()) / 3;
typicalPrices.add(typicalPrice);
}
// 计算N期平均典型价格
double avgTypicalPrice = calculateMAFromDoubleList(typicalPrices, CCI_PERIOD);
// 计算平均偏差
double sumAbsDev = 0.0;
for (int i = n - CCI_PERIOD; i < n; i++) {
sumAbsDev += Math.abs(typicalPrices.get(i) - avgTypicalPrice);
}
double avgDev = sumAbsDev / CCI_PERIOD;
// 计算CCI
return (typicalPrices.get(n - 1) - avgTypicalPrice) / (0.015 * avgDev);
}
private static double calculateMA(List<StockDailyData> stockDataList, int period) {
if (stockDataList.size() < period) {
throw new IllegalArgumentException("数据不足,无法计算" + period + "日均线");
}
double sum = 0.0;
for (int i = stockDataList.size() - period; i < stockDataList.size(); i++) {
sum += stockDataList.get(i).getClosePrice();
}
return sum / period;
}
private static double calculateMAFromDoubleList(List<Double> prices, int period) {
if (prices.size() < period) {
throw new IllegalArgumentException("数据不足,无法计算" + period + "日均线");
}
double sum = 0.0;
for (int i = prices.size() - period; i < prices.size(); i++) {
sum += prices.get(i);
}
return sum / period;
}
private static String generateReport(double cciValue, String signalType, String signalDescription,
String pricePosition, double currentClose, double twentyDayMA) {
return String.format(
"CCI分析报告(14日周期)\n" +
"当前CCI值: %.1f | 信号类型: %s | 信号描述: %s\n" +
"价格位置: %s | 当前价格: %.2f | 20日均线: %.2f\n" +
"核心逻辑: %s",
cciValue, signalType, signalDescription,
pricePosition, currentClose, twentyDayMA,
getSignalLogic(signalType, cciValue)
);
}
private static String getSignalLogic(String signalType, double cciValue) {
if (signalType.contains("买入信号")) {
return "CCI上穿-100线 + 价格站上20日均线 + 市场情绪从超卖转为常态";
} else if (signalType.contains("卖出信号")) {
return "CCI下穿+100线 + 价格回调至常态区间 + 市场情绪从超买转为常态";
} else if (signalType.contains("强势上涨")) {
return "CCI持续在+100以上,趋势强劲,应持有多头仓位";
} else if (signalType.contains("强势下跌")) {
return "CCI持续在-100以下,趋势疲软,应持币观望";
}
return "CCI在常态区间(-100~100),无明确信号";
}
}

157
src/main/java/com/deepchart/utils/MATools.java

@ -1,157 +0,0 @@
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<StockDailyData> stockDataList) {
if (stockDataList == null || stockDataList.size() < 20) {
throw new IllegalArgumentException("数据不足,至少需要20个交易日的收盘价");
}
// 提取收盘价确保按时间顺序最新在最后
List<Double> 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<Double> 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;
}
}

102
src/main/java/com/deepchart/utils/MAUtil.java

@ -0,0 +1,102 @@
package com.deepchart.utils;
import com.deepchart.entity.StockDailyData;
import java.util.*;
public class MAUtil {
// 周期定义短期5日中期20日长期40日
private static final int SHORT_PERIOD = 5;
private static final int MID_PERIOD = 20;
private static final int LONG_PERIOD = 40;
/**
* 对股票日线数据进行MA指标分析返回JSON格式的中文字符串结果
* @param stockDataList 至少包含40个交易日的StockDailyData列表按时间升序排列
* @return JSON格式的分析结果字符串全中文内容
*/
public static String analyzeMA(List<StockDailyData> stockDataList) {
if (stockDataList == null || stockDataList.size() < LONG_PERIOD) {
return buildJsonResult("错误", "无信号", "数据不足,至少需要40个交易日", 0.0, 0.0, null);
}
List<Double> closePrices = new ArrayList<>();
for (StockDailyData data : stockDataList) {
closePrices.add(data.getClosePrice());
}
int n = closePrices.size(); // 应为40
double shortMA = calculateMA(closePrices, SHORT_PERIOD);
double midMA = calculateMA(closePrices, MID_PERIOD);
Double longMA = calculateMA(closePrices, LONG_PERIOD);
// 1. 趋势判断
String trend = "震荡趋势";
if (shortMA > midMA && midMA > longMA) {
trend = "上升趋势";
} else if (shortMA < midMA && midMA < longMA) {
trend = "下降趋势";
}
// 2. 金叉/死叉判断5日上穿20日为金叉下穿为死叉
String crossSignal = "无交叉信号";
if (n >= Math.max(SHORT_PERIOD, MID_PERIOD) + 1) {
double prevShortMA = calculateMA(closePrices.subList(0, n - 1), SHORT_PERIOD);
double prevMidMA = calculateMA(closePrices.subList(0, n - 1), MID_PERIOD);
if (prevShortMA <= prevMidMA && shortMA > midMA) {
crossSignal = "金叉信号";
} else if (prevShortMA >= prevMidMA && shortMA < midMA) {
crossSignal = "死叉信号";
}
}
// 3. 均线排列状态
String arrangement = "均线粘合";
if (Math.abs(shortMA - midMA) / midMA < 0.005 && Math.abs(midMA - longMA) / longMA < 0.005) {
arrangement = "均线粘合";
} else if (shortMA > midMA && midMA > longMA) {
arrangement = "多头排列";
} else if (shortMA < midMA && midMA < longMA) {
arrangement = "空头排列";
}
return buildJsonResult(trend, crossSignal, arrangement, shortMA, midMA, longMA);
}
private static double calculateMA(List<Double> 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;
}
// 手动构建轻量级 JSON 字符串全中文值
private static String buildJsonResult(String trend, String crossSignal, String arrangement,
double shortMA, double midMA, Double longMA) {
StringBuilder sb = new StringBuilder();
sb.append("趋势:").append(escapeJson(trend)).append(",");
sb.append("交叉信号:").append(escapeJson(crossSignal)).append(",");
sb.append("均线排列:").append(escapeJson(arrangement)).append(",");
sb.append("五日均线:").append(String.format("%.4f", shortMA)).append(",");
sb.append("二十日均线:").append(String.format("%.4f", midMA)).append(",");
if (longMA != null) {
sb.append("四十日均线:").append(String.format("%.4f", longMA));
} else {
sb.append("四十日均线:无");
}
return sb.toString();
}
private static String escapeJson(String str) {
if (str == null) return "";
return str.replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
}
}
Loading…
Cancel
Save