【python量化交易】—— 多因子选股策略 - Qteasy自定义交易策略【附源码】

45 篇文章 6 订阅
19 篇文章 1 订阅

使用qteasy自定义并回测一个多因子选股策略

我们今天使用qteasy来回测一个多因子选股交易策略,qteasy是一个功能全面且易用的量化交易策略框架,Github地址在这里。使用它,能轻松地获取历史数据,创建交易策略并完成回测和优化,还能实盘运行。项目文档在这里。

为了继续本章的内容,您需要安装qteasy【教程1】,并下载历史数据到本地【教程2、),详情可以参考更多教程【教程3】

建议您先按照前面教程的内容了解qteasy的使用方法,然后再参考这里的例子创建自己的交易策略。

策略思想

本策略每隔1个月定时触发,根据Fama-French三因子模型对每只股票进行回归,得到其alpha值。
假设Fama-French三因子模型可以完全解释市场,则alpha为负表明市场低估该股,因此应该买入。

策略思路:

计算市场收益率、个股的账面市值比和市值,并对后两个进行了分类,
根据分类得到的组合分别计算其市值加权收益率、SMB和HML.
对各个股票进行回归(假设无风险收益率等于0)得到alpha值.

选取alpha值小于0并为最小的10只股票进入标的池,平掉不在标的池的股票并等权买入在标的池的股票

回测数据:SHSE.000300的成份股

回测时间为:2019-05-01 到 2022-05-01

定义策略

本策略需要先定义一个加权收益率计算函数,然后在策略中调用这个函数完成计算。

import qteasy as qt
import numpy as np

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__(
                pars=pars,
                par_count=3,
                par_types=['float', 'float', 'float'],  # 参数1:大小市值分类界限,参数2:小/中bp分界线,参数3,中/大bp分界线
                par_range=[(0.01, 0.99), (0.01, 0.49), (0.50, 0.99)],
                name='MultiFactor',
                description='根据Fama-French三因子回归模型估算HS300成分股的alpha值选股',
                strategy_run_timing='close',  # 在周期结束(收盘)时运行
                strategy_run_freq='m',  # 每月执行一次选股(每周或每天都可以)
                strategy_data_types='pb, total_mv, close',  # 执行选股需要用到的股票数据
                data_freq='d',  # 数据频率(包括股票数据和参考数据)
                window_length=20,
                use_latest_data_cycle=True,
                reference_data_types='close-000300.SH',  # 选股需要用到市场收益率,作为参考数据传入
                max_sel_count=10,  # 最多选出10支股票
                sort_ascending=True,  # 选择因子最小的股票
                condition='less',  # 仅选择因子小于某个值的股票
                lbound=0,  # 仅选择因子小于0的股票
                ubound=0,  # 仅选择因子小于0的股票
        )
    
    def realize(self, h, r=None, t=None, pars=None):

        size_gate_percentile, bp_small_percentile, bp_large_percentile = self.pars
        # 读取投资组合的数据PB和total_MV的最新值
        pb = h[:, -1, 0]  # 当前所有股票的PB值
        mv = h[:, -1, 1]  # 当前所有股票的市值
        pre_close = h[:, -2, 2]  # 当前所有股票的前收盘价
        close = h[:, -1, 2]  # 当前所有股票的最新收盘价

        # 读取参考数据(r)
        market_pre_close = r[-2, 0]  # HS300的昨收价
        market_close = r[-1, 0]  # 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

# 策略定义完毕

运行策略

设置回测参数,运行策略

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', "close")  # 设置仓位调整公式,仓位目标为1.0*s0,即持仓百分比总和等于100%
op.run(mode=1,
       invest_start='20190501',  # 回测起始时间
       invest_end='20220501',  # 回测结束时间
       asset_type='E',  # 股票
       asset_pool=shares,  # 股票池
       trade_batch_size=100,  # 交易最小批量
       sell_batch_size=1,  # 卖出最小批量
       trade_log=True,  # 产生交易记录
      )

print()

运行结果如下:

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

在这里插入图片描述

设置另外的回测区间从2016-04-05到2021-02-01,运行策略,可以看到在不同的区间下该策略都是有效的

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', "close")  # 设置仓位调整公式,仓位目标为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()

运行结果如下:

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

在这里插入图片描述

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值