package com.yami.trading.api.controller;
|
|
import com.yami.trading.bean.data.domain.Kline;
|
import com.yami.trading.bean.item.domain.Item;
|
import com.yami.trading.common.domain.Result;
|
import com.yami.trading.common.exception.YamiShopBindException;
|
import com.yami.trading.common.util.DateUtils;
|
import com.yami.trading.common.web.ResultObject;
|
import com.yami.trading.huobi.constants.KlinePeriodEnum;
|
import com.yami.trading.huobi.data.TimeZoneConverterService;
|
import com.yami.trading.huobi.tradingview.service.TradingViewService;
|
import com.yami.trading.service.data.DataService;
|
import com.yami.trading.service.item.ItemService;
|
import io.swagger.annotations.Api;
|
import io.swagger.annotations.ApiOperation;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RestController;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* K线图
|
*/
|
@RestController
|
@CrossOrigin
|
@Api(tags = "K线图实时行情")
|
@Slf4j
|
public class KlineController {
|
@Autowired
|
private ItemService itemService;
|
@Autowired
|
@Qualifier("dataService")
|
private DataService dataService;
|
|
@Autowired
|
private TimeZoneConverterService timeZoneConverterService;
|
|
@Autowired
|
private TradingViewService tradingViewService;
|
|
@Autowired
|
RedisTemplate redisTemplate;
|
|
|
@ApiOperation(value = "行情")
|
@GetMapping("/api/hobi!getKline.action")
|
public Result<List<Map<String, Object>>> getKline(@RequestParam String symbol, @RequestParam String line) {
|
ResultObject resultObject = new ResultObject();
|
// Normalize line type
|
line = normalizeLineType(line);
|
|
try {
|
// Fetch item details based on symbol
|
Item bySymbol = itemService.findBySymbol(symbol);
|
|
// Handle non-cryptos separately
|
if (!Item.cryptos.equals(bySymbol.getType())) {
|
return getKlineForNonCryptos(bySymbol, line, symbol);
|
}
|
|
// Fetch Kline data from service (for cryptos)
|
List<Kline> data = this.dataService.kline(symbol, line);
|
|
// Return an empty response if no data is found
|
if (Objects.isNull(data)) {
|
return Result.succeed(this.build(null, line, symbol));
|
}
|
|
// Format Kline data with proper timestamp based on the requested line type
|
formatKlineTimestamps(data, line);
|
|
return Result.succeed(this.build(data, line, symbol));
|
|
} catch (Exception e) {
|
log.error("getKline error", e);
|
throw new YamiShopBindException("k线图获取失败");
|
}
|
}
|
|
private String normalizeLineType(String line) {
|
switch (line.toLowerCase()) {
|
case "1quarter": return Kline.PERIOD_QUARTER;
|
case "1year": return Kline.PERIOD_YEAR;
|
default: return line;
|
}
|
}
|
|
private Result<List<Map<String, Object>>> getKlineForNonCryptos(Item bySymbol, String line, String symbol) {
|
|
String twForLine = getTwForLine(line);
|
|
// Cache keys
|
String cacheKey = "klineData:" + bySymbol.getSymbolData() + ":" + twForLine;
|
String cacheTimeKey = "klineDataTime:" + bySymbol.getSymbolData() + ":" + twForLine;
|
|
// Check if data is in cache
|
List<Kline> cachedData = (List<Kline>) redisTemplate.opsForValue().get(cacheKey);
|
Long lastUpdateTime = (Long) redisTemplate.opsForValue().get(cacheTimeKey);
|
|
if (cachedData != null && lastUpdateTime != null) {
|
long currentTime = System.currentTimeMillis();
|
if ((currentTime - lastUpdateTime) <= TimeUnit.MINUTES.toMillis(5)) {
|
return Result.succeed(this.build(cachedData, twForLine, symbol));
|
}
|
}
|
|
// Fetch Kline data from third-party API if not in cache
|
List<Kline> data = fetchKlineDataFromApi(bySymbol, twForLine);
|
|
// Cache the newly fetched data
|
redisTemplate.opsForValue().set(cacheKey, data);
|
redisTemplate.opsForValue().set(cacheTimeKey, System.currentTimeMillis());
|
|
// Format Kline data timestamps
|
formatKlineTimestamps(data, line);
|
|
return Result.succeed(this.build(data, line, symbol));
|
}
|
|
private List<Kline> fetchKlineDataFromApi(Item bySymbol, String line) {
|
List<Kline> data = new ArrayList<>();
|
//港股特别处理
|
String symbol = bySymbol.getSymbolData();
|
if(bySymbol.getOpenCloseType().equalsIgnoreCase(Item.HK_STOCKS)){
|
symbol = "HKEX:" + Integer.valueOf(bySymbol.getSymbolData());
|
}
|
|
Map<String, List<com.yami.trading.huobi.tradingview.api.model.Kline>> klineData = tradingViewService.getKlineData(symbol, line).join();
|
List<com.yami.trading.huobi.tradingview.api.model.Kline> klines = klineData.get(symbol);
|
|
klines.forEach(k -> {
|
Kline kline = new Kline();
|
kline.setSymbol(bySymbol.getSymbolData());
|
kline.setTs(k.getTimestamp());
|
kline.setOpen(k.getOpen());
|
kline.setHigh(k.getHigh());
|
kline.setLow(k.getLow());
|
kline.setClose(k.getClose());
|
kline.setPeriod(Kline.PERIOD_1MIN);
|
kline.setAmount(BigDecimal.ZERO.doubleValue());
|
kline.setVolume(k.getVolume());
|
data.add(kline);
|
});
|
|
return data;
|
}
|
|
private void formatKlineTimestamps(List<Kline> data, String line) {
|
String dateFormat = getDateFormatForLine(line);
|
data.forEach(datum -> {
|
datum.setCurrentTime(DateUtils.timeStamp2Date(String.valueOf(datum.getTs()), dateFormat));
|
});
|
}
|
|
private String getDateFormatForLine(String line) {
|
switch (line.toLowerCase()) {
|
case "1day":
|
case "5day":
|
case "1mon":
|
case "1week":
|
case "quarter":
|
case "year":
|
return "yyyy-MM-dd";
|
case "1min":
|
return "HH:mm";
|
default:
|
return "MM-dd HH:mm";
|
}
|
}
|
|
private String getTwForLine(String line) {
|
switch (line.toLowerCase()) {
|
case "1day": return "1D";
|
case "5day": return "5D";
|
case "1mon": return "1M";
|
case "1week": return "1W";
|
case "quarter": return "6M";
|
case "year":
|
return "12M";
|
case "1min":
|
return "1D";
|
default:
|
return "1D";
|
}
|
}
|
|
private List<Map<String, Object>> build(List<Kline> data, String line, String symbol) {
|
//log.info("------> KlineContrller.build comein ...");
|
Set<Long> tsSet = new HashSet<>();
|
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
|
Item bySymbol = itemService.findBySymbol(symbol);
|
for (int i = 0; i < data.size(); i++) {
|
Kline kline = data.get(i);
|
Long ts = kline.getTs();
|
if (tsSet.contains(ts)) {
|
continue;
|
} else {
|
tsSet.add(ts);
|
}
|
|
int decimal = bySymbol.getDecimals();
|
double low = kline.getLow();
|
double high = kline.getHigh();
|
double open = kline.getOpen();
|
double close = kline.getClose();
|
if ( low == 0) {
|
continue;
|
}
|
if (high == 0) {
|
continue;
|
}
|
if (close == 0) {
|
continue;
|
}
|
if (open == 0) {
|
continue;
|
}
|
Map<String, Object> map = new HashMap<>();
|
map.put("line", line);
|
map.put("symbol", kline.getSymbol());
|
map.put("timestamp", ts);
|
map.put("decimals", decimal);
|
map.put("ts", ts);
|
map.put("current_time", kline.getCurrentTime());
|
map.put("open", BigDecimal.valueOf(open).setScale(decimal, RoundingMode.HALF_UP));
|
map.put("close", BigDecimal.valueOf(close).setScale(decimal, RoundingMode.HALF_UP));
|
map.put("high", BigDecimal.valueOf(high).setScale(decimal, RoundingMode.HALF_UP));
|
map.put("low", BigDecimal.valueOf(low).setScale(decimal, RoundingMode.HALF_UP));
|
map.put("volume", kline.getVolume());
|
list.add(map);
|
}
|
return list;
|
}
|
|
}
|