基于注解+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));
                });
    }
}

目录结构


posted @ 2022-07-29 00:57  riusky  阅读(47)  评论(0编辑  收藏  举报