|
@@ -119,9 +119,11 @@ public class SpotDataQueryService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 年线不从币安获取,由月线聚合计算
|
|
|
|
|
|
|
+ // 季线(3MONTH)和年线(1YEAR)不从币安获取,由月线聚合计算
|
|
|
List<Map<String, Object>> dataResult;
|
|
List<Map<String, Object>> dataResult;
|
|
|
- if ("1YEAR".equals(dbPeriod)) {
|
|
|
|
|
|
|
+ if ("3MONTH".equals(dbPeriod)) {
|
|
|
|
|
+ dataResult = queryQuarterlyKlineAggregated(symbol, endIdx, pageSize);
|
|
|
|
|
+ } else if ("1YEAR".equals(dbPeriod)) {
|
|
|
dataResult = queryYearlyKlineAggregated(symbol, endIdx, pageSize);
|
|
dataResult = queryYearlyKlineAggregated(symbol, endIdx, pageSize);
|
|
|
} else {
|
|
} else {
|
|
|
dataResult = queryKlineFromDatabase(symbol, dbPeriod, endIdx, pageSize);
|
|
dataResult = queryKlineFromDatabase(symbol, dbPeriod, endIdx, pageSize);
|
|
@@ -175,6 +177,92 @@ public class SpotDataQueryService {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ------------------------------------------------------------------ 季线聚合(从月线计算)
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询季线数据:从月线(1MONTH)按自然季度聚合,按时间升序返回。
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 季度定义:Q1=1-3月,Q2=4-6月,Q3=7-9月,Q4=10-12月。
|
|
|
|
|
+ * open=首月开盘价,close=末月收盘价,high/low 取极值,volume/amount 累加。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param symbol 交易对(小写)
|
|
|
|
|
+ * @param endIdx 结束索引(秒,0 表示不限)
|
|
|
|
|
+ * @param pageSize 最大季度数
|
|
|
|
|
+ * @return 季线数据列表(升序)
|
|
|
|
|
+ */
|
|
|
|
|
+ @DS("market")
|
|
|
|
|
+ private List<Map<String, Object>> queryQuarterlyKlineAggregated(String symbol, long endIdx, int pageSize) {
|
|
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
|
|
+ try {
|
|
|
|
|
+ String tableName = SpotConstant.TABLE_KLINE_PREFIX + symbol.toLowerCase();
|
|
|
|
|
+ int monthLimit = pageSize * 3;
|
|
|
|
|
+ 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>> quarterGroups = new LinkedHashMap<>();
|
|
|
|
|
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
|
|
|
|
+ for (int i = monthly.size() - 1; i >= 0; i--) {
|
|
|
|
|
+ Kline k = monthly.get(i);
|
|
|
|
|
+ cal.setTimeInMillis(k.getBeginTime());
|
|
|
|
|
+ int quarterKey = cal.get(Calendar.YEAR) * 4 + cal.get(Calendar.MONTH) / 3;
|
|
|
|
|
+ quarterGroups.computeIfAbsent(quarterKey, q -> new ArrayList<>()).add(k);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
|
|
|
+
|
|
|
|
|
+ List<Integer> keys = new ArrayList<>(quarterGroups.keySet());
|
|
|
|
|
+ int startIdx = Math.max(0, keys.size() - pageSize);
|
|
|
|
|
+ for (int ki = startIdx; ki < keys.size(); ki++) {
|
|
|
|
|
+ int quarterKey = keys.get(ki);
|
|
|
|
|
+ List<Kline> months = quarterGroups.get(quarterKey);
|
|
|
|
|
+
|
|
|
|
|
+ int year = quarterKey / 4;
|
|
|
|
|
+ int startMonth = (quarterKey % 4) * 3;
|
|
|
|
|
+ cal.set(year, startMonth, 1, 0, 0, 0);
|
|
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
|
|
+ long quarterBeginMs = cal.getTimeInMillis();
|
|
|
|
|
+ long id = quarterBeginMs / 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(quarterBeginMs)));
|
|
|
|
|
+ result.add(klineData);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("聚合季线数据失败: symbol={}", symbol, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// ------------------------------------------------------------------ 年线聚合(从月线计算)
|
|
// ------------------------------------------------------------------ 年线聚合(从月线计算)
|
|
|
|
|
|
|
|
/**
|
|
/**
|