|
|
@@ -119,7 +119,13 @@ public class SpotDataQueryService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- List<Map<String, Object>> dataResult = queryKlineFromDatabase(symbol, dbPeriod, endIdx, pageSize);
|
|
|
+ // 年线不从币安获取,由月线聚合计算
|
|
|
+ List<Map<String, Object>> dataResult;
|
|
|
+ if ("1YEAR".equals(dbPeriod)) {
|
|
|
+ dataResult = queryYearlyKlineAggregated(symbol, endIdx, pageSize);
|
|
|
+ } else {
|
|
|
+ dataResult = queryKlineFromDatabase(symbol, dbPeriod, endIdx, pageSize);
|
|
|
+ }
|
|
|
response.put(SpotConstant.RESPONSE_FIELD_DATA, dataResult);
|
|
|
} catch (Exception e) {
|
|
|
log.error("查询 K 线数据失败: channel={}, symbol={}", channel, symbol, e);
|
|
|
@@ -169,6 +175,93 @@ public class SpotDataQueryService {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ // ------------------------------------------------------------------ 年线聚合(从月线计算)
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询年线数据:从月线(1MONTH)按自然年聚合,按时间升序返回。
|
|
|
+ * <p>
|
|
|
+ * 币安不提供年线,year K-line 由月线聚合得到:
|
|
|
+ * open=首月开盘价,close=末月收盘价,high=最高,low=最低,volume/amount 累加。
|
|
|
+ *
|
|
|
+ * @param symbol 交易对(小写)
|
|
|
+ * @param endIdx 结束索引(秒,0 表示不限)
|
|
|
+ * @param pageSize 最大年数
|
|
|
+ * @return 年线数据列表(升序)
|
|
|
+ */
|
|
|
+ @DS("market")
|
|
|
+ private List<Map<String, Object>> queryYearlyKlineAggregated(String symbol, long endIdx, int pageSize) {
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ String tableName = SpotConstant.TABLE_KLINE_PREFIX + symbol.toLowerCase();
|
|
|
+ // 年线最多 pageSize 年,月线最多需要 pageSize*12 条
|
|
|
+ int monthLimit = pageSize * 12;
|
|
|
+ Long endTime = endIdx > 0 ? endIdx * SpotConstant.MILLISECONDS_TO_SECONDS : null;
|
|
|
+
|
|
|
+ List<Kline> monthly = klineMapper.selectByPeriodAndTimeRange(
|
|
|
+ tableName, "1MONTH", null, endTime, monthLimit);
|
|
|
+
|
|
|
+ if (monthly.isEmpty()) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按自然年分组(UTC),倒序→升序:先按年倒序分组后,再逆序输出
|
|
|
+ Map<Integer, List<Kline>> yearGroups = new LinkedHashMap<>();
|
|
|
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
|
|
+ // monthly 查询结果是倒序(最新在前),需要逆序遍历使年份从早到晚
|
|
|
+ for (int i = monthly.size() - 1; i >= 0; i--) {
|
|
|
+ Kline k = monthly.get(i);
|
|
|
+ cal.setTimeInMillis(k.getBeginTime());
|
|
|
+ int year = cal.get(Calendar.YEAR);
|
|
|
+ yearGroups.computeIfAbsent(year, y -> new ArrayList<>()).add(k);
|
|
|
+ }
|
|
|
+
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
|
+
|
|
|
+ // 聚合并按升序添加,最多取 pageSize 条
|
|
|
+ List<Integer> years = new ArrayList<>(yearGroups.keySet());
|
|
|
+ int startIdx = Math.max(0, years.size() - pageSize);
|
|
|
+ for (int yi = startIdx; yi < years.size(); yi++) {
|
|
|
+ int year = years.get(yi);
|
|
|
+ List<Kline> months = yearGroups.get(year);
|
|
|
+
|
|
|
+ cal.set(year, Calendar.JANUARY, 1, 0, 0, 0);
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
+ long yearBeginMs = cal.getTimeInMillis();
|
|
|
+ long id = yearBeginMs / SpotConstant.MILLISECONDS_TO_SECONDS;
|
|
|
+
|
|
|
+ BigDecimal high = months.get(0).getHigh();
|
|
|
+ BigDecimal low = months.get(0).getLow();
|
|
|
+ BigDecimal vol = BigDecimal.ZERO;
|
|
|
+ BigDecimal turn = BigDecimal.ZERO;
|
|
|
+ for (Kline m : months) {
|
|
|
+ if (m.getHigh().compareTo(high) > 0) {
|
|
|
+ high = m.getHigh();
|
|
|
+ }
|
|
|
+ if (m.getLow().compareTo(low) < 0) {
|
|
|
+ low = m.getLow();
|
|
|
+ }
|
|
|
+ vol = vol.add(m.getVolume());
|
|
|
+ turn = turn.add(m.getTurnover());
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> klineData = new HashMap<>();
|
|
|
+ klineData.put(SpotConstant.DATA_FIELD_ID, id);
|
|
|
+ klineData.put(SpotConstant.DATA_FIELD_OPEN, months.get(0).getOpen());
|
|
|
+ klineData.put(SpotConstant.DATA_FIELD_CLOSE, months.get(months.size() - 1).getClose());
|
|
|
+ klineData.put(SpotConstant.DATA_FIELD_HIGH, high);
|
|
|
+ klineData.put(SpotConstant.DATA_FIELD_LOW, low);
|
|
|
+ klineData.put(SpotConstant.DATA_FIELD_VOL, vol);
|
|
|
+ klineData.put(SpotConstant.DATA_FIELD_AMOUNT, turn);
|
|
|
+ klineData.put(SpotConstant.DATA_FIELD_DS, sdf.format(new Date(yearBeginMs)));
|
|
|
+ result.add(klineData);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("聚合年线数据失败: symbol={}", symbol, e);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
// ------------------------------------------------------------------ 深度数据
|
|
|
|
|
|
private Map<String, Object> queryDepthData(String channel, Map<String, Object> response, String symbol) {
|