Date

TA-Lib简介

作为一套被业界广泛应用的开源技术分析库(包含技术指标计算和K线模式识别等),TA-Lib自2001年发布以来已经有了十多年的历史。TA-Lib中一共包含大约125个技术指标的计算函数,同时提供了包括C/C++、Java、Perl、Python等多种语言的API。

有什么用

简单来说TA-Lib就是提供了一堆经过长期实践检验的技术指标计算函数。基于现成的计算函数,开发新策略雏形、快速验证某个灵感的时间可以大幅缩短,否则想象一下每开发个策略都要自己实现要用的技术指标,未免太浪费时间。

但是除此以外,TA-Lib还可以有一些其他的用法,举两个例子。

百科全书

坚持每天收盘后选一个自己没用过的指标,输入数据,画个图、跑个回测,开发量化策略很很多其他的技术一样都是熟能生巧。

另外,所有的技术指标在被开发出来的时候,背后都有一定的金融逻辑原理(行为金融学)的支撑,生搬硬套固然不可取,但是放着前人经验完全不看,整天凭自己的空想就弄个机器学习算法在数据上瞎折腾岂不是更浪费时间?

Alpha库

很大一部分CTA类的策略可以总结为几个简单的逻辑框架,比如趋势策略通常可以分解成以下部分:趋势信号(通常是基于某几个参数计算出来的指标值超过某个阈值)、信号过滤(和趋势信号类似)、出场方案(固定点数/百分比的止盈和止损,移动止损)。

因此把逻辑框架的代码搭好后,就可以通过机器学习算法来实现一种自动的策略开发方式:

  1. 从TA-Lib中选取两个指标分别作为趋势信号和信号过滤,结合止损、止盈方案,生成一个策略;

  2. 基于某一组历史数据(如股指的1分钟行情),通过遗传算法来对以上的参数进行光与优化;

  3. 两个指标的参数加起来通常不会超过10个,再加上止盈、止损、移动止损的参数,总参数不会超过15个,在一组高达十几万个数据点的时间序列上进行回测,过度拟合的可能性不大;

  4. 现在云服务器价格也不贵,租一个核多一点的,把算法和数据丢上去7×24小时的跑,Alpha值达到一定标准的策略存下来;

  5. 把上一步中保存下来的策略作为雏形,研究员再来进行针对性的有效性验证和更精细化的策略改进,把策略开发变成有的放矢,而不是盲人摸象。

这种策略开发方式使用传统的商业软件(如TB、MC等)几乎不可能实现,而Python这类开源软件就成为了最好的选择,用户可以自行决定几乎所有的算法(指标如何选择、遗传算法优化参数时如何迭代等等)。

怎么安装

尽管TA-Lib原生提供了基于SWIG封装的Python API,但是由于性能和编译不方便的原因,作者推荐Github上的一位开发者mrjbq7基于Cython封装的版本。

安装过程:

  1. 这里下载TA_Lib-0.4.9-cp27-none-win32.whl放到桌面上,也就是vn.py建议的运行环境Anaconda 2.7 32位

  2. 在桌面上按住Shift点击鼠标右键后,选择在此处打开命令窗口打开cmd

  3. 安装wheel包,在cmd中运行:

    pip install wheel

  4. 安装TA-Lib,在cmd中运行:

    pip install TA_Lib-0.4.9-cp27-none-win32.whl

  5. 打开Python,运行:

    import talib

    没有报错则说明安装成功

一定要尝试自己编译的用户,可以根据该项目的网站上的教程来安装。作者的两台电脑,一台直接安装成功,另一台安装了MinGW的电脑则报GCC编译错误(其实自己编译没有任何意义,感谢加州大学欧文分校打包的whl文件,省去了很多麻烦)。

Linux下的安装建议使用Anaconda的conda工具:

conda install -c quantopian ta-lib=0.4.9

具体可以参考这里:https://anaconda.org/Quantopian/ta-lib

DEMO

下面这个策略DEMO可以直接在vn.trader的CTA模块中使用(回测、模拟交易),请不要用于实盘!

# encoding: UTF-8

import talib as ta
import numpy as np

from ctaBase import *
from ctaTemplate import CtaTemplate


########################################################################
class TalibDoubleSmaDemo(CtaTemplate):
    """基于Talib模块的双指数均线策略Demo"""

    className = 'TalibDoubleSmaDemo'
    author = u'ideaplat'

    # 策略参数
    fastPeriod = 5      # 快速均线参数
    slowPeriod = 20     # 慢速均线参数
    initDays = 5        # 初始化数据所用的天数

    # 策略变量
    bar = None
    barMinute = EMPTY_STRING

    closeHistory = []       # 缓存K线收盘价的数组
    maxHistory = 50         # 最大缓存数量

    fastMa0 = EMPTY_FLOAT   # 当前最新的快速均线数值
    fastMa1 = EMPTY_FLOAT   # 上一根的快速均线数值

    slowMa0 = EMPTY_FLOAT   # 慢速均线数值
    slowMa1 = EMPTY_FLOAT


    # 参数列表,保存了参数的名称
    paramList = ['name',
                 'className',
                 'author',
                 'vtSymbol',
                 'fastPeriod',
                 'slowPeriod']

    # 变量列表,保存了变量的名称
    varList = ['inited',
               'trading',
               'pos',
               'fastMa0',
               'fastMa1',
               'slowMa0',
               'slowMa1']

    # ----------------------------------------------------------------------
    def __init__(self, ctaEngine, setting):
        """Constructor"""
        super(TalibDoubleSmaDemo, self).__init__(ctaEngine, setting)

    # ----------------------------------------------------------------------
    def onInit(self):
        """初始化策略(必须由用户继承实现)"""
        self.writeCtaLog(u'双SMA演示策略初始化')

        initData = self.loadBar(self.initDays)
        for bar in initData:
            self.onBar(bar)

        self.putEvent()

    # ----------------------------------------------------------------------
    def onStart(self):
        """启动策略(必须由用户继承实现)"""
        self.writeCtaLog(u'双SMA演示策略启动')
        self.putEvent()

    # ----------------------------------------------------------------------
    def onStop(self):
        """停止策略(必须由用户继承实现)"""
        self.writeCtaLog(u'双SMA演示策略停止')
        self.putEvent()

    # ----------------------------------------------------------------------
    def onTick(self, tick):
        """收到行情TICK推送(必须由用户继承实现)"""
        # 计算K线
        tickMinute = tick.datetime.minute

        if tickMinute != self.barMinute:
            if self.bar:
                self.onBar(self.bar)

            bar = CtaBarData()
            bar.vtSymbol = tick.vtSymbol
            bar.symbol = tick.symbol
            bar.exchange = tick.exchange

            bar.open = tick.lastPrice
            bar.high = tick.lastPrice
            bar.low = tick.lastPrice
            bar.close = tick.lastPrice

            bar.date = tick.date
            bar.time = tick.time
            bar.datetime = tick.datetime  # K线的时间设为第一个Tick的时间

            # 实盘中用不到的数据可以选择不算,从而加快速度
            # bar.volume = tick.volume
            # bar.openInterest = tick.openInterest

            self.bar = bar  # 这种写法为了减少一层访问,加快速度
            self.barMinute = tickMinute  # 更新当前的分钟

        else:  # 否则继续累加新的K线
            bar = self.bar  # 写法同样为了加快速度

            bar.high = max(bar.high, tick.lastPrice)
            bar.low = min(bar.low, tick.lastPrice)
            bar.close = tick.lastPrice

    # ----------------------------------------------------------------------
    def onBar(self, bar):
        """收到Bar推送(必须由用户继承实现)"""
        # 把最新的收盘价缓存到列表中
        self.closeHistory.append(bar.close)

        # 检查列表长度,如果超过缓存上限则移除最老的数据
        # 这样是为了减少计算用的数据量,提高速度
        if len(self.closeHistory) > self.maxHistory:
            self.closeHistory.pop(0)
        # 如果小于缓存上限,则说明初始化数据尚未足够,不进行后续计算
        else:
            return

        # 将缓存的收盘价数转化为numpy数组后,传入talib的函数SMA中计算
        closeArray = np.array(self.closeHistory)
        fastSMA = ta.SMA(closeArray, self.fastPeriod)
        slowSMA = ta.SMA(closeArray, self.slowPeriod)

        # 读取当前K线和上一根K线的数值,用于判断均线交叉
        self.fastMa0 = fastSMA[-1]
        self.fastMa1 = fastSMA[-2]
        self.slowMa0 = slowSMA[-1]
        self.slowMa1 = slowSMA[-2]

        # 判断买卖
        crossOver = self.fastMa0>self.slowMa0 and self.fastMa1<self.slowMa1     # 金叉上穿
        crossBelow = self.fastMa0<self.slowMa0 and self.fastMa1>self.slowMa1    # 死叉下穿

        # 金叉和死叉的条件是互斥
        if crossOver:
            # 如果金叉时手头没有持仓,则直接做多
            if self.pos == 0:
                self.buy(bar.close, 1)
            # 如果有空头持仓,则先平空,再做多
            elif self.pos < 0:
                self.cover(bar.close, 1)
                self.buy(bar.close, 1)
        # 死叉和金叉相反
        elif crossBelow:
            if self.pos == 0:
                self.short(bar.close, 1)
            elif self.pos > 0:
                self.sell(bar.close, 1)
                self.short(bar.close, 1)

        # 发出状态更新事件
        self.putEvent()

    # ----------------------------------------------------------------------
    def onOrder(self, order):
        """收到委托变化推送(必须由用户继承实现)"""
        # 对于无需做细粒度委托控制的策略,可以忽略onOrder
        pass

    # ----------------------------------------------------------------------
    def onTrade(self, trade):
        """收到成交推送(必须由用户继承实现)"""
        # 对于无需做细粒度委托控制的策略,可以忽略onOrder
        pass

(感谢社区ideaplat用户贡献的代码!作者做了一些小修改。)

将上面的代码保存到一个talibDemo.py文件中后,参考vn.trader下ctaAlgo文件夹内的ctaBacktesting.py运行回测,也可以通过ctaSetting.py进行配置后,在vn.trader中进行模拟交易。

从上面的DEMO中我们可以看到,talib中的技术指标函数主要接受一个numpy数组作为原始数据及若干个指标算法中的参数作为输入,返回的数据也是一个numpy数组,使用起来非常方便。

注意缓存数据时使用的是Python列表而非numpy数组,主要原因是numpy数组的append方法本质是结合原数组中的数据和新的数据生成一个新的数组对象,相对于列表的append开销要高很多。