Home Basic Data Analysis Trading Strategy Analysis using Python and the FFN Package – Part 1

Trading Strategy Analysis using Python and the FFN Package – Part 1

by s666

In this post I will be reviewing and running through examples of using the brilliant python module, “ffn – Financial Functions for Python“, which has been created by Philippe Morissette and released on the MIT license. The github page can be found here (http://pmorissette.github.io/ffn/index.html)

The module helps quickly carry out analysis of trading strategies and financial asset price series/history. It can deal with single series using the “PerformanceStats” class, or multiple combined assets simultaneously using the “GroupStats” class.

Let’s begin with the simple, one asset/strategy case and run through some examples.

We begin by importing the relevant modules (and if working in a Jupyter notebook, the call to allow matplotlib objects to be rendered in the browser).

import pandas as pd
import numpy as np
import ffn
%matplotlib inline

Now lets generate a set of random performance data, spanning a 1000 day period. We start by setting the number of days we want (i.e. 1000), then we use the numpy “randn” call to generate an array of 1000 numbers drawn from the normal distribution. To give our data a slight upward bias, I have then added to these figures a random value chosen from the uniform distribution with a maximum value of 0.2 and a minimum value of 0.0. These random figures represent the daily returns of our trading strategy, or stock price.

I have then created a Pandas DataFrame from this “returns” data and added a column of the cumulative sum of those returns, adding 100 to represent starting capital – these represent the strategy “equity” or the amount of cash we have in our trading account.

The ffn package does allow the direct download of historic stock price data from Yahoo Finance but thought i would use random data in the first instance. I will go over the download of data later in the post.

num_days = 1000
data = (np.random.randn(num_days) + np.random.uniform(low=0.0, high=0.2, size=num_days))
index = pd.date_range('01/01/2010',periods=num_days, freq='D')
data = pd.DataFrame(data,index=index,columns=['Returns'])
data['Equity'] = data.cumsum() + 100
data.iloc[0] = 100

So the “Equity” column of our DataFrame is the one we are most interested in as this is our theoretical trading account equity. We start by using the “calc_stats” method on this data and assigning that to a variable called “perf”

perf = data['Equity'].calc_stats()

If we lok at what “perf” actually is by looking at its “type”, we can see it is a ffn.core.PerformanceStats object. This means we can use any of the PerformanceStats object methods on it.

type(perf)

“ffn.core.PerformanceStats”

Let’s start by plotting the equity curve. This is very similar to just calling the usual “plot()” syntax from Pandas.

perf.plot()

Great stuff, so we now have a visual representation of our equity curve. Let’s move on to some statistics; we can very easily do this using ffn by calling the “display()” method. This will print out the following set of comprehensive statistics.

perf.display()

That is a pretty comprehensive set of statistics – the Sharpe Ratio, Calmar Ratio, Total Returns, CAGR, Max Drawdown, Periodic Returns plus more…that’s not a bad time saver – if we were to have to calculate these all ourselves it could take quite some time.

The next ffn method we might be interested in is that to represent a table of monthly returns. Again, it’s as easy as this:

perf.display_monthly_returns()

How about plotting a visual representation of our strategy drawdown series…it can be carried out with a single line of code:

ffn.to_drawdown_series(data['Equity']).plot(figsize=(15,7),grid=True)

And plotting a histogram of returns is again as simple as:

perf.plot_histogram()

If we want to get hold of the set of comprehensive analysis statistics mentioned above, but this time in a Pandas series, rather than printed out as a table, we can get hold of it as follows:

perf.stats

This Series object is index-able, just like any other Pandas Series so we can pick out any relevant items we need using the following syntax – for example if we wanted the Yearly Sharpe Ratio:

perf.stats['yearly_sharpe']

“1.9283569892842471”

Finally for the “PerformanceStats” object, we can extract the series of “Lookback Returns” as simply as follows (this can also be indexed similarly to the “perf.stats” object mentioned above).

perf.display_lookback_returns()

I’ll leave it here for this post, and in the upcoming post will deal with using the FFN package when considering multiple price/equity series simultaneously using the “GroupStats” object.

Until next time!

You may also like

16 comments

publiclunchresearch March 14, 2018 - 3:39 am

so nice thanks

Reply
Tian June 20, 2018 - 9:57 am

If I use monthly return, how can I use ffn package to do the similar analysis? Thanks.

Reply
s666 June 20, 2018 - 5:18 pm

Hi Tian – what I tend to do if I have monthly returns/price series is just feed in that data as normal and ignore any stats that are presented at less than monthly frequency – so disregard stats calculated on a “daily” basis and just accept the stats named as “monthly” or longer time period.

Reply
Tian June 20, 2018 - 9:58 am

If I use monthly return, how can I use ffn package to do the similar analysis? Thank you.

Reply
LS June 29, 2018 - 2:10 pm

Hi, thanks for your blog and all your articles. I am combing the moving average strategy part 1 with this library and I am getting equity curve but the stats is incomplete. I guess that’s because return of this strategy is very low. Would you just indicate how to adapt the strategy to be combined with the FFN ?
Thanks
LS

Reply
s666 July 1, 2018 - 9:09 am

Hi there – I can definitely try to help. What parts of the stats are incomplete? Do you have enough history of the equity curve to calculate all the stats?

Reply
LS July 10, 2018 - 6:00 pm

Hi, thanks for your help. I am sharing the jupyter file here: http://nbviewer.jupyter.org/gist/Ludek1234/57283adcb4d00467f5fc90a847fbd71c

Reply
Pierpaolo January 10, 2019 - 9:57 pm

Hi, nice article and superb blog. I was trying to use this package but, when using calc_stats() I consistently get the error:

negative_returns = np.minimum(returns, 0.)
RuntimeWarning: invalid value encountered in minimum

even when I replicated your code above. Any guidance? Thanks

Reply
s666 January 10, 2019 - 10:14 pm

That’s strange – have you replicated the code exactly? What about if you use the exact code below:

import pandas as pd
import numpy as np
import ffn
%matplotlib inline

num_days = 1000
data = (np.random.randn(num_days) + np.random.uniform(low=0.0, high=0.2, size=num_days))
index = pd.date_range('01/01/2010',periods=num_days, freq='D')
data = pd.DataFrame(data,index=index,columns=['Returns'])
data['Equity'] = data.cumsum() + 100
data.iloc[0] = 100

perf = data['Equity'].calc_stats()

perf.display()
Reply
Pierpaolo January 11, 2019 - 7:35 am

Copied and pasted that exact code (except for “%matplotlib inline” as I am running Python 3.6.8 under IDLE environment). This is the annoying warning I am always getting:

Warning (from warnings module):
File “C:\Python36-64\lib\site-packages\ffn\core.py”, line 2054
negative_returns = np.minimum(returns, 0.)
RuntimeWarning: invalid value encountered in minimum

Warning (from warnings module):
File “C:\Python36-64\lib\site-packages\ffn\core.py”, line 2056
res = np.divide(er.mean(), std)
RuntimeWarning: divide by zero encountered in true_divide

Should I switch from IDLE to Anaconda environment?

Reply
Pierpaolo January 11, 2019 - 10:37 am

After some research and trials on your exact code, I’ve managed to get rid of the first of the two warnings (the one connected with np.minimum(returns, 0.)) by using the ffn package from github instead of the one coming from pip install. As far as the second warning is concerned (np.divide(er.mean(), std)), the only solution was to suppress the warning by adding the line np.seterr(all=’ignore’) to your code. Looks like a bug in the ffn package still unresolved.

Reply
s666 January 11, 2019 - 5:37 pm

Interesting – so now you have suppressed the warning, the code can be run and the calculations are being made/displayed?

Reply
Pierpaolo January 11, 2019 - 6:14 pm

Yes, the entire replicated code could be run and calculations were displayed. Thanks for you reply.

Reply
s666 January 14, 2019 - 5:23 pm

Great stuff – glad to hear it’s working!!

Reply
dirk December 3, 2019 - 1:05 pm

Hi – Why don’t the monthly returns add up to the YTD in your table above ?

Reply
s666 December 6, 2019 - 6:23 am

Hi dirk, rather than simply adding the returns in a “simple arithmetic return” approach, the “proper” method is actually to calculate the monthly compounded, time weighted return by adding 1 to each monthly return figure and then multiplying all the monthly values together, resulting in a final value, which we then subtract the 1 from to end at the monthly compounded time weighted return.

Reply

Leave a Reply

%d bloggers like this: