1.1各网站汇率抓取说明
<p>各网站汇率抓取说明</p>
<p>基础汇率DTO说明:</p>
<p>@ApiModel("基础汇率记录DTO")
public class BaseExchangeRateHistoryDTO extends CloneSupport<BaseExchangeRateHistoryDTO> implements Serializable {</p>
<pre><code>private static final long serialVersionUID = -3322025815601555217L;
@ApiModelProperty(value = "币种类型")
private AssetType coinType;
@ApiModelProperty(value = "法币类型 -1.CNY -2. USD")
private AssetType priceUnit;
@ApiModelProperty(value = "交易对 1 CNY/USDT 2 CNY/USD 3 USD/USDT 4 CNY/BTC")
private RateTradingEnum priceTrading;
@ApiModelProperty(value = "数据来源")
private RateSourceEnum apiSource;
@ApiModelProperty(value = "价格")
private BigDecimal price;
@ApiModelProperty(value = "数据来源")
private ApiInfoEnum apiInfo;
@ApiModelProperty(value = "1是买价,2是卖价")
private Integer direction;
@ApiModelProperty(value = "接口响应毫秒")
private Long duration;
@ApiModelProperty(value = "HTTP响应前1024字符")
private String response;
@ApiModelProperty(value = "具体的数据来源说明")
private String apiInfoDes;</code></pre>
<p>}</p>
<p>/**</p>
<ul>
<li>汇率抓取数据库映射实体类</li>
<li>
</li>
<li>
<p>@date 2021-12-01
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("fetch_exchange_rate")
@ApiModel(value = "FetchExchangeRate对象", description = "抓取汇率")
public class FetchExchangeRatePO extends BaseEntity<FetchExchangeRatePO> implements Serializable{</p>
<p>@ApiModelProperty(value = "获取批次")
@TableField("fetch_batches")
private String fetchBatches;</p>
<p>@ApiModelProperty(value = "币种类型 1 USDT 2 ETH")
@TableField("coin_type")
private Integer coinType;</p>
<p>@ApiModelProperty(value = "法币类型 -1:CNY")
@TableField("price_unit")
private Integer priceUnit;</p>
<p>@ApiModelProperty(value = "易类型 1:买 2:卖")
@TableField("trade_type")
private Integer tradeType;</p>
<p>@ApiModelProperty(value = "价格")
@TableField("price")
private BigDecimal price;</p>
<p>@ApiModelProperty(value = "数据来源")
@TableField("api_name")
private String apiName;</p>
<p>@ApiModelProperty(value = "数据来源描述")
@TableField("api_info")
private String apiInfo;</p>
<p>@ApiModelProperty(value = "1正常,0异常")
@TableField("status")
private Integer status;</p>
<p>@ApiModelProperty(value = "网络延迟时间")
@TableField("delay_time")
private Long delayTime;
}</p>
</li>
</ul>
<p>一、中国银行、招商银行、网汇率抓取USD
1.1 模拟请求中国银行现汇显示的汇率列表接口
private final List<FetchExchangeRatePO> sendRequest(String symbol,String fetchId){
List<FetchExchangeRatePO> list= Lists.newArrayList();
String url=setParam("");
log.info("url is {}",url);
Long start = System.currentTimeMillis();
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
HttpPost httpPost =setHttpHead(new HttpPost(url));
String html = "";
try {
response = httpClient.execute(httpPost);
/<strong>判断响应状态为200,进行处理</strong>/
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
html = EntityUtils.toString(response.getEntity(), "utf-8");
}
if(StringUtils.isNotBlank(html)){
list=assembleObjByHtml(html,symbol,fetchId,list,start);
log.info("FetchExchangeRatePOList={}",list);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
org.apache.http.client.utils.HttpClientUtils.closeQuietly(response);
org.apache.http.client.utils.HttpClientUtils.closeQuietly(httpClient);
}
return list;
}
1.2 返回数据做处理,需要的各列数据做数据处理
private final List<FetchExchangeRatePO> assembleObjByHtml(String html,String symbol,String fetchId,List<FetchExchangeRatePO> fetchExchangeRatePOList,Long start) {
Document document = Jsoup.parse(html);
Elements tables = document.getElementsByTag("table");
int tableIndex = -1;
for (int i = 0; i < tables.size(); i++) {
Element element = tables.get(i);
String text = element.text();
if (text.indexOf("货币名称") > -1) {
tableIndex = i;
break;
}
}
if (tableIndex > -1) {
Element table = tables.get(tableIndex);
Elements trs = table.select("tr");
for (int i = 1; i < trs.size(); ++i) {
Element tr = trs.get(i);
Elements tds = tr.select("td");
if(tds!=null && tds.size()==7){
Long delayTime = System.currentTimeMillis() - start;
BigDecimal baseNum=new BigDecimal(100);
//买入汇率
BigDecimal buyRate=StringUtils.isEmpty(tds.get(1).text()) ? BigDecimal.ZERO:new BigDecimal(tds.get(1).text());
//卖出汇率
BigDecimal sellRate=StringUtils.isEmpty(tds.get(3).text()) ? BigDecimal.ZERO:new BigDecimal(tds.get(3).text());
AssetType assetType=symbol.equals(USDT_CNY) ? AssetType.USD : AssetType.CNY;</p>
<pre><code> fetchExchangeRatePOList.add(newInstance(assetType,buyRate.divide(baseNum,6,RoundingMode.DOWN) ,fetchId,
AssetType.CNY.getCode(), TradeDirectionEnum.BUY_FROM_USER.getCode(),"现汇买入价",delayTime));
fetchExchangeRatePOList.add(newInstance(assetType,sellRate.divide(baseNum,6, RoundingMode.DOWN) ,fetchId,
AssetType.CNY.getCode(), TradeDirectionEnum.SELL_TO_USER.getCode(),"现汇卖出价",delayTime));
log.info("newInstance Done");
break;
}
}
}
return fetchExchangeRatePOList;
}</code></pre>
<p>1.3 构建baseExchangeRateHistoryDTO做数据封装
BaseExchangeRateHistoryDTO baseExchangeRateHistoryDTO = new BaseExchangeRateHistoryDTO();
baseExchangeRateHistoryDTO.setCoinType(AssetType.USDT);
baseExchangeRateHistoryDTO.setPriceTrading(RateTradingEnum.CNY_USD);
baseExchangeRateHistoryDTO.setPriceUnit(AssetType.CNY);
baseExchangeRateHistoryDTO.setDuration(delayTime);
baseExchangeRateHistoryDTO.setResponse(responseString);
baseExchangeRateHistoryDTO.setPrice(price);
baseExchangeRateHistoryDTO.setDirection(tradeType);
baseExchangeRateHistoryDTO.setApiInfo(apiInfo);
baseExchangeRateHistoryDTO.setApiSource(apiSource);
log.info("bankChina baseExchangeRateHistoryDTO: {}", JacksonUtils.toJson(baseExchangeRateHistoryDTO));
1.4 数据入库
1.4.1 如果此次抓取时间超过阀值,则放弃此次入库
1.4.2 如果此批次抓取数据返回空或者请求没有响应,或者抓取时间时间超过阀值。则修改通道状态为失败
1.4.3 批量保存数据至基础汇率表baseExchangeRateHistory</p>
<p>二、易币付OTC之USDT买1价、易币付OTC之USDT卖1价
1、逻辑同一、汇率获取是走易币付内部rpc接口获取</p>
<p>三、币安抓取USDT-CNY,BTC-CNY,USDT-USD汇率
1、抓取逻辑同一
2、USDT-CNY 抓取数据为前20条
3、从20条数据里跟据有性性规则过滤(拿到一条有效的数据进行入库) ps:汇率有效性过滤规则是可配置的,具体见易币付综合后台配置</p>
<p>//获取有效汇率RiskManagerAuditBeforeConsumer
CommonExchageResp filterExchangeRate = disposeRateDataService
.disposeRateData(PatformKeyEnum.BN, TradeDirectionEnum.valueOf(directionCode), exchanges);
log.info("【币安】汇率过滤,{},原始汇率条数:{},有效汇率:{}",
apiInfoEnum.getDescription(),exchanges.size(), JSON.toJSONString(filterExchangeRate));
取第六价
baseExchangeRateHistoryDTO.setApiInfo(directionCode.equals(TradeDirectionEnum.BUY_FROM_USER.getCode())
?ApiInfoEnum.BNB_CNY_USDT_SIXTH_BUY_PRICE:ApiInfoEnum.BNB_CNY_USDT_SIXTH_SELL_PRICE);
//保存第六价格
exchangeRateSixthPriceService.saveSixthPrice(exchanges,baseExchangeRateHistoryDTO);
4、BTC-CNY,USDT-USD 取第一条的数据即可</p>
<p>四、各交易所的各币种的平均价、三大交易所平均值
1、从数据库中拿各交易所的交易对的价格做平均值</p>
<p>五、coinbase(api.coinbase.com)网汇率
1、抓取逻辑同一、取第一条数据入库</p>
<p>六、CoinDisposeExchangePrice类 各网站的各币种的USD转换价格
1、这个一个公共的处理类 做币种USD转换
2、参数传要处理的汇率数据(list)
3、取银行的默认汇率数据(默认USD:CNY现汇买入价(%s) 默认USD:CNY现汇卖出价(%s))
4、做数据转换
BigDecimal usdUsdtPrice = baseExchangeRate.getPrice();
BigDecimal cnyUsdPrice = bankChinaExchangeRateDTO.getPrice();
BigDecimal thePrice = NumberUtil.mul(usdUsdtPrice, cnyUsdPrice);
baseExchangeRateHistoryDTO.setPrice(thePrice);
5、数据转换后做数据封装
BaseExchangeRateHistoryDTO baseExchangeRateHistoryDTO = new BaseExchangeRateHistoryDTO();
baseExchangeRateHistoryDTO.setCoinType(AssetType.USDT);
baseExchangeRateHistoryDTO.setPriceTrading(RateTradingEnum.CNY_USDT);
baseExchangeRateHistoryDTO.setPriceUnit(AssetType.CNY);
baseExchangeRateHistoryDTO.setDuration(0L);
baseExchangeRateHistoryDTO.setResponse(JacksonUtils.toJson(baseExchangeRate));</p>
<pre><code> BigDecimal usdUsdtPrice = baseExchangeRate.getPrice();
BigDecimal cnyUsdPrice = bankChinaExchangeRateDTO.getPrice();
BigDecimal thePrice = NumberUtil.mul(usdUsdtPrice, cnyUsdPrice);
baseExchangeRateHistoryDTO.setPrice(thePrice);
baseExchangeRateHistoryDTO.setDirection(baseExchangeRate.getDirection());
if(ApiInfoEnum.OKEX_USD_USDT_FIRST_BUY_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.OKEX_BANKCHINA_CNY_USDT_FIRST_BUY_PRICE);
}
if(ApiInfoEnum.OKEX_USD_USDT_FIRST_SELL_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.OKEX_BANKCHINA_CNY_USDT_FIRST_SELL_PRICE);
}
if(ApiInfoEnum.HT_USDT_USD_FIRST_BUY_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.HT_BANKCHINA_CNY_USDT_FIRST_BUY_PRICE);
}
if(ApiInfoEnum.HT_USDT_USD_FIRST_SELL_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.HT_BANKCHINA_CNY_USDT_FIRST_SELL_PRICE);
}
if(ApiInfoEnum.BNB_USDT_USD_FIRST_BUY_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.BNB_BANKCHINA_CNY_USDT_FIRST_BUY_PRICE);
}
if(ApiInfoEnum.BNB_USDT_USD_FIRST_SELL_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.BNB_BANKCHINA_CNY_USDT_FIRST_SELL_PRICE);
}
if(ApiInfoEnum.COINBASE_USDT_USD_FIRST_BUY_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.COINBASE_BANKCHINA_CNY_USDT_FIRST_BUY_PRICE);
}
if(ApiInfoEnum.COINBASE_USDT_USD_FIRST_SELL_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.COINBASE_BANKCHINA_CNY_USDT_FIRST_SELL_PRICE);
}
if(ApiInfoEnum.OTC_CNY_USDT_AVG_FIRST_BUY_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.OTC_BANKCHINA_CNY_USDT_AVG_FIRST_BUY_PRICE);
}
if(ApiInfoEnum.OTC_CNY_USDT_AVG_FIRST_SELL_PRICE.getId().equals(baseExchangeRate.getApiInfoId())){
baseExchangeRateHistoryDTO.setApiInfo(ApiInfoEnum.OTC_BANKCHINA_CNY_USDT_AVG_FIRST_SELL_PRICE);
}</code></pre>
<p>6、数据入库
baseExchangeRateService.mergeBaseExchangeRate(baseExchangeRateHistoryDTO, true);</p>
<p>七、DisposeRateDataService类 处理抓取汇率数据
1、各网站抓取的汇率,需要做有效性过过滤的、调用这个公共方法即可
2、传参有数据来源,交易对,要处理的数据集合
3、查询汇率有效性配置数据
ExchangeRateValidSettingsPO exchangeRateValidSettingsPO = getExchangeRateValidSettings(tradeDirectionEnum.getCode());
4、汇率有效性过滤(从传入的数据集合中根据配置的规则过滤出一条满足条件的数据返回入库)
CommonExchageResp commonExchageResp = null;
List<CommonExchageResp> dataList = new ArrayList<>();
for (CommonExchageResp data:rateDataList) {
//交易限额最低值(人民币)> 5万
if(data.getDownLimit().compareTo(new BigDecimal(Integer.parseInt(exchangeRateValidSettingsPO.getMin().toString()))) > 0){
log.info("处理抓取汇率数据 最低限额 > 汇率有效性配置值 downLimit={},min={}"
,data.getDownLimit().toString(), exchangeRateValidSettingsPO.getMin().toString());
continue;
}
//交易限额最高值于最低值(人民币)的差 < 5000
if(data.getUpLimit().subtract(data.getDownLimit()).compareTo(
new BigDecimal(Integer.parseInt(exchangeRateValidSettingsPO.getDiff().toString()))) < 0){
log.info("处理抓取汇率数据 交易限额最高值于最低值(人民币)的差 < 汇率有效性配置值 upLimit-downLimit={}, diff={}"
,data.getUpLimit().subtract(data.getDownLimit()).toString()
,exchangeRateValidSettingsPO.getDiff().toString());
continue;
}
//挂单人成交量 < 20
if(data.getTradingVolume()<exchangeRateValidSettingsPO.getTrading()){
log.info("处理抓取汇率数据 挂单人成交量 < 汇率有效性配置值 tradingVolume={}, trading={}"
,data.getTradingVolume().toString(),exchangeRateValidSettingsPO.getTrading().toString());
continue;
}
//挂单人成交率 < 90%
if (data.getCloseRate().multiply(Constant.HUNDRED).compareTo(exchangeRateValidSettingsPO.getTradingRate()) < 0) {
log.info("处理抓取汇率数据 挂单人成交率 < 汇率有效性配置值 closeRate={}, tradingRate={}"
,data.getCloseRate().multiply(Constant.HUNDRED).toString(),
exchangeRateValidSettingsPO.getTradingRate().toString());
continue;
}
dataList.add(data);
}
log.info("处理抓取汇率数据 筛选数据count={}", dataList.size());
if(dataList.size()< Constant.RATE_VALID_NUM){
log.info("处理抓取汇率数据 筛选数据小于"+Constant.RATE_VALID_NUM+"条");
//芒果告警
baseExchangeRateService.sendRateWarnMangoMsg(patformKeyEnum.getPatformName(), patformKeyEnum.getKey());
return null;
}
//可用数据列表的价格平均值
BigDecimal priceSum = dataList.stream().map(CommonExchageResp::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal priceAvg = priceSum.divide(new BigDecimal(dataList.size()), 8, RoundingMode.CEILING);
log.info("处理抓取汇率数据 筛选数据priceAvg={}", priceAvg.toString());
//从满足的数据List里取一条最可靠的数据
for (CommonExchageResp c:dataList) {
//当前一条汇率金额/所有可用数据平均值 计算结果是否大于1% 不可用
if(c.getPrice().divide(priceAvg, 4, RoundingMode.CEILING).subtract(BigDecimal.ONE).abs()
.compareTo(exchangeRateValidSettingsPO.getDiffRate()) <= 0){
log.info("处理抓取汇率数据 当前一条汇率金额/所有可用数据平均值-1={},diffRate={}",c.getPrice()
.divide(priceAvg, 4, RoundingMode.CEILING).subtract(BigDecimal.ONE).abs().toString(),
exchangeRateValidSettingsPO.getDiffRate().toString());
log.info("处理抓取汇率数据 可用数据c="+JacksonUtils.toJson(c));
commonExchageResp = c;
break;
}
}
return commonExchageResp;
}</p>
<p>八、非小号、火币、抹茶、OverBit、
1、抓取逻辑同一,取第一条数据即可</p>
<p>九、okex
1、抓取前20条数据
2、做有效性过滤
//获取有效汇率
CommonExchageResp filterExchangeRate = disposeRateDataService
.disposeRateData(PatformKeyEnum.OKX,TradeDirectionEnum.valueOf(directionCode), exchanges);
log.info("【Okx】汇率过滤,原始汇率条数:{},有效汇率:{}",exchanges.size(), JSON.toJSONString(filterExchangeRate));
if(Objects.nonNull(filterExchangeRate)){
thePrice=filterExchangeRate.getPrice();
}</p>
<p>baseExchangeRateHistoryDTO.setApiInfo(directionCode.equals(TradeDirectionEnum.BUY_FROM_USER.getCode())
?ApiInfoEnum.OKX_CNY_USDT_SIXTH_BUY_PRICE:ApiInfoEnum.OKX_CNY_USDT_SIXTH_SELL_PRICE);
//保存第六价格
exchangeRateSixthPriceService.saveSixthPrice(exchanges,baseExchangeRateHistoryDTO);
3、取第六价</p>