10. 別のカスタム戦略

qteasy は、以下の機能を備えた定量的取引分析ツールキットの完全にローカライズされた展開と運用です。

  • 財務データの取得、クリーニング、保管、処理、可視化、および使用

  • クオンツ取引戦略を作成し、多数の組み込みの基本取引戦略を提供します

  • ベクトル化された高速取引戦略のバックテストと取引結果の評価

  • 取引戦略パラメーターの最適化と評価

  • 取引戦略の展開と実際の運用

このチュートリアルでは、一連の実践的な例を通じて、qteasy の主な機能と使用法を完全に理解します。

10.1. 始める前に

このチュートリアルを開始する前に、次の内容を習得していることを確認してください。

  • qteasy のインストールと構成 —— QTEASY チュートリアル 1

  • ローカル データ ソースをセットアップし、十分な履歴データをローカルにダウンロードしている -

  • トレーダー オブジェクトの作成方法、組み込み取引戦略の使用方法を学びます —— QTEASY チュートリアル 3

  • ミキサーを使用して、複数の単純な戦略を混合してより複雑な取引戦略を作成する方法を学びます —— QTEASY チュートリアル 4

  • 取引戦略をカスタマイズする方法を学ぶ —— QTEASY チュートリアル 5

QTEASY ドキュメント では、組み込み取引戦略の使用、カスタム戦略の作成などに関する詳細なコンテンツも見つけることができます。 qteasy の基本的な使用法にまだ慣れていない場合は、そこにアクセスしてさらに詳しい説明を参照してください。

10.2. このチュートリアルのターゲット

このセクションでは、前のセクションの内容を継続し、qteasy のトレーディング戦略基本クラスを紹介します。最も単純なタイミング トレーディング戦略クラスを紹介した後、qteasy が提供する他の 2 つの戦略基本クラスを使用して多要素銘柄選択戦略を作成する方法を紹介します。

十分な使いやすさを提供するために、qteasy によって提供されるさまざまな戦略基本クラスは本質的に同じであり、ユーザーのコーディング作業負荷を軽減するために提供される前処理形式にすぎません。また、異なる取引戦略基本クラスであっても、特定の取引戦略用に設計された「構文糖」として理解できます。したがって、多くの場合、同じ取引戦略を複数の異なる取引戦略基本クラスを使用して実装できます。したがって、このセクションでは、2 つの異なる戦略基本クラスを使用して、アルファ銘柄選択トレーディング戦略を実装します。

10.3. アルファ銘柄選択戦略の仕組み

ここで説明するアルファ銘柄選択戦略は、毎週または毎月実行できる低頻度の銘柄選択戦略です。銘柄が選択されるたびに、HS300 インデックスのすべての構成銘柄が対象となり、特定の基準に従ってこれら 300 銘柄に優先順位が付けられます。その中から上位30銘柄を選んで均等に保有、つまり月に1回リバランスを行い、リバランス中に順位の低い銘柄を売却し、順位の高い銘柄を購入して均等保有を確保します。

アルファ銘柄選択戦略のランキング基準は、各銘柄の 2 つの財務指標、EV (企業価値) と EBITDA (利息、税金、減価償却前利益) から計算されます。各銘柄について、EBITDA に対する EV の比率を計算します。この比率が 0 より大きい場合、上場企業が利益を上げていることを示します (EBITDA がプラスであるため)。この場合、比率は企業が 1 元の利益を得るために投資する必要がある企業価値の総額を表します。当然のことながら、この比率は低いほど良いです。例えば、以下の上場企業2社のデータは以下の通りです。

  • 会社 A の EBITDA は 1,000 万、企業市場価値は 100 億です。 EV/EBITDA = 1000。これは、企業の市場価値 1,000 元ごとに 1 元の利益を得ることができることを示します。

  • B 社の EBITDA は 1,000 万、企業市場価値は 1,000 億です。 EV/EBITDA = 10000、企業の市場価値 10,000 元ごとに 1 元の利益を得ることができることを示します。

通常、私たちは企業の市場価値が低くても同じ利益を上げている A 社の方が優れていると自然に感じます。現時点ではA社のランキングは比較的高いと考えられます。

上記のルールに従い、毎月末にHS300構成銘柄の全上場企業を小型から大型までランク付けし、EV/EBITDAが0未満の企業を除外します(もちろん、利益がマイナスの企業は除外する必要があります)。その後、保有する上位 30 社を選択します。これがアルファ銘柄選択取引戦略です。

実際、指標によってランク付けされるこのような銘柄選択戦略のために、qteasy はそれを直接実装できる組み込みの取引戦略を提供します。

built_in_doc を使用して、この組み込み取引戦略のドキュメントを表示します。

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

出力は次のとおりです。

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

ただし、この組み込み取引戦略は、銘柄選択要素として qteasy 組み込み履歴データ タイプの使用のみをサポートします。例えば、PE(株価収益率)や利益などのデータはqteasyに過去のデータが組み込まれており、直接参照することができます。ただし、qteasy の組み込み履歴データで銘柄選択要素が見つからない場合、組み込みの取引戦略を直接使用することはできません。 EV/EBITDA インジケーターは計算されたインジケーターであるため、カスタム取引戦略を使用し、カスタム戦略内でこのインジケーターを計算する必要があります。

10.4. 銘柄選択指標の計算

EV/EBITDA を計算するには、少なくとも qteasy が EV と EBITDA に次の 2 種類の履歴データを提供したかどうかを確認する必要があります。

API: find_history_data() を使用して、履歴データ型が qteasy でサポートされているかどうかを確認できます。まず、ebitda を調べます。

>>> qt.find_history_data('ebitda')

出力は次のとおりです。

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

qteasy のデータ型は、qteasy.DataType を介して作成する必要があります。 DataType は、一種の履歴データ タイプ、つまり、qteasy が履歴データから直接抽出できる情報のカテゴリを表します。データ型オブジェクトを通じて、qteasy は統一されたデータ インターフェイスを提供し、ユーザーはデータ型の保存方法や保存場所を気にすることなく、さまざまな種類の履歴データを簡単に取得できます。同時に、qteasy は、型処理、周波数変換、ティッカー マッチングなどの複雑な基盤となるデータ ロジックをすべて完全にカプセル化しているため、ユーザーは各種類のデータの保存方法を気にする必要がなく、直接使用できます。

qteasy データ型の詳細については、QTEASY ドキュメント を参照してください。

qteasyDataType には 3 つの属性が含まれます。

  • name: データ型の名前。たとえば、上記の戻り値の ebitda

  • freq: データの頻度。たとえば、上記の戻り値の q は四半期データを表します。

  • asset_type: データの資産タイプ。たとえば、上記の戻り値の E は株式データを表します

これら 3 つの属性を合わせて一意のデータ型を定義します。 qteasy には、多数の組み込み履歴データ タイプが含まれています。ユーザーは、これらのデータ型を自分で計算したり、生データを処理してこれらのデータ型を導出する必要がなく、これらのデータ型を直接使用して履歴データを取得できます。

上記の戻り値から、qteasy の組み込みの履歴データ型では、EBITDA が標準の履歴データ型であることがわかります。このデータは上場企業の財務指標テーブル financial から取得され、データ頻度は q (四半期) です。

次にEVを見てみましょう。

>>> qt.find_history_data('ev')

出力は次のとおりです。

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

これは、qteasy の組み込み履歴データ型の中に EV という名前の履歴データ型が見つからなかったことを示しています。この場合、パラメーター fuzzy=True を使用して確認できます。

>>> qt.find_history_data('ev', 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                                                                                                         
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   上市公司财务指标 - 每股营业收入
========================================================================

上の表は、qteasy ですでに定義されており、直接使用できるデータ型を示しています。 name / freq / asset 列に注目してください。これらはそれぞれデータ型名、データ頻度、資産タイプを表します。これら 3 つの列を合わせて、一意のデータ型を定義します。ユーザーは、qteasy.DataType(name, freq, asset) を介してデータ型オブジェクトを作成し、そのデータ型の履歴データを取得できます。

EV は qteasy の組み込みの履歴データ タイプには含まれていませんが、総収益や 1 株あたりの利益など、EV に関連する履歴データ タイプがいくつかあることがわかります。これらのデータ タイプは EV に関連していますが、必要な EV ではありません。

ただし、EV は次の式を使用して計算できることがわかっています。

\[::\]

上記の財務指標は qteasy によって直接サポートされています

  • 市場価値合計 - データタイプ: total_mv

  • 負債総額 - データタイプ: total_liab

  • 合計現金 - データタイプ: c_cash_equ_end_period

したがって、これをテストして、これらのデータ型の詳細な説明を確認できます。

>>> qt.find_history_data('total_mv', 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                                                                               
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     股票技术指标 - 总市值(亿元)
========================================================================

total_mv データ型には、1 万元単位と 1 億元単位の 2 つのバージョンがあることに注意してください。 EV/EBITDAを計算する場合、厳密には単位は重要ではありませんが、それ以外の場合には注意が必要です。ここではこのデータを10,000倍して単位を統一します。

ここではDataType('total_mv', 'd', 'E')を選択します。このデータ タイプは、毎日の上場企業の時価総額の合計を 10,000 元単位で表します。

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

ここではデータ型 DataType('total_liab', 'q', 'E') を選択できます。このデータ タイプは、各四半期末の上場企業の負債総額を元単位で表します。

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

現金に関連するデータの種類は多数ありますが、必要な現金および現金同等物の合計は DataType(c_cash_equ_end_period, 'q', 'E') です。このデータ タイプは、各四半期末における上場企業の現金および現金同等物の合計を表します。

上記の情報に基づいて、EV を計算するために次の 4 つのデータ タイプを選択できます。

  • DataType('total_mv', 'd', 'E')、このデータ タイプは、毎日の上場企業の時価総額の合計を 10,000 元単位で表します。

  • DataType('total_liab', 'q', 'E'): このデータ タイプは、各四半期末の上場企業の負債総額を人民元で表します。

  • DataType('c_cash_equ_end_period', 'q', 'E'): このデータ タイプは、各四半期末における上場企業の現金および現金同等物の合計を人民元で表します。

簡単なテストを実行できます。 qteasy は、次のデータ タイプの履歴データを直接取得できる非常に便利な API get_history_data() を提供します。

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

銘柄選択要素が計算されたことがわかり、取引戦略の定義を開始できます。

10.5. FactorSorter を使用してアルファ銘柄選択戦略を定義する

qteasy は、このタイプの時限銘柄選択取引戦略用の FactorSorter 取引戦略クラスを提供します。名前が示すように、このトレーディング戦略基本クラスを使用すると、ユーザーは戦略の実装方法で一連の銘柄選択要素を計算できるため、戦略は銘柄選択要素の値に従ってすべての銘柄を自動的に並べ替え、上位の銘柄を選択できます。ソート方法については、スクリーニングルールや株式保有ウェイト等を戦略パラメータにより設定可能です。

上記の取引戦略の定義を満たしている場合、FactorSorter 戦略基本クラスを使用すると非常に便利です。

次に、段階的に定義してみましょう。まず、FactorSorterを継承してクラスを定義します。前のセクションでは、カスタム ストラテジーの __init__() メソッドで名前、説明、デフォルト パラメーター、およびその他の情報を定義しました。ただし、__init__() メソッドを無視して、ストラテジー オブジェクトの作成時にパラメーターのみを渡すこともできます。これも可能です。ここでそれを行います:

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

前のセクションと同様に、realize() の最初のステップは履歴データを取得することです。過去のデータには、total_mv, total_liab, c_cash_equ_end_period, ebitda の 4 つのタイプが含まれていることがわかっています。これら 4 つの履歴データ シリーズは 4 つの DataType として定義され、戦略に渡されます。これらの履歴データを戦略で使用するには、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,息税折旧摊销前利润
        ...

self.get_data() メソッドは、各データ項目のデータ ID を介して、対応する履歴データを取得します。デフォルトでは、各データ項目の ID は、名前、アセット タイプ、および頻度の組み合わせです。次に例を示します。

  • DataType('total_mv', 'd', 'E'): このデータ型の ID は total_mv_E_d です。

  • DataType('total_liab', 'q', 'E'): このデータ型の ID は total_liab_E_d です。

取引戦略の実行が開始される前に、qteasy は対応する取引データを準備します。すべての取引データは numpy 配列に保存されます。列の数はユニバース内の株の数に対応し、各列はユニバース内の 1 つの株に対応します。行数は時間枠の長さに対応し、各行は時間枠内の時点に対応し、昇順で並べ替えられます。最後の列は、取引時に表示される最新の履歴データを表します。

このルールに従って、i 番目の株式の取引日に確認できる最新の履歴データを取得するには、array(-1, i) にアクセスするだけです。以下のループを使用すると、すべての株式のデータに同時にアクセスできます。

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

ただし、for ループを使用してデータにアクセスするのは比較的非効率です。時間を節約するために、戦略では可能な限りベクトル化された操作を使用する必要があります。

上記の準備ができていれば、銘柄選択要素を計算するのが非常に便利です。さらに、FactorSorter 戦略基本クラスを使用しているため、銘柄選択係数を計算した後、銘柄選択係数を直接返すことができ、qteasy が残りの銘柄選択操作を処理します。

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

これで、わずか 6 行のコードで、カスタムの Alpha 銘柄選択取引戦略が定義されました。とても簡単なことではありませんか?

OK、バックテストの結果を見てみましょう?

10.6. 取引戦略のバックテストの結果

ストラテジ クラスの __init__() メソッドを無視したため、ストラテジ オブジェクトをインスタンス化するときに完全なストラテジ パラメータを入力する必要があります。

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

次に、Operator オブジェクトを作成します。位置比率を制御したいため、「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)

バックテストの結果:

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

バックテストの結果によると、この戦略は上海および深セン 300 指数を効果的に上回ることはできませんが、全体的にはドローダウンが比較的小さく、リスクが低いため、良好な底値戦略となっています。

しかし、戦略のパフォーマンスは私たちの議論の焦点では​​ありません。次に、FactorSorter 基本クラスを使用せずに同じアルファ銘柄選択戦略を定義する方法を見てみましょう。

10.7. GeneralStg を使用してアルファ銘柄選択戦略を定義する

以前に 2 つの戦略基本クラスを紹介しました。

  • RuleIterator: ユーザーは銘柄の銘柄選択ルールを定義するだけでよく、qteasy は株式プール内のすべての銘柄に同じルールを適用し、銘柄ごとに異なる調整可能なパラメーターを設定することもできます。

  • FactorSorter:ユーザーは銘柄選択要素を定義するだけで、qteasyがソート後の銘柄選択要素に基づいて保有するのに最適な銘柄を自動的に選択し、適格でない銘柄を売却します。

GeneralStg は、qteasy が提供する基本的な戦略ベースクラスですが、ユーザーのコーディング負荷を軽減するための「シンタックスシュガー」機能は提供しませんが、シンタックスシュガーがないからこそ、より自由に取引戦略を作成するために使用できる真の「ユニバーサル」戦略クラスです。

上記のアルファ銘柄選択取引戦略は、FactorSorter を使用して簡単に実装できますが、GeneralStg を理解するために、それを使用して同じ戦略を作成する方法を見てみましょう。

完全なコードは次のとおりです。


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    

上記のコードと FactorSorter のコードを比較すると、GeneralStg のコードでは銘柄選択係数を計算した後の係数処理作業がより多くなっていることがわかります。

  • ゼロ未満の因子を削除する

  • 残りの因子を並べ替えて最小の 30 個を選択します

  • 選択した銘柄のポジション比率を3.3%に設定します

実際、上記の作業はすべて、FactorSorter によって提供される「構文糖」であり、ここで手動で実装する必要があります。上記の例で使用したソートおよびその他のコードは、FactorSorter から直接抽出されたnumpy コードを高度に最適化したものであり、その実行速度が非常に速く、一般ユーザーが作成できるコードよりもはるかに高速であることは注目に値します。したがって、条件が許す限り、ユーザーはこれらの構文シュガーを可能な限り使用するように努め、必要な場合にのみソート コードを作成する必要があります。

上記のコードを研究することもできますが、GeneralStg 戦略クラスを使用する場合、戦略の出力は銘柄選択要素ではなく、銘柄の目標ポジションである必要があることに注意してください。

バックテストの結果は次のとおりです。

10.8. バックテストの結果:

同じデータを使用したバックテスト:

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
            )

バックテストの結果:

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

2 つの取引戦略の結果は基本的に同じです

10.9. 要約

このセクションでは、qteasy が提供する他の 2 つの取引戦略基本クラス FactorSorterGeneralStg の使用法を学習し、実際に 2 つの取引戦略を作成しました。異なる基本クラスが使用されますが、同じアルファ銘柄選択取引戦略が作成されます。

次のセクションでは、カスタム取引戦略の紹介を続けますが、より複雑な例を使用してカスタム取引戦略の使用法を示します。乞うご期待!