金融风控中使用注解实现一个策略模式
基金,股票,期货,债券,衍生品…… 金融工具多种多样,每种不同的金融工具有不同的风险控制需求,如何实现根据特定的种类自动去找对应的实现策略?
选用非传统的策略模式
注解+lmada表达式实现,下面以期货策略为例
自定义策略注解
使用自定义注解表明是什么模块是什么策略,其中有两个层级的注解,方法上和类上面,类上面表示模块,方法上的注解表示策略
package com.lowrisk.strategy.annotation;
import java.lang.annotation.*;
/**
* @author Riusky
*/
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Strategy {
/**
* 标识不同的模块 product(产品风控)、credit(信用风控)
*/
String module() default "";
/**
* 具体的策略类型
*/
String value() default "";
}
定义一个函数式接口
对应不同模块的不同策略我们在方法上实现这个接口 方法上*
package com.lowrisk.strategy.ifce;
/**
*
* @param <T> 策略的入参类型
* @param <R> 策略的返回值类型
*
*/
@FunctionalInterface
public interface IStrategyHandler<T, R> {
/**
* 策略
* @param param 参数
*/
R apply(T param);
/*
* 需要确定传入的参数 和 返回的参数
* 这里使用泛型 具体实现具体讨论
* java1.8之后提供的一个注解 @FunctionalInterface 使用在接口上
* @FunctionlInterface
* 1.通常用于函数式编程
* 2.除了可以和普通接口一样写Impl之外,还可通过Lambda表达式进行构造,而不用写Impl class。
* @FunctionlInterface的使用规则
* 1.该注解只能标记在"有且仅有一个抽象方法"的接口上,该接口继承的接口的抽象方法也计算在内。
* 2.被注解的接口可以有默认方法/静态方法,或者重写Object的方法
* 静态方法可以使用接口.方法名() 默认方法给实现类用的,需要实现类对象的方式调用
*
* 实现该接口的基础类 可以直接使用lamda的方式 return parms -> {} 形式实现
* */
}
实现ApplicationContextAware 的方法
实现ApplicationContextAware 的方法在项目启动时将其类上和方法上的注解作为k,对应的实现作为value加入到一个全局的Map<String, IStrategyHandler<T, R>>,其中k = 类上的注解值+“####”+方法上的注解值,value相当于一个lamda实现
package com.lowrisk.strategy.Context;
import com.lowrisk.strategy.ifce.IStrategyHandler;
import com.lowrisk.strategy.annotation.Strategy;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 项目初始化时需要加载所有的策略到一个 k,v 容器中 ,实现ApplicationContextAware初始化加载
* ApplicationContextAware 相关说明: https://blog.csdn.net/weixin_42437243/article/details/106195298
* 当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean
*
* @author Riusky
*/
public abstract class AbstractStrategyContext<T, R> implements ApplicationContextAware {
/**
* spring上下文公用的策略处理Map 每个key 对应一个具体执行的策略
* implMap 策略实现的 k,v 存储
*/
private Map<String, IStrategyHandler<T, R>> implMap = new ConcurrentHashMap<>();
private static final Logger log = LoggerFactory.getLogger(AbstractStrategyContext.class);
private final String DELIMITER = "####";
/**
* 获得bean 的class
*
* @param <K> 类型
*/
protected abstract <K> Class<K> getClazz();
/**
* 返回spring中的beanName
*/
protected abstract String getBeanName();
/**
* 执行函数
*
* @param strategy 策略类型
* @param module 模块
* @param param 参数
* @return
*/
public R execute(String module, String strategy, T param) {
String key = StringUtils.join(module, DELIMITER, strategy);
IStrategyHandler<T, R> handler = implMap.get(key);
log.debug("策略实现集合{}", implMap);
if (handler == null) {
throw new RuntimeException("没有找到具体的实现,该实现为: " + key);
}
R apply = handler.apply(param);
return apply;
}
@Override
@SuppressWarnings("unchecked")
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.error("AbstractStrategy 执行");
Class<Object> clazz = getClazz();
Object bean = applicationContext.getBean(getBeanName(), clazz);
Strategy strategyAnnotation = bean.getClass().getAnnotation(Strategy.class);
if (strategyAnnotation == null) {
log.error("类[{}]没有添加Strategy注解", clazz);
return;
}
// 模块的名称
String module = strategyAnnotation.module();
// getDeclaredMethod() 获取的是类自身声明的所有方法,包含public、protected和private方法。
// 相关说明: https://blog.csdn.net/gao_chun/article/details/42875575
Method[] declaredMethods = clazz.getDeclaredMethods();
if (ArrayUtils.isEmpty(declaredMethods)) {
//一个方法都没有肯定没有策略注解的方法
throw new RuntimeException(clazz + "没有添加相关策略方法");
}
// productid####strategyA
// productid####strategyB
for (Method declaredMethod : declaredMethods) {
Strategy annotation = declaredMethod.getAnnotation(Strategy.class);
if (annotation == null) {
//没有注解的不加入通用策略Map中
continue;
}
try {
// 用module和 四个 #### 加上 value 组成map的key
String key = StringUtils.join(module, DELIMITER, annotation.value());
//返回的是函数式接口的实现体 类似于 IStrategyHandler<T, R> handler = new IStrategyHandler<T, R> ();
//让这个策略方法转变为有具体实现的策略方法
IStrategyHandler<T, R> handler = (IStrategyHandler<T, R>) declaredMethod.invoke(bean);
implMap.put(key, handler);
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("初始化模块加入发生了错误,模块[{}]策略未能添加到spring上下文中", module, e);
}
}
}
}
创建一个期货的上下文继承抽象的策略上下文,即上面哪个类
重载其中的两个方法,getClazz(),getBeanName() ,这两个方法是被扫描的Bean,其他类型的资产同理(基金,股票,债券……),这样项目启动时,就会找到具体实现类上的注解,并执行setApplicationContext() 设置Map结构中的数据,这样就可以根据不同的条件走Map中不同的策略实现
package com.lowrisk.strategy.Context.ext;
import com.lowrisk.strategy.Context.AbstractStrategyContext;
import com.lowrisk.strategy.impl.FutureStrategyImpl;
import org.springframework.stereotype.Component;
/**
* 期货限制的上下文 这里可以对每个期货设置统一的方法
* @author Riusky
* '@SuppressWarnings('rawtypes')' 忽略泛型代码的原型使用警告
* */
@Component
@SuppressWarnings("rawtypes")
public class FutureStrategyContext extends AbstractStrategyContext {
public static final String FUTURE_STRATEGY_IMPL = "futureStrategyImpl";
@Override
public Class<FutureStrategyImpl> getClazz() {
return FutureStrategyImpl.class;
}
@Override
public String getBeanName() {
return FUTURE_STRATEGY_IMPL;
}
}
设计期货具体的实现类
可以看到期货上下文需要一个具体的策略功能实现类,下面设计一个具体功能的实现类,有通用接口+实现类组成,有3个通用的策略,传入参数校验策略,衍生数据补齐(即需要通过数据库,接口等获取数据)策略,通过策略,通过数据补齐策略,可以实现数据和具体的判断策略解耦,性能优化也放到数据补齐上面
1.通用的策略接口和通用的私有方法
package com.lowrisk.strategy.impl.commomifce;
import com.lowrisk.strategy.dto.DeriveDTO;
import com.lowrisk.strategy.dto.InDTO;
import com.lowrisk.strategy.dto.OutDTO;
import com.lowrisk.strategy.dto.future.FutureInDto;
import com.lowrisk.strategy.dto.future.FutureOutDto;
import com.lowrisk.strategy.ifce.IStrategyHandler;
import org.apache.poi.ss.formula.functions.T;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.Future;
/**
* @author Riusky
*/
public interface CommonStrategyIfce {
/**
* 参数校验的策略
*/
public IStrategyHandler<? extends InDTO, ? extends OutDTO> paramsCheck();
/**
* 衍生数据添加,需要查询数据库的操作,并将数据添加到输入参数之中
*/
public IStrategyHandler<? extends InDTO, ? extends OutDTO> paramsEnhance();
/**
* 不需要走策略的数据即放行的策略
*/
public IStrategyHandler<? extends InDTO, ? extends OutDTO> pass();
/**
* 设置对象的参数
*/
public default <T> void setDeriveDTOParams(DeriveDTO futureDeriveDTO, String methodname, Future<T> submit1) {
Object invoke;
try {
// 得到class对象
Class<? extends DeriveDTO> aClass = futureDeriveDTO.getClass();
//拿到参数
while (!submit1.isDone()) {
T params = submit1.get();
//得到方法对象
Method method = aClass.getMethod(methodname, params.getClass());
//执行该方法
method.invoke(futureDeriveDTO, params);
}
} catch (NoSuchMethodException | SecurityException e) {
invoke = null;
} catch (IllegalAccessException | InvocationTargetException e) {
//在非静态方法中调用static方法的error
invoke = null;
} catch (Exception e) {
invoke = null;
}
}
}
2.具体的期货策略实现类
package com.lowrisk.strategy.impl;
import com.lowrisk.limit.basedata.future.ads_future_base_info.domain.AdsFutureBaseinfo;
import com.lowrisk.limit.basedata.future.ads_future_base_info.service.IAdsFutureBaseinfoService;
import com.lowrisk.strategy.annotation.Strategy;
import com.lowrisk.limit.basedata.future.ads_future_base_info.domain.FutureExchangeLimit;
import com.lowrisk.strategy.dto.InListDTO;
import com.lowrisk.strategy.dto.future.*;
import com.lowrisk.strategy.impl.commomifce.CommonStrategyIfce;
import com.lowrisk.strategy.ifce.IStrategyHandler;
import com.lowrisk.strategy.impl.threadpool.GlobalThradPool;
import com.lowrisk.strategy.impl.threadpool.futurethread.ExecuteDAOThread;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
@Strategy(module = "Future")
public class FutureStrategyImpl implements CommonStrategyIfce {
private static final String DEVELOPER = "Riusky";
private static final Logger log = LoggerFactory.getLogger(FutureStrategyImpl.class);
/**
* 期货方向的code 40--空开 41--多开 50--空平 51--多平
*/
public static final String OPEN_SHORT = "40"; //+
public static final String OPEN_LONG = "41"; // +
public static final String CLOSE_LONG = "51"; // -
public static final String CLOSE_SHORT = "50"; // -
@Autowired
private IAdsFutureBaseinfoService iAdsFutureBaseinfoService;
/**
* 汇总传入的批量数据
*/
@Strategy(value = "sumVol")
public IStrategyHandler<InListDTO, FutureOutDto> sumVol() {
return inDto -> {
log.error("期货模块做空的限制 openLong executeStart");
//TODO Hotfix_future_position_limit_sum_vol
//1.汇总传入的批量数据的vol 按照symbol + account + direction 分组汇总
// --inDto.getFutureDeriveDTO().xxxObject 设置对应的类型 类似 List<Map<K,V>>
//2.修改持仓限制(不需要考虑方向) 和 开仓限制(只需要考虑开仓的量)
// -- inDto.getFutureDeriveDTO().xxxObject().get("symbol","account") or .get("symbol","account","direction")
//持仓限制: 之前是加的单券的vol 现在换成汇总的数据 开仓限制同上
FutureOutDto outDto = new FutureOutDto("期货", "汇总数据");
return outDto;
};
}
/**
* 期货模块参数校验 这些通用实现上的注解不可省略 类上的注解对于有继承性 方法和属性上的注解没有继承性
*/
@Override
@Strategy(value = "paramsCheck")
public IStrategyHandler<FutureInDto, FutureOutDto> paramsCheck() {
return inDto -> {
// 可以理解为这里使用了一个匿名的实现类,实现了单一接口的方式
log.error("期货模块参数校验 paramsCheck executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "参数校验", DEVELOPER);
if (inDto == null) {
outDto.setMessage("参数校验错误:未传入期货数据,期货入参对象为空");
inDto = new FutureInDto();
inDto.createCheckError(outDto.getMessage());
return outDto;
}
String type = inDto.getType();
String accountid = inDto.getAccountid();
BigDecimal amt = inDto.getVol();
String code = inDto.getSymbol();
String crncyCode = inDto.getCrncyCode();
String direction = inDto.getDirection();
String tradedate = inDto.getTradedate();
// isEmpty => return cs == null || cs.length() == 0; 所以为null或者为"" 都返回true
if (StringUtils.isEmpty(code)) {
outDto.setMessage("参数校验错误:未传入期货标的数据");
inDto.createCheckError(outDto.getMessage());
return outDto;
}
if (StringUtils.isEmpty(type)) {
outDto.setMessage("参数校验错误:未传入期货类型参数type");
inDto.createCheckError(outDto.getMessage());
return outDto;
}
if (StringUtils.isEmpty(accountid)) {
outDto.setMessage("参数校验错误:未传入期货的账户ID");
inDto.createCheckError(outDto.getMessage());
return outDto;
}
if (StringUtils.isEmpty(crncyCode)) {
outDto.setMessage("参数校验错误:未传入期货的币种代码,");
inDto.createCheckError(outDto.getMessage());
return outDto;
}
if (StringUtils.isEmpty(direction)) {
outDto.setMessage("参数校验错误:未传入期货的交易方向");
inDto.createCheckError(outDto.getMessage());
return outDto;
}
if (StringUtils.isEmpty(tradedate)) {
outDto.setMessage("参数校验错误:未传入期货的交易时间");
inDto.createCheckError(outDto.getMessage());
return outDto;
}
if (amt == null || amt.compareTo(BigDecimal.ZERO) == 0) {
outDto.setMessage("参数校验错误:交易金额为0,或者null");
inDto.createCheckError(outDto.getMessage());
return outDto;
}
//数据没有问题状态为 true
outDto.setFlag(true);
outDto.setMessage("参数状态正常!");
inDto.addOutDTOList(outDto);
log.error("期货模块参数校验 paramsCheck executeEnd");
return outDto;
};
}
/**
* 期货模块参数增强 补充衍生数据 前提是参数校验通过了
*/
@Override
@Strategy(value = "paramsEnhance")
public IStrategyHandler<FutureInDto, FutureOutDto> paramsEnhance() {
return inDto -> {
log.error("期货模块参数衍生数据 paramsEnhance executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "参数衍生数据添加", DEVELOPER);
AdsFutureBaseinfo adsFutureBaseinfo = null;
//多线程数据查询方法
ExecuteDAOThread<String, AdsFutureBaseinfo> selectAdsFutureBaseinfoBySymbol = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto.getSymbol(), "selectAdsFutureBaseinfoBySymbol");
Future<AdsFutureBaseinfo> submit = GlobalThradPool.executor.submit(selectAdsFutureBaseinfoBySymbol);
// 等待下面的线程执行完毕之后再执行后续的线程
while (!submit.isDone()) {
try {
adsFutureBaseinfo = submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
if (adsFutureBaseinfo == null) {
outDto.setMessage("获取期货基础信息失败,请检查ads_futute_baseinfo," + inDto.getSymbol());
inDto.createCheckError(outDto.getMessage());
return outDto;
} else if (StringUtils.isEmpty(adsFutureBaseinfo.getVarityCode())) {
//存在该symbol的数据 继续判断字段的情况 null || length == 0
outDto.setMessage("获取期货基础信息[品种]失败," + inDto.getSymbol());
inDto.createCheckError(outDto.getMessage());
return outDto;
} else {
//传入衍生数据对象之中 数据正常
inDto.getFutureDeriveDTO().setAdsFutureBaseinfo(adsFutureBaseinfo);
}
inDto.setVarityCode(adsFutureBaseinfo.getVarityCode());
//数据没有问题状态为 true
outDto.setFlag(true);
outDto.setMessage("衍生数据查询正常!");
// 线程对象
ExecuteDAOThread<FutureInDto, Integer> selectFutureOpenLong = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto, "selectFutureOpenLong");
ExecuteDAOThread<FutureInDto, Integer> selectFutureOpenShort = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto, "selectFutureOpenShort");
ExecuteDAOThread<FutureInDto, Integer> selectFutureCloseLong = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto, "selectFutureCloseLong");
ExecuteDAOThread<FutureInDto, Integer> selectFutureCloseShort = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto, "selectFutureCloseShort");
ExecuteDAOThread<FutureInDto, BigDecimal> getFututeOpenLimit = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto, "getFututeOpenLimit");
ExecuteDAOThread<FutureInDto, FutureExchangeLimit> getFututePositionLimit = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto, "getFututePositionLimit");
ExecuteDAOThread<FutureInDto, BigDecimal> getFutureTradeVol = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto, "getFutureTradeVol");
ExecuteDAOThread<FutureInDto, BigDecimal> getFuturePositionVol = new ExecuteDAOThread<>(iAdsFutureBaseinfoService, inDto, "getFuturePositionVol");
// 国债期货的套利套保限制数据
Future<ProductFutureBondLimit> future = GlobalThradPool.executor.submit(() -> {
/*
* ****************************************************
* 可以做特殊处理
* ****************************************************
* */
return iAdsFutureBaseinfoService.getProductFutureBondLimit(inDto);
});
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setProductFutureBondLimit", future);
// 一周的交易数据汇总
Future<ProductWeekTradeDetails> futureTradeLogs = GlobalThradPool.executor.submit(() -> {
/*
* ****************************************************
* 这是一周内的交易汇总数据 不包括当天的数据
* 具体计算时 需要考虑当天的数据
* ****************************************************
* */
return iAdsFutureBaseinfoService.getProductWeekTradeDetails(inDto);
});
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setProductWeekTradeDetails", futureTradeLogs);
//当天的交易数据汇总
Future<ProductDayTradeDetails> productDayTradeDetailsFuture = GlobalThradPool.executor.submit(() -> {
/*
* ****************************************************
* 这是当天的交易汇总数据 实时数据表(视图)
* view_real_time_position
* TODO 目前表里面还没有相应的字段(买入开仓数据 卖出开仓 等等) DOING... END...14点13分20220620
* ****************************************************
* */
return iAdsFutureBaseinfoService.getProductDayTradeDetails(inDto);
});
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setProductDayTradeDetails", productDayTradeDetailsFuture);
Future<Map<String, String>> tradeDate = GlobalThradPool.executor.submit(() -> iAdsFutureBaseinfoService.getDelivDate(inDto));
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setTradeDate", tradeDate);
//线程返回接收对象
Future<Integer> submit1 = GlobalThradPool.executor.submit(selectFutureOpenLong);
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setOpenLong", submit1);
Future<Integer> submit2 = GlobalThradPool.executor.submit(selectFutureOpenShort);
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setOpenShort", submit2);
Future<Integer> submit3 = GlobalThradPool.executor.submit(selectFutureCloseLong);
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setCloseLong", submit3);
Future<Integer> submit4 = GlobalThradPool.executor.submit(selectFutureCloseShort);
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setCloseShort", submit4);
Future<BigDecimal> submit5 = GlobalThradPool.executor.submit(getFututeOpenLimit);
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setOpenLimit", submit5);
Future<FutureExchangeLimit> submit6 = GlobalThradPool.executor.submit(getFututePositionLimit);
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setFutureExchangeLimit", submit6);
Future<BigDecimal> submit7 = GlobalThradPool.executor.submit(getFutureTradeVol);
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setTradeVol", submit7);
Future<BigDecimal> submit8 = GlobalThradPool.executor.submit(getFuturePositionVol);
//设置参数
setDeriveDTOParams(inDto.getFutureDeriveDTO(), "setPositionVol", submit8);
inDto.addOutDTOList(outDto);
log.error("期货模块参数校验 paramsCheck executeEnd");
return outDto;
};
}
/**
* 放行策略 期货校验中存在一部分放行的数据 如:期权不做校验 境外的期货不做校验等
*/
@Override
@Strategy(value = "pass")
public IStrategyHandler<FutureInDto, FutureOutDto> pass() {
return inDto -> {
log.error("期货模块参数校验 paramsCheck executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "放行策略", DEVELOPER);
String crncyCode = inDto.getCrncyCode();
String type = inDto.getType();
if (!"CNY".equals(crncyCode) || "option".equals(type)) {
outDto.setFlag(true);
outDto.setMessage("期权或境外期货不做限制!");
inDto.createCheckSucess(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}
log.error("期货模块参数校验 paramsCheck executeEnd");
return outDto;
};
}
/**
* 做多的期货限仓策略
*/
@Strategy(value = "openLong")
public IStrategyHandler<FutureInDto, FutureOutDto> openLong() {
return inDto -> {
log.error("期货模块做多的限制 openLong executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "开多限制", DEVELOPER);
if (inDto.getFutureDeriveDTO().getOpenLong() > 0 && OPEN_LONG.equals(inDto.getDirection())) {
outDto.setMessage("多开禁投的期货品种&期货合约限制(" + inDto.getFutureDeriveDTO().getAdsFutureBaseinfo().getVarityCode() + "-" + inDto.getSymbol() + ")");
inDto.createCheckError(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
} else {
outDto.setFlag(true);
outDto.setMessage("多开禁投的期货品种&期货合约限制,状态正常!");
inDto.addOutDTOList(outDto);
}
log.error("期货模块做多的限制 openLong executeEnd");
return outDto;
};
}
/**
* 做空的期货限仓策略
*/
@Strategy(value = "openShort")
public IStrategyHandler<FutureInDto, FutureOutDto> openShort() {
return inDto -> {
log.error("期货模块做空的限制 openLong executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "开空限制", DEVELOPER);
if (inDto.getFutureDeriveDTO().getOpenShort() > 0 && OPEN_SHORT.equals(inDto.getDirection())) {
outDto.setMessage("空开禁投的期货品种&期货合约限制(" + inDto.getFutureDeriveDTO().getAdsFutureBaseinfo().getVarityCode() + "-" + inDto.getSymbol() + ")");
inDto.createCheckError(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
} else {
outDto.setMessage("空开禁投的期货品种&期货合约限制:状态正常!");
outDto.setFlag(true);
inDto.addOutDTOList(outDto);
}
log.error("期货模块做空的限制 openLong executeEnd");
return outDto;
};
}
/**
* 平多的期货限仓策略
*/
@Strategy(value = "closeLong")
public IStrategyHandler<FutureInDto, FutureOutDto> closeLong() {
return inDto -> {
log.error("期货模块平多的限制 openLong executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "平多限制", DEVELOPER);
if (inDto.getFutureDeriveDTO().getCloseLong() > 0 && CLOSE_LONG.equals(inDto.getDirection())) {
outDto.setMessage("平多禁投的期货品种&期货合约限制(" + inDto.getFutureDeriveDTO().getAdsFutureBaseinfo().getVarityCode() + "-" + inDto.getSymbol() + ")");
inDto.createCheckError(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
} else {
outDto.setMessage("平多禁投的期货品种&期货合约限制:状态正常!");
outDto.setFlag(true);
inDto.addOutDTOList(outDto);
}
outDto.setFlag(true);
log.error("期货模块平仓[多]的限制 openLong executeEnd");
return outDto;
};
}
/**
* 平空的期货限仓策略
*/
@Strategy(value = "closeShort")
public IStrategyHandler<FutureInDto, FutureOutDto> closeShort() {
return inDto -> {
log.error("期货模块平空的限制 openLong executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "平空限制", DEVELOPER);
if (inDto.getFutureDeriveDTO().getCloseShort() > 0 && CLOSE_SHORT.equals(inDto.getDirection())) {
outDto.setMessage("平空禁投的期货品种&期货合约限制(" + inDto.getFutureDeriveDTO().getAdsFutureBaseinfo().getVarityCode() + "-" + inDto.getSymbol() + ")");
inDto.createCheckError(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
} else {
outDto.setMessage("平空禁投的期货品种&期货合约限制:状态正常!");
outDto.setFlag(true);
inDto.addOutDTOList(outDto);
}
log.error("期货模块平空的限制 openLong executeEnd");
return outDto;
};
}
/**
* 开仓的期货限仓策略
*/
@Strategy(value = "openLimit")
public IStrategyHandler<FutureInDto, FutureOutDto> openLimit() {
return inDto -> {
log.error("期货模块开仓量的限制 openLong executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "开仓量限制", DEVELOPER);
if (!"40".equals(inDto.getDirection()) && !"41".equals(inDto.getDirection())) {
//不是开仓数据 不做判断 40 空开 41 多开
outDto.setFlag(true);
outDto.setMessage("非开仓操作,不做限制!");
inDto.addOutDTOList(outDto);
return outDto;
}
//TODO Hotfix_future_position_limit_sum_vol
BigDecimal openLimit = inDto.getFutureDeriveDTO().getOpenLimit();
BigDecimal amt = inDto.getFutureDeriveDTO().getFutureOpenPosition();
BigDecimal tradeVol = inDto.getFutureDeriveDTO().getTradeVol() == null ? BigDecimal.ZERO : inDto.getFutureDeriveDTO().getTradeVol();
if (openLimit == null) {
outDto.setFlag(true);
outDto.setMessage("不存在开仓限制的数据,不做校验!");
inDto.addOutDTOList(outDto);
return outDto;
}
if (tradeVol.add(amt).compareTo(openLimit) > 0) {
outDto.setMessage("开仓超限, 限额" + openLimit + ",当前:" + tradeVol.add(amt));
inDto.createCheckError(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}
outDto.setFlag(true);
outDto.setMessage("开仓超限状态正常, 限额" + openLimit + ",当前:" + tradeVol.add(amt));
inDto.addOutDTOList(outDto);
log.error("期货模块做多的限制 openLong executeEnd");
return outDto;
};
}
/**
* 持仓的期货限仓策略
*/
@Strategy(value = "positionLimit")
public IStrategyHandler<FutureInDto, FutureOutDto> positionLimit() {
return inDto -> {
log.error("期货模块持仓量限制 openLong executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "持仓量限制", DEVELOPER);
FutureExchangeLimit futureExchangeLimit = inDto.getFutureDeriveDTO().getFutureExchangeLimit();
if (futureExchangeLimit == null || futureExchangeLimit.getPositionLimit() == null) {
outDto.setFlag(true); // 注意 ** 这里设置为true的状态 **
outDto.setMessage("持仓限制数据为空,可能该合约不存在持仓量的限制,请检查ads_future_position_limit_all_l数据表[riskdb]");
inDto.addOutDTOList(outDto);
return outDto;
}
//TODO Hotfix_future_position_limit_sum_vol
BigDecimal positionLimit = futureExchangeLimit.getPositionLimit();
BigDecimal positionVol = inDto.getFutureDeriveDTO().getPositionVol() == null ? BigDecimal.ZERO : inDto.getFutureDeriveDTO().getPositionVol();
BigDecimal amt = inDto.getFutureDeriveDTO().getFutureSumPosition();
if (OPEN_SHORT.equals(inDto.getDirection()) || OPEN_LONG.equals(inDto.getDirection())) {
if (amt.add(positionVol).compareTo(positionLimit) > 0) {
//持仓限制数据
outDto.setMessage("持仓超限, 限额: " + positionLimit + ", 当前:" + amt.add(positionVol) + "[" + inDto.getSymbol() + "]");
inDto.createCheckError(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}
} else {
/* if (positionVol.subtract(amt).compareTo(positionLimit) > 0) {
//持仓限制数据
outDto.setMessage("持仓超限, 限额: " + positionLimit + ", 当前:" + positionVol.subtract(amt) + "[" + inDto.getSymbol() + "]");
inDto.createCheckError(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}*/
outDto.setMessage(String.format("平仓无限制!,当前持仓: %s,平仓: %s", positionVol, amt));
outDto.setFlag(true);
inDto.addOutDTOList(outDto);
return outDto;
}
outDto.setFlag(true);
outDto.setMessage("持仓限额状态正常,限额: " + positionLimit + ", 当前:" + amt.add(positionVol) + "[" + inDto.getSymbol() + "]");
inDto.addOutDTOList(outDto);
log.error("期货模块持仓量限制 openLong executeEnd" + positionLimit + ", 当前:" + amt.add(positionVol) + "[" + inDto.getSymbol() + "]");
return outDto;
};
}
/**
* 持仓的期货限仓策略
*/
@Strategy(value = "arbitrageHedging")
public IStrategyHandler<FutureInDto, FutureOutDto> arbitrageHedging() {
return inDto -> {
log.error("国债期货套利套保 arbitrageHedging executeStart");
FutureOutDto outDto = new FutureOutDto("期货", "国债期货套利套保限制", DEVELOPER);
//1 判断期货类型
String varityCode = inDto.getVarityCode();
String sptype = inDto.getSptype();
if (!"hedging".equals(sptype)) {
outDto.setMessage("非套利套保交易,不进行校验!");
outDto.setFlag(true);
inDto.addOutDTOList(outDto);
return outDto;
}
List<String> list = Stream.of("TS", "TF", "T").collect(Collectors.toList());
if (!list.contains(varityCode)) {
outDto.setMessage("不是国债期货,没有套利套保限制!");
outDto.setFlag(true);
inDto.addOutDTOList(outDto);
return outDto;
}
//2 获取数据
ProductWeekTradeDetails productWeekTradeDetails = inDto.getFutureDeriveDTO().getProductWeekTradeDetails();
ProductFutureBondLimit productFutureBondLimit = inDto.getFutureDeriveDTO().getProductFutureBondLimit();
ProductDayTradeDetails productDayTradeDetails = inDto.getFutureDeriveDTO().getProductDayTradeDetails();
if (productFutureBondLimit == null) {
outDto.setMessage("未查询到当前品种的国债期货限制数据!");
outDto.setFlag(true);
inDto.addOutDTOList(outDto);
return outDto;
}
if (productWeekTradeDetails == null) {
productWeekTradeDetails = new ProductWeekTradeDetails();
}
if (productDayTradeDetails == null) {
productDayTradeDetails = new ProductDayTradeDetails();
}
//3 判断数据
// 3.1 一周的买入开仓+卖出平仓不超过多头额度两倍
BigDecimal weekOpenOfBuyAndCloseOfSale = productWeekTradeDetails.getWeekOpenOfBuyAndCloseOfSale();
// 多头额度的2倍
BigDecimal twoDoubleOpenAmount = productFutureBondLimit.getTwoDoubleOpenAmount(varityCode);
//当天的买入开仓 + 卖出平仓数据
BigDecimal dayOpenOfBuyAndCloseOfSale = productDayTradeDetails.getDayOpenOfBuyAndCloseOfSale();
BigDecimal weekOpenOfSaleAndCloseOfBuy = productWeekTradeDetails.getWeekOpenOfSaleAndCloseOfBuy();
BigDecimal openOfSaleAndCloseOfBuy = productDayTradeDetails.getOpenOfSaleAndCloseOfBuy();
BigDecimal twoDoubleCloseAmount = productFutureBondLimit.getTwoDoubleCloseAmount(varityCode);
// 加上当前这一笔交易 多开 空平
if (CLOSE_SHORT.equals(inDto.getDirection()) || OPEN_LONG.equals(inDto.getDirection())) {
// 一周的vol +当天的vol +当前的vol
BigDecimal add = weekOpenOfBuyAndCloseOfSale.add(dayOpenOfBuyAndCloseOfSale).add(inDto.getVol());
if (add.compareTo(twoDoubleOpenAmount) >= 0) {
outDto.setMessage(String.format("一周的买入开仓+卖出平仓不超过多头额度两倍,一周买入开仓+卖出平仓: %s ,当前交易量: %s 多头额度*2 : %s", weekOpenOfBuyAndCloseOfSale.add(dayOpenOfBuyAndCloseOfSale), inDto.getVol().toString(), twoDoubleOpenAmount));
inDto.createCheckNoLimit(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}
} else {
BigDecimal add1 = weekOpenOfSaleAndCloseOfBuy.add(openOfSaleAndCloseOfBuy).add(inDto.getVol());
if (add1.compareTo(twoDoubleCloseAmount) >= 0) {
outDto.setMessage(String.format("一周的卖出开仓+买入平仓不超过空头额度两倍,一周卖出开仓+买入平仓: %s ,当前交易量: %s , 空头额度*2 : %s", weekOpenOfSaleAndCloseOfBuy.add(openOfSaleAndCloseOfBuy), inDto.getVol().toString(), twoDoubleCloseAmount));
inDto.createCheckNoLimit(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}
}
// 3.3 利率债:国债期货空头合约价值≤利率债现货市值
/*
if (OPEN_SHORT.equals(inDto.getDirection()) || CLOSE_LONG.equals(inDto.getDirection())) {
BigDecimal contractValue = inDto.getFutureDeriveDTO().getContractValue();
BigDecimal rateBond = inDto.getFutureDeriveDTO().getRateBondAmt();
BigDecimal amount = inDto.getAmount() == null ? BigDecimal.ZERO : inDto.getAmount();
if (contractValue.add(amount).compareTo(rateBond) >= 0) {
outDto.setMessage(String.format("国债期货空头合约价值(%s) ≤ 利率债现货市值(%s) ", contractValue, rateBond));
inDto.createCheckNoLimit(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}
}
*/
// 3.4 交割月前一个月的倒数第二个交易日不能再用产品额度,要申请合约额度
//在交割月前一个月的倒数第三个交易日,对下个月要进行交割的合约品种弹出需要平仓的提示
LocalDate date = LocalDate.now(); // get the current date
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
String nowDate = date.format(formatter);
String tradedateLastMonthOf2Day = inDto.getFutureDeriveDTO().getTradedateLastMonthOf2Day();
String tradedateLastMonthOf3Day = inDto.getFutureDeriveDTO().getTradedateLastMonthOf3Day();
if (nowDate.equals(tradedateLastMonthOf3Day)) {
//设置位超限 需要弹出提示框
outDto.setMessage(String.format("该合约(%s)下个月要进行交割,当前是交割月前一个月的倒数第3个交易日,需要尽快平仓!", inDto.getSymbol()));
inDto.createCheckNoLimit(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}
if (nowDate.equals(tradedateLastMonthOf2Day)) {
//设置位超限 需要弹出提示框
outDto.setMessage(String.format("该合约(%s)下个月要进行交割,当前是交割月前一个月的倒数第2个交易日,已不能使用产品额度,需要申请合约额度!", inDto.getSymbol()));
inDto.createCheckNoLimit(outDto.getMessage());
inDto.addOutDTOList(outDto);
return outDto;
}
outDto.setMessage("国债期货套利套保限额正常");
outDto.setFlag(true);
inDto.addOutDTOList(outDto);
return outDto;
};
}
}
入参和出参的设计
IStrategyHandler<? extends InDTO, ? extends OutDTO> 在通用的策略接口里面使用的是InDTO,其实应该使用InObject ,但是业务复杂度没有这么高也就使用了InDTO,使用InObject 会更加灵活,如传入一个List入参等
1.入参设计 接口->实现类->具体的实现类
接口:
package com.lowrisk.strategy.dto;
/*
* 空接口 只为了给入参一个统一的类型
* */
public interface InObject {
}
2.实现类
check字段是该数据最终判断的结果,outDTOList是每一个策略判断的结果(用户不仅想看到最终结果,没有个策略的结果也需要展示,看能否真正下单)
package com.lowrisk.strategy.dto;
import java.util.ArrayList;
import java.util.List;
/**
* 输入的参数,即传入的需要校验的参数
*
* @author Riusky
*/
public class InDTO implements InObject{
/**
* 最终返回时,每一条数据都需要带上这个数据
*/
private Check check;
/**
* 所有策略的运行结果
*/
private List<OutDTO> outDTOList;
/**
* 添加每个策略的最终执行结果
* */
public void addOutDTOList(OutDTO outDTO){
if (this.outDTOList == null) {
this.outDTOList = new ArrayList<>();
}
this.outDTOList.add(outDTO);
}
public List<OutDTO> getOutDTOList() {
return outDTOList;
}
public InDTO setOutDTOList(List<OutDTO> outDTOList) {
this.outDTOList = outDTOList;
return this;
}
public Check getCheck() {
return check;
}
public InDTO setCheck(Check check) {
if (this.check == null) {
this.check = check;
}
return this;
}
/**
* 表记录缺失的返回
* */
public void createCheckError(String message) {
Check check = new Check();
check.setCode("-1");
check.setMessage(message);
this.setCheck(check);
}
/**
* 可以判断的超限
* */
public void createCheckNoLimit(String message) {
Check check = new Check();
check.setCode("1");
check.setMessage(message);
this.setCheck(check);
}
public void createCheckSucess(String message) {
Check check = new Check();
check.setCode("0");
check.setMessage(message);
this.setCheck(check);
}
@Override
public String toString() {
return "InDTO{" +
"check=" + check +
'}';
}
}
3.具体实现类
futureDeriveDTO衍生数据类,包括期货的基础信息,各种限制的值等
package com.lowrisk.strategy.dto.future;
import com.lowrisk.limit.basedata.future.ads_future_base_info.domain.AdsFutureBaseinfo;
import com.lowrisk.limit.basedata.future.ads_future_base_info.domain.FutureExchangeLimit;
import com.lowrisk.strategy.dto.InDTO;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 期货类型规定的入参结构
*
* @author Riusky
*/
public class FutureInDto extends InDTO {
/**
* 标的代码
*/
private String symbol;
/**
* 账户id
*/
private String accountid;
/**
* 期货类型 期货和期权
*/
private String type;
/**
* 交易日期
*/
private String tradedate;
/**
* 交易金额
*/
private BigDecimal vol;
/**
* 交易方向
*/
private String direction;
/**
* 币种代码
*/
private String crncyCode;
/**
* 期货品种
*/
private String varityCode;
/**
* 买入的合约价值
* */
private BigDecimal amount;
/**
* 产品号
*/
private String productid;
/**
* riskId
*/
private String riskId;
public String getRiskId() {
return riskId;
}
public FutureInDto setRiskId(String riskId) {
this.riskId = riskId;
return this;
}
/**
* 套利套保
*/
private String sptype;
/**
* 衍生属性数据对象 **非传入的数据,需要查询数据库** ##简单的单例模式实现##
*/
private FutureDeriveDTO futureDeriveDTO;
// 省略geter,seter......
}
接口调用
controller层
@RestController
@RequestMapping("equity/future")
public class FutureLimitController extends BaseController
{
@Autowired
private FutureLimitService futureLimitService;
/**
* 查询期货限制信息
*/
@PostMapping("/list")
public List<FutureInDto> list(@RequestBody List<FutureInDto> futureInDtos)
{
return futureLimitService.list(futureInDtos);
}
}
Service层,futureStrategyContext.execute(FUTURE, PARAMS_CHECK, futureInDto); 可以根据条件走不通的策略
package com.lowrisk.limit.basedata.future.service;
import cn.hutool.core.util.NumberUtil;
import com.lowrisk.limit.basedata.future.ads_future_base_info.service.IAdsFutureBaseinfoService;
import com.lowrisk.strategy.Context.AbstractStrategyContext;
import com.lowrisk.strategy.dto.InDTO;
import com.lowrisk.strategy.dto.InListDTO;
import com.lowrisk.strategy.dto.InObject;
import com.lowrisk.strategy.dto.OutDTO;
import com.lowrisk.strategy.dto.future.FutureInDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 期货基础信息Controller
*
* @author riusky
* @date 2022-05-16
*/
@Service
public class FutureLimitService {
public static final String FUTURE = "Future";
public static final String SUM_VOL = "sumVol";
public static final String PARAMS_CHECK = "paramsCheck";
public static final String PARAMS_ENHANCE = "paramsEnhance";
public static final String PASS = "pass";
public static final String OPEN_LONG = "openLong";
public static final String OPEN_SHORT = "openShort";
public static final String CLOSE_LONG = "closeLong";
public static final String CLOSE_SHORT = "closeShort";
public static final String OPEN_LIMIT = "openLimit";
public static final String POSITION_LIMIT = "positionLimit";
public static final String ARBITRAGE_HEDGING = "arbitrageHedging";
// treasury bond futures 国债期货
// Arbitrage hedging 套利套保
@Autowired
private IAdsFutureBaseinfoService adsFutureBaseinfoService;
@Autowired
private AbstractStrategyContext<InObject, OutDTO> futureStrategyContext;
/**
* 查询期货限制信息
*/
public List<FutureInDto> list(List<FutureInDto> futureInDtos) {
// 增加一个传入批量数据需要汇总的情况策略 (该策略可以优先执行)
// 与产品号无关 account_id 和 symbol 分组求和的数据
InListDTO inListDTO = new InListDTO();
this.summary(futureInDtos);
inListDTO.setInDTOList(futureInDtos);
// futureStrategyContext.execute(FUTURE, SUM_VOL, inListDTO);
for (FutureInDto futureInDto : futureInDtos) {
//参数校验
futureStrategyContext.execute(FUTURE, PARAMS_CHECK, futureInDto);
//排除不必要的校验数据
futureStrategyContext.execute(FUTURE, PASS, futureInDto);
//根据已有参数,补充校验所需参数
futureStrategyContext.execute(FUTURE, PARAMS_ENHANCE, futureInDto);
//开仓 多
futureStrategyContext.execute(FUTURE, OPEN_LONG, futureInDto);
//开仓 空
futureStrategyContext.execute(FUTURE, OPEN_SHORT, futureInDto);
//平仓 多
futureStrategyContext.execute(FUTURE, CLOSE_LONG, futureInDto);
//平仓 空
futureStrategyContext.execute(FUTURE, CLOSE_SHORT, futureInDto);
//开仓交易所限制
futureStrategyContext.execute(FUTURE, OPEN_LIMIT, futureInDto);
//持仓限制
futureStrategyContext.execute(FUTURE, POSITION_LIMIT, futureInDto);
//国债期货的套利套保
futureStrategyContext.execute(FUTURE, ARBITRAGE_HEDGING, futureInDto);
//国债期货的套利套保限制
if (futureInDto.getCheck() == null) {
futureInDto.createCheckSucess("状态正常!");
}
}
return futureInDtos;
}
public void summary(List<FutureInDto> list) {
list.stream().collect(Collectors.groupingBy(a -> adsFutureBaseinfoService.getGroupIdByAccountIdAndSymbol(a.getAccountid(), a.getSymbol())))
.forEach((k, v) -> {
// 开
BigDecimal open = v.stream().filter(a -> "40".equals(a.getDirection()) || "41".equals(a.getDirection())).map(FutureInDto::getVol).reduce(BigDecimal.ZERO, BigDecimal::add);
// 开仓限制
v.stream().filter(a -> "40".equals(a.getDirection()) || "41".equals(a.getDirection())).forEach(a -> a.getFutureDeriveDTO().setFutureOpenPosition(open));
// 平
BigDecimal close = v.stream().filter(a -> "50".equals(a.getDirection()) || "51".equals(a.getDirection())).map(FutureInDto::getVol).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal sum = NumberUtil.sub(open, close);
// 持仓限制
v.forEach(a -> a.getFutureDeriveDTO().setFutureSumPosition(sum));
});
}
}
目录结构
原创文章,作者:254126420,如若转载,请注明出处:https://blog.ytso.com/277637.html