一个交易策略的诞生

简单了解一下量化交易

如果你对量化交易的概念还不是很熟悉,可以先阅读 一张煎饼果子告诉你什么是程序化交易、算法交易、量化投资、高频交易、 统计套利,留意文末煎饼果子的比喻。

如何设计量化交易策略? 已经讲得很明白了。看完文字介绍,下面我们来动手演示一下整个过程。

开始

下面开始使用abupy回测引擎演示 ,关于 abupy研究的环境的搭建,请参阅《以量化金融研究工具为例: Docker到底给开发人员/测试工程师/数据科学家带来了什么?》

第一步,利用现成指标构建逻辑

上面的文章《如何设计量化交易策略?》提到的第一步是利用现成指标构建逻辑。 好的,我们先去找一个现有的技术指标,我找到了一个cci顺势指标。 根据文中的介绍,cci指标的用法是这样子的:

1、如果CCI指标一直上行突破了100的话,表示此时的股市进入了异常波动的阶段,可能伴随着较大的成交量,可以进行中短线的投资者,此时的买入信号比较明显.
2、反之如果CCI指标向下突破了-100,则代表此时的股市进入了新一轮的下跌趋势,此时可以选择不要操作,保持观望的态度面对市场.
3、如果CCI指标从上行突破100又回到100之内的正常范围,则代表股价这一阶段的上涨行情已经疲软,投资者可以在此时选择卖出.反之CCI突破-100又回到正常范围,则代表下跌趋势已经结束,观察一段时间可能有转折的信号出现,可以先少量买入.

注意CCI指标主要用来判断100到-100范围之外的行情趋势,在这之间的趋势分析应用 CCI指标没有作用和意义,可以选择KDJ指标来分析.另外CCI指标是进行短线操作的投资者比较实用的武器,可以很快帮助交易者找到准确的交易信号.

好的,开始准备买入因子的代码


import  talib
from talib.abstract import *
import numpy
from datetime import datetime
# from numba import jit
from dateutil.parser import *
from dateutil.tz import *
from datetime import *

from abupy import ABuFileUtil
from abupy import AbuFactorBuyBase, AbuFactorBuyXD, BuyCallMixin

class WzFactorBuyCci(AbuFactorBuyBase, BuyCallMixin): 
    """cci买入因子"""
    def _init_self(self, **kwargs):
        """
        param  buy_low: 低吸时的cci阈值,cci指标从下方上穿此值时买入
        param  buy_rising:  追涨时的cci阈值,cci指标从下方上穿此值时买入
        param  cci_timeperiod: cci 指标计算周期,默认14
        """
        self.buy_low = kwargs.pop('buy_low', False ) # 低吸,例如-100 
        self.buy_rising = kwargs.pop('buy_rising', False) #追涨, 例如+100
        self.cci_timeperiod = kwargs.pop('cci_timeperiod', 28 )
        
        super(WzFactorBuyCci, self)._init_self(**kwargs)

        self.init_cci()
        
    
    def init_cci(self):
        """ 计算cci指标并且用pickle缓存起来, 以节省回测时每次都重复计算的时间 """        
        pickle = './cache/' + str(self.kl_pd.name) + '_' + str(self.kl_pd.head(1).date[0]) \
            + '_' + str(self.kl_pd.tail(1).date[0]) + '_CCI' + str(self.cci_timeperiod)           

        if ABuFileUtil.file_exist( pickle ):
            # print ('read from pkl')
            self.cci =ABuFileUtil.load_pickle ( pickle )
        else :
            self.cci = talib.CCI(self.kl_pd.high, self.kl_pd.low, self.kl_pd.close, timeperiod=self.cci_timeperiod)
            ABuFileUtil.dump_pickle( self.cci, pickle )    
    
    
    def fit_day(self, today):
        """ 完成策略因子针对每一个交易日的买入交易策略  """
        today_ind = int(today.key)
        
        # 计算cci指标需要self.cci_timeperiod天的k line数据,这时cci数据还没准备好,不操作
        if  today_ind <  self.cci_timeperiod + 1: 
            return 
        
        # 今天和昨天的cci指标数值
        cci_today = self.cci[today_ind: today_ind+1][0]  
        cci_yesterday = self.cci[today_ind-1: today_ind][0]
        
        # 低吸买
        if(self.buy_low != False and cci_yesterday <= self.buy_low and cci_today > self.buy_low):       
             return self.buy_tomorrow()
             
        #追涨买(buy_rising)
        if(self.buy_rising != False and cci_yesterday < self.buy_rising and cci_today >= self.buy_rising):            
            return self.buy_tomorrow()
    

准备卖出因子的代码:


import  talib
from abupy import ABuFileUtil
from abupy import AbuFactorSellBase, AbuFactorSellXD, ESupportDirection
# from numba import jit

class WzFactorSellCciStopN(AbuFactorSellBase):
    """
    cci 卖出因子
    """
    def _init_self(self, **kwargs):
        """
        param n1: 指标止盈卖出点,这里是cci指标,例如cci≌ +100时
        param n1: 走势不利时卖出点,这里是cci指标,例如cci≌ -100时
        param cci_timeperiod: cci 指标计算周期,默认14
        """
        
        self.n1 = kwargs.pop('n1', False )
        self.n2 = kwargs.pop('n2', False )
        self.cci_timeperiod = kwargs.pop('cci_timeperiod', 14)
      
        super(WzFactorSellCciStopN, self)._init_self(**kwargs)

        self.init_cci()
        
        
        
     def init_cci(self):
        """ 计算cci指标并且用pickle缓存起来, 以节省回测时每次都重复计算的时间 """        
        pickle = './cache/' + str(self.kl_pd.name) + '_' + str(self.kl_pd.head(1).date[0]) \
            + '_' + str(self.kl_pd.tail(1).date[0]) + '_CCI' + str(self.cci_timeperiod)           

        if ABuFileUtil.file_exist( pickle ):
            # print ('read from pkl')
            self.cci =ABuFileUtil.load_pickle ( pickle )
        else :
            self.cci = talib.CCI(self.kl_pd.high, self.kl_pd.low, self.kl_pd.close, timeperiod=self.cci_timeperiod)
            ABuFileUtil.dump_pickle( self.cci, pickle )
            
            
     def fit_day(self, today, orders):
        """ 完成策略因子针对每一个交易日的卖出交易策略  """
         
        today_ind = int(today.key)
        
        # 计算cci指标需要self.cci_timeperiod天的k line数据,这时cci数据还没准备好,不操作
        if  today_ind <  self.cci_timeperiod + 1: 
            return 
        
        # 今天和昨天的cci指标值
        cci_today = self.cci[today_ind: today_ind+1][0]
        cci_yesterday = self.cci[today_ind-1: today_ind][0]
        
        #cci指标从n1(+100附近)上方下穿时卖出
        if (cci_yesterday >= self.n1 and  cci_today < self.n1 ):
            for order in orders:
                self.sell_tomorrow(order)
        
        # cci掉到-100下方, 走势不利了,卖出
        if (cci_yesterday >= self.n2 and  cci_today < self.n2 ):
            for order in orders:
                self.sell_tomorrow(order)
            
        

ps: 买入因子和卖出因子都有两个买卖逻辑,这是偷懒做法,能节省敲代码时间,因为一些数据被重用了,回测运行时效率也快一点。 大家时间充足的话最好养成一个因子只有一个买卖逻辑的做法,这样后期分析交易的时候更容易看清订单买卖的都是由那个因子触发的。

因子代码准备好了,接下来我们利用abupy回测引擎测试了一下cci指标交易法的获利能力如何。

以下使用500w 资金,央视50指成分股进行测试。


import abupy
from abupy import env
from abupy import ABuPickTimeExecute, AbuBenchmark, AbuCapital
from abupy import abu,  EMarketTargetType, EMarketDataFetchMode, EDataCacheType,EMarketSourceType
from abupy import slippage 
from abupy import ABuFileUtil
from abupy import AbuMetricsBase 

from factor.WzFactorBuyCci import   WzFactorBuyCci
from factor.WzFactorSellCciStopN import WzFactorSellCciStopN
from abupy import AbuFactorAtrNStop 

import stock_pool


abupy.env.g_market_target = EMarketTargetType.E_MARKET_TARGET_CN

# 开启针对非集合竞价阶段的涨停,滑点买入价格以高概率在接近涨停的价格买入 
slippage.sbb.g_enable_limit_up = True 
# 将集合竞价阶段的涨停买入成功概率设置为0,如果设置为0.2即20%概率成功买入 
slippage.sbb.g_pre_limit_up_rate = 0 
# 开启针对非集合竞价阶段的跌停,滑点卖出价格以高概率在接近跌停的价格卖出 
slippage.ssb.g_enable_limit_down = True 
# 将集合竞价阶段的跌停卖出成功概率设置为0, 如果设置为0.2即20%概率成功卖出 
slippage.ssb.g_pre_limit_down_rate = 0

# 设置初始资金
read_cash = 5000000

# 交易费用 万二五
def commission(trade_cnt, price):                            
    return 0.00025

commission_dict = {'buy_commission_func': commission,
                        'sell_commission_func': commission}

def loop_back(index_name, cache_path=none):
    global read_cash    
    
    # 根据指数名称获取成分股
    choice_symbols = stock_pool.get(index_name)

    print(choice_symbols)
    
    """ 温习cci指标用法
    > 1、如果CCI指标一直上行突破了100的话,表示此时的股市进入了异常波动的阶段,可能伴随着较大的成交量,可以进行中短线的投资者,此时的买入信号比较明显.
    > 2、反之如果CCI指标向下突破了-100,则代表此时的股市进入了新一轮的下跌趋势,此时可以选择不要操作,保持观望的态度面对市场.
    > 3、如果CCI指标从上行突破100又回到100之内的正常范围,则代表股价这一阶段的上涨行情已经疲软,投资者可以在此时选择卖出.反之CCI突破-100又回到正常范围,则代表下跌趋势已经结束,观察一段时间可能有转折的信号出现,可以先少量买入.
    """
    # 买因: 上面1,cci突破+100追涨买入,暂不启用低吸
    buy_factors = [{'class':WzFactorBuyCci,'buy_low': False,'buy_rising': 100}]
    # 卖因:上面3 和 2 
    sell_factors = [ {'class': WzFactorSellCciStopN, 'n1': 100, 'n2':-100}]
    
    abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash, 
                    buy_factors, sell_factors, 
                    start='2016-05-08', end='2019-06-01', #测试3年
                    choice_symbols =choice_symbols,
                    commission_dict= commission_dict,
                    n_process_pick= 1
                     )
    
    # ABuFileUtil.dump_pickle(abu_result_tuple, cache_path)

    AbuMetricsBase.show_general(*abu_result_tuple, 
                only_show_returns=True
            )
    
    if __name__ == "__main__":
        loop_back('cctv50')

运行回测,结果是这样的:
20190610185517

刚刚的例子只是在cci上穿+100时追涨买入,没有开启低吸。现在根据cci指标使用介绍中的反之CCI突破-100又回到正常范围,则代表下跌趋势已经结束,观察一段时间可能有转折的信号出现,可以先少量买入,更改代码

#buy_factors = [{'class':WzFactorBuyCci,'buy_low': False,'buy_rising': 100}] 改为:
buy_factors = [{'class':WzFactorBuyCci,'buy_low': -100,'buy_rising': 100}]

再次进行回测结果是这样的:
zc

到这里希望读者朋友能明白:在网上/书上看到一个交易策略/指标就直接搬过来用于实盘交易的风险巨大! 因为这些策略/指标提出的时间可能距今已久,或者是针对的市场不同。而且很多指标都是美国人发明的。

ixic

这是纳斯达克指数2013年至今的周线图

sh-week

这是上证指数2013年至今的周线图

你确定美国人针对自己市场发明的策略/指标在中国市场有效吗?不怕,量化交易的好处就是你总是可以写代码用历史数据去验证。

第二步,进行参数优化

在做参数优化之前我们不妨先分析一下为什么策略并不盈利,以便找到优化方向。在行情图中大致看一下cci指标的表现:

sh0

2017年6月 -- 2017年12月上证指数行情图, cci指标反复发出买卖信号,这是没有错的。

sh1

但是在2018年。。。 指数明明在跌啊跌,但是cci指标还是反复地发出买入信号, 这就好比手扶电梯在往下走,而你要逆着电梯往上走, 这能走上去肯定是非常困难的

CCI指标为什么有这样错误的表现呢? 因为它的默认计算周期是14天,对于更早之前的走势是无感知的,也就是它无从了解更长期的/大的趋势是什么,所以它逆着大趋势操作是有可能的。

我们找几个判断大趋势的指标帮它一把。温习一下常用的趋势性指标有:

  • 均线
  • MACD
  • DMI(ADX)

回到上面的两个图,我们可以很容易看到40日均线和60日均线指明电梯是往上的还是往下的。我们要做多(往上走)肯定是选择往上的电梯(MA40 > MA60)更好

再看MACD指标的小图,好像也能很好显示长期趋势:2017年大部分时间MACD的DIFF和DEA线都在0轴上方运行,而2018年大部分时间都在0轴下方运行 -- 和均线表现出的结果一致 (其实MACD的底层也是用均线计算出来的)。

再看DMI(ADX)指标的小图,似乎就不那么清晰了,2018年一时显示向下趋势,一时显示震荡趋势。

*顺便分享一篇吐槽adx指标的文章 https://www.theforexguy.com/average-directional-index-indicator/ *
*ps: 笔者只是分享,并不代表支持其观点,是否如作者所说我们得写代码去验证 *

那么我们用什么长期趋势指标好呢?

没关系我们可以一个一个地都试一遍。 量化交易的好处就是你总是可以写代码用历史数据去验证。

多看几个行情图你还会发现当cci指标靠近-100但又没有击穿-100的时候行情是温水煮青蛙式慢跌的,所以我们还需要1个防止持续慢跌的方法:

ATR指标在止损中的应用

使用个股自身的均线过滤

代码好长,我就不贴了。 直接看结果吧:

cctv50

成功堵住了2018年大熊市,但也堵住了一些上涨。这也许叫做“盈亏同源”。

使用大盘指数的均线过滤

-未完成-

这个和下面几个暂时没空做试验了,有空再继续研究。

使用个股自身的MACD指标过滤

-未完成-

使用大盘指数的MACD指标过滤

-未完成-

使用个股自身的ADX指标过滤

-未完成-

使用大盘指数的ADX指标过滤

-未完成-

第三步,样本外检测

当我在做这个步骤的时候发现这个策略在创业50指的表现极佳:

在新的窗口中打开

下面4-6这几个步骤不是一时就能写下来的, 还需要时间观察跟踪。

第四步,进行观察,判断策略失效的原因是什么

-未完成-

第五步,实盘追踪

-未完成-

第六步,进行交易

-未完成-

第七步,调整或终止策略。

-未完成-

总结

在网上/书上看到一个交易策略/指标就直接搬过来用于实盘交易的风险巨大! 因为这些策略/指标提出的时间可能距今已久,或者是针对的市场不同,非常需要写代码验证。

行情在跌势中尝试抄底或者以其它理由买入就好比手扶电梯在往下走,而你要逆着电梯往上走, 这能走上去肯定是非常困难的。

40日均线和60日均线(有时是10日和40日)能显示行情电梯是往上的还是往下的。

可以利用ATR对付温水煮青蛙式慢跌。


最后:
May the fortune be with you:
愿财富与你同在:
new-image---c3ct5