feat: retifive K线

This commit is contained in:
vercel
2024-04-30 17:11:45 +08:00
parent e6a65ec416
commit 6a4531803f
6 changed files with 317 additions and 162 deletions

View File

@@ -83,11 +83,11 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!--<dependency> <dependency>
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId> <artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.3</version> <version>3.13.3</version>
</dependency>--> </dependency>
<dependency> <dependency>
<groupId>com.github.pagehelper</groupId> <groupId>com.github.pagehelper</groupId>

View File

@@ -1,87 +1,87 @@
//package cn.stock.market.infrastructure.redis; package cn.stock.market.infrastructure.redis;
//
//import java.time.LocalDateTime; import java.time.LocalDateTime;
//import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
//import java.util.Map; import java.util.Map;
//import java.util.Map.Entry; import java.util.Map.Entry;
//import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
//
//import org.redisson.api.RLock; import org.redisson.api.RLock;
//import org.redisson.api.RedissonClient; import org.redisson.api.RedissonClient;
//
//import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
//import com.google.common.collect.Maps; import com.google.common.collect.Maps;
//
//import cn.qutaojing.common.aop.distributedlock.DistributedLockCallback; import cn.qutaojing.common.aop.distributedlock.DistributedLockCallback;
//import cn.qutaojing.common.aop.distributedlock.DistributedLockInfo; import cn.qutaojing.common.aop.distributedlock.DistributedLockInfo;
//import cn.qutaojing.common.aop.distributedlock.DistributedLockTemplate; import cn.qutaojing.common.aop.distributedlock.DistributedLockTemplate;
//import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
//
///** /**
// * *
// * title: SingleDistributedLockTemplate.java * title: SingleDistributedLockTemplate.java
// * *
// * @author xlfd * @author xlfd
// * @email xlfd@gmail.com * @email xlfd@gmail.com
// * @version 1.0 * @version 1.0
// * @created Sep 1, 2020 5:03:20 PM * @created Sep 1, 2020 5:03:20 PM
// */ */
//@Slf4j @Slf4j
//public class SingleDistributedLockTemplate implements DistributedLockTemplate { public class SingleDistributedLockTemplate implements DistributedLockTemplate {
// private RedissonClient redisson; private RedissonClient redisson;
// DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");
//
// public SingleDistributedLockTemplate() { public SingleDistributedLockTemplate() {
// } }
//
// public SingleDistributedLockTemplate(RedissonClient redisson) { public SingleDistributedLockTemplate(RedissonClient redisson) {
// this.redisson = redisson; this.redisson = redisson;
// } }
//
// @Override @Override
// public <T> T lock(DistributedLockCallback<T> callback, DistributedLockInfo... lockInfoList) throws Throwable { public <T> T lock(DistributedLockCallback<T> callback, DistributedLockInfo... lockInfoList) throws Throwable {
// Stopwatch stopwatch = Stopwatch.createStarted(); Stopwatch stopwatch = Stopwatch.createStarted();
// Map<RLock, DistributedLockInfo> lockMap = Maps.newConcurrentMap(); Map<RLock, DistributedLockInfo> lockMap = Maps.newConcurrentMap();
//
// for (DistributedLockInfo info : lockInfoList) { for (DistributedLockInfo info : lockInfoList) {
// RLock lock = getLock(info.getLockName(), info.getFairLock()); RLock lock = getLock(info.getLockName(), info.getFairLock());
// lockMap.put(lock, info); lockMap.put(lock, info);
// } }
//
// try { try {
// for (Entry<RLock, DistributedLockInfo> entry : lockMap.entrySet()) { for (Entry<RLock, DistributedLockInfo> entry : lockMap.entrySet()) {
// RLock lock = entry.getKey(); RLock lock = entry.getKey();
// DistributedLockInfo info = entry.getValue(); DistributedLockInfo info = entry.getValue();
// log.info("{}-准备获取{}分布式锁:{}", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName()); log.info("{}-准备获取{}分布式锁:{}", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName());
// lock.lock(info.getLeaseTime(), info.getTimeUnit()); lock.lock(info.getLeaseTime(), info.getTimeUnit());
// log.info("{}-{}分布式锁:{}获取成功, 耗时:{} ms", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName(), stopwatch.elapsed(TimeUnit.MILLISECONDS)); log.info("{}-{}分布式锁:{}获取成功, 耗时:{} ms", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
// } }
//
// return callback.process(); return callback.process();
// } finally { } finally {
// for (Entry<RLock, DistributedLockInfo> entry : lockMap.entrySet()) { for (Entry<RLock, DistributedLockInfo> entry : lockMap.entrySet()) {
// RLock lock = entry.getKey(); RLock lock = entry.getKey();
// DistributedLockInfo info = entry.getValue(); DistributedLockInfo info = entry.getValue();
// if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) { if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
// lock.unlock(); lock.unlock();
// log.info("{}-{}分布式锁:{}释放成功, 耗时:{} ms", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName(), stopwatch.elapsed(TimeUnit.MILLISECONDS)); log.info("{}-{}分布式锁:{}释放成功, 耗时:{} ms", dateTimeFormatter.format(LocalDateTime.now()), info.getMessage(), info.getLockName(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
// } }
// } }
//
// } }
// } }
//
// private RLock getLock(String lockName, boolean fairLock) { private RLock getLock(String lockName, boolean fairLock) {
// RLock lock; RLock lock;
// if (fairLock) { if (fairLock) {
// lock = redisson.getFairLock(lockName); lock = redisson.getFairLock(lockName);
// } else { } else {
// lock = redisson.getLock(lockName); lock = redisson.getLock(lockName);
// } }
// return lock; return lock;
// } }
//
// public void setRedisson(RedissonClient redisson) { public void setRedisson(RedissonClient redisson) {
// this.redisson = redisson; this.redisson = redisson;
// } }
//} }

View File

@@ -1,70 +1,71 @@
//package cn.stock.market.infrastructure.redis.config; package cn.stock.market.infrastructure.redis.config;
//
//import java.time.Duration; import java.time.Duration;
//
//import org.redisson.api.RedissonClient; import org.redisson.api.RedissonClient;
//import org.springframework.cache.CacheManager; import org.redisson.api.RedissonClient;
//import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.CacheManager;
//import org.springframework.context.annotation.Bean; import org.springframework.cache.annotation.EnableCaching;
//import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Bean;
//import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.context.annotation.Configuration;
//import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheConfiguration;
//import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.cache.RedisCacheManager;
//import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.connection.RedisConnectionFactory;
//import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.core.RedisTemplate;
//import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
//import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext;
// import org.springframework.data.redis.serializer.StringRedisSerializer;
//import cn.qutaojing.common.aop.distributedlock.DistributedLockTemplate;
//import cn.stock.market.infrastructure.redis.SingleDistributedLockTemplate; import cn.qutaojing.common.aop.distributedlock.DistributedLockTemplate;
// import cn.stock.market.infrastructure.redis.SingleDistributedLockTemplate;
///**
// * /**
// * @author xlfd *
// * @email xlfd@gmail.com * @author xlfd
// * @version 1.0 * @email xlfd@gmail.com
// * @created Jun 3, 2021 4:56:28 PM * @version 1.0
// */ * @created Jun 3, 2021 4:56:28 PM
//@Configuration */
//@EnableCaching @Configuration
//public class RedisConfig { @EnableCaching
// public class RedisConfig {
// @Bean
// public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { @Bean
// RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// redisTemplate.setConnectionFactory(redisConnectionFactory); RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); redisTemplate.setConnectionFactory(redisConnectionFactory);
// redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
// redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// return redisTemplate;
// } return redisTemplate;
// }
// @Bean
// public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { @Bean
// // 配置序列化 public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); // 配置序列化
// config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class))); config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
// config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)));
// // 设置缓存的默认过期时间 ,30分钟
// config.entryTtl(Duration.ofMinutes(30)); // 设置缓存的默认过期时间 ,30分钟
// // 不缓存空值 config.entryTtl(Duration.ofMinutes(30));
// config.disableCachingNullValues(); // 不缓存空值
// RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config) config.disableCachingNullValues();
// .build(); RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config)
// return cacheManager; .build();
// } return cacheManager;
// }
// /**
// * 分布式锁实现 /**
// * @param redissonClient * 分布式锁实现
// * @return * @param redissonClient
// */ * @return
// @Bean */
// public DistributedLockTemplate distributedLockTemplate(RedissonClient redissonClient) { @Bean
// return new SingleDistributedLockTemplate(redissonClient); public DistributedLockTemplate distributedLockTemplate(RedissonClient redissonClient) {
// } return new SingleDistributedLockTemplate(redissonClient);
//} }
}

View File

@@ -1,20 +1,34 @@
package cn.stock.market.lesg; package cn.stock.market.lesg;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.stock.market.domain.basic.entity.RetifiveStock; import cn.stock.market.domain.basic.entity.RetifiveStock;
import cn.stock.market.dto.RetifiveStockInfo; import cn.stock.market.dto.RetifiveStockInfo;
import cn.stock.market.dto.StockHistoryResponse;
import cn.stock.market.utils.RefinitivUtil; import cn.stock.market.utils.RefinitivUtil;
import com.ag.exception.SysTipsException;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.refinitiv.ema.access.*; import com.refinitiv.ema.access.*;
import com.refinitiv.ema.rdm.EmaRdm; import com.refinitiv.ema.rdm.EmaRdm;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner; import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.SECONDS;
/** /**
* RefinitivConsumer * RefinitivConsumer
@@ -25,6 +39,9 @@ import java.util.List;
@Service @Service
@Slf4j @Slf4j
public class RefinitivConsumer implements ApplicationRunner { public class RefinitivConsumer implements ApplicationRunner {
@Resource
private RedisTemplate redisTemplate;
@Value("${refinitiv.market-data.service-name}") @Value("${refinitiv.market-data.service-name}")
private String serviceName; private String serviceName;
@@ -40,12 +57,29 @@ public class RefinitivConsumer implements ApplicationRunner {
@Value("${refinitiv.market-data.batch-request-timeout}") @Value("${refinitiv.market-data.batch-request-timeout}")
private long timeout; private long timeout;
@Value("${refinitiv.token.username}")
private String tokenUsername;
@Value("${refinitiv.token.password}")
private String tokenPassword;
@Value("${refinitiv.token.appKey}")
private String appKey;
@Value("${refinitiv.token.url}")
private String tokenUrl;
private OmmConsumer consumer = null; private OmmConsumer consumer = null;
public static final String TOP_ACTIVES_SYMBOL = ".AV.BO"; public static final String TOP_ACTIVES_SYMBOL = ".AV.BO";
public static final String TOP_GAINERS_SYMBOL = ".PG.BO"; public static final String TOP_GAINERS_SYMBOL = ".PG.BO";
public static final String TOP_LOSERS_SYMBOL = ".PL.BO"; public static final String TOP_LOSERS_SYMBOL = ".PL.BO";
public static final String SCOPE = "trapi";
public static final String GRANT_TYPE = "password";
public InstrumentData[] getDataBySymbol(String symbol) throws Exception { public InstrumentData[] getDataBySymbol(String symbol) throws Exception {
String[] items = symbol.split(","); String[] items = symbol.split(",");
log.info("Quote request for: {}", java.util.Arrays.toString(items)); log.info("Quote request for: {}", java.util.Arrays.toString(items));
@@ -164,13 +198,19 @@ public class RefinitivConsumer implements ApplicationRunner {
* @throws Exception e * @throws Exception e
*/ */
public List<RetifiveStockInfo> getTopStockList(String symbol) throws Exception { public List<RetifiveStockInfo> getTopStockList(String symbol) throws Exception {
List<RetifiveStockInfo> list = new ArrayList<>();
InstrumentData[] result = getDataBySymbol(symbol); InstrumentData[] result = getDataBySymbol(symbol);
if (result.length == 0 || isInvalidResult(result[0])) { if (result.length == 0 || isInvalidResult(result[0])) {
return new ArrayList<>(); return list;
} }
List<String> topSymbols = RefinitivUtil.decodeTopData(result[0]); List<String> topSymbols = RefinitivUtil.decodeTopData(result[0]);
return getStockList(String.join(",", topSymbols)); for (String topSymbol : topSymbols) {
RetifiveStockInfo stockInfo = getDetail(topSymbol);
list.add(stockInfo);
}
return list;
} }
/** /**
@@ -214,4 +254,100 @@ public class RefinitivConsumer implements ApplicationRunner {
consumer.uninitialize(); consumer.uninitialize();
} }
} }
public JSONArray getKLink(String symbol,String resolution){
String token = getToken();
if(StringUtils.isBlank(token)){
throw new SysTipsException("token为空");
}
JSONObject data = JSONObject.parseObject(token);
token = data.getString("access_token");
String baseURL = "https://api.refinitiv.com/data/historical-pricing/v1/views/intraday-summaries/";
int countback = 5;
String interval = null;
if(StringUtils.equals("H",resolution)){
countback = 30;
interval = "PT1H";
}else if(StringUtils.equals("D",resolution)){
interval ="PT1D";
}else if(StringUtils.equals("W",resolution)){
countback = 30;
interval ="PT1W";
}else if(StringUtils.equals("M",resolution)){
countback = 30;
interval ="PT1M";
}
String resourceEndpoint = baseURL + symbol;
HttpResponse response = HttpUtil.createGet(resourceEndpoint)
.form("eventTypes","trade,quote")
.form("start","2023-05-11T18:30:12.000000000Z")
.form("count",countback)
.form("interval",interval)
.header("Authorization","Bearer " + token)
.execute();
if(response.getStatus() != 200){
throw new SysTipsException("获取股票K线失败");
}
return JSONArray.parseArray(response.body());
}
public String getToken(){
String toekn = null;
if(ObjectUtil.isNotNull(redisTemplate.opsForValue().get("token"))){
toekn = redisTemplate.opsForValue().get("token").toString();
}
if(StringUtils.isNotBlank(toekn)){
JSONObject data = JSONObject.parseObject(toekn);
if(Long.valueOf(data.get("expiry_tm").toString()) > System.currentTimeMillis()){
return toekn;
}
toekn = requestNewToken(data.get("refresh_token").toString());
return toekn;
}
toekn = requestNewToken("");
return toekn;
}
public String requestNewToken(String refreshToken){
if(StringUtils.isNotBlank(refreshToken)){
HttpResponse response = HttpUtil.createPost(tokenUrl)
.form("refresh_token",refreshToken)
.form("grant_type","refresh_token")
.header("Content-Type", "application/x-www-form-urlencoded")
.basicAuth(appKey,"")
.execute();
if(response.getStatus() != 200){
throw new SysTipsException("获取token失败");
}
JSONObject data = JSONObject.parseObject(response.body());
data.put("expiry_tm",System.currentTimeMillis() + (Long.valueOf(data.get("expires_in").toString())-10));
String toekn = data.toString();
redisTemplate.opsForValue().set("token",toekn);
redisTemplate.expire("token",1, TimeUnit.DAYS);
return toekn;
}
HttpResponse response = HttpUtil.createPost(tokenUrl)
.form("username",tokenUsername)
.form("password",tokenPassword)
.form("grant_type","password")
.form("scope",SCOPE)
.form("takeExclusiveSignOnControl","true")
.header("Content-Type", "application/x-www-form-urlencoded")
.basicAuth(appKey,"")
.execute();
if(response.getStatus() != 200){
throw new SysTipsException("获取token失败");
}
JSONObject data = JSONObject.parseObject(response.body());
data.put("expiry_tm",System.currentTimeMillis() + (Long.valueOf(data.get("expires_in").toString())-10));
String toekn = data.toString();
redisTemplate.opsForValue().set("token",toekn);
redisTemplate.expire("token",1, TimeUnit.DAYS);
return toekn;
}
} }

View File

@@ -131,4 +131,17 @@ public class RefinitivApiController {
return ServerResponse.createByError(); return ServerResponse.createByError();
} }
@ApiOperation(value = "查询股票K线", httpMethod = "GET", response = RetifiveStockInfo.class)
@ApiImplicitParams({
@ApiImplicitParam(name = "symbol",value = "股票对应代码symbol",dataType ="String",required = true, paramType = "query"),
@ApiImplicitParam(name = "symbol",value = "股票对应代码symbol",dataType ="String",required = true, paramType = "query"),
@ApiImplicitParam(name = "resolution", value = "单位:60 1D 1W 1D 对应H,D,W,Y", required = true, dataType = "String", paramType = "query"),
})
@GetMapping("/getKlink")
@EncryptFilter(decryptRequest = false)
public ServerResponse<?> getKlink(@RequestParam("symbol") String symbol, @RequestParam String resolution) {
return ServerResponse.createBySuccess(refinitivConsumer.getKLink(symbol,resolution));
}
} }

View File

@@ -3,7 +3,7 @@ spring:
show-sql: true show-sql: true
# Redis配置 # Redis配置
redis: redis:
host: 43.132.212.180 host: 149.88.86.7
password: ruTZ9J3gaDhknJ password: ruTZ9J3gaDhknJ
port: 36379 port: 36379
database: 1 database: 1
@@ -67,3 +67,8 @@ refinitiv:
password: 8EyCWDQ%XITcGRM@RhKokxX05FZJe1 password: 8EyCWDQ%XITcGRM@RhKokxX05FZJe1
clientId: 662b37b071144c9f8f2507d93037a019a010ae0e clientId: 662b37b071144c9f8f2507d93037a019a010ae0e
batch-request-timeout: 60000 batch-request-timeout: 60000
token:
username: GE-A-10288435-3-16229
password: Pyc^U)D9k-u))N8n+M3zYa=,+CoQujkvjizR
appKey: 87aa0e9fcb034b2aa63344bc41cc3a3f8aeadd6c
url: https://api.refinitiv.com/auth/oauth2/v1/token