行业轮动量化策略【源码】

策略分享
行业轮动
标签: #<Tag:0x00007fcf64290398> #<Tag:0x00007fcf64290208>

(iQuant) #1

本文是行业轮动策略的源码。

克隆策略

行业轮动策略

    {"Description":"实验创建于3/23/2018","Summary":"","Graph":{"EdgesInternal":[{"DestinationInputPortId":"-21:instruments","SourceOutputPortId":"-4:data"}],"ModuleNodes":[{"Id":"-4","ModuleId":"BigQuantSpace.instruments.instruments-v2","ModuleParameters":[{"Name":"start_date","Value":"2016-01-01","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"2018-03-22","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"market","Value":"CN_STOCK_A","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"instrument_list","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"max_count","Value":0,"ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"rolling_conf","NodeId":"-4"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-4","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":1,"Comment":"","CommentCollapsed":true},{"Id":"-21","ModuleId":"BigQuantSpace.trade.trade-v3","ModuleParameters":[{"Name":"start_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"handle_data","Value":"# 回测引擎:每日数据处理函数,每天执行一次\ndef bigquant_run(context, data):\n # 按月调仓\n if context.trading_day_index % 20 != 0:\n return \n \n date = data.current_dt.strftime('%Y-%m-%d') # 日期\n # 整理出当天要买入的股票\n buy_industry = context.daily_buy_industry[date] \n stock_to_buy = []\n for ind in buy_industry:\n ind = int(ind[2:8]) # 转化为行业代码的数字格式\n stock_to_buy.append(context.daily_buy_stock[date][ind])\n stock_to_buy = sum(stock_to_buy, [])\n \n # 通过positions对象,使用列表生成式的方法获取目前持仓的股票列表\n stock_hold_now = [equity.symbol for equity in context.portfolio.positions]\n # 继续持有的股票:调仓时,如果买入的股票已经存在于目前的持仓里,那么应继续持有\n no_need_to_sell = [i for i in stock_hold_now if i in stock_to_buy]\n # 需要卖出的股票\n stock_to_sell = [i for i in stock_hold_now if i not in no_need_to_sell]\n \n # 卖出\n for stock in stock_to_sell:\n # 如果该股票停牌,则没法成交。因此需要用can_trade方法检查下该股票的状态\n # 如果返回真值,则可以正常下单,否则会出错\n # 因为stock是字符串格式,我们用symbol方法将其转化成平台可以接受的形式:Equity格式\n if data.can_trade(context.symbol(stock)):\n # order_target_percent是平台的一个下单接口,表明下单使得该股票的权重为0,\n # 即卖出全部股票,可参考回测文档\n context.order_target_percent(context.symbol(stock), 0)\n \n # 如果当天没有买入的股票,就返回\n if len(stock_to_buy) == 0:\n return\n\n # 等权重买入 \n weight = 1 / len(stock_to_buy)\n \n # 买入\n for stock in stock_to_buy:\n if data.can_trade(context.symbol(stock)):\n # 下单使得某只股票的持仓权重达到weight,因为weight大于0,因此是等权重买入\n context.order_target_percent(context.symbol(stock), weight)","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"prepare","Value":"# 回测引擎:准备数据,只执行一次\ndef bigquant_run(context):\n start_date = context.start_date\n end_date = context.end_date\n # 获取目前的行业列表\n industry = list(set(D.history_data(D.instruments(), end_date, end_date, ['industry_sw_level1']).industry_sw_level1))\n # 获取行业指数的行情数据\n industry = ['SW'+str(j)+'.SHA' for j in industry]\n data = D.history_data(industry, start_date, end_date, ['close','name'])\n\n # 计算此处每日动量较高的行业\n ret_data = data.groupby('instrument').apply(calcu_ret)\n ret_data.reset_index(inplace=True, drop=True)\n ret_data['date'] = ret_data['date'].map(lambda x:x.strftime('%Y-%m-%d'))\n context.daily_buy_industry = pd.Series({dt:seek_head_industry(ret_data.set_index('date').ix[dt]) for dt in list(set(ret_data.date))})\n\n # 每个交易日 每个行业的优质股 \n # 优质股的确定依据是:净资产收益率 (TTM)、营业收入同比增长率、归属母公司股东的净利润同比增长率\n features_data = D.features(D.instruments(start_date, end_date), start_date, end_date, ['fs_roe_ttm_0', 'fs_operating_revenue_yoy_0', 'fs_net_profit_yoy_0', 'industry_sw_level1_0'])\n # 整理出每个行业的优质股票\n context.daily_buy_stock = features_data.groupby(['date', 'industry_sw_level1_0']).apply(seek_head_stock)\n\n# 计算不同周期的动量\ndef calcu_ret(df):\n df = df.sort_values('date')\n for i in [42, 84, 126]: # 分别代表2月、4月、半年的动量\n df['ret_%s'%i] = df['close']/df['close'].shift(i)-1 \n return df\n\n# 计算出得分\ndef seek_head_industry(df):\n for j in ['ret_42','ret_84','ret_126']:\n df['%s'%j] = df['%s'%j].rank(ascending=True) \n df['score'] = 0.4*df['ret_42']+0.3*df['ret_84']+0.3*df['ret_126'] # 得分的权重分别为0.4、0.3、0.3\n result = df.sort_values('score', ascending=False)\n return list(result.instrument)[:3] # 前3个行业\n\n# 选出特定行业优质股票\ndef seek_head_stock(df):\n result = df.sort_values(['fs_roe_ttm_0', 'fs_net_profit_yoy_0', 'fs_operating_revenue_yoy_0'], ascending=False)\n return list(result.instrument[:10]) # 每个行业选10只股票","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"initialize","Value":"# 回测引擎:初始化函数,只执行一次\ndef bigquant_run(context):\n # 手续费设置\n context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5)) \n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"before_trading_start","Value":"# 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。\ndef bigquant_run(context, data):\n pass\n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"volume_limit","Value":0.025,"ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"order_price_field_buy","Value":"open","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"order_price_field_sell","Value":"open","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"capital_base","Value":1000000,"ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"benchmark","Value":"000300.SHA","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"auto_cancel_non_tradable_orders","Value":"True","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"data_frequency","Value":"daily","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"price_type","Value":"后复权","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"plot_charts","Value":"True","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"backtest_only","Value":"False","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"amount_integer","Value":"False","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"instruments","NodeId":"-21"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"options_data","NodeId":"-21"}],"OutputPortsInternal":[{"Name":"raw_perf","NodeId":"-21","OutputType":null}],"UsePreviousResults":false,"moduleIdForCode":3,"Comment":"","CommentCollapsed":true}],"SerializedClientData":"<?xml version='1.0' encoding='utf-16'?><DataV1 xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'><Meta /><NodePositions><NodePosition Node='-4' Position='225,114,200,200'/><NodePosition Node='-21' Position='230,302,200,200'/></NodePositions><NodeGroups /></DataV1>"},"IsDraft":true,"ParentExperimentId":null,"WebService":{"IsWebServiceExperiment":false,"Inputs":[],"Outputs":[],"Parameters":[{"Name":"交易日期","Value":"","ParameterDefinition":{"Name":"交易日期","FriendlyName":"交易日期","DefaultValue":"","ParameterType":"String","HasDefaultValue":true,"IsOptional":true,"ParameterRules":[],"HasRules":false,"MarkupType":0,"CredentialDescriptor":null}}],"WebServiceGroupId":null,"SerializedClientData":"<?xml version='1.0' encoding='utf-16'?><DataV1 xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'><Meta /><NodePositions></NodePositions><NodeGroups /></DataV1>"},"DisableNodesUpdate":false,"Category":"user","Tags":[],"IsPartialRun":true}
    In [10]:
    # 本代码由可视化策略环境自动生成 2018年3月23日 14:41
    # 本代码单元只能在可视化模式下编辑。您也可以拷贝代码,粘贴到新建的代码单元或者策略,然后修改。
    
    
    m1 = M.instruments.v2(
        start_date='2016-01-01',
        end_date='2018-03-22',
        market='CN_STOCK_A',
        instrument_list='',
        max_count=0
    )
    
    # 回测引擎:每日数据处理函数,每天执行一次
    def m3_handle_data_bigquant_run(context, data):
        # 按月调仓
        if context.trading_day_index % 20 != 0:
            return 
        
        date = data.current_dt.strftime('%Y-%m-%d') # 日期
        # 整理出当天要买入的股票
        buy_industry = context.daily_buy_industry[date]  
        stock_to_buy = []
        for ind in buy_industry:
            ind = int(ind[2:8]) # 转化为行业代码的数字格式
            stock_to_buy.append(context.daily_buy_stock[date][ind])
        stock_to_buy = sum(stock_to_buy, [])
        
        # 通过positions对象,使用列表生成式的方法获取目前持仓的股票列表
        stock_hold_now = [equity.symbol for equity in context.portfolio.positions]
        # 继续持有的股票:调仓时,如果买入的股票已经存在于目前的持仓里,那么应继续持有
        no_need_to_sell = [i for i in stock_hold_now if i in stock_to_buy]
        # 需要卖出的股票
        stock_to_sell = [i for i in stock_hold_now if i not in no_need_to_sell]
      
        # 卖出
        for stock in stock_to_sell:
            # 如果该股票停牌,则没法成交。因此需要用can_trade方法检查下该股票的状态
            # 如果返回真值,则可以正常下单,否则会出错
            # 因为stock是字符串格式,我们用symbol方法将其转化成平台可以接受的形式:Equity格式
            if data.can_trade(context.symbol(stock)):
                # order_target_percent是平台的一个下单接口,表明下单使得该股票的权重为0,
                #   即卖出全部股票,可参考回测文档
                context.order_target_percent(context.symbol(stock), 0)
        
        # 如果当天没有买入的股票,就返回
        if len(stock_to_buy) == 0:
            return
    
        # 等权重买入 
        weight =  1 / len(stock_to_buy)
        
        # 买入
        for stock in stock_to_buy:
            if data.can_trade(context.symbol(stock)):
                # 下单使得某只股票的持仓权重达到weight,因为weight大于0,因此是等权重买入
                context.order_target_percent(context.symbol(stock), weight)
    # 回测引擎:准备数据,只执行一次
    def m3_prepare_bigquant_run(context):
        start_date = context.start_date
        end_date = context.end_date
        # 获取目前的行业列表
        industry = list(set(D.history_data(D.instruments(), end_date, end_date, ['industry_sw_level1']).industry_sw_level1))
        # 获取行业指数的行情数据
        industry = ['SW'+str(j)+'.SHA' for j in industry]
        data = D.history_data(industry, start_date, end_date, ['close','name'])
    
        # 计算此处每日动量较高的行业
        ret_data = data.groupby('instrument').apply(calcu_ret)
        ret_data.reset_index(inplace=True, drop=True)
        ret_data['date'] = ret_data['date'].map(lambda x:x.strftime('%Y-%m-%d'))
        context.daily_buy_industry = pd.Series({dt:seek_head_industry(ret_data.set_index('date').ix[dt]) for dt in list(set(ret_data.date))})
    
        # 每个交易日 每个行业的优质股 
        # 优质股的确定依据是:净资产收益率 (TTM)、营业收入同比增长率、归属母公司股东的净利润同比增长率
        features_data = D.features(D.instruments(start_date, end_date), start_date, end_date, ['fs_roe_ttm_0', 'fs_operating_revenue_yoy_0', 'fs_net_profit_yoy_0', 'industry_sw_level1_0'])
        # 整理出每个行业的优质股票
        context.daily_buy_stock = features_data.groupby(['date', 'industry_sw_level1_0']).apply(seek_head_stock)
    
    # 计算不同周期的动量
    def calcu_ret(df):
        df = df.sort_values('date')
        for i in [42, 84, 126]: # 分别代表2月、4月、半年的动量
            df['ret_%s'%i] = df['close']/df['close'].shift(i)-1 
        return df
    
    # 计算出得分
    def seek_head_industry(df):
        for j in ['ret_42','ret_84','ret_126']:
            df['%s'%j] = df['%s'%j].rank(ascending=True) 
        df['score'] = 0.4*df['ret_42']+0.3*df['ret_84']+0.3*df['ret_126']  # 得分的权重分别为0.4、0.3、0.3
        result = df.sort_values('score', ascending=False)
        return list(result.instrument)[:3]  # 前3个行业
    
    # 选出特定行业优质股票
    def seek_head_stock(df):
        result = df.sort_values(['fs_roe_ttm_0', 'fs_net_profit_yoy_0', 'fs_operating_revenue_yoy_0'], ascending=False)
        return list(result.instrument[:10]) # 每个行业选10只股票
    # 回测引擎:初始化函数,只执行一次
    def m3_initialize_bigquant_run(context):
        # 手续费设置
        context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5)) 
    
    # 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。
    def m3_before_trading_start_bigquant_run(context, data):
        pass
    
    m3 = M.trade.v3(
        instruments=m1.data,
        start_date='',
        end_date='',
        handle_data=m3_handle_data_bigquant_run,
        prepare=m3_prepare_bigquant_run,
        initialize=m3_initialize_bigquant_run,
        before_trading_start=m3_before_trading_start_bigquant_run,
        volume_limit=0.025,
        order_price_field_buy='open',
        order_price_field_sell='open',
        capital_base=1000000,
        benchmark='000300.SHA',
        auto_cancel_non_tradable_orders=True,
        data_frequency='daily',
        price_type='后复权',
        plot_charts=True,
        backtest_only=False,
        amount_integer=False
    )
    
    [2018-03-23 14:36:35.353946] INFO: bigquant: instruments.v2 开始运行..
    [2018-03-23 14:36:35.361383] INFO: bigquant: 命中缓存
    [2018-03-23 14:36:35.363330] INFO: bigquant: instruments.v2 运行完成[0.009448s].
    [2018-03-23 14:36:35.399952] INFO: bigquant: backtest.v7 开始运行..
    [2018-03-23 14:39:15.018858] INFO: algo: set price type:backward_adjusted
    [2018-03-23 14:40:07.388117] INFO: Performance: Simulated 541 trading days out of 541.
    [2018-03-23 14:40:07.389843] INFO: Performance: first open: 2016-01-04 01:30:00+00:00
    [2018-03-23 14:40:07.391252] INFO: Performance: last close: 2018-03-22 07:00:00+00:00
    
    • 收益率29.33%
    • 年化收益率12.73%
    • 基准收益率7.76%
    • 阿尔法0.09
    • 贝塔1.1
    • 夏普比率0.35
    • 胜率0.678
    • 盈亏比1.018
    • 收益波动率24.01%
    • 信息比率0.62
    • 最大回撤22.38%
    [2018-03-23 14:40:10.819357] INFO: bigquant: backtest.v7 运行完成[215.419508s].
    

    本文部分代码参考了@eqsxin 的代码,感谢!


    行业轮动策略
    (Base) #4

    mark,我也在写,正好借鉴一下


    (MUSDHAN) #5

    小白一枚,想请问楼主怎么导入数据来使用这个策略


    (FT2728244) #6

    无法运行哈,求解


    (o_o918) #7

    mark


    (yangfei) #8

    请问在什么软件上编译和运行, 谢谢