4. Multifaktorielle Aktienauswahlstrategie

Referenzquelle: docs/_joinquant_migration_source/Example_04_Multifactor Stock Selection.ipynb Erste Markdown-Zelle.

Diese Strategie wird monatlich angewendet, indem für jede Aktie eine Regressionsanalyse mithilfe des Fama-French-Drei-Faktoren-Modells durchgeführt wird, um den Alpha-Wert zu ermitteln. Unter der Annahme, dass das Fama-French-Drei-Faktoren-Modell den Markt vollständig erklären kann, deutet ein negativer Alpha-Wert darauf hin, dass der Markt die Aktie unterbewertet und somit einen Kauf rechtfertigt.

Strategie und Vorgehensweise:

Die Marktrendite, das Kurs-Buchwert-Verhältnis und die Marktkapitalisierung einzelner Aktien wurden berechnet, wobei die beiden letztgenannten Kennzahlen kategorisiert wurden. Basierend auf den kategorisierten Portfolios wurden die marktkapitalisierungsgewichtete Rendite, SMB und HML berechnet. Für jede Aktie wurde eine Regression durchgeführt (unter der Annahme eines risikofreien Zinssatzes von 0), um den Alpha-Wert zu ermitteln.

Die zehn Aktien mit den niedrigsten Alpha-Werten (unter 0) werden für die Zielauswahl ausgewählt. Aktien, die nicht zur Zielauswahl gehören, werden entfernt, und die verbleibenden Aktien werden gleichgewichtet erworben.

Backtesting-Daten: SHSE.000300-Bestandteile

Der Backtesting-Zeitraum erstreckte sich vom 1. Mai 2019 bis zum 1. Mai 2022.

4.1. Strategie definieren

import qteasy as qt
import numpy as np
from qteasy import Parameter, StgData
def market_value_weighted(stock_return, mv, mv_cat, bp_cat, mv_target, bp_target):
    """ 根据mv_target和bp_target计算市值加权收益率

    """
    sel = (mv_cat == mv_target) & (bp_cat == bp_target)
    mv_total = np.nansum(mv[sel])
    mv_weight = mv / mv_total
    return_total = np.nansum(stock_return[sel] * mv_weight[sel])
    return return_total


class MultiFactors(qt.FactorSorter):
    
    def __init__(self, pars: tuple = (0.5, 0.3, 0.7)):
        super().__init__(
                name='MultiFactor',
                description='根据Fama-French三因子回归模型估算HS300成分股的alpha值选股',
                pars=[Parameter((0.01, 0.99), par_type='float', name='size_gate', value=0.5),  # 参数1:大小市值分类界限
                      Parameter((0.01, 0.49), par_type='float', name='pb_s', value=0.3),  # 参数2:小/中bp分界线
                      Parameter((0.50, 0.99), par_type='float', name='pb_l', value=0.7)],  # 参数3,中/大bp分界线
                data_types=[StgData('pb', freq='d', asset_type='E', window_length=20, use_latest_data_cycle=True), 
                            StgData('total_mv', freq='d', asset_type='E', window_length=2, use_latest_data_cycle=True), 
                            StgData('close', freq='d', asset_type='E', window_length=20, use_latest_data_cycle=True),
                            StgData('close-000300.SH', freq='d', asset_type='IDX', window_length=20, use_latest_data_cycle=True)],  # 执行选股需要用到的股票数据
                max_sel_count=10,  # 最多选出10支股票
                sort_ascending=True,  # 选择因子最小的股票
                condition='less',  # 仅选择因子小于某个值的股票
                lbound=0,  # 仅选择因子小于0的股票
                ubound=0,  # 仅选择因子小于0的股票
        )
    
    def realize(self):

        size_gate_percentile, bp_small_percentile, bp_large_percentile = self.get_pars('size_gate', 'pb_s', 'pb_l')
        # 读取投资组合的数据PB和total_MV的最新值
        pb, mv, closes, market_closes = self.get_data('pb_E_d', 'total_mv_E_d', 'close_E_d', 'close-000300.SH_IDX_d')
        pb = pb[-1]  # 当前所有股票的PB值
        mv = mv[-1]  # 当前所有股票的市值
        pre_close = closes[-2]  # 当前所有股票的前收盘价
        close = closes[-1]  # 当前所有股票的最新收盘价

        # 读取参考数据(r)
        market_pre_close = market_closes[-2]  # HS300的昨收价
        market_close = market_closes[-1]  # HS300的收盘价

        # 计算账面市值比,为pb的倒数
        bp = pb ** -1
        # 计算市值的50%的分位点,用于后面的分类
        size_gate = np.nanquantile(mv, size_gate_percentile)
        # 计算账面市值比的30%和70%分位点,用于后面的分类
        bm_30_gate = np.nanquantile(bp, bp_small_percentile)
        bm_70_gate = np.nanquantile(bp, bp_large_percentile)
        # 计算每只股票的当日收益率
        stock_return = pre_close / close - 1

        # 根据每只股票的账面市值比和市值,给它们分配bp分类和mv分类
        # 市值小于size_gate的cat为1,否则为2
        mv_cat = np.ones_like(mv)
        mv_cat += (mv > size_gate).astype('float')
        # bp小于30%的cat为1,30%~70%之间为2,大于70%为3
        bp_cat = np.ones_like(bp)
        bp_cat += (bp > bm_30_gate).astype('float')
        bp_cat += (bp > bm_70_gate).astype('float')

        # 获取小市值组合的市值加权组合收益率
        smb_s = (market_value_weighted(stock_return, mv, mv_cat, bp_cat, 1, 1) +
                 market_value_weighted(stock_return, mv, mv_cat, bp_cat, 1, 2) +
                 market_value_weighted(stock_return, mv, mv_cat, bp_cat, 1, 3)) / 3
        # 获取大市值组合的市值加权组合收益率
        smb_b = (market_value_weighted(stock_return, mv, mv_cat, bp_cat, 2, 1) +
                 market_value_weighted(stock_return, mv, mv_cat, bp_cat, 2, 2) +
                 market_value_weighted(stock_return, mv, mv_cat, bp_cat, 2, 3)) / 3
        smb = smb_s - smb_b
        # 获取大账面市值比组合的市值加权组合收益率
        hml_b = (market_value_weighted(stock_return, mv, mv_cat, bp_cat, 1, 3) +
                 market_value_weighted(stock_return, mv, mv_cat, bp_cat, 2, 3)) / 2
        # 获取小账面市值比组合的市值加权组合收益率
        hml_s = (market_value_weighted(stock_return, mv, mv_cat, bp_cat, 1, 1) +
                 market_value_weighted(stock_return, mv, mv_cat, bp_cat, 2, 1)) / 2
        hml = hml_b - hml_s

        # 计算市场收益率
        market_return = market_pre_close / market_close - 1

        coff_pool = []
        # 对每只股票进行回归获取其alpha值
        for rtn in stock_return:
            x = np.array([[market_return, smb, hml, 1.0]])
            y = np.array([[rtn]])
            # OLS估计系数
            coff = np.linalg.lstsq(x, y)[0][3][0]
            coff_pool.append(coff)

        # 以alpha值为股票组合的选股因子执行选股
        factors = np.array(coff_pool)

        return factors

4.2. Operative Strategie

Backtesting-Parameter festlegen und die Strategie ausführen.

shares = qt.filter_stock_codes(index='000300.SH', date='20190501')

alpha = MultiFactors()
op = qt.Operator(alpha, signal_type='PT', run_freq='ME')
qt.run(op=op,
       mode=1,
       invest_start='20160405',
       invest_end='20210201',
       asset_type='E',
       asset_pool=shares,
       trade_batch_size=100,
       sell_batch_size=1,
       trade_log=True,
      )

Die Ergebnisse des Laufs sind wie folgt:

         ====================================
         |                                  |
         |       BACK TESTING RESULT        |
         |                                  |
         ====================================
    
    qteasy running mode: 1 - History back testing
    time consumption for operate signal creation: 0.0 ms
    time consumption for operation back looping:  6 sec 502.5 ms
    
    investment starts on      2019-05-06 00:00:00
    ends on                   2022-04-29 00:00:00
    Total looped periods:     3.0 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
    000063.SZ    1        1      2     2.7%      0.0%     97.3%  
    000100.SZ    2        2      4     5.9%      0.0%     94.1%  
    000157.SZ    3        3      6     8.6%      0.0%     91.4%  
    000333.SZ    1        1      2     2.7%      0.0%     97.3%  
    000338.SZ    2        2      4     5.5%      0.0%     94.5%  
    000413.SZ    1        1      2     2.9%      0.0%     97.1%  
    000423.SZ    1        1      2     2.7%      0.0%     97.3%  
    000425.SZ    1        1      2     2.7%      0.0%     97.3%  
    000625.SZ    2        2      4     5.6%      0.0%     94.4%  
    000651.SZ    1        1      2     2.7%      0.0%     97.3%  
    ...            ...     ...   ...      ...       ...       ...
    603185.SH    1        1      2     5.8%      0.0%     94.2%  
    603290.SH    1        1      2     5.8%      0.0%     94.2%  
    688005.SH    3        3      6     7.9%      0.0%     92.1%  
    002756.SZ    1        1      2     2.7%      0.0%     97.3%  
    600039.SH    1        1      2     2.8%      0.0%     97.2%  
    600803.SH    1        1      2     2.9%      0.0%     97.1%  
    688187.SH    1        1      2     2.9%      0.0%     97.1%  
    000983.SZ    1        1      2     2.9%      0.0%     97.1%  
    600732.SH    3        3      6     8.2%      0.0%     91.8%  
    601699.SH    1        2      3     8.5%      0.0%     91.5%   
    
    Total operation fee:     ¥    3,356.25
    total investment amount: ¥  100,000.00
    final value:              ¥  252,942.40
    Total return:                   152.94% 
    Avg Yearly return:               36.48%
    Skewness:                         -0.19
    Kurtosis:                          3.08
    Benchmark return:                 9.00% 
    Benchmark Yearly return:          2.93%
    
    ------strategy loop_results indicators------ 
    alpha:                            0.413
    Beta:                             0.458
    Sharp ratio:                      1.511
    Info ratio:                       0.086
    250 day volatility:               0.283
    Max drawdown:                    28.83% 
        peak / valley:        2021-12-23 / 2022-04-26
        recovered on:         Not recovered!
    
    ===========END OF REPORT=============
    

png

Richten Sie einen weiteren Backtesting-Zeitraum vom 5. April 2016 bis zum 1. Februar 2021 ein und führen Sie die Strategie aus. Sie werden sehen, dass die Strategie in verschiedenen Zeiträumen effektiv ist.

shares = qt.filter_stock_codes(index='000300.SH', date='20190501')
alpha = MultiFactors()  # 实例化策略
op = qt.Operator(alpha, signal_type='PT')  # 创建Operator交易员对象,使用PT信号类型(仓位目标信号)
op.op_type = 'stepwise'
op.set_blender('1.0*s0')  # 设置仓位调整公式,仓位目标为1.0*s0,即持仓百分比总和等于100%
op.run(mode=1,
       invest_start='20160405',  # 回测起始时间
       invest_end='20210201',  # 回测结束时间
       asset_type='E',  # 股票
       asset_pool=shares,  # 股票池
       trade_batch_size=100,  # 交易最小批量
       sell_batch_size=1,  # 卖出最小批量
       trade_log=True,  # 产生交易记录
      )

print()

Die Ergebnisse des Laufs sind wie folgt:

         ====================================
         |                                  |
         |       BACK TESTING RESULT        |
         |                                  |
         ====================================
    
    qteasy running mode: 1 - History back testing
    time consumption for operate signal creation: 0.0 ms
    time consumption for operation back looping:  8 sec 335.0 ms
    
    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
    000063.SZ    2        2      4     3.4%      0.0%     96.6%  
    000100.SZ    3        3      6     5.2%      0.0%     94.8%  
    000157.SZ    1        1      2     1.8%      0.0%     98.2%  
    000333.SZ    2        2      4     3.4%      0.0%     96.6%  
    000338.SZ    1        1      2     1.7%      0.0%     98.3%  
    000413.SZ    2        2      4     3.6%      0.0%     96.4%  
    000596.SZ    1        1      2     1.8%      0.0%     98.2%  
    000625.SZ    3        3      6     5.3%      0.0%     94.7%  
    000629.SZ    1        1      2     1.7%      0.0%     98.3%  
    000651.SZ    1        1      2     1.7%      0.0%     98.3%  
    ...            ...     ...   ...      ...       ...       ...
    688005.SH    1        2      3     3.3%      0.0%     96.7%  
    000733.SZ    1        1      2     1.8%      0.0%     98.2%  
    002180.SZ    1        1      2     1.7%      0.0%     98.3%  
    600039.SH    1        1      2     1.7%      0.0%     98.3%  
    600803.SH    1        1      2     1.7%      0.0%     98.3%  
    601615.SH    1        1      2     1.8%      0.0%     98.2%  
    000983.SZ    2        2      4     3.3%      0.0%     96.7%  
    600732.SH    3        4      7     6.7%      0.0%     93.3%  
    600754.SH    1        1      2     1.8%      0.0%     98.2%  
    601699.SH    1        1      2     1.7%      0.0%     98.3%   
    
    Total operation fee:     ¥    7,063.30
    total investment amount: ¥  100,000.00
    final value:              ¥  584,928.02
    Total return:                   484.93% 
    Avg Yearly return:               44.15%
    Skewness:                         -0.14
    Kurtosis:                          2.77
    Benchmark return:                65.96% 
    Benchmark Yearly return:         11.06%
    
    ------strategy loop_results indicators------ 
    alpha:                            0.428
    Beta:                             0.371
    Sharp ratio:                      1.376
    Info ratio:                       0.076
    250 day volatility:               0.287
    Max drawdown:                    35.84% 
        peak / valley:        2018-06-12 / 2019-01-02
        recovered on:         2019-03-05
    
    ===========END OF REPORT=============

png