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 :
Thank you for this bit of information, complete time saver, thank you.
Thanks, I really appreciate this post.
http://e英語.com/ Thanks for that awesome posting. It saved MUCH time 🙂
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)
Thanks, will try to fix this asap.
Oh, one more thing: do you know of an existing, complete Cython implementation of the talib interface? (fingers crossed!)
Cheers,
-Inactivist
No, but I guess that you could generate the full wrappers using cwrap!
A fully wrapped TA-Lib using Cython is available here:
https://github.com/mrjbq7/ta-lib
I just took another approach by wrapping ta_abstract.h. It works fine and is pretty slick. Will push the code to github asap.