Compare commits

...

3 Commits

Author SHA1 Message Date
majun 47ffb8deef 11.24 11:08 5 days ago
majun bf36f084b4 11.18 22:22 1 week ago
majun 423959272a 11.18 16:50 2 weeks ago
  1. 19
      pom.xml
  2. 125
      src/main/java/com/deepchart/Main.java
  3. 27
      src/main/java/com/deepchart/StockApiDemo.java
  4. 39
      src/main/java/com/deepchart/controller/IndexController.java
  5. 19
      src/main/java/com/deepchart/entity/KDJData.java
  6. 22
      src/main/java/com/deepchart/entity/MACDData.java
  7. 18
      src/main/java/com/deepchart/entity/StockDailyData.java
  8. 9
      src/main/java/com/deepchart/entity/StockInfo.java
  9. 14
      src/main/java/com/deepchart/service/IndexService.java
  10. 34
      src/main/java/com/deepchart/service/impl/IndexServiceImpl.java
  11. 102
      src/main/java/com/deepchart/utils/KDJUtil.java
  12. 108
      src/main/java/com/deepchart/utils/KDUtil.java
  13. 227
      src/main/java/com/deepchart/utils/MACDUtil.java
  14. 91
      src/main/java/com/deepchart/utils/RSIUtil.java
  15. 24
      src/main/java/com/deepchart/utils/Result.java
  16. 110
      src/main/java/com/deepchart/utils/StockDataUtil.java

19
pom.xml

@ -33,23 +33,18 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.22.0</version> <!-- 或更高版本,如2.13.0等 -->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>

125
src/main/java/com/deepchart/Main.java

@ -0,0 +1,125 @@
package com.deepchart;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.tools.FunctionDefinition;
import com.alibaba.dashscope.tools.ToolCallBase;
import com.alibaba.dashscope.tools.ToolCallFunction;
import com.alibaba.dashscope.tools.ToolFunction;
import com.alibaba.dashscope.utils.JsonUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class Main {
// 若使用新加坡地域的模型请释放下列注释
// static {Constants.baseHttpApiUrl="https://dashscope-intl.aliyuncs.com/api/v1";}
/**
* 第一步定义工具的本地实现
* @param arguments 模型传入的包含工具所需参数的JSON字符串
* @return 工具执行后的结果字符串
*/
public static String getCurrentWeather(String arguments) {
try {
// 模型提供的参数是JSON格式的需要我们手动解析
ObjectMapper objectMapper = new ObjectMapper();
JsonNode argsNode = objectMapper.readTree(arguments);
String location = argsNode.get("location").asText();
// 用随机结果来模拟真实的API调用或业务逻辑
List<String> weatherConditions = Arrays.asList("晴天", "多云", "雨天");
String randomWeather = weatherConditions.get(new Random().nextInt(weatherConditions.size()));
return location + "今天是" + randomWeather + "。";
} catch (Exception e) {
// 异常处理确保程序健壮性
return "无法解析地点参数。";
}
}
public static void main(String[] args) {
try {
// 第二步向模型描述注册我们的工具
String weatherParamsSchema =
"{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"城市或县区,比如北京市、杭州市、余杭区等。\"}},\"required\":[\"location\"]}";
FunctionDefinition weatherFunction = FunctionDefinition.builder()
.name("get_current_weather") // 工具的唯一标识名必须与本地实现对应
.description("当你想查询指定城市的天气时非常有用。") // 清晰的描述能帮助模型更好地决定何时使用该工具
.parameters(JsonUtils.parseString(weatherParamsSchema).getAsJsonObject())
.build();
Generation gen = new Generation();
String userInput = "北京天气咋样";
List<Message> messages = new ArrayList<>();
messages.add(Message.builder().role(Role.USER.getValue()).content(userInput).build());
// 第四步首次调用模型将用户的请求和我们定义好的工具列表一同发送给模型
GenerationParam param = GenerationParam.builder()
.model("qwen-plus") // 指定需要调用的模型
.apiKey("sk-5bf09a590daf408cb1126c2c6684e982") // 从环境变量中获取API Key
.messages(messages) // 传入当前的对话历史
.tools(Arrays.asList(ToolFunction.builder().function(weatherFunction).build())) // 传入可用的工具列表
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.build();
GenerationResult result = gen.call(param);
Message assistantOutput = result.getOutput().getChoices().get(0).getMessage();
messages.add(assistantOutput); // 将模型的首次回复也加入到对话历史中
// 第五步检查模型的回复判断它是否请求调用工具
if (assistantOutput.getToolCalls() == null || assistantOutput.getToolCalls().isEmpty()) {
// 情况A模型没有调用工具而是直接给出了回答
System.out.println("无需调用天气查询工具,直接回复:" + assistantOutput.getContent());
} else {
// 情况B模型决定调用工具
// 使用 while 循环可以处理模型连续调用多次工具的场景
while (assistantOutput.getToolCalls() != null && !assistantOutput.getToolCalls().isEmpty()) {
ToolCallBase toolCall = assistantOutput.getToolCalls().get(0);
// 从模型的回复中解析出工具调用的具体信息要调用的函数名参数
ToolCallFunction functionCall = (ToolCallFunction) toolCall;
String funcName = functionCall.getFunction().getName();
String arguments = functionCall.getFunction().getArguments();
System.out.println("正在调用工具 [" + funcName + "],参数:" + arguments);
// 根据工具名在本地执行对应的Java方法
String toolResult = getCurrentWeather(arguments);
// 构造一个 role "tool" 的消息其中包含工具的执行结果
Message toolMessage = Message.builder()
.role("tool")
.toolCallId(toolCall.getId())
.content(toolResult)
.build();
System.out.println("工具返回:" + toolMessage.getContent());
messages.add(toolMessage); // 将工具的返回结果也加入到对话历史中
// 第六步再次调用模型
param.setMessages(messages);
result = gen.call(param);
assistantOutput = result.getOutput().getChoices().get(0).getMessage();
messages.add(assistantOutput);
}
// 第七步打印模型经过总结后生成的最终回复
System.out.println("助手最终回复:" + assistantOutput.getContent());
}
} catch (NoApiKeyException | InputRequiredException e) {
System.err.println("错误: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}

27
src/main/java/com/deepchart/StockApiDemo.java

@ -0,0 +1,27 @@
package com.deepchart;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class StockApiDemo {
public static void main(String[] args) throws Exception {
String url = "https://www.stockapi.com.cn/v1/quota/macd2";
// 构建参数
String params = "code=" + URLEncoder.encode("002131", StandardCharsets.UTF_8) +
"&cycle=9&startDate=2025-10-10&endDate=2025-10-15&longCycle=26&shortCycle=12&calculationCycle=100";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url + "?" + params))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(data -> System.out.println("日线行情数据:" + data))
.join();
}
}

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

@ -1,10 +1,49 @@
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 {
@Autowired
private IndexService indexService;
@PostMapping("/macd")
public Result macd(@RequestBody StockInfo stock) {
List<StockDailyData> list = indexService.getStockData(stock);
String result = indexService.macd(list);
return Result.success("success", result);
}
@PostMapping("/kdj")
public Result kdj(@RequestBody StockInfo stock) {
List<StockDailyData> list = indexService.getStockData(stock);
String result = indexService.kdj(list);
return Result.success("success", result);
}
@PostMapping("/kd")
public Result kd(@RequestBody StockInfo stock) {
List<StockDailyData> list = indexService.getStockData(stock);
String result = indexService.kd(list);
return Result.success("success", result);
}
@PostMapping("/rsi")
public Result rsi(@RequestBody StockInfo stock) {
List<StockDailyData> list = indexService.getStockData(stock);
String result = indexService.rsi(list);
return Result.success("success", result);
}
}

19
src/main/java/com/deepchart/entity/KDJData.java

@ -0,0 +1,19 @@
package com.deepchart.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.time.LocalDate;
/**
* KDJ指标计算结果实体类
*/
@Data
@AllArgsConstructor
public class KDJData {
private LocalDate date; // 交易日日期
private double rsv; // RSV
private double k; // K
private double d; // D
private double j; // J
}

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; }
}

18
src/main/java/com/deepchart/entity/StockDailyData.java

@ -0,0 +1,18 @@
package com.deepchart.entity;
import lombok.Data;
import java.time.LocalDate;
/**
* 股票单日行情基本数据实体类
*/
@Data
public class StockDailyData {
private LocalDate date; // 交易日日期
private double openPrice; // 开盘价
private double closePrice; // 收盘价
private double lowPrice; // 最低价
private double highPrice; // 最高价
private Long volume; // 成交量
}

9
src/main/java/com/deepchart/entity/StockInfo.java

@ -0,0 +1,9 @@
package com.deepchart.entity;
import lombok.Data;
@Data
public class StockInfo {
private String market;
private String code;
}

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

@ -1,4 +1,18 @@
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);
String kdj(List<StockDailyData> list);
String kd(List<StockDailyData> list);
String rsi(List<StockDailyData> list);
}

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

@ -1,8 +1,42 @@
package com.deepchart.service.impl;
import com.deepchart.entity.StockDailyData;
import com.deepchart.entity.StockInfo;
import com.deepchart.service.IndexService;
import com.deepchart.utils.*;
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);
}
@Override
public String kdj(List<StockDailyData> list) {
return KDJUtil.calculateAndAnalyze(list);
}
@Override
public String kd(List<StockDailyData> list) {
return KDUtil.generateReport(list);
}
@Override
public String rsi(List<StockDailyData> list) {
return RSIUtil.generateReport(list, 14);
}
}

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

@ -0,0 +1,102 @@
package com.deepchart.utils;
import com.deepchart.entity.KDJData;
import com.deepchart.entity.StockDailyData;
import java.util.*;
/**
* KDJ指标分析工具类
*/
public class KDJUtil {
private static final int N = 9; // 默认周期
private static final int M1 = 3; // 平滑周期 K
private static final int M2 = 3; // 平滑周期 D
public static String calculateAndAnalyze(List<StockDailyData> dataList) {
if (dataList == null || dataList.size() < N) {
return "数据不足,无法计算KDJ指标。";
}
List<KDJData> kdjList = new ArrayList<>();
Queue<Double> lowQueue = new LinkedList<>();
Queue<Double> highQueue = new LinkedList<>();
for (int i = 0; i < dataList.size(); i++) {
StockDailyData today = dataList.get(i);
lowQueue.offer(today.getLowPrice());
highQueue.offer(today.getHighPrice());
// 维持窗口大小为 N
if (lowQueue.size() > N) {
lowQueue.poll();
highQueue.poll();
}
// 不足N天跳过
if (i < N - 1) continue;
double lowestLow = Collections.min(lowQueue);
double highestHigh = Collections.max(highQueue);
double rsv = ((today.getClosePrice() - lowestLow) / (highestHigh - lowestLow)) * 100;
double k, d, j;
if (kdjList.isEmpty()) {
k = rsv;
d = k;
} else {
KDJData prev = kdjList.get(kdjList.size() - 1);
k = (2.0 / 3) * prev.getK() + (1.0 / 3) * rsv;
d = (2.0 / 3) * prev.getD() + (1.0 / 3) * k;
}
j = 3 * k - 2 * d;
kdjList.add(new KDJData(today.getDate(), rsv, k, d, j));
}
return generateReport(kdjList);
}
private static String generateReport(List<KDJData> kdjList) {
StringBuilder report = new StringBuilder("📈【KDJ技术分析报告】\n");
if (kdjList.isEmpty()) {
return report.append("❌ 数据不完整,无法提供有效分析。\n").toString();
}
KDJData latest = kdjList.get(kdjList.size() - 1);
// 超买/超卖判断
if (latest.getK() > 80 || latest.getD() > 80 || latest.getJ() > 100) {
report.append("🔴 当前处于超买区域,请注意回调风险!\n");
} else if (latest.getK() < 20 || latest.getD() < 20 || latest.getJ() < 0) {
report.append("🟢 当前处于超卖区域,存在反弹机会!\n");
} else {
report.append("🟡 当前KDJ指标运行平稳,无明显买卖信号。\n");
}
// 交叉信号检测简单实现
if (kdjList.size() >= 2) {
KDJData prev = kdjList.get(kdjList.size() - 2);
if (prev.getK() <= prev.getD() && latest.getK() > latest.getD()) {
report.append("✨ 发生金叉,建议关注买入时机(尤其在低位)。\n");
}
if (prev.getK() >= prev.getD() && latest.getK() < latest.getD()) {
report.append("🔻 发生死叉,建议控制仓位或考虑卖出(尤其在高位)。\n");
}
}
// J 线强化提示
if (latest.getJ() > 100) {
report.append("⚠️ J值超过100,进入极端超买状态,短期可能剧烈回调。\n");
} else if (latest.getJ() < 0) {
report.append("⚠️ J值低于0,进入极端超卖状态,短期可能强势反弹。\n");
}
return report.toString();
}
}

108
src/main/java/com/deepchart/utils/KDUtil.java

@ -0,0 +1,108 @@
package com.deepchart.utils;
import com.deepchart.entity.KDJData;
import com.deepchart.entity.StockDailyData;
import java.util.*;
/**
* KD指标分析工具类
*/
public class KDUtil {
private static final int N = 9; // 默认周期长度
private static final double SMOOTH_K = 1.0 / 3; // K 平滑系数
private static final double SMOOTH_D = 1.0 / 3; // D 平滑系数
private static List<KDJData> calculateKD(List<StockDailyData> dataList) {
if (dataList == null || dataList.size() < N) {
throw new IllegalArgumentException("数据不足,至少需要" + N + "条记录");
}
List<KDJData> kdList = new ArrayList<>();
Deque<Double> highs = new ArrayDeque<>(N);
Deque<Double> lows = new ArrayDeque<>(N);
Double prevK = null;
Double prevD = null;
for (int i = 0; i < dataList.size(); i++) {
StockDailyData data = dataList.get(i);
highs.offerLast(data.getHighPrice());
lows.offerLast(data.getLowPrice());
if (highs.size() > N) {
highs.pollFirst();
lows.pollFirst();
}
if (i >= N - 1) {
double hhv = Collections.max(highs);
double llv = Collections.min(lows);
double rsv = ((data.getClosePrice() - llv) / (hhv - llv)) * 100;
double kValue = (prevK == null ? rsv : prevK * (1 - SMOOTH_K) + rsv * SMOOTH_K);
double dValue = (prevD == null ? kValue : prevD * (1 - SMOOTH_D) + kValue * SMOOTH_D);
kdList.add(new KDJData(data.getDate(), rsv, kValue, dValue, 0));
prevK = kValue;
prevD = dValue;
} else {
kdList.add(new KDJData(data.getDate(), 0, 0, 0, 0)); // 占位符
}
}
return kdList;
}
public static String generateReport(List<StockDailyData> stockDataList) {
List<KDJData> kdList = calculateKD(stockDataList);
StringBuilder report = new StringBuilder();
report.append("📈 股票KD指标分析报告\n");
report.append("=======================\n");
if (kdList.isEmpty()) {
report.append("❌ 数据不足,无法生成分析。\n");
return report.toString();
}
KDJData latest = kdList.get(kdList.size() - 1);
KDJData previous = kdList.size() >= 2 ? kdList.get(kdList.size() - 2) : null;
double k = latest.getK();
double d = latest.getD();
// 判断超买或超卖
if (k > 80 && d > 80) {
report.append("🔴 当前处于【超买】状态,注意回调风险。\n");
} else if (k < 20 && d < 20) {
report.append("🟢 当前处于【超卖】状态,关注反弹机会。\n");
} else {
report.append("🟡 当前处于中性区域,暂无明显买卖信号。\n");
}
// 判断金叉/死叉
if (previous != null) {
boolean isGoldenCross = previous.getK() <= previous.getD() && k > d;
boolean isDeathCross = previous.getK() >= previous.getD() && k < d;
if (isGoldenCross && d < 30) {
report.append("✨ 出现金叉(K线上穿D线),特别是在低位,是潜在买入信号。\n");
} else if (isDeathCross && d > 70) {
report.append("⚠️ 出现死叉(K线下穿D线),尤其是在高位,建议谨慎卖出。\n");
}
}
// 多空趋势判断
if (k > 50 && d > 50 && k > d) {
report.append("⬆️ 当前为多头趋势,可考虑持股观望。\n");
} else if (k < 50 && d < 50 && k < d) {
report.append("⬇️ 当前为空头趋势,宜控制仓位。\n");
} else {
report.append("🔄 当前为震荡行情,短线操作更适宜。\n");
}
return report.toString();
}
}

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");
}
}

91
src/main/java/com/deepchart/utils/RSIUtil.java

@ -0,0 +1,91 @@
package com.deepchart.utils;
import com.deepchart.entity.StockDailyData;
import java.util.ArrayList;
import java.util.List;
/**
* RSI指标技术分析工具类
*/
public class RSIUtil {
public static Double calculateRSI(List<StockDailyData> dataList, int period) {
if (dataList == null || dataList.size() <= period) {
throw new IllegalArgumentException("数据不足,无法计算" + period + "日RSI");
}
List<Double> ups = new ArrayList<>();
List<Double> downs = new ArrayList<>();
for (int i = 1; i <= period; i++) {
StockDailyData today = dataList.get(dataList.size() - period + i - 1);
StockDailyData yesterday = dataList.get(dataList.size() - period + i - 2);
double change = today.getClosePrice() - yesterday.getClosePrice();
if (change > 0) {
ups.add(change);
downs.add(0.0);
} else {
ups.add(0.0);
downs.add(-change); // 取正值
}
}
double avgUp = ups.stream().mapToDouble(Double::doubleValue).average().orElse(0);
double avgDown = downs.stream().mapToDouble(Double::doubleValue).average().orElse(0);
if (avgDown == 0) {
return 100.0;
}
return 100 - (100 / (1 + avgUp / avgDown));
}
public static String generateReport(List<StockDailyData> dataList, int period) {
try {
Double rsiValue = calculateRSI(dataList, period);
StringBuilder report = new StringBuilder();
report.append("📊 【RSI技术分析报告】\n");
report.append(String.format("📈 当前 %d 日 RSI 值为:%.2f\n", period, rsiValue));
if (rsiValue >= 80) {
report.append("🔴 状态:严重超买!警惕高位回调风险。\n");
} else if (rsiValue >= 70) {
report.append("🟠 状态:轻度超买,短期有回调压力。\n");
} else if (rsiValue <= 20) {
report.append("🟢 状态:严重超卖!关注反弹机会。\n");
} else if (rsiValue <= 30) {
report.append("🟡 状态:轻度超卖,短期内可能反弹。\n");
} else {
report.append("🔵 状态:处于中性区间,震荡行情延续。\n");
}
if (rsiValue > 50) {
report.append("📈 多方力量较强,适合逢低介入。\n");
} else {
report.append("📉 空方占优,应谨慎操作或减仓观望。\n");
}
// 判断是否出现背离信号简单比较最近两日收盘价与RSI变化
if (dataList.size() >= period + 2) {
StockDailyData lastDay = dataList.get(dataList.size() - 1);
StockDailyData prevDay = dataList.get(dataList.size() - 2);
Double rsiLast = calculateRSI(dataList.subList(0, dataList.size()), period);
Double rsiPrev = calculateRSI(dataList.subList(0, dataList.size() - 1), period);
if (lastDay.getClosePrice() > prevDay.getClosePrice() && rsiLast < rsiPrev) {
report.append("⚠️ 出现顶背离迹象,请注意上涨动能减弱风险。\n");
} else if (lastDay.getClosePrice() < prevDay.getClosePrice() && rsiLast > rsiPrev) {
report.append("✅ 出现底背离迹象,下跌动能减弱,或迎来反弹。\n");
}
}
return report.toString();
} catch (Exception e) {
return "❌ 分析失败:" + e.getMessage();
}
}
}

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);
}
}

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

@ -0,0 +1,110 @@
package com.deepchart.utils;
import com.deepchart.entity.StockDailyData;
import com.deepchart.entity.StockInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 股票行情基本数据工具类
*/
@Component
public class StockDataUtil {
private static final String STOCK_DATA_PREFIX = "https://api.homilychart.com/link";
private static final String TOKEN = "8nkj4QBV1RPIb4CzoRTnbZi0+fEeMx8pywnIlrmTxdwROKkuwWqAWu9orpkpeXVqL98DPfeonNYpHv+mucA";
private static final ObjectMapper objectMapper = new ObjectMapper();
public List<StockDailyData> getStockData(StockInfo stock) {
List<List<Object>> kLine20 = List.of();
List<List<Object>> JN = List.of();
try {
// 1. 构建请求URL
String url = STOCK_DATA_PREFIX + "/api/aiGoldBullData";
// 2. 使用Map构造请求体参数
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("aigoldBullPrivilegeState", 1);
requestBody.put("marketList", "can,usa,hk,vi,sg,th,in,cn,gb,my");
requestBody.put("market", stock.getMarket());
requestBody.put("code", stock.getCode());
// 3. 将Map转换为JSON字符串
String jsonInputString = objectMapper.writeValueAsString(requestBody);
// 4. 创建HTTP连接
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("token", TOKEN);
connection.setDoOutput(true);
// 5. 发送请求数据
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 6. 解析响应
if (connection.getResponseCode() == 200) {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
// 解析JSON响应
Map<String, Object> jsonResponse = objectMapper.readValue(response.toString(), Map.class);
Map<String, Object> data = (Map<String, Object>) jsonResponse.get("data");
Map<String, Object> bull = (Map<String, Object>) data.get("AIGoldBull");
kLine20 = (List<List<Object>>) bull.get("KLine20");
JN = (List<List<Object>>) bull.get("JN");
}
}
// 7. 关闭连接
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
Map<String, Long> volMap = new HashMap<>();
for (List<Object> v : JN) {
Object volObj = v.get(5);
Long volume = volObj instanceof Number ? ((Number) volObj).longValue() : Long.valueOf(volObj.toString());
volMap.put((String) v.get(0), volume);
}
List<StockDailyData> list = new ArrayList<>();
for (List<Object> v : kLine20) {
StockDailyData s = new StockDailyData();
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));
s.setVolume(volMap.get((String) v.get(0)));
list.add(s);
}
return list;
}
}
Loading…
Cancel
Save