package com.yami.trading.huobi.data.job;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.yami.trading.bean.item.domain.Item;
|
import com.yami.trading.huobi.data.model.ETFData;
|
import com.yami.trading.huobi.data.model.ETFResponse;
|
import com.yami.trading.huobi.data.model.RealTimeData;
|
import com.yami.trading.huobi.data.model.RealTimeResponse;
|
import com.yami.trading.service.item.ItemService;
|
import lombok.Data;
|
import lombok.extern.slf4j.Slf4j;
|
import org.checkerframework.checker.units.qual.A;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpStatus;
|
import org.springframework.http.ResponseEntity;
|
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.stereotype.Component;
|
import org.springframework.web.client.RestTemplate;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.util.*;
|
|
/**
|
* @program: trading-order-master
|
* @description:
|
* @create: 2025-09-28 11:20
|
**/
|
@Slf4j
|
@Component
|
public class ETFDataSynchronizationJob {
|
|
@Autowired
|
private ItemService itemService;
|
|
@Autowired
|
private RestTemplate restTemplate;
|
|
private static final String LIST_API_URL = "https://www.tsanghi.com/api/fin/etf/XNAS/list?token=9668db3503214cd19a831a9f866923b9";
|
private static final String REALTIME_API_URL = "https://www.tsanghi.com/api/fin/etf/XNAS/realtime?token=9668db3503214cd19a831a9f866923b9&ticker=";
|
|
/**
|
* 同步ETF数据定时任务
|
*/
|
// @Scheduled(cron = "0 * * * * ?")
|
public void syncETFData() {
|
try {
|
log.info("开始同步ETF数据...");
|
|
// 获取ETF列表
|
List<ETFData> etfList = getETFList();
|
if (etfList == null || etfList.isEmpty()) {
|
log.warn("未获取到ETF列表数据");
|
return;
|
}
|
|
log.info("共获取到{}个ETF数据", etfList.size());
|
|
int successCount = 0;
|
int errorCount = 0;
|
|
// 处理每个ETF
|
for (ETFData etf : etfList) {
|
try {
|
if (processETFItem(etf)) {
|
successCount++;
|
} else {
|
errorCount++;
|
}
|
} catch (Exception e) {
|
log.error("处理ETF {} 时发生错误: {}", etf.getTicker(), e.getMessage());
|
errorCount++;
|
}
|
}
|
|
log.info("ETF数据同步完成: 成功 {}, 失败 {}", successCount, errorCount);
|
|
} catch (Exception e) {
|
log.error("同步ETF数据时发生错误: {}", e.getMessage(), e);
|
}
|
}
|
|
/**
|
* 获取ETF列表
|
*/
|
private List<ETFData> getETFList() {
|
try {
|
ResponseEntity<ETFResponse> response = restTemplate.exchange(
|
LIST_API_URL,
|
HttpMethod.GET,
|
null,
|
ETFResponse.class
|
);
|
|
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
|
ETFResponse apiResponse = response.getBody();
|
if (apiResponse.getCode() == 200) {
|
return apiResponse.getData();
|
} else {
|
log.error("API返回错误: {} - {}", apiResponse.getCode(), apiResponse.getMsg());
|
}
|
}
|
} catch (Exception e) {
|
log.error("获取ETF列表失败: {}", e.getMessage(), e);
|
}
|
return null;
|
}
|
|
/**
|
* 处理单个ETF项目
|
*/
|
private boolean processETFItem(ETFData etf) {
|
// 只处理活跃的ETF
|
if (etf.getIsActive() == null || etf.getIsActive() != 1) {
|
log.info("跳过非活跃ETF: {}", etf.getTicker());
|
return false;
|
}
|
|
// 检查是否已存在
|
Item existingItem = itemService.getOne(new LambdaQueryWrapper<Item>()
|
.eq(Item::getSymbol,etf.getTicker())
|
);
|
if (existingItem != null) {
|
log.info("ETF {} 已存在,跳过", etf.getTicker());
|
return true;
|
}
|
|
// 获取实时数据计算小数位
|
RealTimeData realtimeData = getRealtimeData(etf.getTicker());
|
if (realtimeData == null) {
|
log.warn("无法获取ETF {} 的实时数据", etf.getTicker());
|
return false;
|
}
|
|
// 创建Item对象
|
Item item = createItemFromETFData(etf, realtimeData);
|
|
// 保存到数据库
|
return itemService.save(item);
|
}
|
|
/**
|
* 获取实时数据
|
*/
|
private RealTimeData getRealtimeData(String ticker) {
|
try {
|
String url = REALTIME_API_URL + ticker;
|
ResponseEntity<RealTimeResponse> response = restTemplate.exchange(
|
url,
|
HttpMethod.GET,
|
null,
|
RealTimeResponse.class
|
);
|
|
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
|
RealTimeResponse apiResponse = response.getBody();
|
if (apiResponse.getCode() == 200) {
|
return apiResponse.getData().get(0);
|
}
|
}
|
} catch (Exception e) {
|
log.error("获取ETF {} 实时数据失败: {}", ticker, e.getMessage());
|
}
|
return null;
|
}
|
|
/**
|
* 根据收盘价计算小数位数和PIPS
|
*/
|
private ETFDecimalInfo calculateDecimalInfo(BigDecimal closePrice) {
|
ETFDecimalInfo info = new ETFDecimalInfo();
|
|
if (closePrice == null) {
|
// 默认值
|
info.setDecimals(2);
|
info.setPips(new BigDecimal("0.01"));
|
info.setPipsAmount(new BigDecimal("0.01"));
|
return info;
|
}
|
|
try {
|
String priceStr = closePrice.stripTrailingZeros().toPlainString();
|
int decimalPlaces = 0;
|
|
// 查找小数点位置
|
int dotIndex = priceStr.indexOf('.');
|
if (dotIndex != -1) {
|
// 计算实际的小数位数(去除末尾的0)
|
decimalPlaces = priceStr.length() - dotIndex - 1;
|
}
|
|
// 设置小数位数,至少为2位
|
int decimals = Math.max(decimalPlaces, 2);
|
info.setDecimals(decimals);
|
|
// 计算PIPS(最小价格单位)
|
BigDecimal pips = BigDecimal.ONE.divide(
|
BigDecimal.TEN.pow(decimals),
|
decimals,
|
RoundingMode.HALF_UP
|
);
|
|
info.setPips(pips);
|
info.setPipsAmount(pips);
|
|
} catch (Exception e) {
|
log.error("计算小数位信息失败,使用默认值", e);
|
info.setDecimals(2);
|
info.setPips(new BigDecimal("0.01"));
|
info.setPipsAmount(new BigDecimal("0.01"));
|
}
|
|
return info;
|
}
|
|
/**
|
* 创建Item对象
|
*/
|
private Item createItemFromETFData(ETFData etf, RealTimeData realtimeData) {
|
Item item = new Item();
|
|
// 生成UUID
|
String uuid = UUID.randomUUID().toString().replace("-", "");
|
item.setUuid(uuid);
|
|
// 基础信息
|
item.setName(etf.getName());
|
item.setEnName(etf.getName());
|
item.setSymbol(etf.getTicker());
|
item.setSymbolData(etf.getTicker());
|
item.setSymbolFullName(etf.getName());
|
|
// 计算小数位信息
|
item.setDecimals(2);
|
item.setPips(new BigDecimal("0.01"));
|
item.setPipsAmount(new BigDecimal("0.01"));
|
|
// 固定值设置
|
item.setAdjustmentValue(BigDecimal.ZERO);
|
item.setUnitAmount(new BigDecimal("1000"));
|
item.setUnitFee(new BigDecimal("30"));
|
item.setMarket("indices");
|
item.setMultiple(BigDecimal.ZERO);
|
item.setBorrowingRate(BigDecimal.ZERO);
|
item.setType("indices");
|
item.setCategory("Miscellaneous");
|
item.setSorted("100");
|
item.setOpenCloseType("indices");
|
item.setFake("0");
|
item.setShowStatus("1");
|
item.setTradeStatus("1");
|
item.setQuoteCurrency("USDT");
|
item.setFaceValue(0.01);
|
|
// 创建时间和更新时间
|
item.setCreateTime(new Date());
|
item.setUpdateTime(new Date());
|
|
return item;
|
}
|
|
/**
|
* 小数位信息辅助类
|
*/
|
@Data
|
private static class ETFDecimalInfo {
|
private Integer decimals;
|
private BigDecimal pips;
|
private BigDecimal pipsAmount;
|
}
|
}
|