基于注解+lamda实现策略模式
2022/7/29 6:22:54
本文主要是介绍基于注解+lamda实现策略模式,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
金融风控中使用注解实现一个策略模式
基金,股票,期货,债券,衍生品...... 金融工具多种多样,每种不同的金融工具有不同的风险控制需求,如何实现根据特定的种类自动去找对应的实现策略?
选用非传统的策略模式
注解+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)); }); } }
目录结构
这篇关于基于注解+lamda实现策略模式的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南