基于ATR确定头寸的择时股票量化策略

策略分享
bigstudio
标签: #<Tag:0x00007f5bf985b1d0> #<Tag:0x00007f5bf985b068>

(iQuant) #1

本文的策略类似海龟策略,不同的是加入了资金管理和仓位控制,策略逻辑较之前的海龟策略更为复杂。希望通过本文的介绍,大家对如何开发量化策略、如何使用可视化界面有更深的认识。

策略设计如下

  • 股票池:给定。股票数量越多,策略运行时间越长。
  • 起始时间:20160101-20171121
  • 头寸确定:基于ATR确定买入数量,1单位股票=账户权益*0.01/ATR,即每次买入的股票带来的损失占总账户的1%
  • 入场:最新价格为55日价格高点,买入一单元股票
  • 加仓:最新价格>上一次买入价格+0.5*ATR,买入一单元股票,最多3次加仓
  • 出场:最新价格为20日价格低点,清空仓位
  • 止损:最新价格<上一次买入价格-2*ATR,清空仓位

完整策略如下,资金管理还有待完善。至于效果这么好,主要就是既定的股票池部分股票近两年有行情。欢迎 克隆

克隆策略

    {"Description":"实验创建于2017/11/22","Summary":"","Graph":{"EdgesInternal":[{"DestinationInputPortId":"-8687:input_1","SourceOutputPortId":"-8421:data"},{"DestinationInputPortId":"-8692:instruments","SourceOutputPortId":"-8421:data"},{"DestinationInputPortId":"-8692:options_data","SourceOutputPortId":"-8687:data_1"}],"ModuleNodes":[{"Id":"-8421","ModuleId":"BigQuantSpace.instruments.instruments-v2","ModuleParameters":[{"Name":"start_date","Value":"2016-01-01","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"2017-11-21","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"market","Value":"CN_STOCK_A","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"instrument_list","Value":"000001.SZA\n000002.SZA\n000089.SZA\n000333.SZA\n600519.SZA\n601198.SHA\n002310.SZA\n601998.SHA\n601377.SHA\n601966.SHA\n000728.SZA\n601766.SHA\n600660.SHA\n002450.SZA\n600600.SHA\n600030.SHA\n000898.SZA\n002508.SZA \n601888.SHA","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"max_count","Value":0,"ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"rolling_conf","NodeId":"-8421"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-8421","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":1,"IsPartOfPartialRun":null,"Comment":"","CommentCollapsed":true},{"Id":"-8687","ModuleId":"BigQuantSpace.cached.cached-v3","ModuleParameters":[{"Name":"run","Value":"def add_column(df, series, name):\n df[name] = series\n return df\n# 计算atr函数\ndef atr(high,low,close,window):\n a=high-low\n b=np.abs(close.shift(1)-high)\n c=np.abs(close.shift(1)-low)\n tr=a.where(a>b,b)\n tr=tr.where(tr>c,c)\n return tr.rolling(window).mean()\n\n# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端\ndef bigquant_run(input_1, input_2, input_3):\n # 示例代码如下。在这里编写您的代码\n df = input_1.read_pickle()\n \n start_date = df['start_date']\n end_date = df['end_date']\n fields = ['open','high','low','close']\n instruments = df['instruments']\n data = D.history_data(instruments, start_date, end_date, fields)\n atr_data = data.groupby('instrument').apply(lambda x:add_column(x,atr(x.high,x.low,x.close,20),'ATR')).set_index('date') \n data = DataSource.write_df(atr_data)\n return Outputs(data_1=data, data_2=None, data_3=None)\n","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_1","NodeId":"-8687"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_2","NodeId":"-8687"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_3","NodeId":"-8687"}],"OutputPortsInternal":[{"Name":"data_1","NodeId":"-8687","OutputType":null},{"Name":"data_2","NodeId":"-8687","OutputType":null},{"Name":"data_3","NodeId":"-8687","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":2,"IsPartOfPartialRun":null,"Comment":"","CommentCollapsed":true},{"Id":"-8692","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 dt = data.current_dt.strftime('%Y-%m-%d') # \n for i in context.instruments:\n # 当天没有该股票的ATR数据就continue \n try:\n atr = context.atr_data.ix[dt].set_index('instrument').ix[i]['ATR']\n except KeyError:\n continue\n # 如果该股票的ATR为nan或者为0,就填充其为100 \n if numpy.isnan(atr) == False and atr != 0:\n a_unit = int(context.portfolio.portfolio_value*0.01/atr)\n else:\n a_unit = 100\n \n sid = context.symbol(i)\n price = data.current(sid, 'price') \n high_point = data.history(sid, 'high', context.entry_pos_period, '1d').max()\n low_point = data.history(sid, 'low', context.exit_pos_period, '1d').min()\n stock_hold_now = [equity.symbol for equity in context.portfolio.positions]\n \n # 计算加仓点和止损点\n if i in stock_hold_now:\n add_pos_point = context.portfolio.positions[sid].last_sale_price+0.5*atr\n stop_loss_point = context.portfolio.positions[sid].last_sale_price-2*atr\n else:\n add_pos_point = np.nan\n stop_loss_point = np.nan\n # 卖出逻辑和加仓逻辑\n if i in stock_hold_now:\n # 是否需要止损\n if price<stop_loss_point and context.portfolio.positions[sid].amount>0 and data.can_trade(sid):\n order_target_value(sid, 0)\n context.unit_remark[i] = 0\n # 是否需要出场 \n if price<low_point and context.portfolio.positions[sid].amount>0 and data.can_trade(sid):\n order_target_value(sid, 0) \n context.unit_remark[i] = 0\n # 是否需要加仓,超过4 unit就不加仓了\n if price>add_pos_point and context.portfolio.positions[sid].amount>0 and data.can_trade(sid) and context.unit_remark[i]<4:\n order(sid, a_unit)\n context.unit_remark[i] = context.unit_remark[i]+1\n else:\n pass\n # 买入逻辑\n if price>high_point and context.portfolio.positions[sid].amount==0 and data.can_trade(sid):\n order(sid, a_unit)\n context.unit_remark[i] = 1\n else:\n pass\n \n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"prepare","Value":"# 回测引擎:准备数据,只执行一次\ndef bigquant_run(context):\n pass\n","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 context.entry_pos_period = 55\n context.exit_pos_period = 20\n context.unit_remark = {}\n context.atr_data = context.options['data'].read_df()\n context.instruments = m1.data.read_pickle()['instruments']\n \n \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","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":"10000000","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":"-8692"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"options_data","NodeId":"-8692"}],"OutputPortsInternal":[{"Name":"raw_perf","NodeId":"-8692","OutputType":null}],"UsePreviousResults":false,"moduleIdForCode":3,"IsPartOfPartialRun":null,"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='-8421' Position='-7,-30,200,200'/><NodePosition Node='-8687' Position='288,154,200,200'/><NodePosition Node='-8692' Position='297.3484191894531,407.3031921386719,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 [4]:
    # 本代码由可视化策略环境自动生成 2018年1月12日 09:56
    # 本代码单元只能在可视化模式下编辑。您也可以拷贝代码,粘贴到新建的代码单元或者策略,然后修改。
    
    
    m1 = M.instruments.v2(
        start_date='2016-01-01',
        end_date='2017-11-21',
        market='CN_STOCK_A',
        instrument_list="""000001.SZA
    000002.SZA
    000089.SZA
    000333.SZA
    600519.SZA
    601198.SHA
    002310.SZA
    601998.SHA
    601377.SHA
    601966.SHA
    000728.SZA
    601766.SHA
    600660.SHA
    002450.SZA
    600600.SHA
    600030.SHA
    000898.SZA
    002508.SZA 
    601888.SHA""",
        max_count=0
    )
    
    def add_column(df, series, name):
        df[name] = series
        return df
    # 计算atr函数
    def atr(high,low,close,window):
        a=high-low
        b=np.abs(close.shift(1)-high)
        c=np.abs(close.shift(1)-low)
        tr=a.where(a>b,b)
        tr=tr.where(tr>c,c)
        return tr.rolling(window).mean()
    
    # Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
    def m2_run_bigquant_run(input_1, input_2, input_3):
        # 示例代码如下。在这里编写您的代码
        df = input_1.read_pickle()
        
        start_date = df['start_date']
        end_date = df['end_date']
        fields = ['open','high','low','close']
        instruments =  df['instruments']
        data = D.history_data(instruments, start_date, end_date, fields)
        atr_data  = data.groupby('instrument').apply(lambda x:add_column(x,atr(x.high,x.low,x.close,20),'ATR')).set_index('date')  
        data = DataSource.write_df(atr_data)
        return Outputs(data_1=data, data_2=None, data_3=None)
    
    m2 = M.cached.v3(
        input_1=m1.data,
        run=m2_run_bigquant_run
    )
    
    # 回测引擎:每日数据处理函数,每天执行一次
    def m3_handle_data_bigquant_run(context, data):
        
        dt = data.current_dt.strftime('%Y-%m-%d') #  
        for i in context.instruments:
            # 当天没有该股票的ATR数据就continue 
            try:
                atr = context.atr_data.ix[dt].set_index('instrument').ix[i]['ATR']
            except KeyError:
                continue
            # 如果该股票的ATR为nan或者为0,就填充其为100    
            if numpy.isnan(atr) == False and atr != 0:
                a_unit = int(context.portfolio.portfolio_value*0.01/atr)
            else:
                a_unit = 100
                
            sid = context.symbol(i)
            price = data.current(sid, 'price')  
            high_point = data.history(sid, 'high', context.entry_pos_period, '1d').max()
            low_point = data.history(sid, 'low', context.exit_pos_period, '1d').min()
            stock_hold_now = [equity.symbol for equity in context.portfolio.positions]
            
            # 计算加仓点和止损点
            if i in stock_hold_now:
                add_pos_point = context.portfolio.positions[sid].last_sale_price+0.5*atr
                stop_loss_point = context.portfolio.positions[sid].last_sale_price-2*atr
            else:
                add_pos_point = np.nan
                stop_loss_point = np.nan
            # 卖出逻辑和加仓逻辑
            if i in stock_hold_now:
                # 是否需要止损
                if price<stop_loss_point and context.portfolio.positions[sid].amount>0 and data.can_trade(sid):
                    order_target_value(sid, 0)
                    context.unit_remark[i] = 0
                # 是否需要出场  
                if price<low_point and context.portfolio.positions[sid].amount>0 and data.can_trade(sid):
                    order_target_value(sid, 0)   
                    context.unit_remark[i] = 0
                # 是否需要加仓,超过4 unit就不加仓了
                if price>add_pos_point and context.portfolio.positions[sid].amount>0 and data.can_trade(sid) and context.unit_remark[i]<4:
                    order(sid, a_unit)
                    context.unit_remark[i] = context.unit_remark[i]+1
            else:
                pass
            # 买入逻辑
            if price>high_point and context.portfolio.positions[sid].amount==0 and data.can_trade(sid):
                order(sid, a_unit)
                context.unit_remark[i] = 1
            else:
                pass
       
    
    # 回测引擎:准备数据,只执行一次
    def m3_prepare_bigquant_run(context):
        pass
    
    # 回测引擎:初始化函数,只执行一次
    def m3_initialize_bigquant_run(context):
       
        context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
        context.entry_pos_period = 55
        context.exit_pos_period = 20
        context.unit_remark = {}
        context.atr_data = context.options['data'].read_df()
        context.instruments = m1.data.read_pickle()['instruments']
         
         
    
    # 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。
    def m3_before_trading_start_bigquant_run(context, data):
        pass
    
    m3 = M.trade.v3(
        instruments=m1.data,
        options_data=m2.data_1,
        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,
        order_price_field_buy='open',
        order_price_field_sell='open',
        capital_base=10000000,
        benchmark='000300.SHA',
        auto_cancel_non_tradable_orders=True,
        data_frequency='daily',
        price_type='后复权',
        plot_charts=True,
        backtest_only=False,
        amount_integer=False
    )
    
    [2018-01-12 09:33:54.399985] INFO: bigquant: instruments.v2 开始运行..
    [2018-01-12 09:33:54.403059] INFO: bigquant: 命中缓存
    [2018-01-12 09:33:54.404061] INFO: bigquant: instruments.v2 运行完成[0.004115s].
    [2018-01-12 09:33:54.413884] INFO: bigquant: cached.v3 开始运行..
    [2018-01-12 09:33:54.416539] INFO: bigquant: 命中缓存
    [2018-01-12 09:33:54.417416] INFO: bigquant: cached.v3 运行完成[0.003547s].
    [2018-01-12 09:33:54.443414] INFO: bigquant: backtest.v7 开始运行..
    [2018-01-12 09:33:54.566067] INFO: algo: set price type:backward_adjusted
    [2018-01-12 09:35:01.856489] INFO: Blotter: 2017-02-24 cancel order Equity(5 [002508.SZA]) 
    [2018-01-12 09:35:05.990673] INFO: Blotter: 2017-03-23 cancel order Equity(2 [601888.SHA]) 
    [2018-01-12 09:35:48.283417] INFO: Performance: Simulated 460 trading days out of 460.
    [2018-01-12 09:35:48.284975] INFO: Performance: first open: 2016-01-04 01:30:00+00:00
    [2018-01-12 09:35:48.287002] INFO: Performance: last close: 2017-11-21 07:00:00+00:00
    
    • 收益率72.55%
    • 年化收益率34.83%
    • 基准收益率13.04%
    • 阿尔法0.3
    • 贝塔0.25
    • 夏普比率2.06
    • 胜率--
    • 盈亏比--
    • 收益波动率14.79%
    • 信息比率1.45
    • 最大回撤8.82%
    [2018-01-12 09:35:50.422076] INFO: bigquant: backtest.v7 运行完成[115.978664s].
    

    600000.SHA下 代码运行良好,换成002865.SZA就出现KEYERROR
    5条线连出一个价值选股策略