3. Estrategia de oferta colectiva

Fuente de referencia: docs/_joinquant_migration_source/Example_03_集集竞价选股.ipynb La primera celda de Markdown.

Esta estrategia obtiene los datos de las acciones constituyentes de SHSE.000300 Shanghai y Shenzhen 300 y cuenta el número de días en los que el precio de apertura es mayor que el precio de cierre anterior dentro de los 30 días. Cuando este número supera el umbral de 10, se agrega al grupo de acciones. Luego, cierre las posiciones de las acciones que no están en el grupo de acciones y asigne equitativamente las acciones en el grupo de acciones. El intervalo de negociación es de un mes.

Datos de prueba retrospectiva: SHSE.000300 Acciones que componen el índice 300 de Shanghai y Shenzhen Tiempo de prueba retrospectiva: 2016-04-05 a 2021-02-01

>>> import qteasy as qt
>>> import pandas as pd
>>> import numpy as np
>>> htypes = 'open, close'
>>> shares = qt.filter_stock_codes(index='000300.SH', date='20220131')
>>> print(shares[0:10])
>>> dt = qt.get_history_data(htypes, shares=shares, asset_type='any', freq='m')
    ['000001.SZ', '000002.SZ', '000063.SZ', '000066.SZ', '000069.SZ', '000100.SZ', '000157.SZ', '000166.SZ', '000301.SZ', '000333.SZ']

3.1. El primer método para establecer estrategias personalizadas es generar directamente señales comerciales proporcionales PS utilizando datos de posición y datos de selección de acciones:

Utilice la clase de estrategia GeneralStrategy para calcular los factores de selección de acciones, eliminar todos los factores menores que cero, ordenar y extraer las treinta acciones principales. Genere señales comerciales de acuerdo con la siguiente lógica: 1. Verifique la posición actual. Si las acciones en la posición no están seleccionadas, véndalas todas. 2. Verifique la posición actual. Si las acciones recién seleccionadas no están en la posición, compre igualmente las acciones recién seleccionadas.

Establezca el tipo de señal comercial en PS y genere la señal comercial. Dado que la generación de señales comerciales requiere el uso de datos de posición, no se puede utilizar el modo de generación por lotes y solo se puede utilizar el modo stepwise.

>>> import numpy as np
>>> class GroupPS(qt.GeneralStg):
...     
...     def realize(self):
...         
...         # 读取策略参数(开盘价大于收盘价的天数)
...         n_day = self.get_pars('n_day')
... 
...         # 从历史数据编码中读取四种历史数据的最新数值
...         opens, closes = self.get_data('open_E_d', 'close_E_d')  
...         opens = opens[-30:]  # 从前一交易日起前30天内开盘价
...         closes = closes[-30:]  # 
...         
...         # 从持仓数据中读取当前的持仓数量,并找到持仓股序号
...         own_amounts = self.get_data('proc.own_amounts')
...         owned = np.where(own_amounts > 0)[1]  # 所有持仓股的序号
...         not_owned = np.where(own_amounts == 0)[1]  # 所有未持仓的股票序号
...         
...         # 选股因子为开盘价大于收盘价的天数,使用astype将True/False结果改为1/0,便于加总
...         factors = ((opens - closes) > 0).astype('float')
...         # 所有开盘价-收盘价>0的结果会被转化为1,其余结果转化为0,因此可以用sum得到开盘价大于收盘价的天数
...         factors = factors.sum(axis=0)
...         # 选出开盘价大于收盘价天数大于十天的所有股票的序号
...         all_args = np.arange(len(factors))
...         selected = np.where(factors > n_day)[0]
...         not_selected = np.setdiff1d(all_args, selected)
...         # 计算选出的股票的数量
...         selected_count = len(selected)
...         
...         # 开始生成交易信号
...         signal = np.zeros_like(factors)
...         # 如果持仓为正,且未被选中,生成全仓卖出交易信号
...         own_but_not_selected = np.intersect1d(owned, not_selected)
...         signal[own_but_not_selected] = -1  # 在PS信号模式下 -1 代表全仓卖出
...         
...         if selected_count == 0:
...             # 如果选中的数量为0,则不需要生成买入信号,可以直接返回只有卖出的信号
...             return signal
...         
...         # 如果持仓为零,且被选中,生成全仓买入交易信号
...         selected_but_not_own = np.intersect1d(not_owned, selected)
...         signal[selected_but_not_own] = 1. / selected_count  # 在PS信号模式下,+1 代表全仓买进 (如果多只股票均同时全仓买进,则会根据资金总量平均分配资金)
... 
...         return signal

Cree un objeto Operator y pruebe la estrategia comercial

>>> from qteasy import Parameter, StgData
>>> alpha = GroupPS(name='GroupPS',
...                 description='本策略每隔1个月定时触发, 从SHSE.000300成份股中选择过去30天内开盘价大于前收盘价的天数大于10天的股票买入',
...                 pars=[Parameter((3, 25), par_type='int', name='n_day', value=10)],
...                 data_types=[StgData('open', freq='d', asset_type='E', window_length=32), 
...                                      StgData('close', freq='d', asset_type='E', window_length=32)],
...                )  
>>> op = qt.Operator(alpha, signal_type='PS', run_freq='ME')
>>> op.op_type = 'stepwise'
>>> op.set_parameter(0, par_values=(20,))
>>> qt.run(op=op, mode=1,
...        asset_type='E',
...        asset_pool=shares,
...        invest_start='20160405',
...        invest_end='20210201',
...        trade_batch_size=100,
...        sell_batch_size=1,
...        trade_log=True)

Los resultados son los siguientes:

         ====================================
         |                                  |
         |       BACK TESTING RESULT        |
         |                                  |
         ====================================
    
    qteasy running mode: 1 - History back testing
    time consumption for operate signal creation: 0.0ms
    time consumption for operation back looping:  12s 766.3ms
    
    investment starts on      2016-04-05 00:00:00
    ends on                   2021-02-01 00:00:00
    Total looped periods:     4.8 years.
    
    -------------operation summary:------------
    Only non-empty shares are displayed, call 
    "loop_result["oper_count"]" for complete operation summary
    
              Sell Cnt Buy Cnt Total Long pct Short pct Empty pct
    000001.SZ    1        1      2     1.7%      0.0%     98.3%  
    000069.SZ    2        2      4     3.4%      0.0%     96.6%  
    000301.SZ    3        3      6    13.6%      0.0%     86.4%  
    000333.SZ    1        1      2     1.7%      0.0%     98.3%  
    000338.SZ    1        1      2     3.5%      0.0%     96.5%  
    000596.SZ    1        1      2     1.8%      0.0%     98.2%  
    000651.SZ    1        1      2     1.7%      0.0%     98.3%  
    000776.SZ    1        1      2     1.7%      0.0%     98.3%  
    000786.SZ    1        1      2     1.7%      0.0%     98.3%  
    000800.SZ    2        2      4     5.4%      0.0%     94.6%  
    ...            ...     ...   ...      ...       ...       ...
    603806.SH    2        2      4     3.6%      0.0%     96.4%  
    603939.SH    0        1      1     3.7%      0.0%     96.3%  
    688599.SH    0        1      1     3.7%      0.0%     96.3%  
    000408.SZ    1        1      2     1.7%      0.0%     98.3%  
    002648.SZ    2        2      4     3.4%      0.0%     96.6%  
    300751.SZ    1        1      2     1.7%      0.0%     98.3%  
    688065.SH    1        1      2     1.7%      0.0%     98.3%  
    600674.SH    2        2      4     3.7%      0.0%     96.3%  
    600803.SH    1        1      2     1.7%      0.0%     98.3%  
    601615.SH    1        1      2     1.7%      0.0%     98.3%   
    
    Total operation fee:     ¥    1,290.37
    total investment amount: ¥  100,000.00
    final value:              ¥  216,271.60
    Total return:                   116.27% 
    Avg Yearly return:               17.32%
    Skewness:                          0.29
    Kurtosis:                          7.52
    Benchmark return:                65.96% 
    Benchmark Yearly return:         11.06%
    
    ------strategy loop_results indicators------ 
    alpha:                            0.115
    Beta:                             0.525
    Sharp ratio:                      0.956
    Info ratio:                       0.017
    250 day volatility:               0.149
    Max drawdown:                    18.93% 
        peak / valley:        2018-05-25 / 2018-09-11
        recovered on:         2019-04-18
    
    ===========END OF REPORT=============

png

3.2. El segundo método para establecer estrategias personalizadas implica el uso de señales comerciales PT para establecer objetivos de posición:

Después de completar el cálculo de los factores de selección de acciones, establezca directamente el objetivo de posición para cada acción. De esta manera, no es necesario conocer los datos de posición y la señal de posición objetivo se puede emitir directamente. Durante el proceso de backtest, se generan señales comerciales en función de la posición real.

>>> class GroupPT(qt.GeneralStg):
...     
...     def realize(self):
... 
...         # 读取策略参数(开盘价大于收盘价的天数)
...         
...         # 读取策略参数(开盘价大于收盘价的天数)
...         n_day = self.get_pars('n_day')
... 
...         # 从历史数据编码中读取四种历史数据的最新数值
...         opens, closes = self.get_data('open_E_d', 'close_E_d')  # 从前一交易日起前30天内开盘价
...         opens = opens[-30:]
...         closes = closes[-30:]
...         
...         # 选股因子为开盘价大于收盘价的天数,使用astype将True/False结果改为1/0,便于加总
...         factors = ((opens - closes) > 0).astype('float')
...         # 所有开盘价-收盘价>0的结果会被转化为1,其余结果转化为0,因此可以用sum得到开盘价大于收盘价的天数
...         factors = factors.sum(axis=0)
...         # import pdb; pdb.set_trace()
...         # 选出开盘价大于收盘价天数大于十天的所有股票的序号
...         all_args = np.arange(len(factors))
...         selected = np.where(factors > n_day)[0]
...         not_selected = np.setdiff1d(all_args, selected)
...         # 计算选出的股票的数量
...         selected_count = len(selected)
...         print(f'now {selected_count} shares are selected!')
...         
...         # 开始生成交易信号
...         signal = np.zeros_like(factors)
...         if selected_count == 0:
...             return signal
...         # 所有被选中的股票均设置为正持仓目标
...         signal[selected] = 1. / selected_count  
...         # 未被选中的股票持仓目标被设置为0
...         signal[not_selected] = 0
...         
...         return signal

Cree un objeto Operator y comience a realizar pruebas retrospectivas de la estrategia comercial

>>> alpha = GroupPT(name='GroupPS',
...                 description='本策略每隔1个月定时触发, 从SHSE.000300成份股中选择过去30天内开盘价大于前收盘价的天数大于10天的股票买入',
...                 pars=[Parameter((3, 25), par_type='int', name='n_day', value=10)],
...                 data_types=[StgData('open', freq='d', asset_type='E', window_length=32), 
...                             StgData('close', freq='d', asset_type='E', window_length=32)],
...                )  
>>> op = qt.Operator(alpha, signal_type='PT', run_freq='ME')
>>> op.set_parameter(0, par_values=(20,))
>>> qt.run(op=op, mode=1,
...        asset_type='E',
...        asset_pool=shares,
...        invest_start='20160405',
...        invest_end='20210201',
...        trade_batch_size=100,
...        sell_batch_size=1,
...        trade_log=True)

Los resultados del backtest comercial son los siguientes:

         ====================================
         |                                  |
         |       BACK TESTING RESULT        |
         |                                  |
         ====================================
    
    qteasy running mode: 1 - History back testing
    time consumption for operate signal creation: 399.1ms
    time consumption for operation back looping:  8s 621.5ms
    
    investment starts on      2016-04-05 00:00:00
    ends on                   2021-02-01 00:00:00
    Total looped periods:     4.8 years.
    
    -------------operation summary:------------
    Only non-empty shares are displayed, call 
    "loop_result["oper_count"]" for complete operation summary
    
              Sell Cnt Buy Cnt Total Long pct Short pct Empty pct
    000001.SZ    1        1       2    1.7%      0.0%     98.3%  
    000069.SZ    2        2       4    3.4%      0.0%     96.6%  
    000301.SZ    6        5      11   13.6%      0.0%     86.4%  
    000338.SZ    1        1       2    1.7%      0.0%     98.3%  
    000596.SZ    1        1       2    1.8%      0.0%     98.2%  
    000625.SZ    1        1       2    1.7%      0.0%     98.3%  
    000661.SZ    1        1       2    1.7%      0.0%     98.3%  
    000776.SZ    1        1       2    1.7%      0.0%     98.3%  
    000786.SZ    1        1       2    1.7%      0.0%     98.3%  
    000800.SZ    2        3       5    5.4%      0.0%     94.6%  
    ...            ...     ...   ...      ...       ...       ...
    603806.SH    2        2       4    3.6%      0.0%     96.4%  
    603939.SH    0        2       2    3.7%      0.0%     96.3%  
    688599.SH    0        2       2    3.7%      0.0%     96.3%  
    000408.SZ    1        1       2    1.7%      0.0%     98.3%  
    002648.SZ    1        1       2    1.7%      0.0%     98.3%  
    300751.SZ    1        1       2    1.7%      0.0%     98.3%  
    688065.SH    1        1       2    1.7%      0.0%     98.3%  
    600674.SH    2        2       4    3.7%      0.0%     96.3%  
    600803.SH    1        1       2    1.7%      0.0%     98.3%  
    601615.SH    1        1       2    1.7%      0.0%     98.3%   
    
    Total operation fee:     ¥    1,375.35
    total investment amount: ¥  100,000.00
    final value:              ¥  216,215.03
    Total return:                   116.22% 
    Avg Yearly return:               17.31%
    Skewness:                          0.23
    Kurtosis:                          6.63
    Benchmark return:                65.96% 
    Benchmark Yearly return:         11.06%
    
    ------strategy loop_results indicators------ 
    alpha:                            0.110
    Beta:                             0.542
    Sharp ratio:                      1.015
    Info ratio:                       0.017
    250 day volatility:               0.139
    Max drawdown:                    18.58% 
        peak / valley:        2016-04-14 / 2017-01-16
        recovered on:         2017-09-27
    
    ===========END OF REPORT=============

png