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 1Configure 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
EBITDAde 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
EBITDAde 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
ebitdaen el valor de retorno anteriorfreq: la frecuencia de los datos, por ejemplo
qen el valor de retorno anterior, que representa datos trimestralesactivo_tipo: el tipo de activo de los datos; por ejemplo,
Een 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_mvPasivos totales - Tipo de datos:
total_liabEfectivo 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 estotal_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, yqteasyaplicará 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, yqteasyseleccionará 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!