Browse Source

11.24 11:08

完成四个股票基本指标的插件
majun
majun 5 days ago
parent
commit
47ffb8deef
  1. 7
      pom.xml
  2. 125
      src/main/java/com/deepchart/Main.java
  3. 27
      src/main/java/com/deepchart/StockApiDemo.java
  4. 21
      src/main/java/com/deepchart/controller/IndexController.java
  5. 19
      src/main/java/com/deepchart/entity/KDJData.java
  6. 1
      src/main/java/com/deepchart/entity/StockDailyData.java
  7. 6
      src/main/java/com/deepchart/service/IndexService.java
  8. 18
      src/main/java/com/deepchart/service/impl/IndexServiceImpl.java
  9. 102
      src/main/java/com/deepchart/utils/KDJUtil.java
  10. 108
      src/main/java/com/deepchart/utils/KDUtil.java
  11. 91
      src/main/java/com/deepchart/utils/RSIUtil.java
  12. 18
      src/main/java/com/deepchart/utils/StockDataUtil.java

7
pom.xml

@ -33,6 +33,13 @@
<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>

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

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

@ -25,4 +25,25 @@ public class IndexController {
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
}

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

@ -14,4 +14,5 @@ public class StockDailyData {
private double closePrice; // 收盘价
private double lowPrice; // 最低价
private double highPrice; // 最高价
private Long volume; // 成交量
}

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

@ -9,4 +9,10 @@ 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);
}

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

@ -3,8 +3,7 @@ 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 com.deepchart.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -25,4 +24,19 @@ public class IndexServiceImpl implements IndexService {
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();
}
}

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

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

@ -30,14 +30,15 @@ public class StockDataUtil {
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/superBrainData";
String url = STOCK_DATA_PREFIX + "/api/aiGoldBullData";
// 2. 使用Map构造请求体参数
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("brainPrivilegeState", 1);
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());
@ -71,8 +72,9 @@ public class StockDataUtil {
// 解析JSON响应
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<Object>>) brain.get("KLine20");
Map<String, Object> bull = (Map<String, Object>) data.get("AIGoldBull");
kLine20 = (List<List<Object>>) bull.get("KLine20");
JN = (List<List<Object>>) bull.get("JN");
}
}
@ -83,6 +85,13 @@ public class StockDataUtil {
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) {
@ -92,6 +101,7 @@ public class StockDataUtil {
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);
}

Loading…
Cancel
Save