AI量化知识树

A股股票选股模板策略

由clearyf创建,最终由clearyf 被浏览 19 用户

导语

鉴于很多用户询问止赢止损、大盘风控和ST股退市股卖出过滤的混合问题,这里进行一个模版说明。

模板说明

首先,所有的测试集数据在预测之前过滤,可能会导致持仓后的股票丢失排序而无法卖出或排序混乱。 因此我们尽量使用数据合并功能,把股票名称、大盘风控等指标合并到预测结果中,保证输入给回测模块的股票数据的完整性。

我们尽量在回测模块外通过数据源等模块进行数据抽取。这里介绍常用的几种数据读取,包括:

  • 从 instruments_CN_STOCK_A表中抽取股票名称列name;
  • 从 指数特征抽取模块中抽取指数的相关风控条件因子(例如过去五日涨幅<-0.05就判定为有风险,否则为无风险)
  • 从预测分支抽取所需的其他数据比如涨跌停状态price_limit_status_0等。这里使用了一个技巧即:用来后续过滤使用的因子/指标单独放在一个证券代码列表模块中而不作为模型因子参与模型的训练。注意连接个算法模块的仅是第一个输入特征列表的输出结果!

通过数据合并后,我们的预测数据在原来的date\instrument\score\position列的基础上,增加了每日所需的大盘风控条件bm_0列、股票名称name列和涨跌停状态price_limit_status_0,以便于我们后续在回测模块中条件判断和使用。最后利用排序模块 按照每天的股票预测得分或排名进行重新排序。这里要特别注意预测结果的每日顺序是否正确!

随后我们通过初始化函数读入的数据中就包含了所需要的数据列,我们直接在主函数中引用这些列名就可以实现每日的数据获取了。

    today = data.current_dt.strftime('%Y-%m-%d')
    # 按日期过滤得到今日的预测数据
    ranker_prediction = context.ranker_prediction[
        context.ranker_prediction.date == today]

然后我们利用每日的数据ranker_prediction的各列进行条件判断:

大盘风控代码

    #大盘风控模块,读取风控数据    
    benckmark_risk=ranker_prediction['bm_0'].values[0]

    #当risk为1时,市场有风险,全部平仓,不再执行其它操作
    if benckmark_risk > 0:
        for instrument in positions.keys():
            context.order_target(context.symbol(instrument), 0)
        print(today,'大盘风控止损触发,全仓卖出')
        return

资金分配并定义卖出列表

    # 1. 资金分配
    # 平均持仓时间是hold_days,每日都将买入股票,每日预期使用 1/hold_days 的资金
    # 实际操作中,会存在一定的买入误差,所以在前hold_days天,等量使用资金;之后,尽量使用剩余资金(这里设置最多用等量的1.5倍)
    is_staging = context.trading_day_index < context.options['hold_days'] # 是否在建仓期间(前 hold_days 天)
    cash_avg = context.portfolio.portfolio_value / context.options['hold_days']
    cash_for_buy = min(context.portfolio.cash, (1 if is_staging else 1.5) * cash_avg)
    cash_for_sell = cash_avg - (context.portfolio.cash - cash_for_buy)
    stock_sold = [] # 记录卖出的股票,防止多次卖出出现空单

.根据需要加入止赢止损模块

    #------------------------START:止赢止损模块(含建仓期)---------------
    current_stopwin_stock=[]
    current_stoploss_stock = []   
    positions_cost={e.symbol:p.cost_basis for e,p in context.portfolio.positions.items()}
    if len(positions)>0:
        for instrument in positions.keys():
            stock_cost=positions_cost[instrument]  
            stock_market_price=data.current(context.symbol(instrument),'price')  
            # 赚9%且为可交易状态就止盈
            if stock_market_price/stock_cost-1>=0.09 and data.can_trade(context.symbol(instrument)):
                context.order_target_percent(context.symbol(instrument),0)
                cash_for_sell -= positions[instrument]
                current_stopwin_stock.append(instrument)
            # 亏5%并且为可交易状态就止损
            if stock_market_price/stock_cost-1 <= -0.05 and data.can_trade(context.symbol(instrument)):   
                context.order_target_percent(context.symbol(instrument),0)
                cash_for_sell -= positions[instrument]
                current_stoploss_stock.append(instrument)
        if len(current_stopwin_stock)>0:
            print(today,'止盈股票列表',current_stopwin_stock)
            stock_sold += current_stopwin_stock # 注意此处卖出的股票要记录到列表中
        if len(current_stoploss_stock)>0:
            print(today,'止损股票列表',current_stoploss_stock)
            stock_sold += current_stoploss_stock   # 注意此处卖出的股票要记录到列表中
    #--------------------------END: 止赢止损模块--------------------------

根据需要增加固定天数卖出逻辑

    #--------------------------START:持有固定天数卖出(不含建仓期)-----------
    current_stopdays_stock = []
    positions_lastdate = {e.symbol:p.last_sale_date for e,p in context.portfolio.positions.items()}
    # 不是建仓期(在前hold_days属于建仓期)
    if not is_staging:
        for instrument in positions.keys():
            #如果上面的止盈止损已经卖出过了,就不要重复卖出以防止产生空单
            if instrument in stock_sold:
                continue
            # 今天和上次交易的时间相隔hold_days就全部卖出 datetime.timedelta(context.options['hold_days'])也可以换成自己需要的天数,比如datetime.timedelta(5)
            if data.current_dt - positions_lastdate[instrument]>=datetime.timedelta(5) and data.can_trade(context.symbol(instrument)):
                context.order_target_percent(context.symbol(instrument), 0)
                current_stopdays_stock.append(instrument)
                cash_for_sell -= positions[instrument]
        if len(current_stopdays_stock)>0:        
            print(today,'固定天数卖出列表',current_stopdays_stock)
            stock_sold += current_stopdays_stock  # 注意此处卖出的股票要记录到列表中
    #-------------------------  END:持有固定天数卖出-----------------------

根据需要增加持仓ST股和退市股卖出逻辑

    #-------------------------- START: ST和退市股卖出 ---------------------  
    st_stock_list = []
    for instrument in positions.keys():
        try:
            instrument_name = ranker_prediction[ranker_prediction.instrument==instrument].name.values[0]
            # 如果股票状态变为了st或者退市 则卖出
            if 'ST' in instrument_name or '退' in instrument_name:
                if instrument in stock_sold:
                    continue
                if data.can_trade(context.symbol(instrument)):
                    context.order_target(context.symbol(instrument), 0)
                    st_stock_list.append(instrument)
                    cash_for_sell -= positions[instrument]
        except:
            continue
    if st_stock_list!=[]:
        print(today,'持仓出现st股/退市股',st_stock_list,'进行卖出处理')    
        stock_sold += st_stock_list  # 注意此处卖出的股票要记录到列表中
    #-------------------------- END: ST和退市股卖出 --------------------- 

轮仓卖出逻辑

    # 3. 生成轮仓卖出订单:hold_days天之后才开始卖出;对持仓的股票,按机器学习算法预测的排序末位淘汰
    if not is_staging and cash_for_sell > 0:
        instruments = list(reversed(list(ranker_prediction.instrument[ranker_prediction.instrument.apply(
                lambda x: x in positions)])))
        for instrument in instruments:
            # 如果资金够了就不卖出了
            if cash_for_sell <= 0:
                break
            #防止多个止损条件同时满足,出现多次卖出产生空单
            if instrument in stock_sold:
                continue
            context.order_target(context.symbol(instrument), 0)
            cash_for_sell -= positions[instrument]
            stock_sold.append(instrument)  # 轮仓卖出的股票可以选择添加到卖出列表中

轮仓买入逻辑

    # 4. 生成轮仓买入订单:按机器学习算法预测的排序,买入前面的stock_count只股票
    # 计算今日跌停的股票
    dt_list = list(ranker_prediction[ranker_prediction.price_limit_status_0==1].instrument)
    # 计算今日ST/退市的股票
    st_list = list(ranker_prediction[ranker_prediction.name.str.contains('ST')|ranker_prediction.name.str.contains('退')].instrument)
    # 计算所有禁止买入的股票池banned_list,包括已卖出的股票和跌停的股票以及ST/退市股票
    banned_list = stock_sold+dt_list+st_list
    buy_cash_weights = context.stock_weights
    buy_instruments=[k for k in list(ranker_prediction.instrument) if k not in banned_list][:len(buy_cash_weights)]
    max_cash_per_instrument = context.portfolio.portfolio_value * context.max_cash_per_instrument
    for i, instrument in enumerate(buy_instruments):
        cash = cash_for_buy * buy_cash_weights[i]
        if cash > max_cash_per_instrument - positions.get(instrument, 0):
            # 确保股票持仓量不会超过每次股票最大的占用资金量
            cash = max_cash_per_instrument - positions.get(instrument, 0)
        if cash > 0:
            context.order_value(context.symbol(instrument), cash)

策略案例

https://bigquant.com/experimentshare/edf4e389928841baacc7ab79e0f78c56


https://bigquant.com/experimentshare/f582dbfdc71b433d83ce410e0e830a1f

\

标签

回测模块股票排序