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.