From d4be4cc69f18b01cc39bd3f9dc9497a828848ca8 Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Tue, 30 Sep 2025 11:18:44 +0800
Subject: [PATCH] 1
---
trading-order-huobi/src/main/java/com.yami.trading.huobi/hobi/internal/XueQiuDataServiceImpl.java | 371 ++++++++++++++++++++++++++++++++++++++++------------
1 files changed, 282 insertions(+), 89 deletions(-)
diff --git a/trading-order-huobi/src/main/java/com.yami.trading.huobi/hobi/internal/XueQiuDataServiceImpl.java b/trading-order-huobi/src/main/java/com.yami.trading.huobi/hobi/internal/XueQiuDataServiceImpl.java
index aed4a9c..029c123 100644
--- a/trading-order-huobi/src/main/java/com.yami.trading.huobi/hobi/internal/XueQiuDataServiceImpl.java
+++ b/trading-order-huobi/src/main/java/com.yami.trading.huobi/hobi/internal/XueQiuDataServiceImpl.java
@@ -7,6 +7,7 @@
import com.google.common.collect.Lists;
import com.yami.trading.bean.cms.Infomation;
import com.yami.trading.bean.data.domain.*;
+import com.yami.trading.bean.item.domain.Item;
import com.yami.trading.common.util.RedisUtil;
import com.yami.trading.huobi.data.DataCache;
import com.yami.trading.huobi.data.internal.DepthTimeObject;
@@ -16,6 +17,7 @@
import com.yami.trading.huobi.hobi.http.HttpMethodType;
import com.yami.trading.service.cms.InfomationService;
import com.yami.trading.service.item.ItemService;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
@@ -27,6 +29,7 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -36,6 +39,7 @@
* 完成需求k线图采集
*/
@Component
+@Slf4j
public class XueQiuDataServiceImpl {
// https://stock.xueqiu.com/v5/stock/chart/kline.json?symbol=TSLA&begin=1682695800000&period=120m&type=before&count=-500&indicator=kline";
public final static String klineUrl = "https://stock.xueqiu.com/v5/stock/chart/kline.json?symbol={}&begin={}&period={}&type=before&count=-500&indicator=kline";
@@ -45,7 +49,7 @@
/**
* live
*/
- public final static String live = "https://stock.xueqiu.com/v5/stock/quote.json";
+ public final static String live = "https://www.tsanghi.com/api/fin/";
public final static String markets = "https://stock.xueqiu.com/v5/stock/quote.json";
public final static String pankou = "https://stock.xueqiu.com/v5/stock/realtime/pankou.json";
@@ -322,55 +326,164 @@
}
public List<Realtime> realtimeSingle(String symbols) {
- List<Realtime> list = new ArrayList<Realtime>();
+ List<Realtime> list = new ArrayList<>();
try {
- List<String> strings = Arrays.asList(symbols.split(","));
- String cookie = HttpHelper.getCookie("https://xueqiu.com/");
- for (String symbol: strings) {
- String result = HttpHelper.sendGetHttp(live, "symbol=" + symbol, cookie);
- JSONObject resultJson = JSON.parseObject(result);
- String code = resultJson.getString("error_code");
- if ("0".equals(code)) {
- JSONObject jsonObject = resultJson.getJSONObject("data").getJSONObject("quote");
- Realtime realtime = new Realtime();
- String currency;
- currency = symbol;
- int decimal = itemService.getDecimal(currency);
- realtime.setSymbol(currency);
- realtime.setName(currency);
- Long timestamp = jsonObject.getLong("timestamp");
- if (timestamp.toString().length() > 13) {
- timestamp = timestamp / 1000;
+ List<String> symbolList = Arrays.asList(symbols.split(","));
+
+ for (String symbol : symbolList) {
+ try {
+ Item item = itemService.findBySymbol(symbol);
+ if (item == null) {
+ log.warn("未找到对应的item: {}", symbol);
+ continue;
}
- realtime.setTs(timestamp);
- realtime.setOpen(jsonObject.getBigDecimal("open").setScale(decimal, RoundingMode.HALF_UP));
- realtime.setClose(jsonObject.getBigDecimal("current").setScale(decimal, RoundingMode.HALF_UP));
- realtime.setHigh(jsonObject.getBigDecimal("high").setScale(decimal, RoundingMode.HALF_UP));
- realtime.setLow(jsonObject.getBigDecimal("low").setScale(decimal, RoundingMode.HALF_UP));
- realtime.setMarketCapital(jsonObject.getLong("market_capital"));
- realtime.setFloatMarketCapital(jsonObject.getLong("float_market_capital"));
- realtime.setPeForecast(jsonObject.getBigDecimal("pe_forecast"));
- realtime.setVolumeRatio(jsonObject.getBigDecimal("volume_ratio"));
- realtime.setTurnoverRate(jsonObject.getBigDecimal("turnover_rate"));
- BigDecimal amount = jsonObject.getBigDecimal("amount");
- if (amount == null) {
- amount = BigDecimal.ZERO;
+
+ String type = "";
+ String exchange = "";
+
+ // 根据类型确定API路径和交易所
+ if ("US-stocks".equals(item.getType())) {
+ type = "stock";
+ exchange = "XNAS"; // 美股纳斯达克
+ } else if ("indices".equals(item.getType()) || "ETF".equals(item.getType())) {
+ type = "etf";
+ exchange = "XNAS"; // ETF也在纳斯达克
+ } else {
+ // 其他类型,默认为股票和上交所
+ type = "stock";
+ exchange = "XSHG";
}
- realtime.setAmount(amount.setScale(decimal, RoundingMode.HALF_UP));
- BigDecimal volume = jsonObject.getBigDecimal("volume");
- if (volume == null) {
- volume = BigDecimal.ZERO;
+
+ // 构建API URL
+ String url = String.format("https://www.tsanghi.com/api/fin/%s/%s/realtime?token=9668db3503214cd19a831a9f866923b9&ticker=%s",
+ type, exchange, symbol);
+
+ String result = HttpHelper.sendGetHttp(url, null, null);
+ JSONObject resultJson = JSON.parseObject(result);
+
+ String code = resultJson.getString("code");
+ if ("200".equals(code)) {
+ JSONArray dataArray = resultJson.getJSONArray("data");
+
+ // 检查数据是否为空
+ if (dataArray == null || dataArray.isEmpty()) {
+ log.warn("股票 {} 的实时数据为空", symbol);
+ continue;
+ }
+
+ // 取第一个数据对象
+ JSONObject dataObject = dataArray.getJSONObject(0);
+
+ Realtime realtime = new Realtime();
+ int decimal = itemService.getDecimal(symbol);
+
+ realtime.setSymbol(symbol);
+ realtime.setName(item.getName() != null ? item.getName() : symbol);
+
+ // 处理时间戳
+ String dateStr = dataObject.getString("date");
+ long timestamp = parseDateTimeToTimestamp(dateStr);
+ if (Long.toString(timestamp).length() > 13) {
+ timestamp = timestamp / 1000;
+ }
+ realtime.setTs(timestamp);
+
+ // 设置价格数据
+ realtime.setOpen(getBigDecimalValue(dataObject, "open", decimal));
+ realtime.setClose(getBigDecimalValue(dataObject, "close", decimal));
+ realtime.setHigh(getBigDecimalValue(dataObject, "high", decimal));
+ realtime.setLow(getBigDecimalValue(dataObject, "low", decimal));
+
+ // 设置成交量和成交额
+ realtime.setVolume(getBigDecimalValue(dataObject, "volume", decimal));
+ realtime.setAmount(getBigDecimalValue(dataObject, "amount", decimal));
+
+ // 设置昨收价
+ BigDecimal preClose = getBigDecimalValue(dataObject, "pre_close", decimal);
+ if (preClose != null) {
+ // 如果Realtime类有preClose字段,取消注释
+ // realtime.setPreClose(preClose);
+ }
+
+ // 处理盘口数据(如果存在)
+ JSONArray buyPriceArray = dataObject.getJSONArray("buy_price");
+ JSONArray sellPriceArray = dataObject.getJSONArray("sell_price");
+ JSONArray buyVolumeArray = dataObject.getJSONArray("buy_volume");
+ JSONArray sellVolumeArray = dataObject.getJSONArray("sell_volume");
+
+ if (buyPriceArray != null && !buyPriceArray.isEmpty()) {
+ realtime.setBid(getBigDecimalFromArray(buyPriceArray, 0, decimal));
+ }
+ if (sellPriceArray != null && !sellPriceArray.isEmpty()) {
+ realtime.setAsk(getBigDecimalFromArray(sellPriceArray, 0, decimal));
+ }
+
+ list.add(realtime);
+ } else {
+ log.warn("API返回错误代码: {}, 股票: {}", code, symbol);
}
- realtime.setVolume(volume.setScale(decimal, RoundingMode.HALF_UP));
-// realtime.setAsk(realtimeJson.getBigDecimal("ask").setScale(decimal, RoundingMode.HALF_UP));
-// realtime.setBid(realtimeJson.getBigDecimal("pb").setScale(decimal, RoundingMode.HALF_UP));
- list.add(realtime);
+ } catch (Exception e) {
+ log.error("处理股票 {} 时发生错误", symbol, e);
}
}
} catch (Exception e) {
- logger.error("error", e);
+ log.error("获取实时数据失败", e);
}
return list;
+ }
+
+ /**
+ * 将日期时间字符串转换为时间戳
+ * 格式: "yyyy-mm-dd hh:mm:ss"
+ */
+ private long parseDateTimeToTimestamp(String dateTimeStr) {
+ try {
+ if (dateTimeStr == null || dateTimeStr.isEmpty()) {
+ return System.currentTimeMillis();
+ }
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ Date date = sdf.parse(dateTimeStr);
+ return date.getTime();
+ } catch (Exception e) {
+ log.error("日期时间解析失败: {}", dateTimeStr, e);
+ return System.currentTimeMillis();
+ }
+ }
+
+ /**
+ * 安全获取BigDecimal值
+ */
+ private BigDecimal getBigDecimalValue(JSONObject jsonObject, String key, int decimal) {
+ try {
+ BigDecimal value = jsonObject.getBigDecimal(key);
+ if (value == null) {
+ return BigDecimal.ZERO;
+ }
+ return value.setScale(decimal, RoundingMode.HALF_UP);
+ } catch (Exception e) {
+ log.warn("获取字段 {} 失败", key, e);
+ return BigDecimal.ZERO;
+ }
+ }
+
+ /**
+ * 从JSONArray中获取BigDecimal值
+ */
+ private BigDecimal getBigDecimalFromArray(JSONArray jsonArray, int index, int decimal) {
+ try {
+ if (jsonArray == null || jsonArray.size() <= index) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal value = jsonArray.getBigDecimal(index);
+ if (value == null) {
+ return BigDecimal.ZERO;
+ }
+ return value.setScale(decimal, RoundingMode.HALF_UP);
+ } catch (Exception e) {
+ log.warn("从数组获取数据失败", e);
+ return BigDecimal.ZERO;
+ }
}
public List<Realtime> realtime(String symbols) {
@@ -389,25 +502,25 @@
map.put(Kline.PERIOD_1MON, buildOneMonthPeriod(symbol));
map.put(Kline.PERIOD_1DAY, buildOneDayPeriod(symbol));
map.put(Kline.PERIOD_5DAY, buildFiveDayPeriod(symbol));
- map.put(Kline.PERIOD_QUARTER, buildOneQuarterPeriod(symbol));
+// map.put(Kline.PERIOD_QUARTER, buildOneQuarterPeriod(symbol));
map.put(Kline.PERIOD_YEAR, buildOneYearPeriod(symbol));
return map;
}
public List<Kline> buildOneDayPeriod(String currency) {
- return getTimeseriesByPeriod(currency, "day", Kline.PERIOD_1DAY, 365);
+ return getTimeseriesByPeriod(currency, "daily", Kline.PERIOD_1DAY, 365);
}
public List<Kline> buildOneWeekPeriod(String currency) {
- return getTimeseriesByPeriod(currency, "week", Kline.PERIOD_1WEEK, 365 * 5);
+ return getTimeseriesByPeriod(currency, "weekly", Kline.PERIOD_1WEEK, 365 * 5);
}
public List<Kline> buildOneMonthPeriod(String currency) {
- return getTimeseriesByPeriod(currency, "month", Kline.PERIOD_1MON, 365 * 5);
+ return getTimeseriesByPeriod(currency, "monthly", Kline.PERIOD_1MON, 365 * 5);
}
public List<Kline> buildOneQuarterPeriod(String currency) {
@@ -415,7 +528,7 @@
}
public List<Kline> buildOneYearPeriod(String currency) {
- return getTimeseriesByPeriod(currency, "year", Kline.PERIOD_YEAR, 365 * 100);
+ return getTimeseriesByPeriod(currency, "yearly", Kline.PERIOD_YEAR, 365 * 100);
}
/**
@@ -588,64 +701,144 @@
public List<Kline> getTimeseriesByPeriod(String currency, String periodXieQiu, String sysPeriod, long limitDays) {
List<Kline> resList = new ArrayList<>();
- long nowTs = System.currentTimeMillis();
- long startTs = System.currentTimeMillis() - limitDays * 24 * 60 * 60 * 1000;
- long begin = nowTs;
- String cookie = HttpHelper.getCookie("https://xueqiu.com/");
- Set<Long> tsSet = new HashSet<>();
- while (begin > startTs) {
- String url = StrUtil.format(klineUrl, currency, begin, periodXieQiu);
- String json = HttpHelper.sendGetHttp(url, null, cookie);
+ try {
+ // 获取商品信息判断类型
+ Item itemData = itemService.findBySymbol(currency);
+ String type = "stock"; // 默认股票
+ String exchange = "XNAS"; // 默认纳斯达克
+
+ if (itemData != null) {
+ if ("ETF".equals(itemData.getType()) || "etf".equals(itemData.getType()) ||
+ "indices".equals(itemData.getType()) || currency.startsWith("51") ||
+ currency.startsWith("15") || currency.startsWith("16")) {
+ type = "etf";
+ // ETF可能在不同的交易所,根据实际情况调整
+ exchange = "XNAS"; // 或者可能是XSHG等其他交易所
+ } else if ("US-stocks".equals(itemData.getType())) {
+ type = "stock";
+ exchange = "XNAS";
+ } else {
+ // 其他类型默认为A股股票
+ type = "stock";
+ exchange = "XSHG";
+ }
+ } else {
+ // 如果没有找到item信息,根据symbol特征猜测类型
+ if (currency.startsWith("51") || currency.startsWith("15") || currency.startsWith("16")) {
+ type = "etf";
+ exchange = "XSHG"; // A股ETF
+ } else if (currency.matches("[A-Z]+")) {
+ type = "stock";
+ exchange = "XNAS"; // 美股
+ } else {
+ type = "stock";
+ exchange = "XSHG"; // 默认A股
+ }
+ }
+
+ log.debug("获取K线数据,symbol: {}, 类型: {}, 交易所: {}, 周期: {}",
+ currency, type, exchange, periodXieQiu);
+
+ // 构建API URL
+ String url = StrUtil.format("https://www.tsanghi.com/api/fin/{}/{}/{}?token=9668db3503214cd19a831a9f866923b9&ticker={}&order=2",
+ type, exchange, periodXieQiu, currency);
+
+ // 发送HTTP请求
+ String json = HttpHelper.sendGetHttp(url, null, null);
JSONObject resultJson = JSON.parseObject(json);
- JSONArray dataArray = resultJson.getJSONObject("data").getJSONArray("item");
- List<List> lists = dataArray.toJavaList(List.class);
- long minTS = begin;
- for (List result : lists) {
+
+ // 检查API响应是否成功
+ if (resultJson == null || !resultJson.containsKey("data")) {
+ log.error("API响应数据格式异常: {}", json);
+ return resList;
+ }
+
+ JSONArray dataArray = resultJson.getJSONArray("data");
+ if (dataArray == null || dataArray.isEmpty()) {
+ log.warn("未获取到K线数据");
+ return resList;
+ }
+
+ // 计算时间范围
+ long endTime = System.currentTimeMillis();
+ long startTime = endTime - limitDays * 24 * 60 * 60 * 1000L;
+
+ Set<String> dateSet = new HashSet<>();
+
+ // 解析数据
+ for (int i = 0; i < dataArray.size(); i++) {
+ JSONObject item = dataArray.getJSONObject(i);
+
+ // 解析日期
+ String dateStr = item.getString("date");
+ if (dateSet.contains(dateStr)) {
+ continue;
+ }
+ dateSet.add(dateStr);
+
+ // 将日期转换为时间戳
+ long ts = parseDateToTimestamp(dateStr);
+
+ // 检查时间范围
+ if (ts < startTime) {
+ continue;
+ }
+
Kline kline = new Kline();
kline.setSymbol(currency);
kline.setPeriod(sysPeriod);
- // 毫秒
- long ts = Long.parseLong(result.get(0).toString());
- if (Long.toString(ts).length() > 13) {
- ts = ts / 1000;
- }
- if (tsSet.contains(ts)) {
- continue;
- } else {
- tsSet.add(ts);
- }
kline.setTs(ts);
- kline.setOpen(new BigDecimal(result.get(2).toString()));
- kline.setClose(new BigDecimal(result.get(5).toString()));
- kline.setHigh(new BigDecimal(result.get(3).toString()));
- kline.setLow(new BigDecimal(result.get(4).toString()));
- kline.setVolume(new BigDecimal(result.get(1).toString()));
- kline.setAmount(new BigDecimal(result.get(9).toString()));
+ kline.setOpen(item.getBigDecimal("open"));
+ kline.setClose(item.getBigDecimal("close"));
+ kline.setHigh(item.getBigDecimal("high"));
+ kline.setLow(item.getBigDecimal("low"));
+ kline.setVolume(item.getBigDecimal("volume"));
+
+ // 如果有amount字段就设置,没有就设为0
+ if (item.containsKey("amount")) {
+ kline.setAmount(item.getBigDecimal("amount"));
+ } else {
+ kline.setAmount(BigDecimal.ZERO);
+ }
+
+ // 修复K线数据(如果有修复逻辑)
if (klineService != null) {
klineService.repairKline(kline);
}
+
resList.add(kline);
- if (ts < minTS) {
- minTS = ts;
- }
+ }
+ // 按时间戳升序排序
+ Collections.sort(resList);
+
+ // 如果需要设置开盘价为前一根的收盘价
+ int len = resList.size();
+ for (int i = 1; i < len; i++) {
+ resList.get(i).setOpen(resList.get(i - 1).getClose());
}
- if (minTS == begin) {
- break;
- }
- begin = minTS;
- if (begin < startTs) {
- break;
- }
+
+ } catch (Exception e) {
+ log.error("获取K线数据失败: {}", currency, e);
}
- Collections.sort(resList);
- int len = resList.size();
- for (int i = 1; i < len; i++) {
- resList.get(i).setOpen(resList.get(i - 1).getClose());
- }
+
return resList;
+ }
+ /**
+ * 将日期字符串转换为时间戳
+ * 格式: "yyyy-mm-dd"
+ */
+ private long parseDateToTimestamp(String dateStr) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ Date date = sdf.parse(dateStr);
+ return date.getTime();
+ } catch (Exception e) {
+ log.error("日期解析失败: {}", dateStr, e);
+ return System.currentTimeMillis();
+ }
}
public List<Kline> getTimeseriesByMinute(String currency, int minute, long limitDays) {
--
Gitblit v1.9.3