10. Erstellen Sie benutzerdefinierte Faktor-Aktienauswahl-Handelsstrategien

qteasy ist ein vollständig lokalisiertes und betriebenes Quantitative Trading-Analyse-Toolkit mit folgenden Funktionen:

  • Erfassung, Reinigung, Speicherung und Verarbeitung von Finanzdaten, Visualisierung und Verwendung

  • Erstellung quantitativer Handelsstrategien und Bereitstellung einer Vielzahl von integrierten grundlegenden Handelsstrategien

  • Vektorisierte Hochgeschwindigkeits-Backtests von Handelsstrategien und Bewertung der Handelsergebnisse

  • Optimierung und Bewertung von Handelsstrategieparametern

  • Bereitstellung von Handelsstrategien und Durchführung von Live-Trades

Durch diese Tutorials werden Sie anhand einer Reihe praktischer Beispiele die Hauptfunktionen und Verwendungsmethoden von qteasy vollständig verstehen.

10.1. Vorbereitungen vor dem Start

Bevor Sie mit diesem Tutorial beginnen, stellen Sie bitte sicher, dass Sie Folgendes beherrschen:

  • Installieren und Konfigurieren von qteasy - QTEASY-Tutorial 1

  • Richten Sie eine lokale Datenquelle ein und haben Sie genügend historische Daten auf die lokale heruntergeladen –

  • Lernen Sie, Händlerobjekte zu erstellen und integrierte Handelsstrategien zu verwenden, - QTEASY Tutorial 3

  • Lernen Sie, einen Mixer zu verwenden, um mehrere einfache Strategien zu komplexeren Handelsstrategien zu verbinden - QTEASY Tutorial 4

  • Erfahren Sie, wie Sie Ihre Handelsstrategie anpassen——QTEASY-Tutorial 5

Im QTEASY-Dokument finden Sie auch weitere Informationen zur Verwendung integrierter Handelsstrategien, zur Erstellung benutzerdefinierter Strategien usw. Wenn Sie mit den grundlegenden Verwendungsmethoden von qteasy noch nicht vertraut sind, können Sie dort weitere detaillierte Erklärungen finden.

10.2. Ziele dieses Abschnitts

In diesem Abschnitt werden wir den Inhalt des vorherigen Abschnitts fortsetzen und die Handelsstrategie-Basisklasse qteasy vorstellen. Nachdem wir die einfachste Timing-Trading-Strategieklasse vorgestellt haben, stellen wir vor, wie die anderen beiden von qteasy bereitgestellten Strategie-Basisklassen verwendet werden, um eine Multi-Faktor-Aktienauswahlstrategie zu erstellen.

Um einen ausreichenden Komfort zu gewährleisten, sind die verschiedenen Strategie-Basisklassen von qteasy im Wesentlichen identisch. Es handelt sich lediglich um Vorverarbeitungsformen, die den Programmieraufwand des Benutzers reduzieren. Verschiedene Handelsstrategie-Basisklassen können sogar als „syntaktischer Zucker“ für spezifische Handelsstrategien verstanden werden. Daher kann dieselbe Handelsstrategie oft mit mehreren verschiedenen Handelsstrategie-Basisklassen implementiert werden. In diesem Abschnitt verwenden wir daher zwei verschiedene Strategie-Basisklassen, um eine Alpha-Aktienauswahl-Handelsstrategie zu implementieren.

10.3. Alpha-Aktienauswahlstrategie, Ideen zur Aktienauswahl

Die hier besprochene Alpha-Aktienauswahlstrategie ist eine Strategie mit niedriger Aktienfrequenz. Diese Strategie kann wöchentlich oder monatlich angewendet werden. Bei jeder Aktienauswahl werden alle im HS300-Index enthaltenen Aktien durchsucht, nach bestimmten Kriterien priorisiert und die 30 besten Aktien daraus ausgewählt und gleichmäßig gehalten. Das heißt, das Portfolio wird monatlich angepasst, wobei die Aktien mit niedrigerem Ranking verkauft und die Aktien mit höherem Ranking gekauft werden. Dabei wird sichergestellt, dass die Aktienbestände gleich bleiben.

Das Ranking der Alpha-Aktienauswahlstrategie basiert auf zwei Finanzkennzahlen jeder Aktie: EV (Unternehmensmarktwert) und EBITDA (Gewinn vor Zinsen, Steuern und Abschreibungen). Das Verhältnis von EV zu EBITDA wird für jede Aktie berechnet. Ist dieses Verhältnis größer als 0, bedeutet dies, dass das börsennotierte Unternehmen profitabel ist (da das EBITDA positiv ist). Dieses Verhältnis stellt den gesamten Unternehmenswert dar, der für jeden Dollar Gewinn investiert werden muss. Je niedriger dieses Verhältnis, desto besser. Die Daten der folgenden beiden börsennotierten Unternehmen lauten beispielsweise wie folgt:

  • Das EBITDA von Unternehmen A beträgt 10 Millionen und sein Marktwert 10 Milliarden. EV/EBITDA = 1000. Dies bedeutet, dass das Unternehmen pro 1.000 Yuan Marktwert 1 Yuan Gewinn erzielen kann.

  • Das EBITDA von Unternehmen B beträgt ebenfalls 10 Millionen und sein Marktwert 100 Milliarden. EV/EBITDA = 10.000, was bedeutet, dass das Unternehmen pro 10.000 Yuan Marktwert einen Gewinn von 1 Yuan erzielen kann.

Aus gesundem Menschenverstand würden wir natürlich annehmen, dass Unternehmen A besser ist, da es bei geringerem Marktwert den gleichen Gewinn erzielt. Derzeit gehen wir davon aus, dass Unternehmen A einen höheren Rang einnimmt.

Gemäß den oben genannten Regeln werden wir am letzten Tag jedes Monats alle börsennotierten Unternehmen, die Bestandteile des HS300 sind, von klein bis groß einstufen, Unternehmen mit einem EV/EBITDA von weniger als 0 ausschließen (Unternehmen mit negativen Gewinnen sollten natürlich ausgeschlossen werden) und dann die 30 besten Unternehmen zum Halten auswählen, was der Alpha-Aktienauswahl-Handelsstrategie entspricht.

Tatsächlich bietet „qteasy“ ähnlich dieser Strategie zur Aktienauswahl mit Sortierung nach Indikatoren eine integrierte Handelsstrategie, die direkt implementiert werden kann:

Verwenden Sie „built_in_doc“, um die Dokumentation für diese integrierte Handelsstrategie anzuzeigen:

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

Die Ausgabe ist wie folgt:

以股票过去一段时间内的财务指标的平均值作为选股因子选股
        基础选股策略。以股票的历史指标的平均值作为选股因子,因子排序参数可以作为策略参数传入
        改变策略数据类型,根据不同的历史数据选股,选股参数可以通过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.)]
    策略不支持参考数据,不支持交易数据

Diese integrierte Handelsstrategie unterstützt jedoch nur die in „qteasy“ integrierten historischen Datentypen als Aktienauswahlfaktoren. Beispielsweise sind KGV, Gewinn und andere Daten integrierte historische Daten von „qteasy“ und können direkt referenziert werden. Wenn der Aktienauswahlfaktor jedoch nicht in den integrierten historischen Daten von „qteasy“ enthalten ist, kann die integrierte Handelsstrategie nicht direkt verwendet werden. Der EV/EBITDA-Indikator ist ein berechneter Indikator, daher müssen wir eine benutzerdefinierte Handelsstrategie verwenden und diesen Indikator in der benutzerdefinierten Strategie berechnen.

10.4. Berechnung von Aktienauswahlindikatoren

Um EV/EBITDA zu berechnen, müssen wir zunächst bestätigen, ob die historischen Daten von EV und EBITDA bereits in „qteasy“ bereitgestellt werden:

Wir können find_history_data() verwenden, um zu sehen, ob die historischen Daten von qteasy unterstützt werden.

>>> qt.find_history_data('ebitda')

Die Ausgabe ist wie folgt:

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']

Datentypen in „qteasy“ müssen über „qteasy.DataType“ erstellt werden. „DataType“ stellt eine Art historischen Datentyp dar – d. h. eine Kategorie von Informationen, die „qteasy“ direkt aus historischen Daten extrahieren kann. Durch Datentypobjekte bietet „qteasy“ eine einheitliche Datenschnittstelle, die es Benutzern ermöglicht, problemlos verschiedene Arten historischer Daten abzurufen, ohne sich Gedanken darüber machen zu müssen, wie der Datentyp oder wo er gespeichert wird. Gleichzeitig kapselt „qteasy“ die gesamte komplexe zugrunde liegende Datenlogik wie Typverarbeitung, Frequenzkonvertierung und Ticker-Matching vollständig, sodass Benutzer sich nicht um die Speichermethode der einzelnen Datentypen kümmern müssen und diese direkt verwenden können.

Eine detailliertere Einführung in den Datentyp „qteasy“ finden Sie in der QTEASY-Dokumentation.

qteasys DataType enthält drei Attribute:

  • Name: der Name des Datentyps, zum Beispiel „ebitda“ im obigen Rückgabewert

  • freq: Die Häufigkeit der Daten, zum Beispiel „q“ im obigen Rückgabewert, der vierteljährliche Daten darstellt

  • asset_type: der Asset-Typ der Daten; Beispielsweise steht „E“ im obigen Rückgabewert für Bestandsdaten

Diese drei Attribute definieren zusammen einen eindeutigen Datentyp. „qteasy“ verfügt über eine große Anzahl integrierter historischer Datentypen. Benutzer können diese Datentypen direkt verwenden, um historische Daten zu erhalten, ohne sie selbst berechnen oder Rohdaten verarbeiten zu müssen, um diese Datentypen abzuleiten.

Anhand des obigen Rückgabewerts können wir erkennen, dass EBITDA in den integrierten historischen Datentypen von „qteasy“ ein standardmäßiger historischer Datentyp ist. Diese Daten stammen aus der Tabelle „Finanzindikatoren“ der börsennotierten Unternehmen und die Datenhäufigkeit ist „q“ (vierteljährlich):

Schauen Sie sich als nächstes EV an:

>>> qt.find_history_data('ev')

Die Ausgabe ist wie folgt:

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: []
========================================================================

Dies weist darauf hin, dass unter den integrierten historischen Datentypen von „qteasy“ kein historischer Datentyp namens EV gefunden wurde; In diesem Fall können Sie zur Bestätigung den Parameter „fuzzy=True“ verwenden.

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

Die Ausgabe ist wie folgt:

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   上市公司财务指标 - 每股营业收入
========================================================================

Die obige Tabelle listet die Datentypen auf, die bereits in „qteasy“ definiert wurden und direkt verwendet werden können. Bitte achten Sie auf die Spalten „Name“ / „Häufigkeit“ / „Asset“, die jeweils den Datentypnamen, die Datenhäufigkeit und den Asset-Typ darstellen. Zusammen definieren diese drei Spalten einen eindeutigen Datentyp. Benutzer können über „qteasy.DataType(name, freq, asset)“ ein Datentypobjekt erstellen, um die historischen Daten dieses Datentyps zu erhalten.

Obwohl EV nicht zu den integrierten historischen Datentypen von „qteasy“ gehört, können wir sehen, dass es einige historische Datentypen gibt, die sich auf EV beziehen, wie z. B. Gesamtumsatz, Gewinn pro Aktie usw. Diese Datentypen beziehen sich auf EV, sind aber nicht die EV, die wir benötigen.

Wir wissen jedoch, dass EV mit der folgenden Formel berechnet werden kann:

\[::\]

Die oben genannten Finanzindikatoren werden direkt von „qteasy“ unterstützt:

  • Gesamtmarktwert - Datentyp: total_mv

  • Gesamtverbindlichkeiten – Datentyp: „total_liab“

  • Gesamtbargeld – Datentyp: „c_cash_equ_end_period“

So können wir es testen und uns die detaillierten Erklärungen dieser Datentypen ansehen:

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

Sie erhalten die folgende Ausgabe:

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     股票技术指标 - 总市值(亿元)
========================================================================

Beachten Sie, dass der Datentyp „total_mv“ zwei Versionen hat: eine in Einheiten von 10.000 Yuan und eine in Einheiten von 100 Millionen Yuan. Bei der Berechnung von EV/EBITDA ist die Einheit streng genommen nicht wichtig, in anderen Fällen muss man jedoch darauf achten. Hier multiplizieren wir diese Daten mit 10.000, um die Einheiten zu vereinheitlichen.

Hier wählen wir DataType('total_mv', 'd', 'E'). Dieser Datentyp stellt die gesamte Marktkapitalisierung eines börsennotierten Unternehmens an jedem Tag in Einheiten von 10.000 Yuan dar.

>>> 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  上市公司资产负债表 - 负债及股东权益总计
========================================================================

Hier können wir den Datentyp DataType('total_liab', 'q', 'E') wählen. Dieser Datentyp stellt die Gesamtverbindlichkeiten eines börsennotierten Unternehmens am Ende jedes Quartals in Yuan-Einheiten dar.

>>> 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                         实施-每股分红(税前)
========================================================================

Es gibt viele Datentypen im Zusammenhang mit Bargeld, aber der Gesamtbetrag an Bargeld und Bargeldäquivalenten, den wir benötigen, ist „DataType(c_cash_equ_end_period, ‚q‘, ‚E‘)“. Dieser Datentyp stellt die gesamten Zahlungsmittel und Zahlungsmitteläquivalente eines börsennotierten Unternehmens am Ende jedes Quartals dar.

Basierend auf den oben genannten Informationen können wir die folgenden vier Datentypen zur Berechnung des EV auswählen:

  • DataType('total_mv', 'd', 'E'), dieser Datentyp stellt die gesamte Marktkapitalisierung eines börsennotierten Unternehmens an jedem Tag in Einheiten von 10.000 Yuan dar.

  • DataType('total_liab', 'q', 'E'): Dieser Datentyp stellt die Gesamtverbindlichkeiten eines börsennotierten Unternehmens am Ende jedes Quartals in Yuan dar.

  • DataType('c_cash_equ_end_period', 'q', 'E'): Dieser Datentyp stellt die gesamten Zahlungsmittel und Zahlungsmitteläquivalente eines börsennotierten Unternehmens am Ende jedes Quartals in Yuan dar.

Wir können einen Schnelltest durchführen. „qteasy“ bietet eine sehr praktische API: „get_history_data()“, die historische Daten für diese Datentypen direkt abrufen kann:

# 创建数据类型对象
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]

Sie sehen, dass die Aktienauswahlfaktoren berechnet wurden, sodass wir mit der Definition der Handelsstrategie beginnen können.

10.5. Definieren von Alpha-Aktienauswahlstrategien mit „FactorSorter“

Für diese Art von zeitgesteuerter Aktienauswahlstrategie bietet „qteasy“ die Handelsstrategieklasse „FactorSorter“. Wie der Name schon sagt, ermöglicht diese Basisklasse die Berechnung von Aktienauswahlfaktoren in der Strategieimplementierung. So sortiert die Strategie automatisch alle Aktien nach dem Wert des Aktienauswahlfaktors und wählt die am besten bewerteten Aktien aus. Sortiermethode, Auswahlregeln, Aktiengewichtung usw. lassen sich über Strategieparameter festlegen.

Wenn Sie zur oben definierten Handelsstrategie passen, ist es sehr praktisch, die Basisklasse der FactorSorter-Strategie zu verwenden.

Definieren wir es Schritt für Schritt. Zunächst erben wir „FactorSorter“ und definieren eine Klasse. Im vorherigen Abschnitt haben wir Name, Beschreibung und Standardparameter in der Methode „init()“ der benutzerdefinierten Strategie definiert. Wir können die Methode „init()“ jedoch auch ignorieren und nur Parameter und andere Informationen beim Erstellen des Strategieobjekts übergeben. Dies ist ebenfalls möglich. Wir tun dies hier:

>>> 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  # 直接返回选股因子,策略就定义好了

Wie im vorherigen Abschnitt besteht der erste Schritt in „realize()“ darin, historische Daten abzurufen. Wir wissen, dass die historischen Daten vier Typen umfassen: „total_mv, total_liab, c_cash_equ_end_period, ebitda“. Diese vier historischen Datenreihen werden als vier „DataType“ definiert und an die Strategie übergeben. Um diese historischen Daten in der Strategie zu verwenden, können Sie „self.get_data()“ direkt aufrufen:

# 从历史数据编码中读取四种历史数据的最新数值
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,息税折旧摊销前利润
        ...

Die Methode „self.get_data()“ ruft die entsprechenden historischen Daten über die Daten-ID jedes Datenelements ab. Standardmäßig ist die ID jedes Datenelements eine Kombination aus Name, Asset-Typ und Häufigkeit, zum Beispiel:

  • DataType('total_mv', 'd', 'E'): Die ID dieses Datentyps ist „total_mv_E_d“.

  • DataType('total_liab', 'q', 'E'): Die ID dieses Datentyps lautet: total_liab_E_d.

Bevor die Handelsstrategie in die Tat umgesetzt wird, bereitet qteasy die entsprechenden Handelsdaten vor. Alle Handelsdaten werden in einem Numpy-Array gespeichert: Die Anzahl der Spalten entspricht der Anzahl der Aktien im Universum, und jede Spalte entspricht einer Aktie im Universum; Die Anzahl der Zeilen entspricht der Länge des Zeitfensters, jede Zeile entspricht einem Zeitpunkt im Zeitfenster und ist in aufsteigender Reihenfolge sortiert, und die letzte Spalte stellt die neuesten historischen Daten dar, die zum Zeitpunkt des Handels sichtbar waren.

Wenn Sie dieser Regel folgen, greifen Sie einfach auf array(-1,i) zu, um die neuesten historischen Daten zu erhalten, die am Handelstag für die i-te Aktie angezeigt werden können. Mit der folgenden Schleife können Sie zum gleichen Zeitpunkt auf die Daten aller Bestände zugreifen:

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]}')

Allerdings ist die Verwendung einer for-Schleife für den Zugriff auf Daten relativ ineffizient. Um Zeit zu sparen, sollten Sie in Ihrer Strategie so weit wie möglich vektorisierte Operationen verwenden.

Nach den oben genannten Vorbereitungen ist die Berechnung des Aktienauswahlfaktors sehr einfach. Da wir außerdem die Strategie-Basisklasse „FactorSorter“ verwenden, können wir nach der Berechnung des Aktienauswahlfaktors diesen direkt zurückgeben, und „qteasy“ übernimmt die restlichen Aktienauswahlvorgänge:

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

An diesem Punkt wurde eine benutzerdefinierte Alpha-Aktienauswahl-Handelsstrategie mit nur sechs Codezeilen definiert. Ist das nicht ganz einfach?

Okay, sehen wir uns die Backtest-Ergebnisse an.

10.6. Backtesting-Ergebnisse von Handelsstrategien

Da wir die Methode __init__() der Strategieklasse ignoriert haben, müssen wir beim Instanziieren eines Strategieobjekts die vollständigen Strategieparameter übergeben:

>>> 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名
... )  

Erstellen Sie dann ein „Operator“-Objekt. Da wir das Positionsverhältnis steuern möchten, ist es besser, den Signaltyp „PT“ zu verwenden:

>>> 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)

Die Ergebnisse des Backtests lauten wie folgt:

     ====================================
     |                                  |
     |       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=============

Bildbeschreibung hier einfügen

Die Backtest-Ergebnisse zeigen, dass diese Strategie den CSI 300 Index nicht sehr effektiv übertreffen kann, aber insgesamt ist der Drawdown kleiner und das Risiko niedriger, was sie zu einer guten Bottom-Line-Strategie macht.

Die Performance der Strategie steht jedoch nicht im Mittelpunkt unserer Diskussion. Sehen wir uns an, wie dieselbe Alpha-Aktienauswahlstrategie ohne Verwendung der Basisklasse „FactorSorter“ definiert werden kann.

10.7. Definieren Sie eine Alpha-Aktienauswahlstrategie mit „GeneralStg“

Zwei Strategie-Basisklassen wurden bereits erwähnt:

  • RuleIterator: Benutzer müssen nur Aktienauswahlregeln für eine Aktie definieren, und qteasy kann dieselben Regeln auf alle schlechten Aktien im Aktienpool anwenden und auch unterschiedliche anpassbare Parameter für unterschiedliche Aktien festlegen.

  • FactorSorter: Benutzer müssen nur einen Aktienauswahlfaktor definieren, und qteasy kann automatisch entsprechend dem Aktienauswahlfaktor sortieren und die besten Aktien zum Halten auswählen sowie nicht qualifizierte Aktien verkaufen.

GeneralStg ist die grundlegendste Strategie-Basisklasse von „qteasy“. Sie bietet keine „Syntax Sugar“-Funktion, um den Programmieraufwand zu reduzieren. Gerade aufgrund des fehlenden Syntax Sugars handelt es sich jedoch um eine wirklich universelle Strategieklasse, mit der Handelsstrategien freier entwickelt werden können.

Die oben beschriebene Alpha-Aktienauswahl-Handelsstrategie könnte problemlos mit FactorSorter umgesetzt werden. Um jedoch GeneralStg zu verstehen, sehen wir uns an, wie man damit dieselbe Strategie erstellt:

Fügen Sie einfach den vollständigen Code ein:


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    

Wenn wir den obigen Code mit dem Code von „FactorSorter“ vergleichen, können wir feststellen, dass der Code von „GeneralStg“ nach der Berechnung der Aktienauswahlfaktoren zusätzliche Faktorverarbeitungsarbeit erfordert:

  • Eliminieren Sie Faktoren kleiner als Null

  • Sortieren und wählen Sie die 30 kleinsten verbleibenden Faktoren aus

  • Nach der Auswahl der Aktien legen Sie deren Beteiligungsquote auf 3,3 % fest

Tatsächlich handelt es sich bei der gesamten oben genannten Arbeit um den von „FactorSorter“ bereitgestellten „Syntaxzucker“, den wir hier manuell implementieren müssen. Es ist erwähnenswert, dass die Sortiercodes, die ich in den obigen Beispielen verwendet habe, hochoptimierte „Numpy“-Codes sind, die direkt aus „FactorSorter“ extrahiert wurden. Sie laufen sehr schnell, viel schneller als der Code, den normale Benutzer schreiben können. Daher sollten Benutzer, sofern es die Bedingungen erlauben, versuchen, diese syntaktischen Zucker zu verwenden und nur dann selbst Sortiercodes zu schreiben, wenn es unbedingt nötig ist.

Sie können den obigen Code studieren, beachten Sie jedoch, dass bei Verwendung der Strategieklasse „GeneralStg“ die Ausgabe der Strategie die Zielposition der Aktie und nicht der Aktienauswahlfaktor sein sollte.

Werfen wir einen Blick auf die Backtest-Ergebnisse:

10.8. Backtest-Ergebnisse für die erste Strategie

Backtesting mit denselben Daten:

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
            )

Die Ergebnisse des Backtests lauten wie folgt:

     ====================================
     |                                  |
     |       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=============

Bildbeschreibung hier einfügen

Die Ergebnisse der beiden Handelsstrategien sind grundsätzlich gleich

10.9. Rückblick auf diesen Abschnitt

Durch das Studium dieses Abschnitts haben wir gelernt, wie man die beiden anderen Handelsstrategie-Basisklassen „FactorSorter“ und „GeneralStg“ von „qteasy“ verwendet und tatsächlich zwei Handelsstrategien erstellt. Obwohl unterschiedliche Basisklassen verwendet wurden, entstand im Grunde dieselbe Handelsstrategie für die Alpha-Aktienauswahl.

Im nächsten Kapitel werden wir weiterhin benutzerdefinierte Handelsstrategien vorstellen, jedoch anhand eines komplexeren Beispiels demonstrieren, wie man benutzerdefinierte Handelsstrategien einsetzt. Bleiben Sie dran!