abu 发表于 2019-11-1 07:04:12

量化投资:第5节 选股策略的开发

本帖最后由 abu 于 2019-11-1 07:06 编辑

在第一节即说过:
[*]在对的时间,遇见对的人(股票),是一种幸福
[*]在对的时间,遇见错的人(股票),是一种悲伤
[*]在错的时间,遇见对的人(股票),是一声叹息
[*]在错的时间,遇见错的人(股票),是一种无奈
之前的节讲的都是择时(什么时候投资), 本节将讲解选股。1. 选股因子的编写与择时小节类似,实现示例在中abu量化系统实现一个选股策略。如下代码AbuPickRegressAngMinMax为选股因子,它的作用是将股票前期走势进行线性拟合计算一个角度,参数为选股条件,将选股条件作用于角度后进行股票的筛选。<font style="background-color: white;">from abupy import AbuPickStockBase, ps, ABuRegUtil

class AbuPickRegressAngMinMax(AbuPickStockBase):
    """拟合角度选股因子示例类"""
    def _init_self(self, **kwargs):
      """通过kwargs设置拟合角度边际条件,配置因子参数"""

      # 暂时与base保持一致不使用kwargs.pop('a', default)方式
      # fit_pick中 ang > threshold_ang_min, 默认负无穷,即默认所有都符合
      self.threshold_ang_min = -np.inf
      if 'threshold_ang_min' in kwargs:
            # 设置最小角度阀值
            self.threshold_ang_min = kwargs['threshold_ang_min']

      # fit_pick中 ang < threshold_ang_max, 默认正无穷,即默认所有都符合
      self.threshold_ang_max = np.inf
      if 'threshold_ang_max' in kwargs:
            # 设置最大角度阀值
            self.threshold_ang_max = kwargs['threshold_ang_max']

    @ps.reversed_result
    def fit_pick(self, kl_pd, target_symbol):
      """开始根据自定义拟合角度边际参数进行选股"""
      # 计算走势角度
      ang = ABuRegUtil.calc_regress_deg(kl_pd.close, show=False)
      # 根据参数进行角度条件判断
      if self.threshold_ang_min < ang < self.threshold_ang_max:
            return True
      return False
   
    def fit_first_choice(self, pick_worker, choice_symbols, *args, **kwargs):
      raise NotImplementedError('AbuPickRegressAng fit_first_choice unsupported now!')</font>
上面编写的AbuPickRegressAngMinMax即为一个完整的选股策略:
[*]选股策略必须继承自AbuPickStockBase
[*]选股策略必须实现fit_pick,即完成通过选股阶段金融时间序列对股票决策是否选中
[*]选股策略必须实现fit_first_choice, 但是可以raise NotImplementedError,fit_first_choice后面的章节示例
[*]fit_pick上的装饰器ps.reversed_result稍后讲解
选股模块主要功能依托AbuPickStockWorker,其类似择时模块中的AbuPickTimeWorker,其通过init_stock_pickers()函数将所有选股因子实例化,然后在之后的fit()操作中,遍历所有选股因子,使用选股因子的fit_pick()函数,保留所有选股因子的fit_pick()都返回True的股票,只要有一个选股因子的fit_pick结果是False就将股票剔除。详细代码请查阅 AbuPickStockWorker源代码。本节只讲简单讲解选股使用示例,比如只想选取符合上升走势的股票:<font style="background-color: white;">from abupy import AbuPickStockWorker
from abupy import AbuBenchmark, AbuCapital, AbuKLManager

# 选股条件threshold_ang_min=0.0, 即要求股票走势为向上上升趋势
stock_pickers = [{'class': AbuPickRegressAngMinMax,
                  'threshold_ang_min': 0.0, 'reversed': False}]

# 从这几个股票里进行选股,只是为了演示方便
# 一般的选股都会是数量比较多的情况比如全市场股票
choice_symbols = ['usNOAH', 'usSFUN', 'usBIDU', 'usAAPL', 'usGOOG',
                  'usTSLA', 'usWUBA', 'usVIPS']

benchmark = AbuBenchmark()
capital = AbuCapital(1000000, benchmark)
kl_pd_manger = AbuKLManager(benchmark, capital)
stock_pick = AbuPickStockWorker(capital, benchmark, kl_pd_manger,
                              choice_symbols=choice_symbols,
                              stock_pickers=stock_pickers)
stock_pick.fit()
# 打印最后的选股结果
stock_pick.choice_symbols</font>
<font style="background-color: white;">['usSFUN', 'usBIDU', 'usTSLA', 'usWUBA', 'usVIPS']</font>
上面的实现方式和第一节中讲解择时回测的使用时一样通过分解流程方式一步一步实现使用AbuPickStockWorker进行选股,目的是为了更清晰的说明内部操作流程,编码过程会显的有些复杂臃肿。实际上在编写完成一个策略后只需要abu.run_loop_back()函数即可以完成回测,在后面的小节中会进行讲解。上面选股的结果将noah剔除,因为它在回测之前的选股周期内趋势为下降趋势,如下图所示:
<font style="background-color: white;"># 从kl_pd_manger缓存中获取选股走势数据,
# 注意get_pick_stock_kl_pd()为选股数据,get_pick_time_kl_pd()为择时
kl_pd_noah = kl_pd_manger.get_pick_stock_kl_pd('usNOAH')
# 绘制并计算角度
deg = ABuRegUtil.calc_regress_deg(kl_pd_noah.close)
print('noah 选股周期内角度={}'.format(round(deg, 3)))</font>
noah 选股周期内角度=-9.289


注意上面的选股数据要使用择时回测数据之前的一段时间数据,在AbuPickStockBase中定义了xd,min_xd选股周期获取参数,
在AbuKLManager中通过get_pick_stock_kl_pd()函数配合xd,min_xd参数获取选股周期数据更多详情请阅读AbuPickStockBase源代码与AbuKLManager源代码上述选股代码实现在ABuPickStockExecute.do_pick_stock_work()中进行了封装,即讲AbuPickStockWorker及一些零散操作进行封装。更多详情请阅读ABuPickStockExecute,使用示例如下所示:eg:继续使用AbuPickRegressAngMinMax做为选股因子,如下定义threshold_ang_min=0.0, threshold_ang_max=10.0,即只选取上升趋势且上升角度小于10度的股票,下面示例使用ABuPickStockExecute.do_pick_stock_work()函数
<font style="background-color: white;">from abupy import ABuPickStockExecute

stock_pickers = [{'class': AbuPickRegressAngMinMax,
                  'threshold_ang_min': 0.0, 'threshold_ang_max': 10.0,
                  'reversed': False}]

ABuPickStockExecute.do_pick_stock_work(choice_symbols, benchmark,
                                       capital, stock_pickers)</font>
<font style="background-color: white;">['usSFUN', 'usBIDU']</font>
可以看到结果sfun和baidu都符合,下面代码验证一下:<font style="background-color: white;">kl_pd_sfun = kl_pd_manger.get_pick_stock_kl_pd('usSFUN')
print('sfun 选股周期内角度={:.3f}'.format(ABuRegUtil.calc_regress_deg(kl_pd_sfun.close)))</font>


<font style="background-color: white;">sfun 选股周期内角度=8.230</font>
<font style="background-color: white;">kl_pd_baidu = kl_pd_manger.get_pick_stock_kl_pd('usBIDU')
print('bidu 选股周期内角度={:.3f}'.format(ABuRegUtil.calc_regress_deg(kl_pd_baidu.close)))</font>


<font style="background-color: white;">bidu 选股周期内角度=9.644</font>
上面结果显示两支股票在选股周期中的价格趋势拟合角度都在0-10之间。假设修改需求想要选取周期内趋势角度在0度-10度之外的所有股票,可以这样编写代码:<font style="background-color: white;"># 和上面的代码唯一的区别就是reversed=True
stock_pickers = [{'class': AbuPickRegressAngMinMax,
                  'threshold_ang_min': 0.0, 'threshold_ang_max': 10.0,
                  'reversed': True}]

ABuPickStockExecute.do_pick_stock_work(choice_symbols, benchmark,
                                       capital, stock_pickers)</font>
<font style="background-color: white;">['usNOAH', 'usTSLA', 'usWUBA', 'usVIPS']</font>
可以看到结果与之前相反除了baidu和sfun之外所有股票都被选上,由于在AbuPickStockBase中定义函数reversed_result(),它的作用就是定义结果是否反转。具体实现请阅读AbuPickStockBase中reversed_result装饰器的实现。使用实际很简单,在每个具体的选股因子上的fit_pick()函数上根据需要选择是否安上装饰器,如下编写价格选股因子所示:<font style="background-color: white;">class AbuPickStockPriceMinMax(AbuPickStockBase):
    """价格选股因子示例类"""
    def _init_self(self, **kwargs):
      """通过kwargs设置选股价格边际条件,配置因子参数"""

      # 暂时与base保持一致不使用kwargs.pop('a', default)方式
      # fit_pick中选择 > 最小(threshold_price_min), 默认负无穷,即默认所有都符合
      self.threshold_price_min = -np.inf
      if 'threshold_price_min' in kwargs:
            # 最小价格阀值
            self.threshold_price_min = kwargs['threshold_price_min']

      # fit_pick中选择 < 最大(threshold_price_max), 默认正无穷,即默认所有都符合
      self.threshold_price_max = np.inf
      if 'threshold_price_max' in kwargs:
            # 最大价格阀值
            self.threshold_price_max = kwargs['threshold_price_max']

    @ps.reversed_result
    def fit_pick(self, kl_pd, target_symbol):
      """开始根据自定义价格边际参数进行选股"""
      if kl_pd.close.max() < self.threshold_price_max and kl_pd.close.min() > self.threshold_price_min:
            # kl_pd.close的最大价格 < 最大价格阀值 且 kl_pd.close的最小价格 > 最小价格阀值
            return True
      return False

    def fit_first_choice(self, pick_worker, choice_symbols, *args, **kwargs):
      raise NotImplementedError('AbuPickStockPriceMinMax fit_first_choice unsupported now!')</font>
<font style="background-color: white;">备注:本节所编写的选股示例代码以内置在abupy项目代码中可通过如下方式直接导入:</font>
<font style="background-color: white;">from abupy import AbuPickStockPriceMinMax
from abupy import AbuPickRegressAngMinMax</font>
2. 多个选股因子并行执行
[*]ABuPickRegressAngMinMax: threshold_ang_min=0.0, 即要求股票走势为向上,上升趋势
[*]ABuPickStockPriceMinMax threshold_price_min=50.0, 即要求股票在选股周期内股价最小值要大于50.0
继续使用ABuPickStockExecute,使上面两个选股因子同时生效,结果符合的只有BIDU及TSLA,代码如下所示:
<font style="background-color: white;">from abupy import AbuPickStockPriceMinMax


# 选股list使用两个不同的选股因子组合,并行同时生效
stock_pickers = [{'class': AbuPickRegressAngMinMax,
                  'threshold_ang_min': 0.0, 'reversed': False},
               {'class': AbuPickStockPriceMinMax,
                  'threshold_price_min': 50.0,
                  'reversed': False}]

%time ABuPickStockExecute.do_pick_stock_work(choice_symbols, benchmark, capital, stock_pickers)</font>
<font style="background-color: white;">['usBIDU', 'usTSLA']</font>
3. 使用并行来提升选股运行效率与并行择时实现方式类似,选股使用AbuPickStockMaster并行执行多个进程来提升选股效率。具体代码请查询AbuPickStockMaster,下面为使用示例, 使用do_pick_stock_with_process()函数执行默认n_process_pick_stock=8,即默认同时运行8个进程。<font style="background-color: white;"># 选股list使用两个不同的选股因子组合,并行同时生效
stock_pickers = [{'class': AbuPickRegressAngMinMax,
                  'threshold_ang_min': 0.0, 'reversed': False},
               {'class': AbuPickStockPriceMinMax,
                  'threshold_price_min': 50.0,
                  'reversed': False}]

cs = AbuPickStockMaster.do_pick_stock_with_process(capital, benchmark, stock_pickers, choice_symbols)</font>
<font style="background-color: white;">pid:6614 pick stocks complete:100.0%
pid:6615 pick stocks complete:100.0%
pid:6616 pick stocks complete:100.0%
pid:6617 pick stocks complete:100.0%
pid:6618 pick stocks complete:100.0%
pid:6619 pick stocks complete:100.0%
pid:6620 pick stocks complete:100.0%
pid:6621 pick stocks complete:100.0%
pid:6617 done!
pid:6618 done!
pid:6615 done!
pid:6614 done!
pid:6616 done!
pid:6620 done!
pid:6619 done!
pid:6621 done!</font>
从上面输出可以看到选股阶段启动了8个进程,因为股票数量太少,进程启动销毁也需要一定时间,所以上例整体效率并没有提高。更多示例请阅读《量化交易之路》书中的示例以及对应的ipython代码或者python代码

quant001 发表于 2019-11-1 19:03:51

:):):):):):):):)
页: [1]
查看完整版本: 量化投资:第5节 选股策略的开发