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 1Richten 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_mvGesamtverbindlichkeiten – 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=============

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

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!