Interfacing ta-lib with Python using Cython : moving average function example

Following up on my previous post about how Cython could be used to improve the performance, I wanted to show how easy it is to interact with a C library. Here is the example of ta-lib : TA-Lib is widely used by trading software developers requiring to perform technical analysis of financial market data. ta-lib has a SWIG Python interface but I wanted to have my own quick and dirty interface to one specific function. It would be interesting to compare the effectivness of the SWIG interface compared to the Cython (for a later post).

1. Interfacing ta-lib

What you need is a compiled version of the library. On Windows using EPD, I had to install msys to have the needed command for autotools build systems. With msys and the EPD mingw install, I compiled ta-lib with a standard .configure, make, make install. The library and header files were then available within c:\msys\1.0\local\lib and c:\msys\1.0\local\include

My first attempt is to create a Python interface to the moving average function of ta-lib (ta_func.h):

/*
 * TA_MA - Moving average
 *
 * Input  = double
 * Output = double
 *
 * Optional Parameters
 * -------------------
 * optInTimePeriod:(From 1 to 100000)
 *    Number of period
 *
 * optInMAType:
 *    Type of Moving Average
 *
 *
 */
TA_RetCode TA_MA( int    startIdx,
 int    endIdx,
 const double inReal[],
 int           optInTimePeriod, /* From 1 to 100000 */
 TA_MAType     optInMAType,
 int          *outBegIdx,
 int          *outNBElement,
 double        outReal[] );

All I need to do is writing a Cython pyx file :

"""
Cython interface to the ta-lib TA_MA function
Author : Didrik Pinte <dpinte@enthought.com>
Reference : http://ta-lib.org
"""

import numpy
cimport numpy as np

ctypedef int TA_RetCode 
# extract the needed part of ta_libc.h that I will use in the inerface
cdef extern from "ta_libc.h":
    enum: TA_SUCCESS   
    # ! can't use const in function declaration (cython 0.12 restriction) - just removing them does the trick
    TA_RetCode TA_MA(int startIdx, int endIdx, double inReal[], int optInTimePeriod, int optInMAType, int *outBegIdx, int *outNbElement, double outReal[]) 
    TA_RetCode TA_Initialize()
    TA_RetCode TA_Shutdown()

def moving_average(np.ndarray[np.float_t, ndim=1] inreal, 
               int begIdx=0, int endIdx=-1,
               int optInTimePeriod=1, int optInMAType=0):
    """
    Computes a moving average on the inreal array using ta-lib TA_MA function

    Parameters:
    -----------------
    inreal : ndarray
        A one-dimensional numpy array of float
    begIdx : int
        Starting index in inreal
    endIdx : int
        Ending index in inreal
    optInTimePeriod : int 
       Number of period from 1 to 100000
    optInMAType : int
          Type of Moving Average (see ta_defs.h for a description)

    Returns:
    ------------
        a tuple containaing:
             - outbegidx : begining index in the returned array
             - outnbelement : number of elements
             - outreal :  one dimensional numpy array with the computed moving average

    FIXME : add a check on optInMAType method
    """
   
    cdef int outbegidx
    cdef int outnbelement
    cdef np.ndarray[np.float_t, ndim=1] outreal = numpy.zeros_like(inreal)

    if endIdx == -1:
        endIdx = inreal.shape[0]-1
    
    retCode =  TA_Initialize()
    if retCode != TA_SUCCESS:
        raise Exception("Cannot initialize TA-Lib (%d)!\n" % retCode)
    else:
        retCode =  TA_MA(begIdx, endIdx, <double *>inreal.data, 10, 1, &outbegidx, &outnbelement, <double *>outreal.data)
    TA_Shutdown()

    return (outbegidx, outnbelement, outreal)

The interesting piece are how the numpy arrays do share a data pointer with ta-lib. This is highly efficient as there is no copy of the data between the two libraries.

The next step is to have a setup.py file that can build the Cython extension (supporting both linux2 and win32 platforms) :

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

import numpy
import sys

if sys.platform == "linux2" :
    include_talib_dir = "/usr/local/include/ta-lib/"
    lib_talib_dir = "/usr/local/lib/"
elif sys.platform == "win32":
    include_talib_dir = r"c:\msys\1.0\local\include\ta-lib"
    lib_talib_dir = r"c:\msys\1.0\local\lib"    
    
ext = Extension("talib", ["talib.pyx"],
    include_dirs=[numpy.get_include(), 
                  include_talib_dir],
    library_dirs=[lib_talib_dir],
    libraries=["ta_lib"]
)

setup(ext_modules=[ext],
    cmdclass = {'build_ext': build_ext})

Then compiling the extension :

python setup.py build_ext --inplace

And finally testing your Cython module :

import numpy
import pylab

import talib

TEST_LEN = 1000

r = numpy.arange(TEST_LEN)
idata = numpy.random.random((TEST_LEN))

(bidx, elements, odata) = talib.moving_average(idata)

pylab.plot(r, idata, ‘b-‘, label="original")
pylab.plot(r, odata, ‘g-‘, label="MA")
pylab.legend()
pylab.show()

You will have something like the following screenshot using a 100 element input array :

About these ads

9 Responses to Interfacing ta-lib with Python using Cython : moving average function example

  1. Andy Hawkins says:

    Thank you for this bit of information, complete time saver, thank you.

  2. orange says:

    Thanks, I really appreciate this post.

  3. http://e英語.com/ Thanks for that awesome posting. It saved MUCH time :-)

  4. inactivist says:

    Thanks for this great example.

    I noticed that the example code does not respect the values of optInTimePeriod or optInMAType. The correct invocation should read:

    retCode = TA_MA(begIdx, endIdx, inreal.data, optInTimePeriod, optInMAType, &outbegidx, &outnbelement, outreal.data)

  5. inactivist says:

    Oh, one more thing: do you know of an existing, complete Cython implementation of the talib interface? (fingers crossed!)

    Cheers,

    -Inactivist

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: