10. Otra estrategia personalizada

qteasy es un conjunto de herramientas de análisis comercial cuantitativo para implementación y operación totalmente localizado, con las siguientes funciones:

  • Adquisición, limpieza, almacenamiento, procesamiento, visualización y uso de datos financieros.

  • Cree estrategias comerciales cuantitativas y proporcione una gran cantidad de estrategias comerciales básicas integradas.

  • Backtesting de estrategias comerciales vectorizadas de alta velocidad y evaluación de resultados comerciales

  • Optimización y evaluación de parámetros de estrategia comercial.

  • Implementación y operación en vivo de estrategias comerciales.

En este tutorial, comprenderá completamente las funciones principales y el uso de qteasy a través de una serie de ejemplos prácticos.

10.1. Antes de empezar

Asegúrese de dominar el siguiente contenido antes de comenzar este tutorial:

  • Instalar y configurar qteasy —— QTEASY Tutorial 1

  • Configure una fuente de datos local y haya descargado suficientes datos históricos al local.

  • Aprenda a crear un objeto comercial, utilice estrategias comerciales integradas —— QTEASY Tutorial 3

  • Aprenda a usar el mezclador para combinar múltiples estrategias simples en estrategias comerciales más complejas —— QTEASY Tutorial 4

  • Aprenda a personalizar estrategias comerciales —— QTEASY Tutorial 5

En la QTEASY documentación, también puede encontrar más contenido sobre el uso de estrategias comerciales integradas, la creación de estrategias personalizadas, etc. Si aún no estás familiarizado con el uso básico de qteasy, puedes ir allí para ver explicaciones más detalladas.

10.2. Objetivo de este tutorial

En esta sección, continuaremos el contenido de la sección anterior e introduciremos la clase base de estrategia comercial de qteasy. Después de presentar la clase de estrategia comercial de sincronización más simple, presentaremos cómo utilizar las otras dos clases base de estrategia proporcionadas por qteasy para crear una estrategia de selección de acciones de múltiples factores.

Para proporcionar suficiente facilidad de uso, las diversas clases base de estrategias proporcionadas por qteasy son esencialmente las mismas, solo se proporciona un formulario de preprocesamiento para reducir la carga de trabajo de codificación del usuario, e incluso diferentes clases base de estrategias comerciales pueden entenderse como «azúcar de sintaxis» diseñado para estrategias comerciales específicas. Por lo tanto, la misma estrategia comercial a menudo se puede implementar utilizando múltiples clases base de estrategias comerciales diferentes. Por lo tanto, en esta sección utilizaremos dos clases base de estrategias diferentes para implementar una estrategia comercial de selección de acciones Alpha.

10.3. Cómo funciona la estrategia de selección de acciones Alpha

La estrategia de selección de acciones Alpha que estamos analizando aquí es una estrategia de selección de acciones de baja frecuencia que puede ejecutarse semanalmente o mensualmente. Cada vez que se selecciona la acción, recorrerá todas las acciones que componen el índice HS300 y priorizará estas 300 acciones de acuerdo con ciertos criterios. Seleccione las 30 acciones principales entre ellas, manténgalas en partes iguales, es decir, reequilibre las acciones una vez al mes, venda las acciones con clasificaciones más bajas durante el reequilibrio, compre las acciones con clasificaciones más altas y asegúrese de que las acciones se mantengan en partes iguales.

La base de clasificación de la estrategia de selección de acciones Alpha se calcula a partir de dos indicadores financieros para cada acción: EV (valor empresarial) y EBITDA (ganancias antes de intereses, impuestos, depreciación y amortización). Para cada acción, calcule la relación entre EV y EBITDA. Cuando este ratio es mayor que 0, indica que la empresa que cotiza en bolsa es rentable (porque EBITDA es positivo). En este caso, la relación representa el valor total de la empresa que debe invertirse para que la empresa obtenga un yuan de beneficio. Naturalmente, cuanto menor sea esta proporción, mejor. Por ejemplo, los datos de las siguientes dos empresas que cotizan en bolsa son los siguientes:

  • La empresa A tiene un EBITDA de diez millones y un valor de mercado empresarial de diez mil millones. EV/EBITDA = 1000. Esto indica que por cada 1000 yuanes del valor de mercado de la empresa, puede obtener 1 yuan de beneficio.

  • La empresa B también tiene un EBITDA de diez millones y un valor de mercado empresarial de cien mil millones. EV/EBITDA = 10000, lo que indica que por cada 10.000 yuanes del valor de mercado de la empresa, puede obtener 1 yuan de beneficio.

Normalmente, naturalmente sentimos que la empresa A es mejor porque obtiene las mismas ganancias con menos valor de mercado de la empresa. En este momento, creemos que la clasificación de la Compañía A es relativamente alta.

De acuerdo con las reglas anteriores, al final de cada mes, clasificaremos todas las empresas que cotizan en bolsa de las acciones constituyentes del HS300, de pequeñas a grandes, y eliminaremos las empresas con EV/EBITDA inferior a 0 (por supuesto, las empresas con beneficios negativos deben eliminarse). Después de eso, seleccione las 30 principales empresas para mantener, que es la estrategia comercial de selección de acciones Alpha.

De hecho, para una estrategia de selección de acciones como esta que clasifica por indicadores, qteasy proporciona una estrategia comercial incorporada que puede implementarla directamente.

Utilice built_in_doc para ver la documentación de esta estrategia comercial integrada:

>>> import qteasy as qt
>>> qt.built_in_doc('finance', print_out=True)

El resultado es el siguiente:

以股票过去一段时间内的财务指标的平均值作为选股因子选股
        基础选股策略。以股票的历史指标的平均值作为选股因子,因子排序参数可以作为策略参数传入
        改变策略数据类型,根据不同的历史数据选股,选股参数可以通过pars传入
    策略参数:
        - sort_ascending: enum, 是否升序排列因子
            - True: 优先选择因子最小的股票,
            - False, 优先选择因子最大的股票
        - weighting: enum, 股票仓位分配比例权重
            - even       :默认值, 所有被选中的股票都获得同样的权重
            - linear     :权重根据因子排序线性分配
            - distance   :股票的权重与他们的指标与最低之间的差值(距离)成比例
            - proportion :权重与股票的因子分值成正比
        - condition: enum, 股票筛选条件
            - any        :默认值,选择所有可用股票
            - greater    :筛选出因子大于ubound的股票
            - less       :筛选出因子小于lbound的股票
            - between    :筛选出因子介于lbound与ubound之间的股票
            - not_between:筛选出因子不在lbound与ubound之间的股票
        - lbound: float, 股票筛选下限值, 默认值np.-inf
        - ubound: float, 股票筛选上限值, 默认值np.inf
        - max_sel_count: float, 抽取的股票的数量(p>=1)或比例(p<1), 默认值: 0.5,表示选中50%的股票
    信号类型:
        PT型: 百分比持仓比例信号
    信号规则:
        使用data_types指定一种数据类型,将股票过去的datatypes数据取平均值,将该平均值作为选股因子进行选股
    策略属性缺省值:
        默认参数: (True, 'even', 'greater', 0, 0, 0.25)
        数据类型: eps 每股收益,单数据输入
        窗口长度: 270
        参数范围: [(True, False),
        ('even', 'linear', 'proportion'),
        ('any', 'greater', 'less', 'between', 'not_between'),
        (-np.inf, np.inf),
        (-np.inf, np.inf),
        (0, 1.)]
    策略不支持参考数据,不支持交易数据

Sin embargo, esta estrategia comercial integrada solo admite el uso de tipos de datos históricos integrados qteasy como factores de selección de acciones. Por ejemplo, datos como PE (relación precio-beneficio) y ganancias son datos históricos integrados en qteasy y se puede hacer referencia a ellos directamente. Pero si el factor de selección de acciones no se puede encontrar en los datos históricos integrados de qteasy, no podrá utilizar la estrategia comercial integrada directamente. El indicador EV/EBITDA es un indicador calculado, por lo que debemos utilizar una estrategia comercial personalizada y calcular este indicador dentro de la estrategia personalizada.

10.4. Calcular indicadores de selección de acciones.

Para calcular EV/EBITDA, debemos al menos confirmar si qteasy ha proporcionado EV y EBITDA estos dos tipos de datos históricos:

Podemos usar la API: find_history_data() para verificar si un tipo de datos históricos es compatible con qteasy. Primero, busque ebitda.

>>> qt.find_history_data('ebitda')

El resultado es el siguiente:

matched following history data, 
use "qt.get_history_data()" to load these historical data by its data_id:
------------------------------------------------------------------------
              freq asset      table                  desc
data_id                                                  
ebitda           q     E  financial  上市公司财务指标 - 息税折旧摊销前利润
========================================================================
['income_ebitda', 'ebitda']

Los tipos de datos en qteasy deben crearse mediante qteasy.DataType. DataType representa un tipo de tipo de datos históricos, es decir, una categoría de información que qteasy puede extraer directamente de los datos históricos. A través de objetos de tipo de datos, qteasy proporciona una interfaz de datos unificada, lo que permite a los usuarios obtener fácilmente varios tipos de datos históricos sin preocuparse por cómo se almacena el tipo de datos o dónde se almacena. Al mismo tiempo, qteasy encapsula completamente toda la compleja lógica de datos subyacente, como el manejo de tipos, la conversión de frecuencia y la coincidencia de tickers, por lo que los usuarios no necesitan preocuparse por el método de almacenamiento de cada tipo de datos y pueden usarlos directamente.

Para obtener una introducción más detallada a los tipos de datos qteasy, consulte la QTEASY documentación.

El DataType de qteasy incluye tres atributos:

  • nombre: el nombre del tipo de datos, por ejemplo ebitda en el valor de retorno anterior

  • freq: la frecuencia de los datos, por ejemplo q en el valor de retorno anterior, que representa datos trimestrales

  • activo_tipo: el tipo de activo de los datos; por ejemplo, E en el valor de retorno anterior representa datos de acciones

Estos tres atributos juntos definen un tipo de datos único. qteasy tiene una gran cantidad de tipos de datos históricos integrados. Los usuarios pueden utilizar directamente estos tipos de datos para obtener datos históricos, sin tener que calcularlos ellos mismos ni procesar datos sin procesar para derivar estos tipos de datos.

Del valor de retorno anterior, podemos ver que en los tipos de datos históricos integrados de qteasy, el EBITDA es un tipo de datos históricos estándar. Estos datos provienen de la tabla de indicadores financieros de las empresas cotizadas financial, y la frecuencia de los datos es q (trimestralmente):

A continuación, mire los vehículos eléctricos:

>>> qt.find_history_data('ev')

El resultado es el siguiente:

matched following history data, 
use "qt.get_history_data()" to load these historical data by its data_id:
------------------------------------------------------------------------
Empty DataFrame
Columns: [freq, asset_type, table_name, description]
Index: []
========================================================================

Esto indica que entre los tipos de datos históricos integrados de qteasy, no se encontró ningún tipo de datos históricos denominado EV; en este caso, puede utilizar el parámetro fuzzy=True para confirmar.

>>> qt.find_history_data('ev', fuzzy=True)

El resultado es el siguiente:

matched following history data, 
use "qt.get_history_data()" to load these historical data by its data_id:
------------------------------------------------------------------------
                                    name  freq asset              table               column                desc
data_id                                                                                                         
sw_level                        sw_level  None   IDX  sw_industry_basic                level         申万行业分类 - 级别
sw_level|%                    sw_level|%  None   IDX  sw_industry_basic                level        申万行业分类筛选 - %
managers_lev                managers_lev     d     E       stk_managers                  lev       公司高管信息 - 岗位类别
total_revenue              total_revenue     q     E             income        total_revenue     上市公司利润表 - 营业总收入
revenue                          revenue     q     E             income              revenue      上市公司利润表 - 营业收入
withdra_biz_devfund  withdra_biz_devfund     q     E             income  withdra_biz_devfund  上市公司利润表 - 提取企业发展基金
express_revenue          express_revenue     q     E            express              revenue  上市公司业绩快报 - 营业收入(元)
total_revenue_ps        total_revenue_ps     q     E          financial     total_revenue_ps  上市公司财务指标 - 每股营业总收入
revenue_ps                    revenue_ps     q     E          financial           revenue_ps   上市公司财务指标 - 每股营业收入
========================================================================

La tabla anterior enumera los tipos de datos que ya se han definido en qteasy y se pueden usar directamente. Preste atención a las columnas name / freq / asset, que representan el nombre del tipo de datos, la frecuencia de los datos y el tipo de activo, respectivamente. Juntas, estas tres columnas definen un tipo de datos único. Los usuarios pueden crear un objeto de tipo de datos a través de qteasy.DataType(name, freq, asset) para obtener los datos históricos de ese tipo de datos.

Aunque EV no se encuentra entre los tipos de datos históricos integrados de qteasy, podemos ver que hay algunos tipos de datos históricos relacionados con EV, como ingresos totales, ganancias por acción, etc. Estos tipos de datos están relacionados con los vehículos eléctricos, pero no son los vehículos eléctricos que necesitamos.

Sin embargo, sabemos que el EV se puede calcular mediante la siguiente fórmula:

\[::\]

Y los indicadores financieros anteriores están respaldados directamente por qteasy

  • Valor de mercado total - Tipo de datos: total_mv

  • Pasivos totales - Tipo de datos: total_liab

  • Efectivo total - Tipo de datos: c_cash_equ_end_period

Entonces podemos probarlo y ver las explicaciones detalladas de estos tipos de datos:

>>> qt.find_history_data('total_mv', fuzzy=True)

Obtendrá el siguiente resultado:

matched following history data, 
use "qt.get_history_data()" to load these historical data by its data_id:
------------------------------------------------------------------------
                      name freq asset             table    column                 desc
data_id                                                                               
ths_total_mv  ths_total_mv    d   IDX   ths_index_daily  total_mv  同花顺指数日K线 - 总市值 (万元)
sw_total_mv    sw_total_mv    d   IDX    sw_index_daily  total_mv   申万指数日K线 - 总市值 (万元)
total_mv          total_mv    d   IDX   index_indicator  total_mv    指数技术指标 - 当日总市值(元)
total_mv          total_mv    d     E   stock_indicator  total_mv    股票技术指标 - 总市值 (万元)
total_mv_2      total_mv_2    d     E  stock_indicator2  total_mv     股票技术指标 - 总市值(亿元)
========================================================================

Tenga en cuenta que el tipo de datos total_mv tiene dos versiones: una en unidades de 10.000 yuanes y otra en unidades de 100 millones de yuanes. Al calcular EV/EBITDA, estrictamente hablando la unidad no es importante, pero en otros casos hay que prestar atención. Aquí multiplicamos este dato por 10.000 para unificar las unidades.

Aquí elegimos DataType('total_mv', 'd', 'E'). Este tipo de datos representa la capitalización de mercado total de una empresa que cotiza en bolsa cada día, en unidades de 10.000 yuanes.

>>> qt.find_history_data('total_liab', fuzzy=True)
matched following history data, 
use "qt.get_history_data()" to load these historical data by its data_id:
------------------------------------------------------------------------
                                    name freq asset    table               column                   desc
data_id                                                                                                 
total_liab                    total_liab    q     E  balance           total_liab       上市公司资产负债表 - 负债合计
total_liab_hldr_eqy  total_liab_hldr_eqy    q     E  balance  total_liab_hldr_eqy  上市公司资产负债表 - 负债及股东权益总计
========================================================================

Aquí podemos elegir el tipo de datos DataType('total_liab', 'q', 'E'). Este tipo de datos representa los pasivos totales de una empresa que cotiza en bolsa al final de cada trimestre, en unidades de yuanes.

>>> qt.find_history_data('cash', fuzzy=True)
matched following history data, 
use "qt.get_history_data()" to load these historical data by its data_id:
------------------------------------------------------------------------
                                                      name freq asset      table                        column                                desc
data_id                                                                                                                                           
cash_reser_cb                                cash_reser_cb    q     E    balance                 cash_reser_cb             上市公司资产负债表 - 现金及存放中央银行款项
ifc_cash_incr                                ifc_cash_incr    q     E   cashflow                 ifc_cash_incr            上市公司现金流量表 - 收取利息和手续费净增加额
oth_cash_pay_oper_act                oth_cash_pay_oper_act    q     E   cashflow         oth_cash_pay_oper_act          上市公司现金流量表 - 支付其他与经营活动有关的现金
st_cash_out_act                            st_cash_out_act    q     E   cashflow               st_cash_out_act              上市公司现金流量表 - 经营活动现金流出小计
n_cashflow_act                              n_cashflow_act    q     E   cashflow                n_cashflow_act           上市公司现金流量表 - 经营活动产生的现金流量净额
n_cashflow_inv_act                      n_cashflow_inv_act    q     E   cashflow            n_cashflow_inv_act           上市公司现金流量表 - 投资活动产生的现金流量净额
oth_cash_recp_ral_fnc_act        oth_cash_recp_ral_fnc_act    q     E   cashflow     oth_cash_recp_ral_fnc_act          上市公司现金流量表 - 收到其他与筹资活动有关的现金
stot_cash_in_fnc_act                  stot_cash_in_fnc_act    q     E   cashflow          stot_cash_in_fnc_act              上市公司现金流量表 - 筹资活动现金流入小计
free_cashflow                                free_cashflow    q     E   cashflow                 free_cashflow                上市公司现金流量表 - 企业自由现金流量
oth_cashpay_ral_fnc_act            oth_cashpay_ral_fnc_act    q     E   cashflow       oth_cashpay_ral_fnc_act          上市公司现金流量表 - 支付其他与筹资活动有关的现金
stot_cashout_fnc_act                  stot_cashout_fnc_act    q     E   cashflow          stot_cashout_fnc_act              上市公司现金流量表 - 筹资活动现金流出小计
n_cash_flows_fnc_act                  n_cash_flows_fnc_act    q     E   cashflow          n_cash_flows_fnc_act           上市公司现金流量表 - 筹资活动产生的现金流量净额
eff_fx_flu_cash                            eff_fx_flu_cash    q     E   cashflow               eff_fx_flu_cash              上市公司现金流量表 - 汇率变动对现金的影响
n_incr_cash_cash_equ                  n_incr_cash_cash_equ    q     E   cashflow          n_incr_cash_cash_equ            上市公司现金流量表 - 现金及现金等价物净增加额
c_cash_equ_beg_period                c_cash_equ_beg_period    q     E   cashflow         c_cash_equ_beg_period            上市公司现金流量表 - 期初现金及现金等价物余额
c_cash_equ_end_period                c_cash_equ_end_period    q     E   cashflow         c_cash_equ_end_period            上市公司现金流量表 - 期末现金及现金等价物余额
incl_cash_rec_saims                    incl_cash_rec_saims    q     E   cashflow           incl_cash_rec_saims     上市公司现金流量表 - 其中:子公司吸收少数股东投资收到的现金
im_net_cashflow_oper_act          im_net_cashflow_oper_act    q     E   cashflow      im_net_cashflow_oper_act      上市公司现金流量表 - 经营活动产生的现金流量净额(间接法)
im_n_incr_cash_equ                      im_n_incr_cash_equ    q     E   cashflow            im_n_incr_cash_equ       上市公司现金流量表 - 现金及现金等价物净增加额(间接法)
net_cash_rece_sec                        net_cash_rece_sec    q     E   cashflow             net_cash_rece_sec        上市公司现金流量表 - 代理买卖证券收到的现金净额(元)
cashflow_credit_impa_loss        cashflow_credit_impa_loss    q     E   cashflow              credit_impa_loss                  上市公司现金流量表 - 信用减值损失
end_bal_cash                                  end_bal_cash    q     E   cashflow                  end_bal_cash                 上市公司现金流量表 - 现金的期末余额
beg_bal_cash                                  beg_bal_cash    q     E   cashflow                  beg_bal_cash               上市公司现金流量表 - 减:现金的期初余额
end_bal_cash_equ                          end_bal_cash_equ    q     E   cashflow              end_bal_cash_equ            上市公司现金流量表 - 加:现金等价物的期末余额
beg_bal_cash_equ                          beg_bal_cash_equ    q     E   cashflow              beg_bal_cash_equ            上市公司现金流量表 - 减:现金等价物的期初余额
cash_ratio                                      cash_ratio    q     E  financial                    cash_ratio                   上市公司财务指标 - 保守速动比率
salescash_to_or                            salescash_to_or    q     E  financial               salescash_to_or       上市公司财务指标 - 销售商品提供劳务收到的现金/营业收入
cash_to_liqdebt                            cash_to_liqdebt    q     E  financial               cash_to_liqdebt                上市公司财务指标 - 货币资金/流动负债
cash_to_liqdebt_withinterest  cash_to_liqdebt_withinterest    q     E  financial  cash_to_liqdebt_withinterest              上市公司财务指标 - 货币资金/带息流动负债
q_salescash_to_or                        q_salescash_to_or    q     E  financial             q_salescash_to_or  上市公司财务指标 - 销售商品提供劳务收到的现金/营业收入(单季度)
cash_div_planned                          cash_div_planned    d     E   dividend                      cash_div                         预案-每股分红(税后)
cash_div_tax_planned                  cash_div_tax_planned    d     E   dividend                  cash_div_tax                         预案-每股分红(税前)
cash_div_approved                        cash_div_approved    d     E   dividend                      cash_div                     股东大会批准-每股分红(税后)
cash_div_tax_approved                cash_div_tax_approved    d     E   dividend                  cash_div_tax                     股东大会批准-每股分红(税前)
cash_div                                          cash_div    d     E   dividend                      cash_div                         实施-每股分红(税后)
cash_div_tax                                  cash_div_tax    d     E   dividend                  cash_div_tax                         实施-每股分红(税前)
========================================================================

Hay muchos tipos de datos relacionados con el efectivo, pero el efectivo total y equivalentes de efectivo que necesitamos es DataType(c_cash_equ_end_period, 'q', 'E'). Este tipo de datos representa el efectivo total y los equivalentes de efectivo de una empresa que cotiza en bolsa al final de cada trimestre.

Según la información anterior, podemos elegir los siguientes cuatro tipos de datos para calcular EV:

  • DataType('total_mv', 'd', 'E'), este tipo de datos representa la capitalización de mercado total de una empresa que cotiza en bolsa cada día, en unidades de 10.000 yuanes.

  • DataType('total_liab', 'q', 'E'): este tipo de datos representa los pasivos totales de una empresa que cotiza en bolsa al final de cada trimestre, en yuanes.

  • DataType('c_cash_equ_end_period', 'q', 'E'): este tipo de datos representa el efectivo total y los equivalentes de efectivo de una empresa que cotiza en bolsa al final de cada trimestre, en yuanes.

Podemos realizar una prueba rápida. qteasy proporciona una API muy conveniente: get_history_data(), que puede recuperar directamente datos históricos para estos tipos de datos:

# 创建数据类型对象
dtypes = [DataType('total_mv', freq='d', asset_type='E'),
          DataType('total_liab', freq='q', asset_type='E'),
          DataType('c_cash_equ_end_period', freq='q', asset_type='E'),
          DataType('ebitda', freq='q', asset_type='E')]
# 获取沪深300指数成分股(这里只获取前20支股票)
shares = qt.filter_stock_codes(index='000300.SH', date='20220131')[:20] 
# 获取所有股票的总市值、总负债、总现金、EBITDA数据
dt = qt.get_history_data(data_types=dtypes, shares=shares, asset_type='any', freq='m')
# 随便选择一支股票,转化为DataFrame检查数据是否正确获取
one_share = shares[1]
df = dt[one_share]
# 计算EV/EBITDA选股因子
df['ev_to_ebitda'] = (df.total_mv + df.total_liab - df.c_cash_equ_end_period) / df.ebitda
print(df)
                total_mv    total_liab  c_cash_equ_end_period        ebitda  \
2022-01-04  2.382041e+07           NaN                    NaN           NaN   
2022-01-05  2.461094e+07           NaN                    NaN           NaN   
2022-01-06  2.447143e+07           NaN                    NaN           NaN   
2022-01-07  2.544796e+07           NaN                    NaN           NaN   
2022-01-10  2.576185e+07           NaN                    NaN           NaN   
...                  ...           ...                    ...           ...   
2022-12-26  2.136561e+07  1.426656e+12           1.158051e+11  2.969171e+10   
2022-12-27  2.152844e+07  1.426656e+12           1.158051e+11  2.969171e+10   
2022-12-28  2.160986e+07  1.426656e+12           1.158051e+11  2.969171e+10   
2022-12-29  2.112137e+07  1.426656e+12           1.158051e+11  2.969171e+10   
2022-12-30  2.116789e+07  1.426656e+12           1.158051e+11  2.969171e+10   

            ev_to_ebitda  
2022-01-04           NaN  
2022-01-05           NaN  
2022-01-06           NaN  
2022-01-07           NaN  
2022-01-10           NaN  
...                  ...  
2022-12-26     51.344518  
2022-12-27     51.399358  
2022-12-28     51.426778  
2022-12-29     51.262258  
2022-12-30     51.277926  

[242 rows x 5 columns]

Podemos ver que se ha calculado el factor de selección de acciones, por lo que podemos comenzar a definir la estrategia comercial.

10.5. Defina la estrategia de selección de acciones Alpha con FactorSorter

qteasy proporciona la clase de estrategia comercial FactorSorter para este tipo de estrategia comercial de selección de acciones cronometrada. Como sugiere el nombre, esta clase base de estrategia comercial permite a los usuarios calcular un conjunto de factores de selección de acciones en el método de implementación de la estrategia, de modo que la estrategia pueda clasificar automáticamente todas las acciones según el valor del factor de selección de acciones y seleccionar las acciones con clasificaciones más altas. En cuanto al método de clasificación, las reglas de selección, los pesos de tenencia de acciones, etc., se pueden establecer a través de parámetros de estrategia.

Si cumple con la definición de la estrategia comercial anterior, será muy conveniente utilizar la clase base de estrategia FactorSorter.

A continuación, vamos a definirlo paso a paso. Primero, herede FactorSorter y defina una clase. En la sección anterior, definimos el nombre, la descripción, los parámetros predeterminados y otra información en el método __init__() de la estrategia personalizada. Sin embargo, también podemos ignorar el método __init__() y solo pasar parámetros al crear el objeto de estrategia. Esto también es posible. Lo hacemos aquí:

>>> class AlphaFac(qt.FactorSorter):  # 注意这里使用FactorSorter策略类
...     
...     def realize(self):
...         # 从历史数据编码中读取四种历史数据的最新数值
...         total_mv = self.get_data('total_mv_E_d')[-1]  # 总市值
...         total_liab = self.get_data('total_liab_E_q')[-1]  # 总负债
...         cash_equ = self.get_data('c_cash_equ_end_period_E_q')[-1]  # 现金及现金等价物总额
...         ebitda = self.get_data('ebitda_E_q')[-1]  # ebitda,息税折旧摊销前利润
...         # 选股因子为EV/EBIDTA,使用下面公式计算
...         factor = (total_mv * 10000 + total_liab - cash_equ) / ebitda
...         return factor  # 直接返回选股因子,策略就定义好了

Igual que en la sección anterior, el primer paso en realize() es recuperar datos históricos. Sabemos que los datos históricos incluyen cuatro tipos: total_mv, total_liab, c_cash_equ_end_period, ebitda. Estas cuatro series de datos históricos se definirán como cuatro DataType y se pasarán a la estrategia. Para utilizar estos datos históricos en la estrategia, puede llamar directamente a self.get_data():

# 从历史数据编码中读取四种历史数据的最新数值
total_mv = self.get_data('total_mv_E_d')[-1]  # 总市值
total_liab = self.get_data('total_liab_E_q'))[-1]  # 总负债
cash_equ = self.get_data('c_cash_equ_end_period_E_q'))[-1]  # 现金及现金等价物总额
ebitda = self.get_data('ebitda_E_q'))[-1]  # ebitda,息税折旧摊销前利润
        ...

El método self.get_data() recupera los datos históricos correspondientes a través del ID de datos de cada elemento de datos. De forma predeterminada, el ID de cada elemento de datos es una combinación de su nombre, tipo de activo y frecuencia, por ejemplo:

  • DataType('total_mv', 'd', 'E'): el ID de este tipo de datos es total_mv_E_d.

  • DataType('total_liab', 'q', 'E'): el ID de este tipo de datos es: total_liab_E_d.

Antes de que la estrategia comercial comience a ejecutarse, qteasy preparará los datos comerciales correspondientes. Todos los datos comerciales se almacenan en una matriz numerosa: el número de columnas corresponde al número de acciones del universo y cada columna corresponde a una acción del universo; el número de filas corresponde a la duración de la ventana de tiempo, cada fila corresponde a un punto de tiempo en la ventana de tiempo y está ordenada en orden ascendente, y la última columna representa los últimos datos históricos visibles en el momento de la negociación.

Siguiendo esta regla, para obtener los datos históricos más recientes que se pueden ver en el día de negociación de la acción i-ésima, simplemente acceda a la matriz (-1, i). Con el siguiente bucle, puede acceder a los datos de todas las acciones al mismo tiempo:

total_mv = self.get_data('total_mv_E_d')
# 循环访问每一支股票的total_mv
for i in len(total_mv[-1]):
    print(f'total mv of share {i}: {total_mv[-1, i]}')

Sin embargo, utilizar un bucle for para acceder a los datos es relativamente ineficiente. Debes utilizar operaciones vectorizadas tanto como sea posible en tu estrategia para ahorrar tiempo.

Si está preparado para lo anterior, es muy conveniente calcular el factor de selección de acciones. Además, dado que utilizamos la clase base de estrategia FactorSorter, después de calcular el factor de selección de acciones, puede devolver directamente el factor de selección de acciones y qteasy se encargará del resto de la operación de selección de acciones:

# 选股因子为EV/EBIDTA,使用下面公式计算
factor = (total_mv * 10000 + total_liab - cash_equ) / ebitda
return factor  # 直接返回选股因子,策略就定义好了

Ahora, con solo seis líneas de código, se define una estrategia comercial de selección de acciones Alpha personalizada. ¿No es muy sencillo?

Bien, veamos cómo son los resultados del backtest.

10.6. Resultados de las estrategias comerciales de backtesting

Dado que ignoramos el método __init__() de la clase de estrategia, se deben ingresar los parámetros completos de la estrategia al crear una instancia del objeto de estrategia:

>>> from qteasy import Parameter, StgData

>>> alpha = AlphaFac(
...     pars=[],
...     name='AlphaSel',
...     description='本策略每隔1个月定时触发计算SHSE.000300成份股的过去的EV/EBITDA并选取EV/EBITDA大于0的股票',
...     data_types=[DataType('total_mv', freq='d', asset_type='E'),
...                 DataType('total_liab', freq='q', asset_type='E'),
...                 DataType('c_cash_equ_end_period', freq='q', asset_type='E'), 
...                 DataType('ebitda', freq='q', asset_type='E')],
...     window_length=[20, 20, 10, 10],  # 现在可以为每一种数据类型设置不同的窗口长度
...     max_sel_count=30,  # 设置选股数量,最多选出30个股票
...     condition='greater',  # 设置筛选条件,仅筛选因子大于ubound的股票
...     ubound=0.0,  # 设置筛选条件,仅筛选因子大于0的股票
...     weighting='even',  # 设置股票权重,所有选中的股票平均分配权重
...     sort_ascending=True,  # 设置排序方式,因子从小到大排序选择头30名
... )  

Luego cree un objeto Operator, como queremos controlar la relación de posición, es mejor usar el tipo de señal “PT”:

>>> op = qt.Operator(alpha, signal_type='PT')
>>> res = op.run(mode=1,
...        asset_type='E',
...        asset_pool=shares,
...        PT_buy_threshold=0.0,
...        PT_sell_threshold=0.0,
...        trade_batch_size=100,
...        sell_batch_size=1)

Resultados del backtesting:

     ====================================
     |                                  |
     |       BACK TESTING RESULT        |
     |                                  |
     ====================================

qteasy running mode: 1 - History back testing
time consumption for operate signal creation: 9.4ms
time consumption for operation back looping:  5s 831.0ms

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
000301.SZ    1        2       3   10.3%      0.0%     89.7%  
000786.SZ    2        3       5   27.5%      0.0%     72.5%  
000895.SZ    1        0       1   62.6%      0.0%     37.4%  
002001.SZ    2        2       4   55.8%      0.0%     44.2%  
002007.SZ    3        1       4   68.3%      0.0%     31.7%  
002027.SZ    2        9      11   41.3%      0.0%     58.7%  
002032.SZ    2        0       2    5.9%      0.0%     94.1%  
002044.SZ    1        1       2    1.8%      0.0%     98.2%  
002049.SZ    1        1       2    5.1%      0.0%     94.9%  
002050.SZ    4        5       9   13.8%      0.0%     86.2%  
...            ...     ...   ...      ...       ...       ...
603517.SH    1        1       2    1.8%      0.0%     98.2%  
603806.SH    6        3       9   39.8%      0.0%     60.2%  
603899.SH    1        1       2   31.0%      0.0%     69.0%  
000408.SZ    3        6       9   35.5%      0.0%     64.5%  
002648.SZ    1        1       2    5.2%      0.0%     94.8%  
002920.SZ    1        1       2    1.7%      0.0%     98.3%  
300223.SZ    1        1       2    5.2%      0.0%     94.8%  
600219.SH    1        1       2    6.1%      0.0%     93.9%  
603185.SH    1        1       2    5.2%      0.0%     94.8%  
688005.SH    1        1       2    5.2%      0.0%     94.8%   

Total operation fee:     ¥      928.22
total investment amount: ¥  100,000.00
final value:              ¥  159,072.14
Total return:                    59.07% 
Avg Yearly return:               10.09%
Skewness:                         -0.28
Kurtosis:                          3.29
Benchmark return:                65.96% 
Benchmark Yearly return:         11.06%

------strategy loop_results indicators------ 
alpha:                           -0.012
Beta:                             1.310
Sharp ratio:                      1.191
Info ratio:                      -0.010
250 day volatility:               0.105
Max drawdown:                    20.49% 
    peak / valley:        2018-05-22 / 2019-01-03
    recovered on:         2019-12-26

===========END OF REPORT=============

Los resultados de las pruebas retrospectivas muestran que esta estrategia no puede superar efectivamente al índice 300 de Shanghai y Shenzhen, pero en general, la caída es relativamente pequeña y el riesgo es bajo, lo que la convierte en una buena estrategia de fondo.

Pero el desempeño de la estrategia no es el foco de nuestra discusión. A continuación, veamos cómo definir la misma estrategia de selección de acciones Alpha sin utilizar la clase base FactorSorter.

10.7. Defina una estrategia de selección de acciones Alpha con GeneralStg

Hemos introducido dos clases base de estrategia antes:

  • RuleIterator: Los usuarios solo necesitan definir reglas de selección de acciones para una acción, y qteasy aplicará las mismas reglas a todas las acciones en el grupo de acciones y también pueden establecer diferentes parámetros ajustables para diferentes acciones.

  • FactorSorter: Los usuarios solo necesitan definir un factor de selección de acciones, y qteasy seleccionará automáticamente las mejores acciones para mantener según el factor de selección de acciones después de clasificarlas y venderá las acciones que no estén calificadas.

Si bien GeneralStg es una clase base de estrategia básica proporcionada por qteasy, no proporciona ninguna función de «azúcar de sintaxis» para ayudar a los usuarios a reducir la carga de trabajo de codificación, pero precisamente porque no tiene azúcar de sintaxis, es una clase de estrategia «universal» real que se puede utilizar para crear estrategias comerciales con mayor libertad.

La estrategia de negociación de selección de acciones Alpha anterior se puede implementar fácilmente con FactorSorter, pero para comprender GeneralStg, veamos cómo usarlo para crear la misma estrategia:

Aquí está el código completo:


class AlphaPT(qt.GeneralStg):
    
    def realize(self, h, r=None, t=None, pars=None):

        # 从历史数据编码中读取四种历史数据的最新数值
        total_mv = h[:, -1, 0]  # 总市值
        total_liab = h[:, -1, 1]  # 总负债
        cash_equ = h[:, -1, 2]  # 现金及现金等价物总额
        ebitda = h[:, -1, 3]  # ebitda,息税折旧摊销前利润
        
        # 选股因子为EV/EBIDTA,使用下面公式计算
        factors = (total_mv + total_liab - cash_equ) / ebitda
        # 处理交易信号,将所有小于0的因子变为NaN
        factors = np.where(factors < 0, np.nan, factors)
        # 选出数值最小的30个股票的序号
        arg_partitioned = factors.argpartition(30)
        selected = arg_partitioned[:30]  # 被选中的30个股票的序号
        not_selected = arg_partitioned[30:]  # 未被选中的其他股票的序号(包括因子为NaN的股票)
        
        # 开始生成PT交易信号
        signal = np.zeros_like(factors)
        # 所有被选中的股票的持仓目标被设置为0.03,表示持有3.3%
        signal[selected] = 0.0333
        # 其余未选中的所有股票持仓目标在PT信号模式下被设置为0,代表目标仓位为0
        signal[not_selected] = 0  
        
        return signal    

Compare el código anterior con el código de FactorSorter, puede encontrar que el código de GeneralStg tiene más trabajo de procesamiento de factores después de calcular el factor de selección de acciones:

  • Eliminar factores menores que cero

  • Ordena y selecciona los 30 factores restantes más pequeños

  • Establecer el ratio de posición de las acciones seleccionadas en 3,3%

De hecho, todo el trabajo anterior es el «azúcar de sintaxis» proporcionado por FactorSorter, que debemos implementar manualmente aquí. Vale la pena señalar que los códigos de clasificación y otros que utilicé en los ejemplos anteriores son códigos numpy altamente optimizados extraídos directamente de FactorSorter, y su velocidad de ejecución es muy rápida, mucho más rápida que el código que los usuarios generales pueden escribir. Por lo tanto, siempre que las condiciones lo permitan, los usuarios deben intentar utilizar estos azúcares de sintaxis tanto como sea posible y escribir código de clasificación solo cuando sea necesario.

Puede estudiar el código anterior, pero tenga en cuenta que si utiliza la clase de estrategia GeneralStg, el resultado de la estrategia debe ser la posición objetivo de la acción, no el factor de selección de acciones.

Aquí están los resultados del backtesting:

10.8. Resultados del backtesting:

Backtesting con los mismos datos:

alpha = AlphaPT(pars=(),
                 par_count=0,
                 par_types=[],
                 par_range=[],
                 name='AlphaSel',
                 description='本策略每隔1个月定时触发计算SHSE.000300成份股的过去的EV/EBITDA并选取EV/EBITDA大于0的股票',
                 data_types='total_mv, total_liab, c_cash_equ_end_period, ebitda',
                 run_freq='m',
                 data_freq='d',
                 window_length=100)
op = qt.Operator(alpha, signal_type='PT')
res = op.run(mode=1,
             asset_type='E',
             asset_pool=shares,
             PT_buy_threshold=0.00,  # 如果设置PBT=0.00,PST=0.03,最终收益会达到30万元
             PT_sell_threshold=0.00,
             trade_batch_size=100,
             sell_batch_size=1,
             trade_log=True
            )

Resultados del backtesting:

     ====================================
     |                                  |
     |       BACK TESTING RESULT        |
     |                                  |
     ====================================

qteasy running mode: 1 - History back testing
time consumption for operate signal creation: 7.2ms
time consumption for operation back looping:  6s 308.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
000301.SZ    1        1       2   10.3%      0.0%     89.7%  
000786.SZ    2        3       5   27.5%      0.0%     72.5%  
000895.SZ    1        1       2   68.7%      0.0%     31.3%  
002001.SZ    2        2       4   57.5%      0.0%     42.5%  
002007.SZ    0        1       1   68.3%      0.0%     31.7%  
002027.SZ    6        7      13   41.3%      0.0%     58.7%  
002032.SZ    3        1       4    7.5%      0.0%     92.5%  
002044.SZ    1        1       2    1.8%      0.0%     98.2%  
002049.SZ    1        1       2    5.1%      0.0%     94.9%  
002050.SZ    4        4       8   13.8%      0.0%     86.2%  
...            ...     ...   ...      ...       ...       ...
603806.SH    5        3       8   62.1%      0.0%     37.9%  
603899.SH    2        3       5   36.3%      0.0%     63.7%  
000408.SZ    3        5       8   35.5%      0.0%     64.5%  
002648.SZ    1        1       2    5.2%      0.0%     94.8%  
002920.SZ    1        1       2    5.1%      0.0%     94.9%  
300223.SZ    1        2       3    5.2%      0.0%     94.8%  
300496.SZ    1        1       2   10.5%      0.0%     89.5%  
600219.SH    1        1       2    6.1%      0.0%     93.9%  
603185.SH    1        1       2    5.2%      0.0%     94.8%  
688005.SH    1        2       3    5.2%      0.0%     94.8%   

Total operation fee:     ¥      985.25
total investment amount: ¥  100,000.00
final value:              ¥  189,723.44
Total return:                    89.72% 
Avg Yearly return:               14.18%
Skewness:                         -0.41
Kurtosis:                          2.87
Benchmark return:                65.96% 
Benchmark Yearly return:         11.06%

------strategy loop_results indicators------ 
alpha:                            0.044
Beta:                             1.134
Sharp ratio:                      1.284
Info ratio:                       0.011
250 day volatility:               0.120
Max drawdown:                    20.95% 
    peak / valley:        2018-05-22 / 2019-01-03
    recovered on:         2019-09-09

===========END OF REPORT=============

Los resultados de las dos estrategias comerciales son básicamente los mismos.

10.9. Resumen

Hemos aprendido el uso de las otras dos clases base de estrategias comerciales FactorSorter y GeneralStg proporcionadas por qteasy en esta sección y, de hecho, creamos dos estrategias comerciales. Aunque se utilizan diferentes clases base, se crea la misma estrategia comercial de selección de acciones Alpha.

En la siguiente sección, continuaremos presentando estrategias comerciales personalizadas, pero usaremos un ejemplo más complejo para demostrar el uso de estrategias comerciales personalizadas. ¡Manténganse al tanto!