情绪指数择时模型


(lpl22) #1

简介

本帖对国信证券的研报——《国信投资者情绪指数择时模型》进行了复现,文末有策略链接,欢迎克隆研究。

研报在借鉴 A. D.Persaud 风险偏好指数的基础上,根据中国股市特点,将该方法完善改进后移植到 A 股市场,构建了国信投资者情绪指数 GSISI。

A.D.Persaud 因为在金融领域的诸多创新而为众人所知,其在研究货币市场时,发明了一种度量投资者风险偏好的良好方法——风险偏好指数(Risk AppetiteIndex)。基本方法是在度量资产的风险与收益之间的相关系数的基础上设计了风险偏好指数。
该报告在借鉴 A. D.Persaud 风险偏好指数的基础上,根据中国股市特点,将该方法完善改进后移植到 A 股市场,构建了国信投资者情绪指数 GSISI(GuoSen Investor Sentiment Index)。

一些预备知识

β系数

β系数也称为贝塔系数(Beta coefficient),是一种风险指数,用来衡量个别股票或股票基金相对于整个股市的价格波动情况。β系数是一种评估证券系统性风险的工具,用以度量一种证券或一个投资证券组合相对总体市场的波动性,在股票、基金等投资术语中常见。
本帖计算方法:$\beta = \frac{Cov(r_a,r_m)}{\sigma_m^2}(其中Cov(r_a,r_m)是证券 a 的收益与市场收益的协方差,\sigma_m^2是市场收益的方差。)$

Pearson相关系数

Pearson相关系数(Pearson CorrelationCoefficient)是用来衡量两个数据集合是否在一条线上面,它用来衡量定距变量间的线性关系。

spearman相关系数

在 统计学中, 以查尔斯·斯皮尔曼命名的斯皮尔曼等级相关系数,即spearman相关系数。经常用希腊字母ρ表示。 它是衡量两个变量的依赖性的 非参数 指标。 它利用单调方程评价两个统计变量的相关性。 如果数据中没有重复值, 并且当两个变量完全单调相关时,斯皮尔曼相关系数则为+1或−1。

构建方法

Beta 系数衡量申万一级行业相对沪深 300 指数的波动性,而Spearman秩相关系数度量申万一级行业的 Beta系数和其收益率之间的等级相关性。
即 Spearman 秩相关系数度量的是当所有申万一级行业的 Beta 系数按照大小排序(即行业 Beta 轮动)时,其收益率的大小排序(即行业收益率轮动)能保持与 Beta 系数排序(即行业 Beta 轮动)一致性的程度和方向。

这种一致性的程度和方向体现投资者悲观或乐观程度,即投资者情绪。因此,我们发现以下规律:

(1)当两个变量(申万一级行业的 Beta 系数和收益率)排序一致性程度上升,方向相同,$\rho_s\geq0$ ,表示投资乐观情绪上扬;
(2)当两个变量(申万一级行业的 Beta 系数和收益率)排序一致性程度上升,方向相反,$\rho_s\leq0$ ,表示投资悲观情绪蔓延

基本思路是首先计算 28 个申万一级行业周收益率以及其相对沪深 300 指数的周 Beta 系数;
然后测算 28 个申万一级行业周收益率与其周 Beta 系数的 Spearman 秩相关系数;
最后以 Spearman 秩相关系数为基础构建国信投资者情绪指数 GSISI。


策略思路
若 GSISI 连续两次发出看多(或看空)信号,则看多(或看空)沪深 300 指数,且保持这个判断,直到连续两次看空(或看多)信号出现,则发生看空(或看多)沪深 300指数的反转判断;若 GSISI 发出多空交叉互现信号,则除最新信号外,前面的交叉信号作废,以最新信号为判断起点,按照前面两条准则重新分析后面的信号。
具体步骤是:

  1. 若国信投资者情绪指数 GSISI>37.1,则作为看多沪深 300 的一次警示信号。若紧接着再次GSISI>37.1 ,则作为看多沪深 300 的确认信号,正式看多沪深300,一次判断完成,且保持此判断,直到有相反的判断出现。若紧接着GSISI<37.1 ,则看多沪深 300 的一次警示信号作废,以此最新的信号为判断起点,进行下一轮的判断。

  2. 类似地,若国信投资者情绪指数GSISI<37.1 ,则作为看空沪深 300 的一次警示信号。若紧接着再次GSISI<37.1 ,则作为看空沪深 300 的确认信号,正式看空沪深300,一次判断完成,且保持此判断,直到有相反的判断出现。若紧接着GSISI>37.1 ,则看空沪深 300 的一次警示信号作废,以此最新的信号为判断起点,进行下一轮的判断。
    (这里的37.1是在对Spearman秩相关系数进行显著性检验后得出的)

  3. 按照步骤(1)(2)循环判断,产生一系列关于沪深 300 的多空观点。如此设计完成国信投资者情绪指数择时模型(GSISI 择时模型)。

如图所示,国信投资者情绪指数 GSISI 基本上暗示了沪深 300 指数的运行趋势,一般而言,投资者情绪乐观时,沪深 300 指数上升,投资者情绪悲观时,沪深 300 指数下行。

提取数据



计算出各个行业的平均利润,用以后续计算

计算GSISI与回测

主函数代码
# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
def bigquant_run(input_1, input_2):
    #链接数据,求出各行业和沪深300平均周收益率
    df = input_2.read_df() 
    df = df[['date','instrument','close']]
    df_1 = input_1
    df_2 = pd.merge(df,df_1,on='date')
    df_2_filter = df_2[df_2.industry_sw_level1!=0]
    df_2_filter = df_2_filter[['date','close','mean_close','industry_sw_level1']]
    df1 = df_2_filter.set_index('date').groupby('industry_sw_level1')[['close','mean_close']].rolling(7).corr()
    df2 = df1.groupby(level=[0,1]).last()['close'].to_frame('corre').reset_index()
    result = df2.merge(df_2_filter,on=['industry_sw_level1','date']).drop_duplicates()#.dropna(how='any')
    result['industry_pct_7'] = result.groupby('industry_sw_level1')['mean_close'].pct_change(6)
    result['hs300_pct_7'] = result.groupby('industry_sw_level1')['close'].pct_change(6)
    result = result.dropna(how='any')
    # 引入函数,为后续计算做准备
    from scipy.stats import spearmanr
    from math import sqrt
   
    def corrcoef(x, y):
        n = len(x)
        # 求和
        sum1 = sum(x)
        sum2 = sum(y)
        # 求乘积之和
        sumofxy = np.sum([a*b for a,b in zip(x,y)])
        # 求平方和
        sumofx2 = sum([pow(i, 2) for i in x])
        sumofy2 = sum([pow(j, 2) for j in y])
        num = sumofxy - (float(sum1) * float(sum2) / n)
        # 计算皮尔逊相关系数
        den = sqrt((sumofx2 - float(sum1 ** 2) / n) * (sumofy2 - float(sum2 ** 2) / n))
        return num / den
    def cal(df):
        from scipy.stats import spearmanr
        i = 1
        result = []
        while i < 101:
            result.append(0)
            i+=1
        for k in range(len(df)):
            if k >= 100:
                x = np.array(df.iloc[k-100:k]['industry_pct_7'])
                y = np.array(df.iloc[k-100:k]['hs300_pct_7'])
                corr = corrcoef(x,y)
                std = x.std()
                beta = corr/std**2
                result.append(corr)
        df_temp = pd.DataFrame(result,index=df.index,columns=['beta'])
        df= pd.concat([df,df_temp],axis=1)
        return df 
    #计算各个行业收益率与沪深300收益率的相关系数与标准差,求出β系数
    result = result.set_index('date').groupby('industry_sw_level1').apply(cal).reset_index()
    result = result[result['beta']!=0]
    result = result.dropna()
    #计算出各个行业β系数与沪深300周收益率的spearman秩相关系数,得出GSISI指数
    def cal_1(df):
        z = []
        from scipy.stats import spearmanr
        x = np.array(df.iloc[:]['industry_pct_7'])
        y = np.array(df.iloc[:]['beta'])
        corr = spearmanr(x,y)[0]
        return corr    
    result_1 = result.groupby('date').apply(cal_1)
    x = pd.DataFrame(result_1,columns=['spearman'])
    #m为交易临界值,编写买卖逻辑。
    m = 0.371
    x['label_+'] = (x['spearman']>=m)
    x['label_-'] = (x['spearman']<=-m)*-0.25
    x['label_0'] = x['label_+'] + x['label_-']
    
    def cal_2(df):
        result = []
        for k in range(len(df)):
            x = df.iloc[k]['label_0']
            y = df.iloc[k-1]['label_0']
            z = x+y
            p = 0
            if z == -0.5:
                p=-1
            if z == 2:
                p=1
            if z == 0.75:
                df.iloc[k]['label_2'] = 0
            result.append(p)
        df_temp = pd.DataFrame(result,index=df.index,columns=['label'])
        df= pd.concat([df,df_temp],axis=1)
        return df     
    result_2 = cal_2(x).sort_index().reset_index()
    result_2['instrument'] =  '000300.HIX'
    result_2['buy_condition'] = result_2['label']==1
    result_2['sell_condition'] = result_2['label']==-1
       
    return Outputs(data_1=result_2)
         
    
克隆策略

    {"Description":"实验创建于2017/8/26","Summary":"","Graph":{"EdgesInternal":[{"DestinationInputPortId":"-1932:instruments","SourceOutputPortId":"-781:data"},{"DestinationInputPortId":"-160:instruments","SourceOutputPortId":"-781:data"},{"DestinationInputPortId":"-172:data2","SourceOutputPortId":"-1932:data"},{"DestinationInputPortId":"-263:input_2","SourceOutputPortId":"-2390:data"},{"DestinationInputPortId":"-60:history_ds","SourceOutputPortId":"-2390:data"},{"DestinationInputPortId":"-2390:instruments","SourceOutputPortId":"-2396:data"},{"DestinationInputPortId":"-60:instruments","SourceOutputPortId":"-2396:data"},{"DestinationInputPortId":"-172:data1","SourceOutputPortId":"-160:data"},{"DestinationInputPortId":"-182:input_1","SourceOutputPortId":"-172:data"},{"DestinationInputPortId":"-263:input_1","SourceOutputPortId":"-182:data_1"},{"DestinationInputPortId":"-60:options_data","SourceOutputPortId":"-263:data_1"}],"ModuleNodes":[{"Id":"-781","ModuleId":"BigQuantSpace.instruments.instruments-v2","ModuleParameters":[{"Name":"start_date","Value":"2014-01-01","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"2018-01-01","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":"-781"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-781","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":2,"IsPartOfPartialRun":null,"Comment":"","CommentCollapsed":true},{"Id":"-1932","ModuleId":"BigQuantSpace.use_datasource.use_datasource-v1","ModuleParameters":[{"Name":"datasource_id","Value":"bar1d_CN_STOCK_A","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"start_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"instruments","NodeId":"-1932"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"features","NodeId":"-1932"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-1932","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":3,"IsPartOfPartialRun":null,"Comment":"","CommentCollapsed":true},{"Id":"-2390","ModuleId":"BigQuantSpace.use_datasource.use_datasource-v1","ModuleParameters":[{"Name":"datasource_id","Value":"bar1d_index_CN_STOCK_A","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"start_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"instruments","NodeId":"-2390"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"features","NodeId":"-2390"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-2390","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":4,"IsPartOfPartialRun":null,"Comment":"得出沪深300与A股股指数据","CommentCollapsed":true},{"Id":"-2396","ModuleId":"BigQuantSpace.instruments.instruments-v2","ModuleParameters":[{"Name":"start_date","Value":"2014-01-01","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"2018-01-01","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"market","Value":"CN_STOCK_A","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"instrument_list","Value":"000300.HIX\n\n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"max_count","Value":0,"ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"rolling_conf","NodeId":"-2396"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-2396","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":5,"IsPartOfPartialRun":null,"Comment":"","CommentCollapsed":true},{"Id":"-160","ModuleId":"BigQuantSpace.use_datasource.use_datasource-v1","ModuleParameters":[{"Name":"datasource_id","Value":"industry_CN_STOCK_A","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"start_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"instruments","NodeId":"-160"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"features","NodeId":"-160"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-160","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":1,"IsPartOfPartialRun":null,"Comment":"","CommentCollapsed":true},{"Id":"-172","ModuleId":"BigQuantSpace.join.join-v3","ModuleParameters":[{"Name":"on","Value":"date,instrument","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"how","Value":"inner","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"sort","Value":"False","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"data1","NodeId":"-172"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"data2","NodeId":"-172"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-172","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":7,"IsPartOfPartialRun":null,"Comment":"得出A股每日数据与行业概念","CommentCollapsed":true},{"Id":"-182","ModuleId":"BigQuantSpace.cached.cached-v3","ModuleParameters":[{"Name":"run","Value":"# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端\ndef bigquant_run(input_1):\n # 示例代码如下。在这里编写您的代码0\n #对数据源提取出来的数据进行初步处理\n df_2 = input_1.read_df()\n df_2.drop(['adjust_factor','concept','industry_sw_level2','industry_sw_level3','amount','high','deal_number','low','open','turn','volume'],axis=1,inplace=True)\n df_3 = df_2.groupby(['date','industry_sw_level1']).apply(lambda x:x['close'].mean()).to_frame('mean_close').reset_index()\n df_3 = df_3.sort_values(by=['industry_sw_level1','date'])\n df_3['yield_rate'] = df_3['mean_close'].pct_change()\n return Outputs(data_1=df_3, data_2=None, data_3=None)\n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"post_run","Value":"# 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。\ndef bigquant_run(outputs):\n return outputs\n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"input_ports","Value":"input_1","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"params","Value":"{}","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"output_ports","Value":"data_1","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_1","NodeId":"-182"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_2","NodeId":"-182"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_3","NodeId":"-182"}],"OutputPortsInternal":[{"Name":"data_1","NodeId":"-182","OutputType":null},{"Name":"data_2","NodeId":"-182","OutputType":null},{"Name":"data_3","NodeId":"-182","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":6,"IsPartOfPartialRun":null,"Comment":"","CommentCollapsed":true},{"Id":"-60","ModuleId":"BigQuantSpace.trade.trade-v4","ModuleParameters":[{"Name":"start_date","Value":"2014-01-01","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"2018-01-01","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"handle_data","Value":"# 回测引擎:每日数据处理函数,每天执行一次\ndef bigquant_run(context, data):\n # 获取今日的日期\n today = data.current_dt.strftime('%Y-%m-%d') \n # 通过positions对象,使用列表生成式的方法获取目前持仓的股票列表\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 \n # 记录用于买入股票的可用现金,因为是尾盘卖股票尾盘买股票,需要记录卖出的股票市值并在买入下单前更新可用现金;\n # 如果是早盘买尾盘卖,则卖出时不需更新可用现金,因为尾盘卖出股票所得现金无法使用\n cash_for_buy = context.portfolio.cash \n \n try:\n buy_stock = context.daily_stock_buy[today] # 当日符合买入条件的股票\n except:\n buy_stock=[] # 如果没有符合条件的股票,就设置为空\n \n try:\n sell_stock = context.daily_stock_sell[today] # 当日符合卖出条件的股票\n except:\n sell_stock=[] # 如果没有符合条件的股票,就设置为空\n \n # 需要卖出的股票:已有持仓中符合卖出条件的股票\n stock_to_sell = [ i for i in stock_hold_now if i in sell_stock ]\n # 需要买入的股票:没有持仓且符合买入条件的股票\n stock_to_buy = [ i for i in buy_stock if i not in stock_hold_now ] \n # 需要调仓的股票:已有持仓且不符合卖出条件的股票\n stock_to_adjust=[ i for i in stock_hold_now if i not in sell_stock ]\n \n # 如果有卖出信号\n if len(stock_to_sell)>0:\n for instrument in stock_to_sell:\n sid = context.symbol(instrument) # 将标的转化为equity格式\n cur_position = context.portfolio.positions[sid].amount # 持仓\n if cur_position > 0 and data.can_trade(sid):\n context.order_target_percent(sid, 0) # 全部卖出\n # 因为是尾盘卖股票尾盘买股票,需要记录卖出的股票市值并在买入下单前更新可用现金;\n # 如果是早盘买尾盘卖,则卖出时不需更新可用现金,因为尾盘卖出股票所得现金无法使用\n cash_for_buy += stock_hold_now[instrument]\n \n # 如果有买入信号/有持仓\n if len(stock_to_buy)+len(stock_to_adjust)>0:\n weight = 1/(len(stock_to_buy)+len(stock_to_adjust)) # 每只股票的比重为等资金比例持有\n for instrument in stock_to_buy+stock_to_adjust:\n sid = context.symbol(instrument) # 将标的转化为equity格式\n if data.can_trade(sid):\n context.order_target_value(sid, weight*cash_for_buy) # 买入","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"prepare","Value":"# 回测引擎:准备数据,只执行一次\ndef bigquant_run(context):\n # 加载预测数据\n df = context.options['data']\n\n # 函数:求满足开仓条件的股票列表\n def open_pos_con(df):\n return list(df[df['buy_condition']>0].instrument)\n\n # 函数:求满足平仓条件的股票列表\n def close_pos_con(df):\n return list(df[df['sell_condition']>0].instrument)\n\n # 每日买入股票的数据框\n context.daily_stock_buy= df.groupby('date').apply(open_pos_con)\n # 每日卖出股票的数据框\n context.daily_stock_sell= df.groupby('date').apply(close_pos_con)","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":"close","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"capital_base","Value":1000000,"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":"product_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":"benchmark","Value":"000300.SHA","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"instruments","NodeId":"-60"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"options_data","NodeId":"-60"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"history_ds","NodeId":"-60"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"benchmark_ds","NodeId":"-60"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"trading_calendar","NodeId":"-60"}],"OutputPortsInternal":[{"Name":"raw_perf","NodeId":"-60","OutputType":null}],"UsePreviousResults":false,"moduleIdForCode":8,"IsPartOfPartialRun":null,"Comment":"","CommentCollapsed":true},{"Id":"-263","ModuleId":"BigQuantSpace.cached.cached-v3","ModuleParameters":[{"Name":"run","Value":"# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端\ndef bigquant_run(input_1, input_2):\n #链接数据,求出各行业和沪深300平均周收益率\n df = input_2.read_df() \n df = df[['date','instrument','close']]\n df_1 = input_1\n df_2 = pd.merge(df,df_1,on='date')\n df_2_filter = df_2[df_2.industry_sw_level1!=0]\n df_2_filter = df_2_filter[['date','close','mean_close','industry_sw_level1']]\n df1 = df_2_filter.set_index('date').groupby('industry_sw_level1')[['close','mean_close']].rolling(7).corr()\n df2 = df1.groupby(level=[0,1]).last()['close'].to_frame('corre').reset_index()\n result = df2.merge(df_2_filter,on=['industry_sw_level1','date']).drop_duplicates()#.dropna(how='any')\n result['industry_pct_7'] = result.groupby('industry_sw_level1')['mean_close'].pct_change(6)\n result['hs300_pct_7'] = result.groupby('industry_sw_level1')['close'].pct_change(6)\n result = result.dropna(how='any')\n # 引入函数,为后续计算做准备\n from scipy.stats import spearmanr\n from math import sqrt\n \n def corrcoef(x, y):\n n = len(x)\n # 求和\n sum1 = sum(x)\n sum2 = sum(y)\n # 求乘积之和\n sumofxy = np.sum([a*b for a,b in zip(x,y)])\n # 求平方和\n sumofx2 = sum([pow(i, 2) for i in x])\n sumofy2 = sum([pow(j, 2) for j in y])\n num = sumofxy - (float(sum1) * float(sum2) / n)\n # 计算皮尔逊相关系数\n den = sqrt((sumofx2 - float(sum1 ** 2) / n) * (sumofy2 - float(sum2 ** 2) / n))\n return num / den\n def cal(df):\n from scipy.stats import spearmanr\n i = 1\n result = []\n while i < 101:\n result.append(0)\n i+=1\n for k in range(len(df)):\n if k >= 100:\n x = np.array(df.iloc[k-100:k]['industry_pct_7'])\n y = np.array(df.iloc[k-100:k]['hs300_pct_7'])\n corr = corrcoef(x,y)\n std = x.std()\n beta = corr/std**2\n result.append(corr)\n df_temp = pd.DataFrame(result,index=df.index,columns=['beta'])\n df= pd.concat([df,df_temp],axis=1)\n return df \n #计算各个行业收益率与沪深300收益率的相关系数与标准差,求出β系数\n result = result.set_index('date').groupby('industry_sw_level1').apply(cal).reset_index()\n result = result[result['beta']!=0]\n result = result.dropna()\n #计算出各个行业β系数与沪深300周收益率的spearman秩相关系数,得出GSISI指数\n def cal_1(df):\n z = []\n from scipy.stats import spearmanr\n x = np.array(df.iloc[:]['industry_pct_7'])\n y = np.array(df.iloc[:]['beta'])\n corr = spearmanr(x,y)[0]\n return corr \n result_1 = result.groupby('date').apply(cal_1)\n x = pd.DataFrame(result_1,columns=['spearman'])\n #m为交易临界值,编写买卖逻辑。\n m = 0.371\n x['label_+'] = (x['spearman']>=m)\n x['label_-'] = (x['spearman']<=-m)*-0.25\n x['label_0'] = x['label_+'] + x['label_-']\n \n def cal_2(df):\n result = []\n for k in range(len(df)):\n x = df.iloc[k]['label_0']\n y = df.iloc[k-1]['label_0']\n z = x+y\n p = 0\n if z == -0.5:\n p=-1\n if z == 2:\n p=1\n if z == 0.75:\n df.iloc[k]['label_2'] = 0\n result.append(p)\n df_temp = pd.DataFrame(result,index=df.index,columns=['label'])\n df= pd.concat([df,df_temp],axis=1)\n return df \n result_2 = cal_2(x).sort_index().reset_index()\n result_2['instrument'] = '000300.HIX'\n result_2['buy_condition'] = result_2['label']==1\n result_2['sell_condition'] = result_2['label']==-1\n \n return Outputs(data_1=result_2)\n ","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"post_run","Value":"# 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。\ndef bigquant_run(outputs):\n return outputs\n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"input_ports","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"params","Value":"{}","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"output_ports","Value":"","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_1","NodeId":"-263"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_2","NodeId":"-263"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"input_3","NodeId":"-263"}],"OutputPortsInternal":[{"Name":"data_1","NodeId":"-263","OutputType":null},{"Name":"data_2","NodeId":"-263","OutputType":null},{"Name":"data_3","NodeId":"-263","OutputType":null}],"UsePreviousResults":true,"moduleIdForCode":9,"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='-781' Position='348,-258,200,200'/><NodePosition Node='-1932' Position='382,-140,200,200'/><NodePosition Node='-2390' Position='739,-14,200,200'/><NodePosition Node='-2396' Position='938,-235,200,200'/><NodePosition Node='-160' Position='-12,-134,200,200'/><NodePosition Node='-172' Position='131,-24,200,200'/><NodePosition Node='-182' Position='324,91,200,200'/><NodePosition Node='-60' Position='881,494,200,200'/><NodePosition Node='-263' Position='552,212,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 [2]:
    # 本代码由可视化策略环境自动生成 2019年2月20日 13:16:52
    # 本代码单元只能在可视化模式下编辑。您也可以拷贝代码,粘贴到新建的代码单元或者策略,然后修改。
    
    
    # Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
    def m6_run_bigquant_run(input_1):
        # 示例代码如下。在这里编写您的代码0
        #对数据源提取出来的数据进行初步处理
        df_2 = input_1.read_df()
        df_2.drop(['adjust_factor','concept','industry_sw_level2','industry_sw_level3','amount','high','deal_number','low','open','turn','volume'],axis=1,inplace=True)
        df_3 = df_2.groupby(['date','industry_sw_level1']).apply(lambda x:x['close'].mean()).to_frame('mean_close').reset_index()
        df_3 = df_3.sort_values(by=['industry_sw_level1','date'])
        df_3['yield_rate'] = df_3['mean_close'].pct_change()
        return Outputs(data_1=df_3, data_2=None, data_3=None)
    
    # 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。
    def m6_post_run_bigquant_run(outputs):
        return outputs
    
    # Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
    def m9_run_bigquant_run(input_1, input_2):
        #链接数据,求出各行业和沪深300平均周收益率
        df = input_2.read_df() 
        df = df[['date','instrument','close']]
        df_1 = input_1
        df_2 = pd.merge(df,df_1,on='date')
        df_2_filter = df_2[df_2.industry_sw_level1!=0]
        df_2_filter = df_2_filter[['date','close','mean_close','industry_sw_level1']]
        df1 = df_2_filter.set_index('date').groupby('industry_sw_level1')[['close','mean_close']].rolling(7).corr()
        df2 = df1.groupby(level=[0,1]).last()['close'].to_frame('corre').reset_index()
        result = df2.merge(df_2_filter,on=['industry_sw_level1','date']).drop_duplicates()#.dropna(how='any')
        result['industry_pct_7'] = result.groupby('industry_sw_level1')['mean_close'].pct_change(6)
        result['hs300_pct_7'] = result.groupby('industry_sw_level1')['close'].pct_change(6)
        result = result.dropna(how='any')
        # 引入函数,为后续计算做准备
        from scipy.stats import spearmanr
        from math import sqrt
       
        def corrcoef(x, y):
            n = len(x)
            # 求和
            sum1 = sum(x)
            sum2 = sum(y)
            # 求乘积之和
            sumofxy = np.sum([a*b for a,b in zip(x,y)])
            # 求平方和
            sumofx2 = sum([pow(i, 2) for i in x])
            sumofy2 = sum([pow(j, 2) for j in y])
            num = sumofxy - (float(sum1) * float(sum2) / n)
            # 计算皮尔逊相关系数
            den = sqrt((sumofx2 - float(sum1 ** 2) / n) * (sumofy2 - float(sum2 ** 2) / n))
            return num / den
        def cal(df):
            from scipy.stats import spearmanr
            i = 1
            result = []
            while i < 101:
                result.append(0)
                i+=1
            for k in range(len(df)):
                if k >= 100:
                    x = np.array(df.iloc[k-100:k]['industry_pct_7'])
                    y = np.array(df.iloc[k-100:k]['hs300_pct_7'])
                    corr = corrcoef(x,y)
                    std = x.std()
                    beta = corr/std**2
                    result.append(corr)
            df_temp = pd.DataFrame(result,index=df.index,columns=['beta'])
            df= pd.concat([df,df_temp],axis=1)
            return df 
        #计算各个行业收益率与沪深300收益率的相关系数与标准差,求出β系数
        result = result.set_index('date').groupby('industry_sw_level1').apply(cal).reset_index()
        result = result[result['beta']!=0]
        result = result.dropna()
        #计算出各个行业β系数与沪深300周收益率的spearman秩相关系数,得出GSISI指数
        def cal_1(df):
            z = []
            from scipy.stats import spearmanr
            x = np.array(df.iloc[:]['industry_pct_7'])
            y = np.array(df.iloc[:]['beta'])
            corr = spearmanr(x,y)[0]
            return corr    
        result_1 = result.groupby('date').apply(cal_1)
        x = pd.DataFrame(result_1,columns=['spearman'])
        #m为交易临界值,编写买卖逻辑。
        m = 0.371
        x['label_+'] = (x['spearman']>=m)
        x['label_-'] = (x['spearman']<=-m)*-0.25
        x['label_0'] = x['label_+'] + x['label_-']
        
        def cal_2(df):
            result = []
            for k in range(len(df)):
                x = df.iloc[k]['label_0']
                y = df.iloc[k-1]['label_0']
                z = x+y
                p = 0
                if z == -0.5:
                    p=-1
                if z == 2:
                    p=1
                if z == 0.75:
                    df.iloc[k]['label_2'] = 0
                result.append(p)
            df_temp = pd.DataFrame(result,index=df.index,columns=['label'])
            df= pd.concat([df,df_temp],axis=1)
            return df     
        result_2 = cal_2(x).sort_index().reset_index()
        result_2['instrument'] =  '000300.HIX'
        result_2['buy_condition'] = result_2['label']==1
        result_2['sell_condition'] = result_2['label']==-1
           
        return Outputs(data_1=result_2)
        
    # 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。
    def m9_post_run_bigquant_run(outputs):
        return outputs
    
    # 回测引擎:每日数据处理函数,每天执行一次
    def m8_handle_data_bigquant_run(context, data):
        # 获取今日的日期
        today = data.current_dt.strftime('%Y-%m-%d')  
        # 通过positions对象,使用列表生成式的方法获取目前持仓的股票列表
        stock_hold_now = {e.symbol: p.amount * p.last_sale_price
                          for e, p in context.perf_tracker.position_tracker.positions.items()} 
        
        # 记录用于买入股票的可用现金,因为是尾盘卖股票尾盘买股票,需要记录卖出的股票市值并在买入下单前更新可用现金;
        # 如果是早盘买尾盘卖,则卖出时不需更新可用现金,因为尾盘卖出股票所得现金无法使用
        cash_for_buy = context.portfolio.cash   
        
        try:
            buy_stock = context.daily_stock_buy[today]  # 当日符合买入条件的股票
        except:
            buy_stock=[]  # 如果没有符合条件的股票,就设置为空
        
        try:
            sell_stock = context.daily_stock_sell[today]  # 当日符合卖出条件的股票
        except:
            sell_stock=[] # 如果没有符合条件的股票,就设置为空
        
        # 需要卖出的股票:已有持仓中符合卖出条件的股票
        stock_to_sell = [ i for i in stock_hold_now if i in sell_stock ]
        # 需要买入的股票:没有持仓且符合买入条件的股票
        stock_to_buy = [ i for i in buy_stock if i not in stock_hold_now ]  
        # 需要调仓的股票:已有持仓且不符合卖出条件的股票
        stock_to_adjust=[ i for i in stock_hold_now if i not in sell_stock ]
        
        # 如果有卖出信号
        if len(stock_to_sell)>0:
            for instrument in stock_to_sell:
                sid = context.symbol(instrument) # 将标的转化为equity格式
                cur_position = context.portfolio.positions[sid].amount # 持仓
                if cur_position > 0 and data.can_trade(sid):
                    context.order_target_percent(sid, 0) # 全部卖出
                    # 因为是尾盘卖股票尾盘买股票,需要记录卖出的股票市值并在买入下单前更新可用现金;
                    # 如果是早盘买尾盘卖,则卖出时不需更新可用现金,因为尾盘卖出股票所得现金无法使用
                    cash_for_buy += stock_hold_now[instrument]
        
        # 如果有买入信号/有持仓
        if len(stock_to_buy)+len(stock_to_adjust)>0:
            weight = 1/(len(stock_to_buy)+len(stock_to_adjust)) # 每只股票的比重为等资金比例持有
            for instrument in stock_to_buy+stock_to_adjust:
                sid = context.symbol(instrument) # 将标的转化为equity格式
                if data.can_trade(sid):
                    context.order_target_value(sid, weight*cash_for_buy) # 买入
    # 回测引擎:准备数据,只执行一次
    def m8_prepare_bigquant_run(context):
        # 加载预测数据
        df = context.options['data']
    
        # 函数:求满足开仓条件的股票列表
        def open_pos_con(df):
            return list(df[df['buy_condition']>0].instrument)
    
        # 函数:求满足平仓条件的股票列表
        def close_pos_con(df):
            return list(df[df['sell_condition']>0].instrument)
    
        # 每日买入股票的数据框
        context.daily_stock_buy= df.groupby('date').apply(open_pos_con)
        # 每日卖出股票的数据框
        context.daily_stock_sell= df.groupby('date').apply(close_pos_con)
    # 回测引擎:初始化函数,只执行一次
    def m8_initialize_bigquant_run(context):
        # 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
        context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
    
    # 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。
    def m8_before_trading_start_bigquant_run(context, data):
        pass
    
    
    m2 = M.instruments.v2(
        start_date='2014-01-01',
        end_date='2018-01-01',
        market='CN_STOCK_A',
        instrument_list='',
        max_count=0
    )
    
    m3 = M.use_datasource.v1(
        instruments=m2.data,
        datasource_id='bar1d_CN_STOCK_A',
        start_date='',
        end_date=''
    )
    
    m1 = M.use_datasource.v1(
        instruments=m2.data,
        datasource_id='industry_CN_STOCK_A',
        start_date='',
        end_date=''
    )
    
    m7 = M.join.v3(
        data1=m1.data,
        data2=m3.data,
        on='date,instrument',
        how='inner',
        sort=False
    )
    
    m6 = M.cached.v3(
        input_1=m7.data,
        run=m6_run_bigquant_run,
        post_run=m6_post_run_bigquant_run,
        input_ports='input_1',
        params='{}',
        output_ports='data_1'
    )
    
    m5 = M.instruments.v2(
        start_date='2014-01-01',
        end_date='2018-01-01',
        market='CN_STOCK_A',
        instrument_list="""000300.HIX
    
    """,
        max_count=0
    )
    
    m4 = M.use_datasource.v1(
        instruments=m5.data,
        datasource_id='bar1d_index_CN_STOCK_A',
        start_date='',
        end_date=''
    )
    
    m9 = M.cached.v3(
        input_1=m6.data_1,
        input_2=m4.data,
        run=m9_run_bigquant_run,
        post_run=m9_post_run_bigquant_run,
        input_ports='',
        params='{}',
        output_ports=''
    )
    
    m8 = M.trade.v4(
        instruments=m5.data,
        options_data=m9.data_1,
        history_ds=m4.data,
        start_date='2014-01-01',
        end_date='2018-01-01',
        handle_data=m8_handle_data_bigquant_run,
        prepare=m8_prepare_bigquant_run,
        initialize=m8_initialize_bigquant_run,
        before_trading_start=m8_before_trading_start_bigquant_run,
        volume_limit=0.025,
        order_price_field_buy='open',
        order_price_field_sell='close',
        capital_base=1000000,
        auto_cancel_non_tradable_orders=True,
        data_frequency='daily',
        price_type='后复权',
        product_type='股票',
        plot_charts=True,
        backtest_only=False,
        benchmark='000300.SHA'
    )
    
    • 收益率32.99%
    • 年化收益率7.63%
    • 基准收益率73.0%
    • 阿尔法0.01
    • 贝塔0.3
    • 夏普比率0.39
    • 胜率0.68
    • 盈亏比0.87
    • 收益波动率13.59%
    • 信息比率-0.03
    • 最大回撤25.14%

    因子逻辑介绍与平台实现方法
    (华尔街的猫) #2

    策略走的比较稳,收益波动率控制住了。