package com.yami.trading.huobi.hobi.internal;
|
|
import cn.hutool.core.util.StrUtil;
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson.JSONObject;
|
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;
|
import com.yami.trading.huobi.data.internal.KlineService;
|
import com.yami.trading.huobi.data.internal.TradeTimeObject;
|
import com.yami.trading.huobi.hobi.http.HttpHelper;
|
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;
|
import org.apache.http.message.BasicNameValuePair;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Component;
|
|
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;
|
import java.util.stream.Collectors;
|
|
/**
|
* 完成需求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";
|
|
@Autowired
|
private InfomationService infomationService;
|
/**
|
* live
|
*/
|
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";
|
public final static String tradeList = "https://stock.xueqiu.com/v5/stock/history/trade.json";
|
|
private static Logger logger = LoggerFactory.getLogger(XueQiuDataServiceImpl.class);
|
|
@Autowired
|
private KlineService klineService;
|
@Autowired
|
private ItemService itemService;
|
|
public static List<Depth> pankous(String symbols) {
|
Map<String, String> param = new HashMap<>();
|
param.put("symbol", symbols);
|
|
String result = HttpHelper.getJSONFromHttpNew(pankou, param, HttpMethodType.GET);
|
JSONObject resultJson = JSON.parseObject(result);
|
String code = resultJson.getString("error_code");
|
if ("0".equalsIgnoreCase(code)) {
|
JSONObject jsonObject = JSON.parseObject(resultJson.getString("data"));
|
List<Depth> depths = new ArrayList<>();
|
Depth depth = new Depth();
|
depth.setSymbol(jsonObject.getString("symbol"));
|
depth.setTs(jsonObject.getLong("timestamp"));
|
List<DepthEntry> asks = new ArrayList<>();
|
List<DepthEntry> bids = new ArrayList<>();
|
DepthEntry asks1 = new DepthEntry();
|
asks1.setAmount(jsonObject.getDouble("sc1") / 100);
|
asks1.setPrice(jsonObject.getDouble("sp1"));
|
|
DepthEntry asks2 = new DepthEntry();
|
asks2.setAmount(jsonObject.getDouble("sc2") / 100);
|
asks2.setPrice(jsonObject.getDouble("sp2"));
|
|
DepthEntry asks3 = new DepthEntry();
|
asks3.setAmount(jsonObject.getDouble("sc3") / 100);
|
asks3.setPrice(jsonObject.getDouble("sp3"));
|
|
DepthEntry asks4 = new DepthEntry();
|
asks4.setAmount(jsonObject.getDouble("sc4") / 100);
|
asks4.setPrice(jsonObject.getDouble("sp4"));
|
|
DepthEntry asks5 = new DepthEntry();
|
asks5.setAmount(jsonObject.getDouble("sc5") / 100);
|
asks5.setPrice(jsonObject.getDouble("sp5"));
|
|
DepthEntry bids1 = new DepthEntry();
|
bids1.setAmount(jsonObject.getDouble("bc1") / 100);
|
bids1.setPrice(jsonObject.getDouble("bp1"));
|
|
DepthEntry bids2 = new DepthEntry();
|
bids2.setAmount(jsonObject.getDouble("bc2") / 100);
|
bids2.setPrice(jsonObject.getDouble("bp2"));
|
|
DepthEntry bids3 = new DepthEntry();
|
bids3.setAmount(jsonObject.getDouble("bc3") / 100);
|
bids3.setPrice(jsonObject.getDouble("bp3"));
|
|
DepthEntry bids4 = new DepthEntry();
|
bids4.setAmount(jsonObject.getDouble("bc4") / 100);
|
bids4.setPrice(jsonObject.getDouble("bp4"));
|
|
DepthEntry bids5 = new DepthEntry();
|
bids5.setAmount(jsonObject.getDouble("bc5") / 100);
|
bids5.setPrice(jsonObject.getDouble("bp5"));
|
|
asks.add(asks1);
|
asks.add(asks2);
|
asks.add(asks3);
|
asks.add(asks4);
|
asks.add(asks5);
|
depth.setAsks(asks);
|
bids.add(bids1);
|
bids.add(bids2);
|
bids.add(bids3);
|
bids.add(bids4);
|
bids.add(bids5);
|
depth.setBids(bids);
|
depths.add(depth);
|
return depths;
|
|
}
|
return Lists.newArrayList();
|
}
|
|
public static List<StockMarket> getMarkets(String symbols) {
|
|
try{
|
String cookie = HttpHelper.getCookie("https://xueqiu.com/");
|
List<StockMarket> stockMarkets = new ArrayList<>();
|
List<String> strings = Arrays.asList(symbols.split(","));
|
for (String symbol: strings) {
|
|
|
String result = HttpHelper.sendGetHttp(markets, "symbol=" + symbol, cookie);
|
JSONObject resultJson = JSON.parseObject(result);
|
String error_code = resultJson.getString("error_code");
|
if("0".equalsIgnoreCase(error_code)){
|
JSONObject data = resultJson.getJSONObject("data");
|
JSONObject market = data.getJSONObject("market");
|
market.put("symbol",symbol);
|
StockMarket stockMarket = market.toJavaObject(StockMarket.class);
|
stockMarkets.add(stockMarket);
|
}
|
}
|
|
return stockMarkets;
|
}catch (Exception e){
|
logger.error("XueQiuData{}",e);
|
return Lists.newArrayList();
|
}
|
}
|
|
public static void main(String[] args) {
|
XueQiuDataServiceImpl service = new XueQiuDataServiceImpl();
|
// List<Kline> sz300750 = service.buildOneYearPeriod("AAPL");
|
// System.out.println(sz300750.size());
|
List<StockMarket> markets1 = service.getMarkets("AAPL,OIL");
|
System.out.println(markets1);
|
}
|
|
public void getInformation() {
|
String cookie = HttpHelper.getCookie("https://xueqiu.com/");
|
String url = "https://xueqiu.com/statuses/livenews/list.json?since_id=-1&max_id=-1&count=15";
|
String json = HttpHelper.sendGetHttp(url, null, cookie);
|
JSONArray items = JSONObject.parseObject(json).getJSONArray("items");
|
List<Infomation> infomations = new ArrayList<>();
|
items.forEach(d -> {
|
Infomation infom = new Infomation();
|
JSONObject data = (JSONObject) d;
|
String dataId = data.getString("id");
|
infom.setDataId(dataId);
|
String description = data.getString("text");
|
infom.setDescription(description);
|
String createAt = data.getString("created_at");
|
infom.setCreatedAt(createAt);
|
infom.setType("1");
|
String originUrl = data.getString("target");
|
infom.setOriginUrl(originUrl);
|
String source = getSource(description);
|
infom.setSource(source);
|
infom.setLang("zh-CN");
|
String key = "infomation" + originUrl;
|
if (RedisUtil.get(key) == null) {
|
infomations.add(infom);
|
// url存一周
|
RedisUtil.set(key, 1, 60 * 60 * 24 * 7);
|
}
|
});
|
if(infomations.size()>1){
|
infomationService.saveBatch(infomations);
|
}
|
}
|
|
public static String getSource(String text) {
|
|
String pattern = "(([^()]*))$"; // 匹配最后一个括号内的文本
|
|
Pattern regex = Pattern.compile(pattern);
|
Matcher matcher = regex.matcher(text);
|
|
if (matcher.find()) {
|
String endText = matcher.group(1);
|
return endText;
|
} else {
|
return "";
|
}
|
}
|
|
/**
|
* 可以多个
|
*
|
* @param symbols
|
*/
|
public static void tradeList(String symbols, boolean isUSStock) {
|
try{
|
List<String> strings = Arrays.asList(symbols.split(","));
|
String cookie = HttpHelper.getCookie("https://xueqiu.com/");
|
for (String symbol: strings) {
|
Map<String, String> param = new HashMap<>();
|
param.put("symbol", symbol);
|
|
StringBuilder parmStr = new StringBuilder();
|
if (null != param && !param.isEmpty()) {
|
List<NameValuePair> parm = new ArrayList<NameValuePair>(param.size());
|
for (Map.Entry<String, String> paramEntity : param.entrySet()) {
|
Object value = paramEntity.getValue();
|
if (null != value && !StringUtils.isBlank(value.toString())) {
|
parm.add(new BasicNameValuePair(paramEntity.getKey(), value.toString()));
|
}
|
}
|
parmStr.append(URLEncodedUtils.format(parm, "UTF-8"));
|
}
|
String result = HttpHelper.sendGetHttp(tradeList, parmStr.toString(), cookie);
|
JSONObject resultJson = JSON.parseObject(result);
|
String code = resultJson.getString("error_code");
|
if ("0".equals(code)) {
|
JSONObject data = resultJson.getJSONObject("data");
|
JSONArray items = data.getJSONArray("items");
|
List<TradeDetails> tradeDetails = items.toJavaList(TradeDetails.class);
|
if (tradeDetails.size() >= 1) {
|
String symbol_flag = tradeDetails.get(0).getSymbol();
|
DataCache.putStockTradeList(symbol_flag, tradeDetails);
|
tradeListToTrade(symbol_flag, tradeDetails);
|
if (isUSStock) {
|
setTradeListToDepth(symbol_flag, tradeDetails);
|
}
|
}
|
}
|
}
|
}catch (Exception e){}
|
|
}
|
|
|
public static void setTradeListToDepth(String symbol, List<TradeDetails> tradeDetails) {
|
Depth depth = new Depth();
|
depth.setTs(tradeDetails.get(0).getTimestamp() / 1000);
|
depth.setSymbol(symbol);
|
List<DepthEntry> asks = tradeDetails.stream().filter(t -> t.getSide() == -1).map(t -> {
|
DepthEntry depthEntry = new DepthEntry();
|
depthEntry.setAmount((double) t.getTrade_volume());
|
depthEntry.setPrice(t.getCurrent());
|
return depthEntry;
|
}).collect(Collectors.toList());
|
List<DepthEntry> bids = tradeDetails.stream().filter(t -> t.getSide() == 1).map(t -> {
|
DepthEntry depthEntry = new DepthEntry();
|
depthEntry.setAmount((double) t.getTrade_volume());
|
depthEntry.setPrice(t.getCurrent());
|
return depthEntry;
|
}).collect(Collectors.toList());
|
depth.setAsks(asks);
|
depth.setBids(bids);
|
|
DepthTimeObject timeObject = new DepthTimeObject();
|
timeObject.setLastTime(new Date());
|
timeObject.setDepth(depth);
|
DataCache.getDepth().put(depth.getSymbol(), timeObject);
|
}
|
|
|
public static void tradeListToTrade(String symbol, List<TradeDetails> tradeDetails) {
|
TradeTimeObject timeObject = DataCache.getTrade().get(symbol);
|
if (timeObject == null) {
|
timeObject = new TradeTimeObject();
|
}
|
timeObject.setLastTime(new Date());
|
|
List<TradeEntry> data = tradeDetails.stream().map(a -> {
|
TradeEntry tradeEntry = new TradeEntry();
|
tradeEntry.setDirection(a.getSide() == 1 ? "sell" : "buy");
|
tradeEntry.setAmount((double) a.getTrade_volume());
|
tradeEntry.setPrice(a.getCurrent());
|
tradeEntry.setTs(a.getTimestamp() / 1000);
|
return tradeEntry;
|
}).collect(Collectors.toList());
|
timeObject.put(symbol, data);
|
DataCache.getTrade().put(symbol, timeObject);
|
}
|
|
/**
|
* 获取原始的K线图数据
|
*
|
* @param symbol
|
* @param cookie
|
* @return
|
*/
|
public String getRawTimeseriesByMinute(String symbol, String cookie) {
|
long nowTs = System.currentTimeMillis();
|
long begin = nowTs;
|
String url = StrUtil.format(klineUrl, symbol, begin, "1m");
|
return HttpHelper.sendGetHttp(url, null, cookie);
|
}
|
|
public List<Realtime> realtimeSingle(String symbols) {
|
List<Realtime> list = new ArrayList<>();
|
try {
|
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;
|
}
|
|
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";
|
}
|
|
// 构建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);
|
}
|
} catch (Exception e) {
|
log.error("处理股票 {} 时发生错误", symbol, e);
|
}
|
}
|
} catch (Exception 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) {
|
List<Realtime> realtimeList = realtimeSingle(symbols);
|
return realtimeList;
|
}
|
|
/**
|
* 1day 历史数据 : 周期 1年
|
* 1week,1mon 历史数据 : 周期 5年
|
* 请求 350次
|
*/
|
public Map<String, List<Kline>> getDailyWeekMonthHistory(String symbol) {
|
Map<String, List<Kline>> map = new HashMap<>();
|
map.put(Kline.PERIOD_1WEEK, buildOneWeekPeriod(symbol));
|
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_YEAR, buildOneYearPeriod(symbol));
|
|
return map;
|
}
|
|
public List<Kline> buildOneDayPeriod(String currency) {
|
return getTimeseriesByPeriod(currency, "daily", Kline.PERIOD_1DAY, 365);
|
|
}
|
|
|
public List<Kline> buildOneWeekPeriod(String currency) {
|
return getTimeseriesByPeriod(currency, "weekly", Kline.PERIOD_1WEEK, 365 * 5);
|
|
}
|
|
public List<Kline> buildOneMonthPeriod(String currency) {
|
return getTimeseriesByPeriod(currency, "monthly", Kline.PERIOD_1MON, 365 * 5);
|
}
|
|
public List<Kline> buildOneQuarterPeriod(String currency) {
|
return getTimeseriesByPeriod(currency, "quarter", Kline.PERIOD_QUARTER, 365 * 100);
|
}
|
|
public List<Kline> buildOneYearPeriod(String currency) {
|
return getTimeseriesByPeriod(currency, "yearly", Kline.PERIOD_YEAR, 365 * 100);
|
}
|
|
/**
|
* 雪球支持120分钟的,讲120分钟的,
|
* Hourly
|
* 4hourly 3月
|
*/
|
public List<Kline> buildFiveDayPeriod(String currency) {
|
List<Kline> result = Lists.newArrayList();
|
List<Kline> timeseriesByOneDay = getTimeseriesByPeriod(currency, "day", Kline.PERIOD_1DAY, 1000);
|
|
Collections.sort(timeseriesByOneDay, Kline::compareTo);
|
int lastIndex = timeseriesByOneDay.size() - 1;
|
for (int i = lastIndex; i >= 5; i = i - 5) {
|
// 1天K线最新的5条数据
|
List<Kline> klineOneTop5 = new ArrayList<>();
|
klineOneTop5.add(timeseriesByOneDay.get(i));
|
klineOneTop5.add(timeseriesByOneDay.get(i - 1));
|
klineOneTop5.add(timeseriesByOneDay.get(i - 2));
|
klineOneTop5.add(timeseriesByOneDay.get(i - 3));
|
klineOneTop5.add(timeseriesByOneDay.get(i - 4));
|
|
Double high = null;
|
Double low = null;
|
for (Kline kline : klineOneTop5) {
|
if (high == null || high <= kline.getHigh().doubleValue()) {
|
high = kline.getHigh().doubleValue();
|
}
|
if (low == null || low >= kline.getLow().doubleValue()) {
|
low = kline.getLow().doubleValue();
|
}
|
}
|
// 保存K线到数据库
|
Kline kline = new Kline();
|
kline.setSymbol(currency);
|
kline.setTs(klineOneTop5.get(4).getTs());
|
kline.setOpen(klineOneTop5.get(4).getOpen());
|
kline.setHigh(new BigDecimal(high));
|
kline.setLow(new BigDecimal(low));
|
kline.setClose(klineOneTop5.get(0).getClose());
|
kline.setPeriod(Kline.PERIOD_5DAY);
|
// 格式化小数点位
|
klineService.formatPoint(kline);
|
BigDecimal sumAmount = klineOneTop5.stream()
|
.map(Kline::getAmount)
|
.filter(amount -> amount != null)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
BigDecimal sumVolume = klineOneTop5.stream()
|
.map(Kline::getVolume)
|
.filter(volume -> volume != null)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
kline.setAmount(sumAmount);
|
kline.setVolume(sumVolume);
|
klineService.repairKline(kline);
|
result.add(kline);
|
}
|
Collections.sort(result);
|
// 不需要首尾连
|
// int len = result.size();
|
// for (int i = 1; i < len; i++) {
|
// result.get(i).setOpen(result.get(i - 1).getClose());
|
// }
|
// 默认是升序。最后一个开始,每次step为2 取数据
|
return result;
|
|
}
|
|
|
/**
|
* 雪球支持120分钟的,讲120分钟的,
|
* Hourly
|
* 4hourly 3月
|
*/
|
public List<Kline> getTimeseriesForFourHourly(String currency) {
|
List<Kline> result = Lists.newArrayList();
|
List<Kline> timeseriesByMinute = getTimeseriesByMinute(currency, 120, 90);
|
Collections.sort(timeseriesByMinute, Kline::compareTo);
|
int lastIndex = timeseriesByMinute.size() - 1;
|
for (int i = lastIndex; i >= 1; i = i - 2) {
|
Kline first = timeseriesByMinute.get(i);
|
Kline secnd = timeseriesByMinute.get(i - 1);
|
Kline kline = new Kline();
|
kline.setPeriod(Kline.PERIOD_4HOUR);
|
kline.setSymbol(currency);
|
Long timestamp = first.getTs();
|
if (timestamp.toString().length() > 13) {
|
timestamp = timestamp / 1000;
|
}
|
kline.setTs(timestamp);
|
kline.setOpen(secnd.getOpen());
|
kline.setClose(first.getClose());
|
kline.setHigh(first.getHigh().compareTo(secnd.getHigh()) > 0 ? first.getHigh() : secnd.getHigh());
|
kline.setLow(first.getLow().compareTo(secnd.getLow()) < 0 ? first.getLow() : secnd.getLow());
|
kline.setAmount(first.getAmount().add(secnd.getAmount()));
|
kline.setVolume(first.getVolume().add(secnd.getVolume()));
|
klineService.repairKline(kline);
|
result.add(kline);
|
}
|
Collections.sort(result);
|
// int len = result.size();
|
// for (int i = 1; i < len; i++) {
|
// result.get(i).setOpen(result.get(i - 1).getClose());
|
// }
|
// 默认是升序。最后一个开始,每次step为2 取数据
|
return result;
|
|
}
|
|
/**
|
* Hourly
|
* 1hourly 2个小时
|
*/
|
public List<Kline> getTimeseriesForTwoHourly(String currency) {
|
return getTimeseriesByMinute(currency, 120, 300);
|
}
|
|
/**
|
* Hourly
|
* 1hourly 1月
|
*/
|
public List<Kline> getTimeseriesForOneHourly(String currency) {
|
return getTimeseriesByMinute(currency, 60, 300);
|
}
|
|
/**
|
* Minute
|
* 30minute 10天
|
* 15minute 5天
|
* 5minute 2天
|
* 1minute 1天
|
*/
|
public List<Kline> getTimeseriesOneMinute(String currency) {
|
return getTimeseriesByMinute(currency, 1, 15);
|
|
}
|
|
/**
|
* Minute
|
* 30minute 10天
|
* 15minute 5天
|
* 5minute 2天
|
* 1minute 1天
|
*/
|
public List<Kline> getTimeseriesFiveMinute(String currency) {
|
return getTimeseriesByMinute(currency, 5, 15);
|
|
}
|
|
/**
|
* Minute
|
* 30minute 10天
|
* 15minute 5天
|
* 5minute 2天
|
* 1minute 1天
|
*/
|
public List<Kline> getTimeseriesFifteenMinute(String currency) {
|
return getTimeseriesByMinute(currency, 15, 15);
|
}
|
|
/**
|
* Minute
|
* 30minute 15
|
* 15minute 15
|
* 5minute 15
|
* 1minute 15
|
*/
|
public List<Kline> getTimeseriesThirtyMinute(String currency) {
|
return getTimeseriesByMinute(currency, 30, 10);
|
}
|
|
public List<Kline> getTimeseriesByPeriod(String currency, String periodXieQiu, String sysPeriod, long limitDays) {
|
List<Kline> resList = new ArrayList<>();
|
|
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);
|
|
// 检查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);
|
kline.setTs(ts);
|
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);
|
}
|
|
// 按时间戳升序排序
|
Collections.sort(resList);
|
|
// 如果需要设置开盘价为前一根的收盘价
|
int len = resList.size();
|
for (int i = 1; i < len; i++) {
|
resList.get(i).setOpen(resList.get(i - 1).getClose());
|
}
|
|
} catch (Exception e) {
|
log.error("获取K线数据失败: {}", currency, e);
|
}
|
|
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) {
|
return getTimeseriesByPeriod(currency, minute + "m", minute + "min", limitDays);
|
}
|
}
|