新版仿ok交易所-后端
1
zj
2025-09-30 d4be4cc69f18b01cc39bd3f9dc9497a828848ca8
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) {