量化交易:星期几是这个股票的‘好日子’

2,229 阅读11分钟

作者: 阿布

阿布量化版权所有 未经允许 禁止转载

abu量化系统github地址 (欢迎+star)

本节界面操作视频教程地址

备注:不熟悉编程的用户可忽略代码具体实现,可直接使用界面ui操作

上一节讲解量化交易中跨市场低频统计套利的示例,本节将示例一个与周期相关的短线择时策略,本节的内容是为《量化交易之路》中的一个小节做的完整策略实例补充。

很多刚接触交易的人总喜欢把交易看成一种有固定收入的工作,比如他们有自己的规矩,周五一定要把所有股票都卖了,安安心心过周末,周一看情况一切良好再把股票买回来。

还有一些人有着很奇怪的癖好认为周三是他的幸运日,在周三买入他选中的股票,有些人每个月第一个周五发工资,市场就是由这些各种各样的人组成的。

某一个股票上的活跃用户在一段短时间内变化并不大,也就是说这些习惯周五卖周一买的人会反复在一支股票上交易,普通投资者普遍的投资方式是针对一支股票不断的进行买卖,他们不会长期持有这支股票,但也不会远离这支股票很长时间,我认为有两点促成了以上事实。

  1. 贪欲:贪欲在这中间起到了很大的作用,当一个人第一次买入一支股票并且持有到有一定利润的时候,他选择卖出这支股票,因为他认为涨的已经很多了,该适当的回调了,之后股价的走势只有两种可能:第一按照他的预期下跌,这样的话他可能选择跌到某种程度再次进场买入这支股票;第二就是继续上涨,这种情况下他会选择不断‘诅咒’这支股票,直到有一天股价上涨到让他无法忍受,从此由‘黑转粉’。

  2. 时间成本与懒惰:一个人类的时间和精力都是有限的,它无法获取市场中所有股票的信息,每次获取熟悉一支股票的时间成本在他看来也是非常巨大,他反复的盯着自己最频繁买卖的那几支股票。

1. 美股周期短线分析

下面先获取沙盒数据中美股一年的数据,做为短线分析示例:

us_choice_symbols = ['usTSLA', 'usNOAH', 'usSFUN', 'usBIDU', 'usAAPL', 'usGOOG', 'usWUBA', 'usVIPS']
kl_dict = {us_symbol[2:]: 
 ABuSymbolPd.make_kl_df(us_symbol, start='2014-07-26', end='2015-07-26') 
 for us_symbol in us_choice_symbols}

从日振幅涨跌幅比来看,只有BIDU和WUBA能勉强有短线套利的空间(值 > 1.8), 但是由于沙盒数据中只有这些symbol,所以暂时忽略这个特证,之后做非沙盒数据全市场周期短线分析时再使用这个值。

ABuKLUtil.wave_change_rate(kl_dict)
TSLA日振幅涨跌幅比:1.778420
NOAH日振幅涨跌幅比:1.733710
SFUN日振幅涨跌幅比:1.784097
BIDU日振幅涨跌幅比:1.812175
AAPL日振幅涨跌幅比:1.664462
GOOG日振幅涨跌幅比:1.573070
WUBA日振幅涨跌幅比:1.913431
VIPS日振幅涨跌幅比:1.568457

下面先一个一个观察每一个股票的周期涨跌概率,可以发现:

  1. 特斯拉在周四上涨的概率最大59%
  2. 诺亚财富也在周四上涨的概率最大65%
  3. 百度在周五上涨概率达到60%
  4. 苹果在周三上涨概率达到56%
ABuKLUtil.date_week_win(kl_dict)

假如择时策略中需要找到每一个股票上涨概率超过55%的交易日,做为策略买入的日子,比如下面示例找特斯拉超过55%的交易日:

tl_dw = ABuKLUtil.date_week_win(kl_dict['TSLA'])
tl_dw_vd = tl_dw[tl_dw.win > 0.55]
tl_dw_vd

可以看到上面的结果就是符号要求的交易日,但是如果虽然周四的胜率很很高,但是周四的上涨比例很低呢,如果上涨比例很低,会造成盈亏比很低,造成最终交易依然亏损,下面使用date_week_mean看看上面各个美股每个交易日的涨跌比例,如下:

ABuKLUtil.date_week_mean(kl_dict)

看看特斯拉满足胜率要求的交易日中的涨跌幅比例,如下:

tl_dwm = ABuKLUtil.date_week_mean(kl_dict['TSLA'])
tl_dwm.loc[tl_dw_vd.index]

可以看到周四的涨跌平均值是0.54,在具体策略编写中可以使用如下两种阀值计算方式,确定周四的涨幅比例是否高于下面两种算法:

abs(tl_dwm.sum()).values[0] / 0.618, abs(tl_dwm._p_change).mean() / 0.618
(0.73209375244226405, 0.45709539602153293)

可以看到第一种算法的值计算为0.73,第二种为0.45,0.54虽然大于0.45但是小于0.73,即虽然特斯拉在周四有大概率的上涨可能,如果使用第一种算法,那么由于涨幅比例不符合要求,在具体策略中将不会发出买入信号。

备注:

  1. 上面的计算中0.618是可以在具体策略中通过参数传递
  2. 无论是上面使用的55%胜率还是0.618都是以制造非均衡概率优势为目的
  3. 在实际策略编写中根据交易量需求,以及市场交易目标数量等等确定具体使用上面那一种算法, 或两个并行生效

下面看看百度上涨概率超过55%的交易日:

bd_dw = ABuKLUtil.date_week_win(kl_dict['BIDU'])
bd_dw_vd = bd_dw[bd_dw.win > 0.55]
bd_dw_vd

看看百度满足胜率要求的交易日中的涨跌幅比例,以及两种阀值计算,如下:

bd_dwm = ABuKLUtil.date_week_mean(kl_dict['BIDU'])
print(abs(bd_dwm.sum()).values[0] / 0.618, abs(bd_dwm._p_change).mean() / 0.618)
bd_dwm.loc[bd_dw_vd.index]
0.171504714657 0.2820732168894443

结果看到第一种算法的值计算为0.17,第二种为0.28,0.25虽然大于0.17但是小于0.28,即如果策略中使用第一种阀值计算方式将满足买入信号发出,如果第二种就不满足。

2. 日胜率均值回复策略

实盘中使用的symbol数量会远远多于本例中使用沙盒数据的数量,策略可以要求两种阀值都满足,且加入更多的非均衡条件构造最终的非均衡结果,但是由于本例沙盒数据量少,所以下面编写策略时采用两种阀值计算方式满足一种即可,策略大概原理如下:

策略的性质属于:均值回复

  1. 默认以40天为周期(8周)结合涨跌阀值计算周几适合买入
  2. 回测运行中每一个月重新计算一次上述的周几适合买入
  3. 在策略日任务中买入信号为:昨天下跌,今天开盘也下跌,且明天是计算出来的上涨概率大的'周几'

具体策略编写如下所示:

备注:不熟悉编程的用户可忽略代码具体实现,也可以直接使用界面ui操作本节ui界面操作教程视频地址

class AbuFactorBuyWD(AbuFactorBuyTD, BuyCallMixin):
    def _init_self(self, **kwargs):
        """
            kwargs中可选参数:buy_dw:    代表周期胜率阀值,默认0.55即55%
            kwargs中可选参数:buy_dwm:   代表涨幅比例阀值系数,默认0.618
            kwargs中可选参数:dw_period: 代表分析dw,dwm所使用的交易周期,默认40天周期(8周)
        """
        self.buy_dw    = kwargs.pop('buy_dw', 0.55)
        self.buy_dwm   = kwargs.pop('buy_dwm', 0.618)
        self.dw_period = kwargs.pop('dw_period', 40)

        # combine_kl_pd中包含择时金融时间数据与择时之前一年的金融时间数据, 先取出择时开始之前的周期数据
        last_kl = self.combine_kl_pd.loc[:self.kl_pd.index[0]]
        if last_kl.shape[0] > self.dw_period:
            last_kl = last_kl[-self.dw_period:]
        # 开始计算周几买,_make_buy_date把结果被放在self.buy_date_week序列中
        self._make_buy_date(last_kl)

    def fit_month(self, today):
        """月任务,每一个重新取之前一年的金融时间序列数据,重新计算一遍'周几买'"""
        end_ind = self.combine_kl_pd[self.combine_kl_pd.date == today.date].key.values[0]
        start_ind = end_ind - self.dw_period  if end_ind - self.dw_period  > 0 else 0
        # 根据当前的交易日,切片过去的一年金融时间序列
        last_kl = self.combine_kl_pd.iloc[start_ind:end_ind]
        # 重新计算一遍'周几买'
        self._make_buy_date(last_kl)

    def fit_day(self, today):
        """日任务:昨天下跌,今天开盘也下跌,根据今天是周几,在不在序列self.buy_date_week中决定今天买不买"""
        if self.yesterday.p_change < 0 and today.open < self.yesterday.close \
            and int(today.date_week) in self.buy_date_week:
            # 由于没有用到今天的收盘价格等,可以直接使用buy_today
            return self.buy_today()
        return None

    # noinspection PyProtectedMember
    def _make_buy_date(self, last_kl):
        self.buy_date_week = []
        # 计算周期内,周期的胜率
        last_dw = ABuKLUtil.date_week_win(last_kl)
        # 摘取大于阀值self.buy_dw的'周几',buy_dw默认0.55
        last_dw_vd = last_dw[last_dw.win >= self.buy_dw]
        """
            eg: last_dw_vd
                       0  1   win
            date_week            
            周四         3  5  0.62
            周五         2  6  0.75
        """
        if len(last_dw_vd) > 0:
            # 如果胜率有符合要求的,使用周几平均涨幅计算date_week_mean
            last_dwm = ABuKLUtil.date_week_mean(last_kl)
            # 摘取满足胜率的last_dw_vd
            last_dwm_vd = last_dwm.loc[last_dw_vd.index]
            """
                eg: last_dwm_vd
                           _p_change
                date_week           
                周四              1.55
                周五              1.12
            """
            # 阀值计算方式1
            dwm1 = abs(last_dwm.sum()).values[0] / self.buy_dwm
            # 阀值计算方式2
            dwm2 = abs(last_dwm._p_change).mean() / self.buy_dwm
            # 如果symbol多可以使用&的关系
            dm_effect = (last_dwm_vd._p_change > dwm1) | (last_dwm_vd._p_change > dwm2)
            buy_date_loc = last_dwm_vd[dm_effect].index
            """
                eg: buy_date_loc
                Index(['周四', '周五'], dtype='object', name='date_week')
            """
            if len(buy_date_loc) > 0:
                # 如果涨跌幅阀值也满足,tolist,eg:['周一', '周二', '周三', '周四', '周五']
                dw_index = last_dw.index.tolist()
                # 如果是一周5个交易日的就是4,如果是比特币等7天交易日的就是6
                max_ind = len(dw_index) - 1
                for bdl in buy_date_loc:
                    sell_ind = dw_index.index(bdl)
                    buy_ind = sell_ind - 1 if sell_ind > 0 else max_ind
                    self.buy_date_week.append(buy_ind)

3. 各个市场回测日胜率均值回复策略

上面的AbuFactorBuyWD即完成了整个策略的编写,下面开始进行回测,如下所示:

# 初始化资金
read_cash = 1000000
# 买入策略AbuFactorBuyWD,参数都使用默认的
buy_factors = [{'class': AbuFactorBuyWD}]
# 卖出策略使用AbuFactorSellNDay,sell_n=1即只持有一天,is_sell_today=True, 持有一天后当天卖出
sell_factors = [{'class': AbuFactorSellNDay, 'sell_n': 1, 'is_sell_today': True}]

# 开始进行美股沙盒数据回测,沙盒数据中美股只有从13年7月到16年7月的数据,其它市场会多一些
abu_result_tuple = run_loo_back(us_choice_symbols, '2013-07-26', '2016-07-26')
买入后卖出的交易数量:146
买入后尚未卖出的交易数量:2
胜率:53.4247%
平均获利期望:1.9614%
平均亏损期望:-1.9299%
盈亏比:1.1534
所有交易收益比例和:0.2369 
所有交易总盈亏和:24655.9100 

可以看到上面的回测中胜率超过了50%,从下面的交易单中可以看到所有交易都只持有了一天,如下:

abu_result_tuple.orders_pd.filter(
    ['symbol', 'buy_date', 'sell_date', 'keep_days', 'profit'])[:7]

上面的策略中计算'周几'上涨概率最大的交易周期默认为40天周期(8周),这个周期长度不能太长也不能太短,因为某一个股票上的活跃用户只是在一段短时间内变化不大,但是一个市场中的参与者随着时间的流逝,也在慢慢不断变化,不断新老交替,就像我们人类,每7年我们就是一个全新的自己,所有细胞血液都将完全更新一遍。

下面使用这个策略对比特币,莱特币进行回测,如下所示:

_ = run_loo_back(['btc', 'ltc'], '2013-07-26', '2017-07-26')
买入后卖出的交易数量:24
买入后尚未卖出的交易数量:0
胜率:58.3333%
平均获利期望:2.8335%
平均亏损期望:-2.6652%
盈亏比:1.8846
所有交易收益比例和:0.1302 
所有交易总盈亏和:18321.1800 

下面使用这个沙盒中A股市场symbol进行回测,如下所示:

cn_choice_symbols = ['002230', '300104', '300059', '601766', '600085', '600036', '600809', '000002', '002594']
_ = run_loo_back(cn_choice_symbols, '2013-07-26', '2017-07-26')
买入后卖出的交易数量:112
买入后尚未卖出的交易数量:1
胜率:55.3571%
平均获利期望:1.8545%
平均亏损期望:-2.0521%
盈亏比:1.0527
所有交易收益比例和:0.1237 
所有交易总盈亏和:6612.0000 

小结:

上面的回测交易由于使用沙盒数据,数据量少,所以实际上的回测效果一般,即如果一个策略有56%的胜率,那么一天只执行10次交易,你的胜率有各种可能,不一定达到56%,但是如果你能一天执行10000次以上,那么你的胜率如果不是56%,不管是更多或者更少,都代表你计算胜率的方式有问题。

所以针对上面这个策略,确定你拥有交易概率优势,只要一天内可以从不同市场、不同股票、不同时段内找到足够多的交易机会,执行足够多的次数,那你最后一定是盈利的,统计套利的核心思想就是这个,不只是要单纯追求胜率,更应该关注大数定律,寻找多元化的交易机会,最终达成理想的胜率。

在之后的章节中会示例不使用沙盒数据,在各个市场中通过这个策略寻找交易进行进行回测,请关注公众号中的代码示例更新

更多阿布量化量化技术文章

更多关于abu量化系统请关注微信公众号: abu_quant