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