复制链接
克隆策略

因子选基策略

版本 v1.0

目录

一、交易规则

选出某因子排名前10的基金; 买入基金持仓60日开始换仓。

因子定义:

close/shift(close, 5):收盘价5日动量

二、策略构建步骤

1、确定基金池和回测时间

通过证券代码列表输入回测的起止日期

2、确定买卖原则

在输入特征列表中通过表达式引擎定义close/shift(close, 5)这个因子 。选出该因子值排名前10的基金。选出的10支基金为买入基金列表,以60个交易日开始换仓。

3、回测

通过 trade 模块中的初始化函数定义交易手续费和滑点; 通过 trade 模块中的主函数(handle函数)查看每日的买卖交易信号,按照买卖原则执行相应的买入/卖出/调仓操作。

三、策略的实现

可视化策略实现如下:

    {"description":"实验创建于2021/11/25","graph":{"edges":[{"to_node_id":"-16:instruments","from_node_id":"287d2cb0-f53c-4101-bdf8-104b137c8601-62:data"},{"to_node_id":"-37:instruments","from_node_id":"287d2cb0-f53c-4101-bdf8-104b137c8601-62:data"},{"to_node_id":"-47:input_1","from_node_id":"-37:data"},{"to_node_id":"-1295:input_data","from_node_id":"-47:data_1"},{"to_node_id":"-76:input_data","from_node_id":"-1295:data"},{"to_node_id":"-1295:features","from_node_id":"-1303:data"},{"to_node_id":"-16:options_data","from_node_id":"-1311:data_1"},{"to_node_id":"-1311:input_1","from_node_id":"-76:data"},{"to_node_id":"-16:history_ds","from_node_id":"-76:data"}],"nodes":[{"node_id":"-16","module_id":"BigQuantSpace.trade.trade-v4","parameters":[{"name":"start_date","value":"","type":"Literal","bound_global_parameter":null},{"name":"end_date","value":"","type":"Literal","bound_global_parameter":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.test_data = context.options['data'].read()\n ","type":"Literal","bound_global_parameter":null},{"name":"handle_data","value":"# 回测引擎:每日数据处理函数,每天执行一次\ndef bigquant_run(context, data):\n \n if (context.trading_day_index-6) % 60 ==0:\n \n today = data.current_dt.strftime('%Y-%m-%d')\n instruments = context.test_data[context.test_data['date']==today].instrument.tolist()\n stock_hold_now = {e.symbol: p.amount * p.last_sale_price\n for e, p in context.perf_tracker.position_tracker.positions.items()}\n buy_stock = [i for i in instruments if i not in stock_hold_now ]\n sell_stock = [i for i in stock_hold_now if i not in instruments]\n cash_for_buy = context.portfolio.cash\n \n \n #买入并持有\n #每标的购买资金\n \n for instrument in buy_stock :\n# print(\"current price=\", data.current(context.symbol(instrument), \"close\"), context.symbol(instrument))\n context.order_target_value(context.symbol(instrument), cash_for_buy/len(buy_stock),order_type=OrderType.MARKET)\n for instrument in sell_stock :\n context.order_target_percent(context.symbol(instrument), 0)\n\n \n ","type":"Literal","bound_global_parameter":null},{"name":"prepare","value":"# 回测引擎:准备数据,只执行一次\ndef bigquant_run(context):\n pass\n","type":"Literal","bound_global_parameter":null},{"name":"before_trading_start","value":"# 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。\ndef bigquant_run(context, data):\n pass\n","type":"Literal","bound_global_parameter":null},{"name":"volume_limit","value":"0","type":"Literal","bound_global_parameter":null},{"name":"order_price_field_buy","value":"close","type":"Literal","bound_global_parameter":null},{"name":"order_price_field_sell","value":"close","type":"Literal","bound_global_parameter":null},{"name":"capital_base","value":"300000","type":"Literal","bound_global_parameter":null},{"name":"auto_cancel_non_tradable_orders","value":"True","type":"Literal","bound_global_parameter":null},{"name":"data_frequency","value":"daily","type":"Literal","bound_global_parameter":null},{"name":"price_type","value":"真实价格","type":"Literal","bound_global_parameter":null},{"name":"product_type","value":"股票","type":"Literal","bound_global_parameter":null},{"name":"plot_charts","value":"True","type":"Literal","bound_global_parameter":null},{"name":"backtest_only","value":"False","type":"Literal","bound_global_parameter":null},{"name":"benchmark","value":"","type":"Literal","bound_global_parameter":null}],"input_ports":[{"name":"instruments","node_id":"-16"},{"name":"options_data","node_id":"-16"},{"name":"history_ds","node_id":"-16"},{"name":"benchmark_ds","node_id":"-16"},{"name":"trading_calendar","node_id":"-16"}],"output_ports":[{"name":"raw_perf","node_id":"-16"}],"cacheable":false,"seq_num":2,"comment":"","comment_collapsed":true},{"node_id":"287d2cb0-f53c-4101-bdf8-104b137c8601-62","module_id":"BigQuantSpace.instruments.instruments-v2","parameters":[{"name":"start_date","value":"2020-03-01","type":"Literal","bound_global_parameter":null},{"name":"end_date","value":"2021-09-13","type":"Literal","bound_global_parameter":null},{"name":"market","value":"CN_MUTFUND","type":"Literal","bound_global_parameter":null},{"name":"instrument_list","value":"\n","type":"Literal","bound_global_parameter":null},{"name":"max_count","value":"0","type":"Literal","bound_global_parameter":null}],"input_ports":[{"name":"rolling_conf","node_id":"287d2cb0-f53c-4101-bdf8-104b137c8601-62"}],"output_ports":[{"name":"data","node_id":"287d2cb0-f53c-4101-bdf8-104b137c8601-62"}],"cacheable":true,"seq_num":7,"comment":"预测数据,用于回测和模拟","comment_collapsed":true},{"node_id":"-37","module_id":"BigQuantSpace.use_datasource.use_datasource-v1","parameters":[{"name":"datasource_id","value":"history_nav_CN_MUTFUND","type":"Literal","bound_global_parameter":null},{"name":"start_date","value":"","type":"Literal","bound_global_parameter":null},{"name":"end_date","value":"","type":"Literal","bound_global_parameter":null}],"input_ports":[{"name":"instruments","node_id":"-37"},{"name":"features","node_id":"-37"}],"output_ports":[{"name":"data","node_id":"-37"}],"cacheable":true,"seq_num":1,"comment":"","comment_collapsed":true},{"node_id":"-47","module_id":"BigQuantSpace.cached.cached-v3","parameters":[{"name":"run","value":"# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端\ndef bigquant_run(input_1, input_2, input_3):\n df = input_1.read() \n df.rename(columns={'nav':'close'}, inplace=True)\n \n df['open'] = df['close'] - 1\n df['high'] = df['close'] + 2\n df['low'] = df['close'] - 2\n df['volume'] = 100000\n df['amount'] = df['volume'] * df['close']\n# df = df.groupby('date').apply(lambda x: x[x['close']>x['close'].mean()*3]).reset_index(drop=True)\n \n \n# from datetime import time\n# trading_date = df['date'].tolist() #.apply(lambda x: x.date()).unique().tolist() # 自定义的交易日历的日期\n# trading_sections = [(time(9,0,0), time(11,30,0)), (time(13,30,0), time(15,0,0)), (time(21,0,0), time(23,0,0))] # 自定义的交易开、收盘时间\n# trading_calendar = get_extend_calendar_with_tradingdays('MyFutCalendar', tradingdays=trading_date, trading_sections=trading_sections) # 生成自定义交易日历\n \n \n data_1 = DataSource.write_df(df)\n# data_2 = DataSource.write_pickle(trading_calendar)\n \n \n return Outputs(data_1=data_1)#,data_2=data_2)","type":"Literal","bound_global_parameter":null},{"name":"post_run","value":"# 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。\ndef bigquant_run(outputs):\n return outputs\n","type":"Literal","bound_global_parameter":null},{"name":"input_ports","value":"","type":"Literal","bound_global_parameter":null},{"name":"params","value":"{}","type":"Literal","bound_global_parameter":null},{"name":"output_ports","value":"","type":"Literal","bound_global_parameter":null}],"input_ports":[{"name":"input_1","node_id":"-47"},{"name":"input_2","node_id":"-47"},{"name":"input_3","node_id":"-47"}],"output_ports":[{"name":"data_1","node_id":"-47"},{"name":"data_2","node_id":"-47"},{"name":"data_3","node_id":"-47"}],"cacheable":true,"seq_num":3,"comment":"","comment_collapsed":true},{"node_id":"-1295","module_id":"BigQuantSpace.derived_feature_extractor.derived_feature_extractor-v3","parameters":[{"name":"date_col","value":"date","type":"Literal","bound_global_parameter":null},{"name":"instrument_col","value":"instrument","type":"Literal","bound_global_parameter":null},{"name":"drop_na","value":"False","type":"Literal","bound_global_parameter":null},{"name":"remove_extra_columns","value":"False","type":"Literal","bound_global_parameter":null},{"name":"user_functions","value":"{}","type":"Literal","bound_global_parameter":null}],"input_ports":[{"name":"input_data","node_id":"-1295"},{"name":"features","node_id":"-1295"}],"output_ports":[{"name":"data","node_id":"-1295"}],"cacheable":true,"seq_num":5,"comment":"","comment_collapsed":true},{"node_id":"-1303","module_id":"BigQuantSpace.input_features.input_features-v1","parameters":[{"name":"features","value":"close/shift(close, 5)\n","type":"Literal","bound_global_parameter":null}],"input_ports":[{"name":"features_ds","node_id":"-1303"}],"output_ports":[{"name":"data","node_id":"-1303"}],"cacheable":true,"seq_num":4,"comment":"","comment_collapsed":true},{"node_id":"-1311","module_id":"BigQuantSpace.cached.cached-v3","parameters":[{"name":"run","value":"# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端\ndef bigquant_run(input_1, input_2, input_3):\n # 示例代码如下。在这里编写您的代码\n df = input_1.read()\n df = df.groupby('date').apply(mom_factor).reset_index(drop=True)\n data_1 = DataSource.write_df(df)\n return Outputs(data_1=data_1, data_2=None, data_3=None)\ndef mom_factor(df):\n x = df.sort_values(by='close/shift(close, 5)', ascending=False)\n x = x[0:10]\n return x","type":"Literal","bound_global_parameter":null},{"name":"post_run","value":"# 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。\ndef bigquant_run(outputs):\n return outputs\n","type":"Literal","bound_global_parameter":null},{"name":"input_ports","value":"","type":"Literal","bound_global_parameter":null},{"name":"params","value":"{}","type":"Literal","bound_global_parameter":null},{"name":"output_ports","value":"","type":"Literal","bound_global_parameter":null}],"input_ports":[{"name":"input_1","node_id":"-1311"},{"name":"input_2","node_id":"-1311"},{"name":"input_3","node_id":"-1311"}],"output_ports":[{"name":"data_1","node_id":"-1311"},{"name":"data_2","node_id":"-1311"},{"name":"data_3","node_id":"-1311"}],"cacheable":true,"seq_num":6,"comment":"","comment_collapsed":true},{"node_id":"-76","module_id":"BigQuantSpace.dropnan.dropnan-v2","parameters":[],"input_ports":[{"name":"input_data","node_id":"-76"},{"name":"features","node_id":"-76"}],"output_ports":[{"name":"data","node_id":"-76"}],"cacheable":true,"seq_num":8,"comment":"","comment_collapsed":true}],"node_layout":"<node_postions><node_position Node='-16' Position='281.4771423339844,227.51148986816406,200,200'/><node_position Node='287d2cb0-f53c-4101-bdf8-104b137c8601-62' Position='160.73947143554688,-298.34022521972656,200,200'/><node_position Node='-37' Position='365.3028869628906,-197.92532348632812,200,200'/><node_position Node='-47' Position='367.1701354980469,-132.43568420410156,200,200'/><node_position Node='-1295' Position='370.9386291503906,-66.81832487881184,200,200'/><node_position Node='-1303' Position='589.4273681640625,-300.52606201171875,200,200'/><node_position Node='-1311' Position='364.13775634765625,97.84961700439453,200,200'/><node_position Node='-76' Position='362.01483154296875,8.058921813964844,200,200'/></node_postions>"},"nodes_readonly":false,"studio_version":"v2"}
    In [9]:
    # 本代码由可视化策略环境自动生成 2022年4月9日 14:59
    # 本代码单元只能在可视化模式下编辑。您也可以拷贝代码,粘贴到新建的代码单元或者策略,然后修改。
    
    
    # Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
    def m3_run_bigquant_run(input_1, input_2, input_3):
        df = input_1.read() 
        df.rename(columns={'nav':'close'}, inplace=True)
        
        df['open'] = df['close'] - 1
        df['high'] = df['close'] + 2
        df['low'] = df['close'] - 2
        df['volume'] = 100000
        df['amount'] = df['volume'] * df['close']
    #     df = df.groupby('date').apply(lambda x: x[x['close']>x['close'].mean()*3]).reset_index(drop=True)
        
        
    #     from datetime import time
    #     trading_date = df['date'].tolist()  #.apply(lambda x: x.date()).unique().tolist() # 自定义的交易日历的日期
    #     trading_sections = [(time(9,0,0), time(11,30,0)), (time(13,30,0), time(15,0,0)), (time(21,0,0), time(23,0,0))]  # 自定义的交易开、收盘时间
    #     trading_calendar = get_extend_calendar_with_tradingdays('MyFutCalendar', tradingdays=trading_date, trading_sections=trading_sections) # 生成自定义交易日历
        
        
        data_1 = DataSource.write_df(df)
    #     data_2 = DataSource.write_pickle(trading_calendar)
     
        
        return Outputs(data_1=data_1)#,data_2=data_2)
    # 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。
    def m3_post_run_bigquant_run(outputs):
        return outputs
    
    # Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
    def m6_run_bigquant_run(input_1, input_2, input_3):
        # 示例代码如下。在这里编写您的代码
        df = input_1.read()
        df = df.groupby('date').apply(mom_factor).reset_index(drop=True)
        data_1 = DataSource.write_df(df)
        return Outputs(data_1=data_1, data_2=None, data_3=None)
    def mom_factor(df):
        x = df.sort_values(by='close/shift(close, 5)', ascending=False)
        x = x[0:10]
        return x
    # 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。
    def m6_post_run_bigquant_run(outputs):
        return outputs
    
    # 回测引擎:初始化函数,只执行一次
    def m2_initialize_bigquant_run(context):
        # 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
        context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
        context.test_data = context.options['data'].read()
        
    # 回测引擎:每日数据处理函数,每天执行一次
    def m2_handle_data_bigquant_run(context, data):
        
        if (context.trading_day_index-6) % 60 ==0:
            
            today = data.current_dt.strftime('%Y-%m-%d')
            instruments = context.test_data[context.test_data['date']==today].instrument.tolist()
            stock_hold_now = {e.symbol: p.amount * p.last_sale_price
                     for e, p in context.perf_tracker.position_tracker.positions.items()}
            buy_stock = [i for i in instruments if i not in stock_hold_now ]
            sell_stock = [i for i in stock_hold_now if i not in instruments]
            cash_for_buy = context.portfolio.cash
          
           
            #买入并持有
            #每标的购买资金
              
            for instrument in buy_stock :
    #             print("current price=", data.current(context.symbol(instrument), "close"), context.symbol(instrument))
                context.order_target_value(context.symbol(instrument), cash_for_buy/len(buy_stock),order_type=OrderType.MARKET)
            for instrument in sell_stock :
                context.order_target_percent(context.symbol(instrument), 0)
    
                
        
    # 回测引擎:准备数据,只执行一次
    def m2_prepare_bigquant_run(context):
        pass
    
    # 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。
    def m2_before_trading_start_bigquant_run(context, data):
        pass
    
    
    m7 = M.instruments.v2(
        start_date='2020-03-01',
        end_date='2021-09-13',
        market='CN_MUTFUND',
        instrument_list="""
    """,
        max_count=0
    )
    
    m1 = M.use_datasource.v1(
        instruments=m7.data,
        datasource_id='history_nav_CN_MUTFUND',
        start_date='',
        end_date=''
    )
    
    m3 = M.cached.v3(
        input_1=m1.data,
        run=m3_run_bigquant_run,
        post_run=m3_post_run_bigquant_run,
        input_ports='',
        params='{}',
        output_ports=''
    )
    
    m4 = M.input_features.v1(
        features="""close/shift(close, 5)
    """
    )
    
    m5 = M.derived_feature_extractor.v3(
        input_data=m3.data_1,
        features=m4.data,
        date_col='date',
        instrument_col='instrument',
        drop_na=False,
        remove_extra_columns=False,
        user_functions={}
    )
    
    m8 = M.dropnan.v2(
        input_data=m5.data
    )
    
    m6 = M.cached.v3(
        input_1=m8.data,
        run=m6_run_bigquant_run,
        post_run=m6_post_run_bigquant_run,
        input_ports='',
        params='{}',
        output_ports=''
    )
    
    m2 = M.trade.v4(
        instruments=m7.data,
        options_data=m6.data_1,
        history_ds=m8.data,
        start_date='',
        end_date='',
        initialize=m2_initialize_bigquant_run,
        handle_data=m2_handle_data_bigquant_run,
        prepare=m2_prepare_bigquant_run,
        before_trading_start=m2_before_trading_start_bigquant_run,
        volume_limit=0,
        order_price_field_buy='close',
        order_price_field_sell='close',
        capital_base=300000,
        auto_cancel_non_tradable_orders=True,
        data_frequency='daily',
        price_type='真实价格',
        product_type='股票',
        plot_charts=True,
        backtest_only=False,
        benchmark=''
    )
    
    • 收益率26.8%
    • 年化收益率17.15%
    • 基准收益率26.69%
    • 阿尔法0.08
    • 贝塔0.46
    • 夏普比率0.74
    • 胜率0.56
    • 盈亏比2.35
    • 收益波动率20.23%
    • 信息比率-0.0
    • 最大回撤22.12%
    bigcharts-data-start/{"__type":"tabs","__id":"bigchart-0ae72f46aef94c10a85156e8beae723a"}/bigcharts-data-end