基于注解+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实现策略模式
基于注解+lamda实现策略模式

原创文章,作者:254126420,如若转载,请注明出处:https://blog.ytso.com/277637.html

(0)
上一篇 2022年7月29日
下一篇 2022年7月29日

相关推荐

发表回复

登录后才能评论