1
zj
yesterday e4b5bb994a493f575d96a63ccb2f819276c66b81
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package com.yami.trading.huobi.data;
 
import cn.hutool.core.collection.CollectionUtil;
import com.yami.trading.bean.data.domain.Kline;
import com.yami.trading.bean.item.domain.Item;
import com.yami.trading.huobi.hobi.HobiDataService;
import com.yami.trading.huobi.tradingview.TradingViewSymbolResolver;
import com.yami.trading.huobi.tradingview.service.TradingViewService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
 
/**
 * 非加密货币 K 线远程拉取:TradingView 多 symbol 重试 → Hobi 数据源兜底。
 */
@Slf4j
@Service
public class NonCryptoKlineRemoteService {
 
    private static final int TV_PER_SYMBOL_TIMEOUT_SECONDS = 5;
 
    @Autowired
    private TradingViewService tradingViewService;
    @Autowired
    private HobiDataService hobiDataService;
 
    public List<Kline> fetch(Item item, String requestLine, String twLine) {
        List<Kline> tvData = fetchFromTradingView(item, twLine, requestLine);
        if (CollectionUtil.isNotEmpty(tvData)) {
            return tvData;
        }
        return fetchFromHobi(item, requestLine);
    }
 
    private List<Kline> fetchFromTradingView(Item item, String twLine, String requestLine) {
        List<String> candidates = TradingViewSymbolResolver.resolveCandidates(item);
        for (String tvSymbol : candidates) {
            try {
                Map<String, List<com.yami.trading.huobi.tradingview.api.model.Kline>> klineData =
                        tradingViewService.getKlineData(tvSymbol, twLine)
                                .get(TV_PER_SYMBOL_TIMEOUT_SECONDS, TimeUnit.SECONDS);
                List<com.yami.trading.huobi.tradingview.api.model.Kline> klines = klineData.get(tvSymbol);
                if ((klines == null || klines.isEmpty()) && klineData != null && !klineData.isEmpty()) {
                    klines = klineData.values().stream()
                            .filter(list -> list != null && !list.isEmpty())
                            .findFirst()
                            .orElse(null);
                }
                if (klines == null || klines.isEmpty()) {
                    continue;
                }
                log.info("TradingView kline hit, symbol={}, tvSymbol={}, bars={}", item.getSymbol(), tvSymbol, klines.size());
                return convertTvKlines(klines, item, requestLine);
            } catch (TimeoutException e) {
                log.warn("TradingView kline timeout, symbol={}, tvSymbol={}, twLine={}", item.getSymbol(), tvSymbol, twLine);
            } catch (Exception e) {
                log.warn("TradingView kline error, symbol={}, tvSymbol={}, twLine={}", item.getSymbol(), tvSymbol, twLine, e);
            }
        }
        return new ArrayList<>();
    }
 
    private List<Kline> convertTvKlines(List<com.yami.trading.huobi.tradingview.api.model.Kline> klines,
                                        Item item, String requestLine) {
        List<Kline> data = new ArrayList<>();
        String period = requestLine != null ? requestLine : Kline.PERIOD_1MIN;
        for (com.yami.trading.huobi.tradingview.api.model.Kline k : klines) {
            Kline kline = new Kline();
            kline.setSymbol(item.getSymbolData());
            kline.setTs(k.getTimestamp());
            kline.setOpen(k.getOpen());
            kline.setHigh(k.getHigh());
            kline.setLow(k.getLow());
            kline.setClose(k.getClose());
            kline.setPeriod(period);
            kline.setAmount(0D);
            kline.setVolume(k.getVolume());
            data.add(kline);
        }
        return data;
    }
 
    private List<Kline> fetchFromHobi(Item item, String line) {
        String symbol = item.getSymbol();
        try {
            List<Kline> klines = fetchPeriodFromHobi(symbol, line);
            if (CollectionUtil.isNotEmpty(klines)) {
                log.info("Hobi kline fallback hit, symbol={}, line={}, bars={}", symbol, line, klines.size());
            }
            return klines == null ? new ArrayList<>() : klines;
        } catch (Exception e) {
            log.warn("Hobi kline fallback failed, symbol={}, line={}", symbol, line, e);
            return new ArrayList<>();
        }
    }
 
    private List<Kline> fetchPeriodFromHobi(String symbol, String line) {
        if (line == null) {
            return new ArrayList<>();
        }
        switch (line.toLowerCase()) {
            case "1min":
                return hobiDataService.getTimeseriesOneMinute(symbol);
            case "5min":
                return hobiDataService.getTimeseriesFiveMinute(symbol);
            case "15min":
                return hobiDataService.getTimeseriesFifteenMinute(symbol);
            case "30min":
                return hobiDataService.getTimeseriesThirtyMinute(symbol);
            case "60min":
                return hobiDataService.getTimeseriesForOneHourly(symbol);
            case "120min":
            case "2hour":
                return hobiDataService.getTimeseriesForTwoHourly(symbol);
            case "4hour":
                return hobiDataService.getTimeseriesForFourHourly(symbol);
            case "1day":
            case "5day":
            case "1week":
            case "1mon":
            case "quarter":
            case "year":
                Map<String, List<Kline>> dailyMap = hobiDataService.getDailyWeekMonthHistory(symbol);
                if (dailyMap == null) {
                    return new ArrayList<>();
                }
                List<Kline> periodData = dailyMap.get(line);
                return periodData == null ? new ArrayList<>() : periodData;
            default:
                return hobiDataService.getTimeseriesOneMinute(symbol);
        }
    }
}