1 changed files with 157 additions and 0 deletions
@ -0,0 +1,157 @@ |
|||||
|
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; |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue