<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Auquan on Medium]]></title>
        <description><![CDATA[Stories by Auquan on Medium]]></description>
        <link>https://medium.com/@auquan?source=rss-2a45747180c6------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*JwxKaa9RCr_xigKSnfRP1Q.png</url>
            <title>Stories by Auquan on Medium</title>
            <link>https://medium.com/@auquan?source=rss-2a45747180c6------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 11 Apr 2026 02:45:02 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@auquan/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Web Dev — Windows(Native/WSL), Mac-OSX or Linux?]]></title>
            <link>https://medium.com/auquan/web-dev-windows-native-wsl-mac-osx-or-linux-775e41aa87f6?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/775e41aa87f6</guid>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[wsl]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[mac]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Mon, 13 May 2019 14:43:05 GMT</pubDate>
            <atom:updated>2019-05-13T14:43:05.620Z</atom:updated>
            <content:encoded><![CDATA[<h3>Web Dev — Windows(Native/WSL), Mac-OSX or Linux?</h3><p>Recently at <a href="https://auquan.com">Auquan</a>, I had to make a choice between Windows, OSX and Linux operating systems. It took me a few weeks to try out all the operating systems and and there were quite a few factors that went into the final decision. In this article, I will break down my experiences, the decision making process and how we finally arrived at Windows(WSL) as the operating system of choice.</p><h3>Context</h3><p>Auquan is currently a team of 5 engineers and the number is expected to grow to 10+. We have a MERN(Mongo-Express-React-Node) stack and so far as a seed stage startup we were able to get away with everyone working on their personal laptops. However, it was becoming clear that we need to get our engineers work machines so that the team can have a standard dev environment. Our current state was: 2 of us were on OSX, 1 on Windows(native) and 2 on Linux. So started the long debate of which OS should we go with.</p><h3>How To Decide?</h3><p>First things first, I decided to give each one a fair shot. I was actively working in OSX and I had a native windows environment as well that I had used on and off over the past year. For a linux environment I fired up an AWS machine as it would serve well for purposes of evaluating. It was around this time that I remembered something like <a href="https://docs.microsoft.com/en-us/windows/wsl/faq">WSL </a>exists and had enjoyed quite the hype when it first arrived. For the uninitiated, WSL is Windows Subsystem For Linux and it lets you have bash support without having to install linux as a separate partition. I added WSL to the mix of contenders and now we had 4 environments to evaluate. Following are the factors we were considering:</p><h4>Ease of Setup</h4><p>Before you can start using an environment you have to set it up so it is important the setup is easy and smooth. For each of the factors I will talk about my experience.</p><p><strong>Windows(Native):</strong> I did the setup through installers and it was fairly simple to get the environment up and running. I did not install a local version of MongoDB so I cannot speak to simplicity of that.</p><p><strong>Mac-OSX/Linux:</strong> Absolutely simple once you have xcode/brew setup. Just run a bunch of commands in terminal, clone your repository and you are good to go.</p><p><strong>WSL: </strong>Getting WSL setup is the added cost here, but you can pick up any guide and you will be set up in ~30 mins 25 of which will be the download time, i.e. its super simple to set up WSL. Afterwards installing node is just like linux but setting up mongoDb required a bit more research as WSL is still very new. It took me exponentially more time than either of the other OS’s to set this configuration up but once I figured out the steps the complexity becomes same as the Windows(native) set up. I will make another post with a full rundown of how to set up a web env on WSL.</p><h4>Ease of Use / Support Issues</h4><p>This factor was to do with how good is the tooling around our environment. Will we need to repeatedly deal with unsupported libraries or minor issues that make the development process harder.</p><p><strong>Windows(Native): </strong>The support framework leaves a lot to be desired. Most new things have to be installed separately and the cmd line just feels lacking. Powershell has improved upon that but for our use case it did not seem to deliver. However considering office/excel is also a part of our workflow windows had the clear edge there and the GUI exploration is just better.</p><p><strong>Mac-OSX: </strong>Great command line and tools around it. iterm has to be the best terminal app and everything you need is a command away. The rest of the OS however did not feel as powerful as windows for our use case of exploring big data sets using GUI or general tooling.</p><p><strong>Linux: </strong>Once again great command line and tooling. The GUI has improved over the years but compared to other 2 it is still lagging behind.</p><blockquote><strong>WSL: </strong>This is where WSL shone, we could get the linux command line while keeping the windows GUI. What’s there to lose? Well, possibly quite a bit. WSL is new and does not have extensive support documentation so you may find yourself on wild goose chases every once in a while. However, most of the issues I dealt with had solutions that were applicable for linux and more importantly there always WAS a solution even if it was not officially supported.</blockquote><h4>Learning Curve</h4><p>If people have to switch OS’s it always comes with an added cost of learning the new OS/env. It slows you down from getting to the bits that actually matter.<strong> </strong>I am yet to meet a person who has not used a windows machine ever. Possibly the younger engineers have not but people in their 20’s have all grown up with windows and the OS is very familiar to everyone. So windows was a clear winner here over OSX or Linux.</p><h4>Associated Hardware</h4><p>Macs are expensive, the quality of hardware justifies some of that cost but a entry level 15 inch Macbook Pro will still cost you <a href="https://www.apple.com/shop/buy-mac/macbook-pro/15-inch-silver-2.2ghz-6-core-256gb#">~$2600 with taxes.</a> To top it the current generation Macbook keyboard is just lackluster and the touch bar is one of the worst gimmicks I have ever experienced. You can easily obtain out performing and great looking hardware from HP/Dell for sub $1500 range. And if you want absolutely beautiful looking hardware just look at HP Spectre or Dell Xps but they will run you a little more.</p><h3>Final Conclusion</h3><p>After working with WSL for over a month I could not find any reasons to not choose it as my environment of choice. Once I installed <a href="https://cmder.net/">cmder</a> I got close to having an iterm like experience and all my everyday commands in WSL just seemed to work. There were a couple gotchas here and there, mostly to do with how the file system operates but a quick google search always gave me answers I needed. My experience, coupled with the linux command line, windows interface and ~$1500 saving on every machine we got led us to choose Windows + WSL as our environment. Next, I will be doing a detailed post on how to set up a MERN environment on WSL but in the meantime let me know your thoughts.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=775e41aa87f6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/web-dev-windows-native-wsl-mac-osx-or-linux-775e41aa87f6">Web Dev — Windows(Native/WSL), Mac-OSX or Linux?</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Long-Short Equity Strategy using Ranking: : Simple Trading Strategies Part 4]]></title>
            <link>https://medium.com/auquan/long-short-equity-trading-strategy-daa41d00a036?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/daa41d00a036</guid>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[trading]]></category>
            <category><![CDATA[investing]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[trading-strategy]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Thu, 11 Jan 2018 06:51:01 GMT</pubDate>
            <atom:updated>2018-01-11T06:51:01.454Z</atom:updated>
            <content:encoded><![CDATA[<p>In the <a href="https://medium.com/auquan/pairs-trading-data-science-7dbedafcfe5a">last post</a>, we covered Pairs trading strategy and demonstrated how to leverage data and mathematical analysis to create and automate a trading strategy.</p><p>Long-Short Equity Strategy is a natural extension of Pairs Trading applied to a basket of stocks.</p><p>Download <a href="https://github.com/Auquan/Tutorials/blob/master/Long-Short%20Strategies%20using%20Ranking.ipynb">Ipython Notebook here</a>.</p><h3>Underlying Principle</h3><p>Long-Short equity strategy is both long and short stocks simultaneously in the market. Just like pairs trading identifies which stock is cheap and which is expensive in a pair, a Long-Short strategy will rank all stocks in a basket to identify which stocks are relatively cheap and expensive. It will then go long (buys) the top <em>n </em>equities based on the ranking, and short (sells) the bottom <em>n</em> for equal amounts of money(Total value of long position = Total value of short position).</p><p>Remember how we said that Pairs Trading is a market neutral strategy? So is a Long-Short strategy as the equal dollar volume long and short positions ensure that the strategy will remain market neutral (immune to market movements). The strategy is also statistically robust — by ranking stocks and entering multiple positions, you are making many bets on your ranking model rather than just a few risky bets. You are also betting purely on the quality of your ranking scheme.</p><h3>What is a Ranking Scheme?</h3><p>A ranking scheme is any model that can assign each stock a number based on how they are expected to perform, where higher is better or worse. Examples could be value factors, technical indicators, pricing models, or a combination of all of the above. For example, you could use a momentum indicator to give a ranking to a basket of trend following stocks: stocks with highest momentum are expected to continue to do well and get the highest ranks; stocks with lowest momentum will perform the worst and get lowest rans.</p><p>The success of this strategy lies almost entirely in the ranking scheme used — the better your ranking scheme can separate high performing stocks from low performing stocks, better the returns of a long-short equity strategy. It automatically follows that developing a ranking scheme is nontrivial.</p><h3>What happens once you have a Ranking Scheme?</h3><p>Once we have determined a ranking scheme, we would obviously like to be able to profit from it. We do this by investing an equal amount of money into buying stocks at the top of the ranking, and selling stocks at the bottom. This ensures that the strategy will make money proportionally to the quality of the ranking only, and will be market neutral.</p><p>Let’s say you are ranking <em>m </em>equities, have <em>n </em>dollars to invest, and want to hold a total of <em>2p</em> positions (where <em>m &gt; 2p</em> ). If the stock at rank 1 is expected to perform the worst and stock at rank <em>m</em> is expected to perform the best:</p><ul><li>You take the stocks in position <em>1,…,p</em> in the ranking, sell <em>n/2p</em> dollars worth of each stock</li><li>For each stock in position <em>m−p,…,m</em> in the ranking, buy <em>n/2p</em> dollars worth of each stock</li></ul><p><strong>Note: Friction Because of Prices</strong> Because stock prices will not always divide <em>n/2p</em> evenly, and stocks must be bought in integer amounts, there will be some imprecision and the algorithm should get as close as it can to this number. For a strategy running with n=100000 and p=500, we see that</p><p><em>n/2p</em>=100000/1000 =100</p><p>This will cause big problems for stocks with prices &gt; 100 since you can’t buy fractional stock. This is alleviated by trading fewer equities or increasing the capital.</p><h3>Let’s run through a hypothetical example</h3><p>We generate random stock names and a random factor on which to rank them. Let’s also assume our future returns are actually dependent on these factor values.</p><pre>import numpy as np<br>import statsmodels.api as sm<br>import scipy.stats as stats<br>import scipy<br>import matplotlib.pyplot as plt<br>import seaborn as sns<br>import pandas as pd</pre><pre>## PROBLEM SETUP ##</pre><pre># Generate stocks and a random factor value for them</pre><pre>stock_names = [&#39;stock &#39; + str(x) for x in range(10000)]<br>current_factor_values = np.random.normal(0, 1, 10000)</pre><pre># Generate future returns for these are dependent on our factor values<br>future_returns = current_factor_values + np.random.normal(0, 1, 10000)</pre><pre># Put both the factor values and returns into one dataframe<br>data = pd.DataFrame(index = stock_names, columns=[&#39;Factor Value&#39;,&#39;Returns&#39;])<br>data[&#39;Factor Value&#39;] = current_factor_values<br>data[&#39;Returns&#39;] = future_returns<br># Take a look<br>data.head(10)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/426/1*T5yw6o2tYc9nXLCVRE5aww.png" /></figure><p>Now that we have factor values and returns, we can see what would happen if we ranked our equities based on factor values, and then entered the long and short positions.</p><pre># Rank stocks<br>ranked_data = data.sort_values(&#39;Factor Value&#39;)</pre><pre># Compute the returns of each basket with a basket size 500, so total (10000/500) baskets<br>number_of_baskets = int(10000/500)<br>basket_returns = np.zeros(number_of_baskets)</pre><pre>for i in range(number_of_baskets):<br>    start = i * 500<br>    end = i * 500 + 500 <br>    basket_returns[i] = ranked_data[start:end][&#39;Returns&#39;].mean()</pre><pre># Plot the returns of each basket<br>plt.figure(figsize=(15,7))<br>plt.bar(range(number_of_baskets), basket_returns)<br>plt.ylabel(&#39;Returns&#39;)<br>plt.xlabel(&#39;Basket&#39;)<br>plt.legend([&#39;Returns of Each Basket&#39;])<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*dwRzD6iLbdLdhWVuRUOqTA.png" /></figure><p>Our strategy is to sell the basket at rank 1 and buy the basket at rank 10. The returns of this strategy are:</p><pre>basket_returns[number_of_baskets<strong>-</strong>1] <strong>-</strong> basket_returns[0]</pre><blockquote>4.172</blockquote><blockquote>We’re basically putting our money on our ranking model being able to separate and spread high performing stocks from low performing stocks.</blockquote><p>For the rest of this post, we’ll talk about how to evaluate a ranking scheme. The nice thing about making money based on the spread of the ranking is that it is unaffected by what the market does.</p><h3>Let’s consider a real world example.</h3><p>We load data for 32 stocks from different sectors in S&amp;P500 and try to rank them.</p><pre>from backtester.dataSource.yahoo_data_source import YahooStockDataSource<br>from datetime import datetime</pre><pre>startDateStr = &#39;2010/01/01&#39;<br>endDateStr = &#39;2017/12/31&#39;<br>cachedFolderName = &#39;/Users/chandinijain/Auquan/yahooData/&#39;<br>dataSetId = &#39;testLongShortTrading&#39;<br>instrumentIds = [&#39;ABT&#39;,&#39;AKS&#39;,&#39;AMGN&#39;,&#39;AMD&#39;,&#39;AXP&#39;,&#39;BK&#39;,&#39;BSX&#39;,<br>                &#39;CMCSA&#39;,&#39;CVS&#39;,&#39;DIS&#39;,&#39;EA&#39;,&#39;EOG&#39;,&#39;GLW&#39;,&#39;HAL&#39;,<br>                &#39;HD&#39;,&#39;LOW&#39;,&#39;KO&#39;,&#39;LLY&#39;,&#39;MCD&#39;,&#39;MET&#39;,&#39;NEM&#39;,<br>                &#39;PEP&#39;,&#39;PG&#39;,&#39;M&#39;,&#39;SWN&#39;,&#39;T&#39;,&#39;TGT&#39;,<br>                &#39;TWX&#39;,&#39;TXN&#39;,&#39;USB&#39;,&#39;VZ&#39;,&#39;WFC&#39;]<br>ds = YahooStockDataSource(cachedFolderName=cachedFolderName,<br>                            dataSetId=dataSetId,<br>                            instrumentIds=instrumentIds,<br>                            startDateStr=startDateStr,<br>                            endDateStr=endDateStr,<br>                            event=&#39;history&#39;)</pre><pre>price = &#39;adjClose&#39;</pre><p>Let’s start by using one month normalized momentum as a ranking indicator</p><pre>## Define normalized momentum<br>def momentum(dataDf, period):<br>    return dataDf.sub(dataDf.shift(period), fill_value=0) / dataDf.iloc[-1]</pre><pre>## Load relevant prices in a dataframe<br>data = ds.getBookDataByFeature()[‘Adj Close’]</pre><pre>#Let&#39;s load momentum score and returns into separate dataframes<br>index = data.index<br>mscores = pd.DataFrame(index=index,columns=assetList)<br>mscores = momentum(data, 30)<br>returns = pd.DataFrame(index=index,columns=assetList)<br>day = 30</pre><p>Now we’re going to analyze our stock behavior and see how our universe of stocks work w.r.t our chosen ranking factor.</p><h3>Analyzing data</h3><h4>Stock behavior</h4><p>We look at how our chosen basket of stocks behave w.r.t our ranking model. To do this, let’s calculate one week forward return for all stocks. Then we can look at the correlation of 1 week forward return with previous 30 day momentum for every stock. Stocks that exhibit positive correlation are trend following and stocks that exhibit negative correlation are mean reverting.</p><pre># Calculate Forward returns<br>forward_return_day = 5<br>returns = data.shift(-forward_return_day)/data -1<br>returns.dropna(inplace = True)</pre><pre># Calculate correlations between momentum and returns<br>correlations = pd.DataFrame(index = returns.columns, columns = [‘Scores’, ‘pvalues’])</pre><pre>mscores = mscores[mscores.index.isin(returns.index)]</pre><pre>for i in correlations.index:<br>    score, pvalue = stats.spearmanr(mscores[i], returns[i])<br>    correlations[‘pvalues’].loc[i] = pvalue<br>    correlations[‘Scores’].loc[i] = score<br>correlations.dropna(inplace = True)<br>correlations.sort_values(‘Scores’, inplace=True)</pre><pre>l = correlations.index.size<br>plt.figure(figsize=(15,7))<br>plt.bar(range(1,1+l),correlations[‘Scores’])<br>plt.xlabel(‘Stocks’)<br>plt.xlim((1, l+1))<br>plt.xticks(range(1,1+l), correlations.index)<br>plt.legend([‘Correlation over All Data’])<br>plt.ylabel(‘Correlation between %s day Momentum Scores and %s-day forward returns by Stock’%(day,forward_return_day));<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/906/1*0bmuMMI8EVDGd6AknoPTAg.png" /></figure><p>All our stocks are mean reverting to some degree! (Obviously we choose the universe to be this way :) ) This tells us that if a stock ranks high on momentum score, we should expect it to perform poorly next week.</p><h4>Correlation between Ranking due to Momentum Score and Returns</h4><p>Next, we need to look at correlation between our ranking score and forward returns of our universe, i.e. how predictive of of forward returns is our ranking factor? Does a high relative rank predict poor relative returns or vice versa?</p><p>To do this, we calculate daily correlation between 30 day momentum and 1 week forward returns of all stocks.</p><pre>correl_scores = pd.DataFrame(index = returns.index.intersection(mscores.index), columns = [‘Scores’, ‘pvalues’])</pre><pre>for i in correl_scores.index:<br>    score, pvalue = stats.spearmanr(mscores.loc[i], returns.loc[i])<br>    correl_scores[‘pvalues’].loc[i] = pvalue<br>    correl_scores[‘Scores’].loc[i] = score<br>correl_scores.dropna(inplace = True)</pre><pre>l = correl_scores.index.size<br>plt.figure(figsize=(15,7))<br>plt.bar(range(1,1+l),correl_scores[‘Scores’])<br>plt.hlines(np.mean(correl_scores[‘Scores’]), 1,l+1, colors=’r’, linestyles=’dashed’)<br>plt.xlabel(‘Day’)<br>plt.xlim((1, l+1))<br>plt.legend([‘Mean Correlation over All Data’, ‘Daily Rank Correlation’])<br>plt.ylabel(‘Rank correlation between %s day Momentum Scores and %s-day forward returns’%(day,forward_return_day));<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*FCwBrTGO6uQlxY1HvI9HhQ.png" /></figure><p>Daily Correlation is quite noisy, but very slightly negative (This is expected, since we said all the stocks are mean reverting). Let’s also look at average monthly correlation of scores with 1 month forward returns.</p><pre>monthly_mean_correl =correl_scores[&#39;Scores&#39;].astype(float).resample(&#39;M&#39;).mean()<br>plt.figure(figsize=(15,7))<br>plt.bar(range(1,len(monthly_mean_correl)+1), monthly_mean_correl)<br>plt.hlines(np.mean(monthly_mean_correl), 1,len(monthly_mean_correl)+1, colors=&#39;r&#39;, linestyles=&#39;dashed&#39;)<br>plt.xlabel(&#39;Month&#39;)<br>plt.xlim((1, len(monthly_mean_correl)+1))<br>plt.legend([&#39;Mean Correlation over All Data&#39;, &#39;Monthly Rank Correlation&#39;])<br>plt.ylabel(&#39;Rank correlation between %s day Momentum Scores and %s-day forward returns&#39;%(day,forward_return_day));<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*XUcxpNV1lKX06HkdYbd4QA.png" /></figure><p>We can see that the average correlation is slightly negative again, but varies a lot daily as well from month to month.</p><h4>Average Basket Return</h4><p>Now we compute the returns of baskets taken out of our ranking. If we rank all equities and then split them into nn groups, what would the mean return be of each group?</p><p>The first step is to create a function that will give us the mean return in each basket in a given the month and a ranking factor.</p><pre>def compute_basket_returns(factor, forward_returns, number_of_baskets, index):</pre><pre>data = pd.concat([factor.loc[index],forward_returns.loc[index]], axis=1)<br>    # Rank the equities on the factor values<br>    data.columns = [&#39;Factor Value&#39;, &#39;Forward Returns&#39;]<br>    data.sort_values(&#39;Factor Value&#39;, inplace=True)<br>    # How many equities per basket<br>    equities_per_basket = np.floor(len(data.index) / number_of_baskets)</pre><pre>basket_returns = np.zeros(number_of_baskets)</pre><pre># Compute the returns of each basket<br>    for i in range(number_of_baskets):<br>        start = i * equities_per_basket<br>        if i == number_of_baskets - 1:<br>            # Handle having a few extra in the last basket when our number of equities doesn&#39;t divide well<br>            end = len(data.index) - 1<br>        else:<br>            end = i * equities_per_basket + equities_per_basket<br>        # Actually compute the mean returns for each basket<br>        #s = data.index.iloc[start]<br>        #e = data.index.iloc[end]<br>        basket_returns[i] = data.iloc[int(start):int(end)][&#39;Forward Returns&#39;].mean()<br>        <br>    return basket_returns</pre><p>We calculate the average return of each basket when equities are ranked based on this score. This should give us a sense of the relationship over a long timeframe.</p><pre>number_of_baskets = 8<br>mean_basket_returns = np.zeros(number_of_baskets)<br>resampled_scores = mscores.astype(float).resample(&#39;2D&#39;).last()<br>resampled_prices = data.astype(float).resample(&#39;2D&#39;).last()<br>resampled_scores.dropna(inplace=True)<br>resampled_prices.dropna(inplace=True)<br>forward_returns = resampled_prices.shift(-1)/resampled_prices -1<br>forward_returns.dropna(inplace = True)</pre><pre>for m in forward_returns.index.intersection(resampled_scores.index):<br>    basket_returns = compute_basket_returns(resampled_scores, forward_returns, number_of_baskets, m)<br>    mean_basket_returns += basket_returns</pre><pre>mean_basket_returns /= l    <br>print(mean_basket_returns)<br># Plot the returns of each basket<br>plt.figure(figsize=(15,7))<br>plt.bar(range(number_of_baskets), mean_basket_returns)<br>plt.ylabel(&#39;Returns&#39;)<br>plt.xlabel(&#39;Basket&#39;)<br>plt.legend([&#39;Returns of Each Basket&#39;])<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/910/1*sRujerR1aWBP0I81Yp64fg.png" /></figure><p>Seems like we are able to separate high performers from low performers with very small success.</p><h4>Spread Consistency</h4><p>Of course, that’s just the average relationship. To get a sense of how consistent this is, and whether or not we would want to trade on it, we should look at it over time. Here we’ll look at the monthly spreads for the first two years. We can see a lot of variation, and further analysis should be done to determine whether this momentum score is tradeable.</p><pre>total_months = mscores.resample(&#39;M&#39;).last().index<br>months_to_plot = 24<br>monthly_index = total_months[:months_to_plot+1]<br>mean_basket_returns = np.zeros(number_of_baskets)<br>strategy_returns = pd.Series(index = monthly_index)<br>f, axarr = plt.subplots(1+int(monthly_index.size/6), 6,figsize=(18, 15))<br>for month in range(1, monthly_index.size):<br>    temp_returns = forward_returns.loc[monthly_index[month-1]:monthly_index[month]]<br>    temp_scores = resampled_scores.loc[monthly_index[month-1]:monthly_index[month]]<br>    for m in temp_returns.index.intersection(temp_scores.index):<br>        basket_returns = compute_basket_returns(temp_scores, temp_returns, number_of_baskets, m)<br>        mean_basket_returns += basket_returns<br>    <br>    strategy_returns[monthly_index[month-1]] = mean_basket_returns[ number_of_baskets-1] - mean_basket_returns[0]<br>    <br>    mean_basket_returns /= temp_returns.index.intersection(temp_scores.index).size<br>    <br>    r = int(np.floor((month-1) / 6))<br>    c = (month-1) % 6<br>    axarr[r, c].bar(range(number_of_baskets), mean_basket_returns)<br>    axarr[r, c].xaxis.set_visible(False)<br>    axarr[r, c].set_title(&#39;Month &#39; + str(month))<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tj9GZtAlEJowkRqvsKo_-A.png" /></figure><pre>plt.figure(figsize=(15,7))<br>plt.plot(strategy_returns)<br>plt.ylabel(‘Returns’)<br>plt.xlabel(‘Month’)<br>plt.plot(strategy_returns.cumsum())<br>plt.legend([‘Monthly Strategy Returns’,’Cumulative Strategy Returns’])<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*da30unH20rHE52uL1K1cIQ.png" /></figure><p>Finally, lets look at the returns if we had bought the last basket and sold the first basket every month (assuming equal capital allocation to each security)</p><pre>total_return = strategy_returns.sum()<br>ann_return = 100*((1 + total_return)**(12.0 /float(strategy_returns.index.size))-1)<br>print(&#39;Annual Returns: %.2f%%&#39;%ann_return)</pre><blockquote>Annual Returns: 5.03%</blockquote><p>We see that we have a very faint ranking scheme that only mildly separates high performing stocks from low performing stocks. Besides, this ranking scheme has no consistency and varies a lot month to month.</p><h3>Finding the correct ranking scheme</h3><p>To execute a long-short equity, you effectively only have to determine the ranking scheme. Everything after that is mechanical. Once you have one long-short equity strategy, you can swap in different ranking schemes and leave everything else in place. It’s a very convenient way to quickly iterate over ideas you have without having to worry about tweaking code every time.</p><p>The ranking schemes can come from pretty much any model as well. It doesn’t have to be a value based factor model, it could be a machine learning technique that predicted returns one-month ahead and ranked based on that.</p><h4>Choice and Evaluation of a Ranking Scheme</h4><blockquote><strong>The ranking scheme is where a long-short equity strategy gets its edge, and is the most crucial component.</strong> Choosing a good ranking scheme is the entire trick, and there is no easy answer.</blockquote><p>A good starting point is to pick existing known techniques, and see if you can modify them slightly to get increased returns. We’ll discuss a few starting points here:</p><ul><li><strong>Clone and Tweak: </strong>Choose one that is commonly discussed and see if you can modify it slightly to gain back an edge. Often times factors that are public will have no signal left as they have been completely arbitraged out of the market. However, sometimes they lead you in the right direction of where to go.</li><li><strong>Pricing Models: </strong>Any model that predicts future returns can be a factor. The future return predicted is now that factor, and can be used to rank your universe. You can take any complicated pricing model and transform it into a ranking.</li><li><strong>Price Based Factors (Technical Indicators): </strong>Price based factors, like we discussed today, take information about the historical price of each equity and use it to generate the factor value. Examples could be moving average measures, momentum ribbons, or volatility measures.</li><li><strong>Reversion vs. Momentum: </strong>It’s important to note that some factors bet that prices, once moving in a direction, will continue to do so. Some factors bet the opposite. Both are valid models on different time horizons and assets, and it’s important to investigate whether the underlying behavior is momentum or reversion based.</li><li><strong>Fundamental Factors (Value Based): </strong>This is using combinations of fundamental values like P.E ratio, dividend etc. Fundamental values contain information that is tied to real world facts about a company, so in many ways can be more robust than prices.</li></ul><p>Ultimately, developing predictive factors is an arms race in which you are trying to stay one step ahead. Factors get arbitraged out of markets and have a lifespan, so it’s important that you are constantly doing work to determine how much decay your factors are experiencing, and what new factors might be used to take their place.</p><h3>Additional Considerations</h3><ul><li><strong>Rebalancing Frequency</strong></li></ul><p>Every ranking system will be predictive of returns over a slightly different timeframe. A price-based mean reversion may be predictive over a few days, while a value-based factor model may be predictive over many months. It is important to determine the timeframe over which your model should be predictive, and statistically verify that before executing your strategy. You don’t want to overfit by trying to optimize the rebalancing frequency — you will inevitably find one that is randomly better than others, but not necessary because of anything in your model. Once you have determined the timeframe on which your ranking scheme is predictive, try to rebalance at about that frequency so you’re taking full advantage of your models.</p><ul><li><strong>Capital Capacity and Transaction Costs</strong></li></ul><p>Every strategy has a minimum and maximum amount of capital it can trade before it stops being profitable. The minimum threshold is usually set by transaction costs.</p><p>Trading many equities will result in high transaction costs. Say that you want to purchase <em>1000</em> equities, you will incur a few thousand dollars in costs per rebalance. Your capital base must be high enough that the transaction costs are a small percentage of the returns being generated by your strategy. For example, if your capital is 100,000$ and your strategy makes 1% per month(1000$) , then all of these returns will be taken up by transaction costs.. You would need to be running the strategy on millions of dollars for it to be profitable over 1000 equities.</p><p>The minimum capacity is quite high as such, and dependent largely on the number of equities traded. However, the maximum capacity is also incredibly high, with long-short equity strategies capable of trading hundreds of millions of dollars without losing their edge. This is true because the strategy rebalances relatively infrequently, and the total dollar volume is divided by the number of equities traded. Therefore dollar-volume per equity is quite low and you don’t have to worry about impacting the market by your trades. Let’s say you’re trading 1000 equities with 100,000,000$. If you rebalance your entire portfolio every month, you are only trading 100,000 dollar-volume per month for each equity, which isn’t enough to be a significant market share for most securities.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=daa41d00a036" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/long-short-equity-trading-strategy-daa41d00a036">Long-Short Equity Strategy using Ranking: : Simple Trading Strategies Part 4</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Pairs Trading using Data-Driven Techniques: Simple Trading Strategies Part 3]]></title>
            <link>https://medium.com/auquan/pairs-trading-data-science-7dbedafcfe5a?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/7dbedafcfe5a</guid>
            <category><![CDATA[trading-strategy]]></category>
            <category><![CDATA[investing]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[trading]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Wed, 20 Dec 2017 03:06:02 GMT</pubDate>
            <atom:updated>2020-03-05T18:30:51.628Z</atom:updated>
            <content:encoded><![CDATA[<p>Pairs trading is a nice example of a strategy based on mathematical analysis. We’ll demonstrate how to leverage data to create and automate a pairs trading strategy.</p><p>Download <a href="https://github.com/Auquan/Tutorials/blob/master/Pairs%20Trading.ipynb">Ipython Notebook here</a>.</p><h3>Underlying Principle</h3><p>Let’s say you have a pair of securities X and Y that have some underlying economic link, for example two companies that manufacture the same product like Pepsi and Coca Cola. You expect the ratio or difference in prices (also called the <em>spread</em>) of these two to remain constant with time. However, from time to time, there might be a divergence in the spread between these two pairs caused by temporary supply/demand changes, large buy/sell orders for one security, reaction for important news about one of the companies etc. In this scenario, one stock moves up while the other moves down relative to each other. <strong>If you expect this divergence to revert back to normal with time, you can make a pairs trade.</strong></p><p>When there is a temporary divergence, the pairs trade would be to sell the <em>outperforming</em> stock (the stock that moved up )and to buy the <em>underperforming</em> stock (the stock that moved down ). You are making a bet that the <em>spread</em> between the two stocks would eventually converge by either the <em>outperforming</em> stock moving back down or the <em>underperforming</em> stock moving back up or both — your trade will make money in all of these scenarios. If both the stocks move up or move down together without changing the spread between them, you don’t make or lose any money.</p><p>Hence, pairs trading is a market neutral trading strategy enabling traders to profit from virtually any market conditions: uptrend, downtrend, or sideways movement.</p><h3>Explaining the Concept: We start by generating two fake securities.</h3><pre><strong>import</strong> <strong>numpy</strong> <strong>as</strong> <strong>np</strong><br><strong>import</strong> <strong>pandas</strong> <strong>as</strong> <strong>pd</strong><br><br><strong>import</strong> <strong>statsmodels</strong><br><strong>from</strong> <strong>statsmodels.tsa.stattools</strong> <strong>import</strong> coint<br><em># just set the seed for the random number generator</em><br>np.random.seed(107)<br><br><strong>import</strong> <strong>matplotlib.pyplot</strong> <strong>as</strong> <strong>plt<br></strong></pre><p>Let’s generate a fake security X and model it’s daily returns by drawing from a normal distribution. Then we perform a cumulative sum to get the value of X on each day.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/874/1*jXndKzDjvSpR6qt4Zf2CkA.png" /><figcaption>Fake Security X with returns drawn from a normal distribution</figcaption></figure><pre># Generate daily returns</pre><pre>Xreturns = np.random.normal(0, 1, 100) </pre><pre># sum them and shift all the prices up</pre><pre>X = pd.Series(np.cumsum(<br>    Xreturns), name=&#39;X&#39;) <br>    + 50<br>X.plot(figsize=(15,7))<br>plt.show()</pre><p>Now we generate Y which has a deep economic link to X, so price of Y should vary pretty similarly as X. We model this by taking X, shifting it up and adding some random noise drawn from a normal distribution.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/884/1*9DkYsCvs-9CHsongpxJ50g.png" /><figcaption>Cointegrated Securities X and Y</figcaption></figure><pre>noise = np.random.normal(0, 1, 100)<br>Y = X + 5 + noise<br>Y.name = &#39;Y&#39;</pre><pre>pd.concat([X, Y], axis=1).plot(figsize=(15,7))</pre><pre>plt.show()</pre><h4>Cointegration</h4><p>Cointegration, very similar to correlation, means that the ratio between two series will vary around a mean. The two series, Y and X follow the follwing:</p><blockquote><em>Y = ⍺ X + e</em></blockquote><p>where ⍺ is the constant ratio and e is white noise. <a href="https://medium.com/auquan/cointegration-and-stationarity-f4d14e1b3aef">Read more here</a></p><p>For pairs trading to work between two timeseries, the expected value of the ratio over time must converge to the mean, i.e. they should be cointegrated.</p><p>The time series we constructed above are cointegrated. We’ll plot the ratio between the two now so we can see how this looks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/884/1*7A8mUTis_t1X1BOFjpvGCQ.png" /><figcaption>Ratio between prices of two cointegrated stocks and it’s mean</figcaption></figure><pre>(Y/X).plot(figsize=(15,7)) </pre><pre>plt.axhline((Y/X).mean(), color=&#39;red&#39;, linestyle=&#39;--&#39;) </pre><pre>plt.xlabel(&#39;Time&#39;)<br>plt.legend([&#39;Price Ratio&#39;, &#39;Mean&#39;])<br>plt.show()</pre><h4>Testing for Cointegration</h4><p>There is a convenient test that lives in statsmodels.tsa.stattools. We should see a very low p-value, as we&#39;ve artificially created two series that are as cointegrated as physically possible.</p><pre><em># compute the p-value of the cointegration test</em><br><em># will inform us as to whether the ratio between the 2 timeseries is stationary</em><br><em># around its mean</em><br>score, pvalue, _ = coint(X,Y)<br><strong>print</strong> pvalue</pre><p>1.81864477307e-17</p><h4>Note: Correlation vs. Cointegration</h4><p>Correlation and cointegration, while theoretically similar, are not the same. Let’s look at examples of series that are correlated, but not cointegrated, and vice versa. First let&#39;s check the correlation of the series we just generated.</p><pre>X.corr(Y)</pre><p>0.951</p><p>That’s very high, as we would expect. But how would two series that are correlated but not cointegrated look? A simple example is two series that just diverge.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/881/1*X1dMxI3g67vsZ78l0IOsmA.png" /><figcaption>Two correlated series (that are not co-integrated)</figcaption></figure><pre>ret1 = np.random.normal(1, 1, 100)<br>ret2 = np.random.normal(2, 1, 100)<br><br>s1 = pd.Series( np.cumsum(ret1), name=&#39;X&#39;)<br>s2 = pd.Series( np.cumsum(ret2), name=&#39;Y&#39;)<br><br>pd.concat([s1, s2], axis=1 ).plot(figsize=(15,7))<br>plt.show()</pre><pre><strong>print</strong> &#39;Correlation: &#39; + str(X_diverging.corr(Y_diverging))<br>score, pvalue, _ = coint(X_diverging,Y_diverging)<br><strong>print</strong> &#39;Cointegration test p-value: &#39; + str(pvalue)</pre><p>Correlation: 0.998<br>Cointegration test p-value: 0.258</p><p>A simple example of cointegration without correlation is a normally distributed series and a square wave.</p><pre>Y2 = pd.Series(np.random.normal(0, 1, 800), name=&#39;Y2&#39;) + 20<br>Y3 = Y2.copy()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/874/1*mI_eFQA1siU6_3K2X2JQ1g.png" /></figure><pre>Y3[0:100] = 30<br>Y3[100:200] = 10<br>Y3[200:300] = 30<br>Y3[300:400] = 10<br>Y3[400:500] = 30<br>Y3[500:600] = 10<br>Y3[600:700] = 30<br>Y3[700:800] = 10<br></pre><pre>Y2.plot(figsize=(15,7))<br>Y3.plot()<br>plt.ylim([0, 40])<br>plt.show()</pre><pre><em># correlation is nearly zero</em><br><strong>print</strong> &#39;Correlation: &#39; + str(Y2.corr(Y3))<br>score, pvalue, _ = coint(Y2,Y3)<br><strong>print</strong> &#39;Cointegration test p-value: &#39; + str(pvalue)</pre><p>Correlation: 0.007546<br>Cointegration test p-value: 0.0</p><p>The correlation is incredibly low, but the p-value shows perfect cointegration!</p><h3>How to make a pairs trade?</h3><p>Because two cointegrated time series (such as X and Y above) drift towards and apart from each other, there will be times when the <em>spread</em> is high and times when the <em>spread</em> is low. We make a pairs trade by buying one security and selling another. This way, if both securities go down together or go up together, we neither make nor lose money — we are market neutral.</p><p>Going back to X and Y above that follow <em>Y = ⍺ X + e</em>, such that ratio (Y/X) moves around it’s mean value <em>⍺, </em>we make money on the ratio of the two reverting to the mean. In order to do this we’ll watch for when X and Y are far apart, i.e <em>⍺ is too high or too low:</em></p><ul><li><strong>Going Long the Ratio</strong> This is when the ratio <em>⍺ </em>is smaller than usual and we expect it to increase. In the above example, we place a bet on this by buying Y and selling X.</li><li><strong>Going Short the Ratio</strong> This is when the ratio <em>⍺ </em>is large and we expect it to become smaller. In the above example, we place a bet on this by selling Y and buying X.</li></ul><p>Note that we always have a “hedged position”: a short position makes money if the security sold loses value, and a long position will make money if a security gains value, so we’re immune to overall market movement. We only make or lose money if securities X and Y move relative to each other.</p><h3>Using Data to find securities that behave like this</h3><p>The best way to do this is to start with securities you suspect may be cointegrated and perform a statistical test. If you just run statistical tests over all pairs, you’ll fall prey to multiple comparison bias.</p><p><strong>Multiple comparisons bias</strong> is simply the fact that there is an increased chance to incorrectly generate a significant p-value when many tests are run, because we are running a lot of tests. If 100 tests are run on random data, we should expect to see 5 p-values below 0.05. If you are comparing <em>n </em>securities for co-integration, you will perform<em> n(n-1)/2 </em>comparisons, and you should expect to see many incorrectly significant p-values, which will increase as you increase. To avoid this, pick a small number of pairs you have reason to suspect might be cointegrated and test each individually. This will result in less exposure to multiple comparisons bias.</p><p>So let’s try to find some securities that display cointegration. Let’s work with a basket of US large cap tech stocks — in S&amp;P 500. These stocks operate in a similar segment and could have cointegrated prices. We scan through a list of securities and test for cointegration between all pairs. It returns a cointegration test score matrix, a p-value matrix, and any pairs for which the p-value was less than 0.05. <strong>This method is prone to multiple comparison bias and in practice the securities should be subject to a second verification step</strong>. Let’s ignore this for the sake of this example.</p><pre>def find_cointegrated_pairs(data):<br>    n = data.shape[1]<br>    score_matrix = np.zeros((n, n))<br>    pvalue_matrix = np.ones((n, n))<br>    keys = data.keys()<br>    pairs = []<br>    for i in range(n):<br>        for j in range(i+1, n):<br>            S1 = data[keys[i]]<br>            S2 = data[keys[j]]<br>            result = coint(S1, S2)<br>            score = result[0]<br>            pvalue = result[1]<br>            score_matrix[i, j] = score<br>            pvalue_matrix[i, j] = pvalue<br>            if pvalue &lt; 0.02:<br>                pairs.append((keys[i], keys[j]))<br>    return score_matrix, pvalue_matrix, pairs</pre><p><strong>Note:</strong> We include the market benchmark (<em>SPX</em>) in our data — the market drives the movement of so many securities that often you might find two seemingly cointegrated securities; but in reality they are not cointegrated with each other but both conintegrated with the market. This is known as a <em>confounding variable</em> and it is important to check for market involvement in any relationship you find.</p><pre>from backtester.dataSource.yahoo_data_source import YahooStockDataSource<br>from datetime import datetime</pre><pre>startDateStr = &#39;2007/12/01&#39;<br>endDateStr = &#39;2017/12/01&#39;<br>cachedFolderName = &#39;yahooData/&#39;<br>dataSetId = &#39;testPairsTrading&#39;<br>instrumentIds = [&#39;SPY&#39;,&#39;AAPL&#39;,&#39;ADBE&#39;,&#39;SYMC&#39;,&#39;EBAY&#39;,&#39;MSFT&#39;,&#39;QCOM&#39;,<br>                 &#39;HPQ&#39;,&#39;JNPR&#39;,&#39;AMD&#39;,&#39;IBM&#39;]<br>ds = YahooStockDataSource(cachedFolderName=cachedFolderName,<br>                            dataSetId=dataSetId,<br>                            instrumentIds=instrumentIds,<br>                            startDateStr=startDateStr,<br>                            endDateStr=endDateStr,<br>                            event=&#39;history&#39;)</pre><pre>data = ds.getBookDataByFeature()[&#39;Adj Close&#39;]</pre><pre>data.head(3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vtnAcE3aIrRbCsruzSfTQw.png" /></figure><p>Now let’s try to find cointegrated pairs using our method.</p><pre># Heatmap to show the p-values of the cointegration test<br># between each pair of stocks</pre><pre>scores, pvalues, pairs = find_cointegrated_pairs(data)<br>import seaborn<br>m = [0,0.2,0.4,0.6,0.8,1]<br>seaborn.heatmap(pvalues, xticklabels=instrumentIds, <br>                yticklabels=instrumentIds, cmap=’RdYlGn_r’, <br>                mask = (pvalues &gt;= 0.98))<br>plt.show()<br>print pairs</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/370/1*3CRqGahGE42kmhmmNZ-wGA.png" /></figure><pre>[(&#39;ADBE&#39;, &#39;MSFT&#39;)]</pre><p>Looks like ‘ADBE’ and ‘MSFT’ are cointegrated. Let’s take a look at the prices to make sure this actually makes sense.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/877/1*IZfHHT7YbwLGm2mt_g7yUg.png" /><figcaption>Plot of Price Ratio between MSFT and ADBE from 2008–2017</figcaption></figure><pre>S1 = data[&#39;ADBE&#39;]<br>S2 = data[&#39;MSFT&#39;]<br>score, pvalue, _ = coint(S1, S2)<br>print(pvalue)<br>ratios = S1 / S2<br>ratios.plot()<br>plt.axhline(ratios.mean())<br>plt.legend([&#39; Ratio&#39;])<br>plt.show()</pre><p>The ratio does look like it moved around a stable mean.The absolute ratio isn’t very useful in statistical terms. It is more helpful to normalize our signal by treating it as a z-score. Z score is defined as:</p><blockquote>Z Score (Value) = (Value — Mean) / Standard Deviation</blockquote><h4>WARNING</h4><p>In practice this is usually done to try to give some scale to the data, but this assumes an underlying distribution. Usually normal. However, much financial data is not normally distributed, and we must be very careful not to simply assume normality, or any specific distribution when generating statistics. The true distribution of ratios could be very fat-tailed and prone to extreme values messing up our model and resulting in large losses.</p><pre><strong>def</strong> zscore(series):<br>    <strong>return</strong> (series - series.mean()) / np.std(series)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/876/1*x8Jg3ccLWlasUYsZjbH-sw.png" /><figcaption>Z Score of Price Ratio between MSFT and ADBE from 2008–2017</figcaption></figure><pre>zscore(ratios).plot()<br>plt.axhline(zscore(ratios).mean())<br>plt.axhline(1.0, color=’red’)<br>plt.axhline(-1.0, color=’green’)<br>plt.show()</pre><p>It’s easier to now observe the ratio now moves around the mean, but sometimes is prone to large divergences from the mean, which we can take advantages of.</p><p>Now that we’ve talked about the basics of pair trading strategy, and identified co-integrated securities based on historical price, let’s try to develop a trading signal. First, let’s recap the steps in developing a trading signal using data techniques:</p><ul><li>Collect reliable Data and clean Data</li><li>Create features from data to identify a trading signal/logic</li><li>Features can be moving averages or ratios of price data, correlations or more complex signals — combine these to create new features</li><li>Generate a trading signal using these features, i.e which instruments are a buy, a sell or neutral</li></ul><h4>Step 1: Setup your problem</h4><p>Here we are trying to create a signal that tells us if the ratio is a buy or a sell at the next instant in time, i.e our prediction variable Y:</p><blockquote><em>Y = Ratio is buy (1) or sell (-1)</em></blockquote><blockquote><em>Y(t)= Sign( Ratio(t+1) — Ratio(t) )</em></blockquote><p>Note we don’t need to predict actual stock prices, or even actual value of ratio (though we could), just the direction of next move in ratio</p><h4>Step 2: Collect Reliable and Accurate Data</h4><p>Auquan Toolbox is your friend here! You only have to specify the stock you want to trade and the datasource to use, and it pulls the required data and cleans it for dividends and stock splits. So our data here is already clean.</p><p>We are using the following data from Yahoo at daily intervals for trading days over last 10 years (~2500 data points): Open, Close, High, Low and Trading Volume</p><h4>Step 3: Split Data</h4><p>Don’t forget this super important step to test accuracy of your models. We’re using the following Training/Validation/Test Split</p><ul><li>Training 7 years ~ 70%</li><li>Test ~ 3 years 30%</li></ul><pre>ratios = data[&#39;ADBE&#39;] / data[&#39;MSFT&#39;]<br>print(len(ratios))<br>train = ratios[:1762]<br>test = ratios[1762:]</pre><p>Ideally we should also make a validation set but we will skip this for now.</p><h4>Step 4: Feature Engineering</h4><p>What could relevant features be? We want to predict the direction of ratio move. We’ve seen that our two securities are cointegrated so the ratio tends to move around and revert back to the mean. It seems our features should be certain measures for the mean of the ratio, the divergence of the current value from the mean to be able to generate our trading signal.</p><p>Let’s use the following features:</p><ul><li>60 day Moving Average of Ratio: Measure of rolling mean</li><li>5 day Moving Average of Ratio: Measure of current value of mean</li><li>60 day Standard Deviation</li><li>z score: (5d MA — 60d MA) /60d SD</li></ul><pre>ratios_mavg5 = train.rolling(window=5,<br>                               center=False).mean()</pre><pre>ratios_mavg60 = train.rolling(window=60,<br>                               center=False).mean()</pre><pre>std_60 = train.rolling(window=60,<br>                        center=False).std()</pre><pre>zscore_60_5 = (ratios_mavg5 - ratios_mavg60)/std_60<br>plt.figure(figsize=(15,7))<br>plt.plot(train.index, train.values)<br>plt.plot(ratios_mavg5.index, ratios_mavg5.values)<br>plt.plot(ratios_mavg60.index, ratios_mavg60.values)</pre><pre>plt.legend([&#39;Ratio&#39;,&#39;5d Ratio MA&#39;, &#39;60d Ratio MA&#39;])</pre><pre>plt.ylabel(&#39;Ratio&#39;)<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/891/1*qtPw3JFKYZ2jPYplKXLY7g.png" /><figcaption>60d and 5d MA of Price Ratios</figcaption></figure><pre>plt.figure(figsize=(15,7))<br>zscore_60_5.plot()<br>plt.axhline(0, color=&#39;black&#39;)<br>plt.axhline(1.0, color=&#39;red&#39;, linestyle=&#39;--&#39;)<br>plt.axhline(-1.0, color=&#39;green&#39;, linestyle=&#39;--&#39;)<br>plt.legend([&#39;Rolling Ratio z-Score&#39;, &#39;Mean&#39;, &#39;+1&#39;, &#39;-1&#39;])<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/876/1*k2yRueePuewJd0KUVmqKrw.png" /><figcaption>60–5 ZScore of Price Ratio</figcaption></figure><p>The <em>Z Score </em>of the rolling means really brings out the mean reverting nature of the ratio!</p><h4>Step 5: Model Selection</h4><p>Let’s start with a really simple model. Looking at the z-score chart, we can see that whenever the z-score feature gets too high, or too low, it tends to revert back. Let’s use +1/-1 as our thresholds for too high and too low, then we can use the following model to generate a trading signal:</p><ul><li>Ratio is buy (1) whenever the z-score is below -1.0 because we expect z score to go back up to 0, hence ratio to increase</li><li>Ratio is sell(-1) when the z-score is above 1.0 because we expect z score to go back down to 0, hence ratio to decrease</li></ul><h4>Step 6: Train, Validate and Optimize</h4><p>Finally, let’s see how our model actually does on real data? Let’s see what this signal looks like on actual ratios</p><pre># Plot the ratios and buy and sell signals from z score<br>plt.figure(figsize=(15,7))</pre><pre>train[60:].plot()<br>buy = train.copy()<br>sell = train.copy()<br>buy[zscore_60_5&gt;-1] = 0<br>sell[zscore_60_5&lt;1] = 0<br>buy[60:].plot(color=’g’, linestyle=’None’, marker=’^’)<br>sell[60:].plot(color=’r’, linestyle=’None’, marker=’^’)<br>x1,x2,y1,y2 = plt.axis()<br>plt.axis((x1,x2,ratios.min(),ratios.max()))<br>plt.legend([‘Ratio’, ‘Buy Signal’, ‘Sell Signal’])<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/877/1*9JAmYCX1tsQJh84MhHtbuQ.png" /><figcaption>Buy and Sell Signal on Price Ratios</figcaption></figure><p>The signal seems reasonable, we seem to sell the ratio (red dots) when it is high or increasing and buy it back when it&#39;s low (green dots) and decreasing. What does that mean for actual stocks that we are trading? Let’s take a look</p><pre># Plot the prices and buy and sell signals from z score<br>plt.figure(figsize=(18,9))<br>S1 = data[&#39;ADBE&#39;].iloc[:1762]<br>S2 = data[&#39;MSFT&#39;].iloc[:1762]</pre><pre>S1[60:].plot(color=&#39;b&#39;)<br>S2[60:].plot(color=&#39;c&#39;)<br>buyR = 0*S1.copy()<br>sellR = 0*S1.copy()</pre><pre># When buying the ratio, buy S1 and sell S2<br>buyR[buy!=0] = S1[buy!=0]<br>sellR[buy!=0] = S2[buy!=0]<br># When selling the ratio, sell S1 and buy S2 <br>buyR[sell!=0] = S2[sell!=0]<br>sellR[sell!=0] = S1[sell!=0]</pre><pre>buyR[60:].plot(color=&#39;g&#39;, linestyle=&#39;None&#39;, marker=&#39;^&#39;)<br>sellR[60:].plot(color=&#39;r&#39;, linestyle=&#39;None&#39;, marker=&#39;^&#39;)<br>x1,x2,y1,y2 = plt.axis()<br>plt.axis((x1,x2,min(S1.min(),S2.min()),max(S1.max(),S2.max())))</pre><pre>plt.legend([&#39;ADBE&#39;,&#39;MSFT&#39;, &#39;Buy Signal&#39;, &#39;Sell Signal&#39;])<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UODoaLtnubtdGafkT9noPQ.png" /><figcaption>Buy and Sell Signals for MSFT and ADBE stocks</figcaption></figure><p>Notice how we sometimes make money on the short leg and sometimes on the long leg, and sometimes both.</p><p>We’re happy with our signal on the training data. Let’s see what kind of profits this signal can generate. We can make a simple backtester which buys 1 ratio (buy 1 ADBE stock and sell ratio x MSFT stock) when ratio is low, sell 1 ratio (sell 1 ADBE stock and buy ratio x MSFT stock) when it’s high and calculate PnL of these trades.</p><pre># Trade using a simple strategy<br>def trade(S1, S2, window1, window2):<br>    <br>    # If window length is 0, algorithm doesn&#39;t make sense, so exit<br>    if (window1 == 0) or (window2 == 0):<br>        return 0<br>    <br>    # Compute rolling mean and rolling standard deviation<br>    ratios = S1/S2<br>    ma1 = ratios.rolling(window=window1,<br>                               center=False).mean()<br>    ma2 = ratios.rolling(window=window2,<br>                               center=False).mean()<br>    std = ratios.rolling(window=window2,<br>                        center=False).std()<br>    zscore = (ma1 - ma2)/std<br>    <br>    # Simulate trading<br>    # Start with no money and no positions<br>    money = 0<br>    countS1 = 0<br>    countS2 = 0<br>    for i in range(len(ratios)):<br>        # Sell short if the z-score is &gt; 1<br>        if zscore[i] &gt; 1:<br>            money += S1[i] - S2[i] * ratios[i]<br>            countS1 -= 1<br>            countS2 += ratios[i]<br>            print(&#39;Selling Ratio %s %s %s %s&#39;%(money, ratios[i], countS1,countS2))<br>        # Buy long if the z-score is &lt; 1<br>        elif zscore[i] &lt; -1:<br>            money -= S1[i] - S2[i] * ratios[i]<br>            countS1 += 1<br>            countS2 -= ratios[i]<br>            print(&#39;Buying Ratio %s %s %s %s&#39;%(money,ratios[i], countS1,countS2))<br>        # Clear positions if the z-score between -.5 and .5<br>        elif abs(zscore[i]) &lt; 0.75:<br>            money += S1[i] * countS1 + S2[i] * countS2<br>            countS1 = 0<br>            countS2 = 0<br>            print(&#39;Exit pos %s %s %s %s&#39;%(money,ratios[i], countS1,countS2))<br>            <br>            <br>    return money</pre><pre>trade(data[&#39;ADBE&#39;].iloc[:1763], data[&#39;MSFT&#39;].iloc[:1763], 5, 60)</pre><p>629.71</p><p>So that strategy seems profitable! Now we can optimize further by changing our moving average windows, by changing the thresholds for buy/sell and exit positions etc and check for performance improvements on validation data.</p><p>We could also try more sophisticated models like Logisitic Regression, SVM etc to make our 1/-1 predictions.</p><p>For now, let’s say we decide to go forward with this model, this brings us to</p><h4>Step 7: Backtest on Test Data</h4><p>Backtesting is simple, we can just use our function from above to see PnL on test data</p><pre>trade(data[‘ADBE’].iloc[1762:], data[‘MSFT’].iloc[1762:], 5, 60)</pre><p>1017.61</p><p>The model does quite well! This makes our first simple pairs trading model.</p><h3>Avoid Overfitting</h3><p>Before ending the discussion, we’d like to give special mention to overfitting. Overfitting is the most dangerous pitfall of a trading strategy. An overfit algorithm may perform wonderfully on a backtest but fails miserably on new unseen data — this mean it has not really uncovered any trend in data and no real predictive power. Let’s take a simple example.</p><p>In our model, we used rolling parameter estimates and may wish to optimize window length. We may decide to simply iterate over all possible, reasonable window length and pick the length based on which our model performs the best . Below we write a simple loop to to score window lengths based on pnl of training data and find the best one.</p><pre># Find the window length 0-254 <br># that gives the highest returns using this strategy<br>length_scores = [trade(data[&#39;ADBE&#39;].iloc[:1762], <br>                data[&#39;MSFT&#39;].iloc[:1762], l, 5) <br>                for l in range(255)]<br>best_length = np.argmax(length_scores)<br>print (&#39;Best window length:&#39;, best_length)</pre><pre>(&#39;Best window length:&#39;, 246)</pre><p>Now we check the performance of our model on test data and we find that this window length is far from optimal! This is because our original choice was clearly overfitted to the sample data.</p><pre># Find the returns for test data<br># using what we think is the best window length<br>length_scores2 = [trade(data[&#39;ADBE&#39;].iloc[1762:], <br>                  data[&#39;MSFT&#39;].iloc[1762:],l,5) <br>                  for l in range(255)]<br>print (best_length, &#39;day window:&#39;, length_scores2[best_length])</pre><pre># Find the best window length based on this dataset, <br># and the returns using this window length<br>best_length2 = np.argmax(length_scores2)<br>print (best_length2, &#39;day window:&#39;, length_scores2[best_length2])</pre><pre>(1, &#39;day window:&#39;, 10.06)<br>(218, &#39;day window:&#39;, 527.92)</pre><p>Clearly fitting to our sample data doesn&#39;t always give good results in the future. Just for fun, let&#39;s plot the length scores computed from the two datasets</p><pre>plt.figure(figsize=(15,7))<br>plt.plot(length_scores)<br>plt.plot(length_scores2)<br>plt.xlabel(&#39;Window length&#39;)<br>plt.ylabel(&#39;Score&#39;)<br>plt.legend([&#39;Training&#39;, &#39;Test&#39;])<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/897/1*ULK9DQwP4Bryw7vK6qTRVQ.png" /></figure><p>We can see that anything above about 90 would be a good choice for our window.</p><p>To avoid overfitting, we can use economic reasoning or the nature of our algorithm to pick our window length. We can also use Kalman filters, which do not require us to specify a length; this method will be covered in another notebook later.</p><h4>Next Steps</h4><p>In this post, we presented some simple introductory approaches to demonstrate the process of developing a pairs trading strategy. In practice one should use more sophisticated statistics, some of which are listed here</p><ul><li>Hurst exponent</li><li>Half-life of mean reversion inferred from an Ornstein–Uhlenbeck process</li><li>Kalman filters</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7dbedafcfe5a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/pairs-trading-data-science-7dbedafcfe5a">Pairs Trading using Data-Driven Techniques: Simple Trading Strategies Part 3</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Time Series Analysis for Financial Data VI— GARCH model and predicting SPX returns]]></title>
            <link>https://medium.com/auquan/time-series-analysis-for-finance-arch-garch-models-822f87f1d755?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/822f87f1d755</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[math]]></category>
            <category><![CDATA[mathematics]]></category>
            <category><![CDATA[finance]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Wed, 13 Dec 2017 07:06:01 GMT</pubDate>
            <atom:updated>2017-12-19T15:14:33.317Z</atom:updated>
            <content:encoded><![CDATA[<p>Download the<a href="https://github.com/Auquan/Tutorials/blob/master/Time%20Series%20Analysis%20-%204.ipynb"> iPython notebook here</a></p><p>In this <a href="https://medium.com/auquan/tagged/mathematics">mini series on Time Series modelling for Financial Data</a>, so far we’ve used AR, MA and a combination of these models on asset prices to try and model how our asset behaves. We’ve found that we were able to model certain time periods well with these models and failed at other times.</p><p>This was because of volatility clustering or heteroskedasticity. In this post, we will discuss conditional heteroskedasticity, leading us to our first conditional heteroskedastic model, known as <strong><em>ARCH</em></strong>. Then we will discuss extensions to ARCH, leading us to the famous Generalised Autoregressive Conditional Heteroskedasticity model of order p,q, also known as <strong><em>GARCH(p,q)</em></strong>. GARCH is used extensively within the financial industry as many asset prices are conditional heteroskedastic.</p><p>Let’s do a quick recap first:</p><p>We have considered the following models so far in this series (it is recommended reading the series in order if you have not done so already):</p><ul><li><a href="https://medium.com/auquan/time-series-analysis-for-financial-data-part-1-stationarity-autocorrelation-and-white-noise-1a1cc2fb23f2">Discrete White Noise and Random Walks</a></li><li><a href="https://medium.com/auquan/time-series-analysis-ii-auto-regressive-models-d0cb1a8a7c43">Auto Regresssive Models AR(p)</a></li><li><a href="https://medium.com/auquan/time-series-analysis-for-financial-data-iii-moving-average-models-cccf027f264e">Moving Average Models MA(q)</a></li><li><a href="https://medium.com/auquan/time-series-analysis-for-finance-arma-models-21695e14c999">Auto Regresssive Moving Average Models ARMA(p,q)</a> and</li><li><a href="https://medium.com/auquan/time-series-analysis-for-finance-arima-models-acb5e39999df">Auto Regresssive Integrated Moving Average Models ARIMA(p,d,q)</a></li></ul><p>Now we are at the final piece of the puzzle. We need a model to examine conditional heteroskedasticity in financial series that exhibit volatility clustering.</p><blockquote>What is conditional heteroskedasticity?</blockquote><p>Conditional heteroskedasticity exists in finance because asset returns are volatile.</p><p>A collection of random variables is <strong>heteroskedastic</strong> if there are subsets of variables within the larger set that have a different variance from the remaining variables.</p><p>Consider a day when equities markets undergo a substantial drop. The market gets into panic mode, automated risk management systems start getting of their long positions by selling their positions and all of this leads to a further fall in prices. An increase in variance from the initial price drop leads to to significant further downward volatility.</p><p>That is, an increase in variance is serially correlated to a further increase in variance in such a “sell-off” period. Or looking at it the other way around, a period of of increased variance is conditional on an initial sell-off . Thus we say that such series are <strong>conditional heteroskedastic.</strong></p><p>Conditionally heteroskedastic(CH) series are non stationary since its variance is not constant in time. One of the challenging aspects of conditional heteroskedastic series is ACF plots of a series with volatility might still appear to be a realisation of stationary discrete white noise.</p><p>How can we incorporate CH in our model? One way could be to create an AR model for the variance itself — a model that actually accounts for the changes in the variance over time using past values of the variance.</p><p>This is the basis of the Autoregressive Conditional Heteroskedastic (ARCH) model.</p><h3>Autoregressive Conditionally Heteroskedastic Models — ARCH(p)</h3><p>ARCH(p) model is simply an AR(p) model applied to the variance of a time series.</p><p>ARCH(1) is given by:</p><blockquote>Var<em>(x(t)) = </em>σ²(t) = <em>⍺*</em>σ²<em>(t-1) + </em>⍺1</blockquote><p>The actual time series is given by:</p><blockquote>x(t) = w(t)* σ(t) = w(t)* ⎷(⍺*σ²(t-1) + ⍺1)</blockquote><p>where w(t) is white noise</p><h4>When To Apply ARCH(p)?</h4><p>Let’s say we fit an AR(p) model and the residuals look almost like white noise but we are concerned about decay of the <em>p</em> lag on a ACF plot of the series. If we find that we can apply an AR(p) to the square of residuals as well, then we have an indication that an ARCH(p) process may be appropriate.</p><p><strong>Note that ARCH(p) should only ever be applied to a series that has already had an appropriate model fitted sufficient to leave the residuals looking like discrete white noise</strong>. Since we can only tell whether ARCH is appropriate or not by squaring the residuals and examining the ACF, we also need to ensure that the mean of the residuals is zero.</p><p>ARCH should only ever be applied to series that do not have any trends or seasonal effects, i.e. that has no (evident) serially correlation. ARIMA is often applied to such a series, at which point ARCH may be a good fit.</p><pre><em># Simulate ARCH(1) series</em><br><em># Var(yt) = a_0 + a_1*y{t-1}**2</em><br><em># if a_1 is between 0 and 1 then yt is white noise</em><br><br>np.random.seed(13)<br><br>a0 = 2<br>a1 = .5<br><br>y = w = np.random.normal(size=1000)<br>Y = np.empty_like(y)<br><br><strong>for</strong> t <strong>in</strong> range(len(y)):<br>    y[t] = w[t] * np.sqrt((a0 + a1*y[t-1]**2))<br><br><em># simulated ARCH(1) series, looks like white noise</em><br>tsplot(y, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sh9NHLvF-mWw8I3Jadoz3A.png" /><figcaption>ARCH(1) series</figcaption></figure><p>Notice the time series looks just like white noise. However, let’s see what happens when we plot the square of the series.</p><pre>tsplot(y**2, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3zSEVwOsMjap1cB1CsmDtg.png" /><figcaption>Square of ARCH(1) series</figcaption></figure><p>Now the ACF, and PACF seem to show significance at lag 1 indicating an AR(1) model for the variance may be appropriate.</p><p>An obvious question to ask at this stage is if we are going to apply an AR(p) process to the variance, why not a Moving Average MA(q) model as well? Or a mixed model such as ARMA(p,q)?</p><p>This is actually the motivation for the Generalised ARCH model, known as GARCH.</p><h3>Generalized Autoregressive Conditionally Heteroskedastic Models — GARCH(p,q)</h3><p>Just like <em>ARCH(p)</em> is <em>AR(p)</em> applied to the variance of a time series, <em>GARCH(p, q)</em> is an <em>ARMA(p,q)</em> model applied to the variance of a time series. The AR(p) models the variance of the residuals (squared errors) or simply our time series squared. The MA(q) portion models the variance of the process.</p><p>The GARCH(1,1) model is:</p><blockquote>σ²(t)<em> = a*</em>σ²(t-1)<em> + b*e²(t-1) + w</em></blockquote><p><em>(a+b)</em> must be less than 1 or the model is unstable. We can simulate a GARCH(1, 1) process below.</p><pre><em># Simulating a GARCH(1, 1) process</em><br><br>np.random.seed(2)<br><br>a0 = 0.2<br>a1 = 0.5<br>b1 = 0.3<br><br>n = 10000<br>w = np.random.normal(size=n)<br>eps = np.zeros_like(w)<br>sigsq = np.zeros_like(w)<br><br><strong>for</strong> i <strong>in</strong> range(1, n):<br>    sigsq[i] = a0 + a1*(eps[i-1]**2) + b1*sigsq[i-1]<br>    eps[i] = w[i] * np.sqrt(sigsq[i])<br><br>_ = tsplot(eps, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9p2yqCl2mv8rBL9ngPOCNw.png" /><figcaption>GARCH(1,1) process</figcaption></figure><p>Again, notice that overall this process closely resembles white noise, however take a look when we view the squared eps series.</p><pre>_ = tsplot(eps**2, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aQLEt2zx7HeG6v7KM_j92w.png" /><figcaption>Square of GARCH(1,1) process</figcaption></figure><p>There is substantial evidence of a conditionally heteroskedastic process via the decay of successive lags. The significance of the lags in both the ACF and PACF indicate we need both AR and MA components for our model. Let’s see if we can recover our process parameters using a GARCH(1, 1) model. Here we make use of the arch_model function from the ARCH package.</p><pre><em># Fit a GARCH(1, 1) model to our simulated EPS series</em><br><em># We use the arch_model function from the ARCH package</em></pre><pre>am = arch_model(eps)<br>res = am.fit(update_freq=5)<br><strong>print</strong>(res.summary())</pre><pre>Iteration:      5,   Func. Count:     38,   Neg. LLF: 12311.7950557<br>Iteration:     10,   Func. Count:     71,   Neg. LLF: 12238.5926559<br>Optimization terminated successfully.    (Exit mode 0)<br>            Current function value: 12237.3032673<br>            Iterations: 13<br>            Function evaluations: 89<br>            Gradient evaluations: 13<br>                     Constant Mean - GARCH Model Results                      <br>====================================================================<br>Dep. Variable:                 y   R-squared:                 -0.000<br>Mean Model:        Constant Mean   Adj. R-squared:            -0.000<br>Vol Model:                 GARCH   Log-Likelihood:          -12237.3<br>Distribution:             Normal   AIC:                      24482.6<br>Method:       Maximum Likelihood   BIC:                      24511.4<br>                                   No. Observations:           10000<br>Date:           Tue, Feb 28 2017   Df Residuals:                9996<br>Time:                   20:52:48   Df Model:                       4<br>                             Mean Model                                  <br>====================================================================<br>                coef    std err      t  P&gt;|t|     95.0% Conf. Int.<br>--------------------------------------------------------------------<br>mu       -6.7225e-03  6.735e-03 -0.998  0.318 [-1.992e-02,6.478e-03]<br>                            Volatility Model                            <br>====================================================================<br>               coef    std err        t      P&gt;|t|  95.0% Conf. Int.<br>--------------------------------------------------------------------<br>omega        0.2021  1.043e-02   19.383  1.084e-83 [  0.182,  0.223]<br>alpha[1]     0.5162  2.016e-02   25.611 1.144e-144 [  0.477,  0.556]<br>beta[1]      0.2879  1.870e-02   15.395  1.781e-53 [  0.251,  0.325]<br>====================================================================</pre><pre>Covariance estimator: robust</pre><p>We can see that the true parameters all fall within the respective confidence intervals.</p><h3>Application to Financial Time Series</h3><p>Now apply the procedure to a financial time series. Here we’re going to use SPX returns. The process is as follows:</p><ul><li>Iterate through combinations of ARIMA(p, d, q) models to best fit our time series.</li><li>Pick the GARCH model orders according to the ARIMA model with lowest AIC.</li><li>Fit the GARCH(p, q) model to our time series.</li><li>Examine the model residuals and squared residuals for autocorrelation</li></ul><p>Here, we first try to fit SPX return to an ARIMA process and find the best order.</p><pre>import auquanToolbox.dataloader as dl</pre><pre>end = ‘2017–01–01’<br>start = ‘2010–01–01’<br>symbols = [‘SPX’]<br>data = dl.load_data_nologs(‘nasdaq’, symbols , start, end)[‘ADJ CLOSE’]<br># log returns<br>lrets = np.log(data/data.shift(1)).dropna()</pre><pre><strong>def</strong> _get_best_model(TS):<br>    best_aic = np.inf <br>    best_order = None<br>    best_mdl = None</pre><pre>    pq_rng = range(5) <em># [0,1,2,3,4]</em><br>    d_rng = range(2) <em># [0,1]</em><br>    <strong>for</strong> i <strong>in</strong> pq_rng:<br>        <strong>for</strong> d <strong>in</strong> d_rng:<br>            <strong>for</strong> j <strong>in</strong> pq_rng:<br>                <strong>try</strong>:<br>                    tmp_mdl = smt.ARIMA(TS, order=(i,d,j)).fit(<br>                        method=&#39;mle&#39;, trend=&#39;nc&#39;<br>                    )<br>                    tmp_aic = tmp_mdl.aic<br>                    <strong>if</strong> tmp_aic &lt; best_aic:<br>                        best_aic = tmp_aic<br>                        best_order = (i, d, j)<br>                        best_mdl = tmp_mdl<br>                <strong>except</strong>: <strong>continue</strong><br>    <strong>print</strong>(&#39;aic: {:6.2f} | order: {}&#39;.format(best_aic, best_order))                    <br>    <strong>return</strong> best_aic, best_order, best_mdl</pre><pre>TS = lrets.SPX<br>res_tup = _get_best_model(TS)</pre><p>aic: -11323.07 | order: (3, 0, 3)</p><pre>order = res_tup[1]<br>model = res_tup[2]</pre><p>Since we&#39;ve already taken the log of returns, we should expect our integrated component d to equal zero, which it does. We find the best model is ARIMA(3,0,3). Now we plot the residuals to decide if they possess evidence of conditional heteroskedastic behaviour</p><pre>tsplot(model.resid, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BUPg4--BzkOwIZNADGEw3w.png" /></figure><p>We find the residuals look like white noise. Let’s look at the square of residuals</p><pre>tsplot(model.resid**2, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hDpMGo1ENrsLktXYVE16aQ.png" /></figure><p>We can see clear evidence of autocorrelation in squared residuals. Let’s fit a GARCH model and see how it does.</p><pre><em># Now we can fit the arch model using the best fit arima model parameters</em></pre><pre>p_ = order[0]<br>o_ = order[1]<br>q_ = order[2]</pre><pre><br>am = arch_model(model.resid, p=p_, o=o_, q=q_, dist=&#39;StudentsT&#39;)<br>res = am.fit(update_freq=5, disp=&#39;off&#39;)<br><strong>print</strong>(res.summary())</pre><pre>              Constant Mean - GARCH Model Results                         <br>====================================================================<br>Dep. Variable:                    None   R-squared:       -56917.881<br>Mean Model:              Constant Mean   Adj. R-squared:  -56917.881<br>Vol Model:                       GARCH   Log-Likelihood:    -4173.44<br>Distribution: Standardized Student&#39;s t   AIC:                8364.88<br>Method:             Maximum Likelihood   BIC:                8414.15<br>                                         No. Observations:      1764<br>Date:                 Tue, Feb 28 2017   Df Residuals:          1755<br>Time:                         20:53:30   Df Model:                 9<br>                               Mean Model                               <br>====================================================================<br>               coef    std err        t      P&gt;|t|  95.0% Conf. Int.<br>--------------------------------------------------------------------<br>mu          -2.3189  9.829e-03 -235.934      0.000 [ -2.338, -2.300]<br>                            Volatility Model                              <br>====================================================================<br>               coef  std err      t     P&gt;|t|       95.0% Conf. Int.<br>--------------------------------------------------------------------<br>omega    1.2926e-04 2.212e-04 0.584     0.559 [-3.043e-04,5.628e-04]<br>alpha[1]     0.0170 1.547e-02 1.099     0.272 [-1.332e-02,4.733e-02]<br>alpha[2]     0.4638 0.207     2.241 2.500e-02    [5.824e-02,  0.869]<br>alpha[3]     0.5190 0.213     2.437 1.482e-02      [  0.102,  0.937]<br>beta[1]  7.9655e-05 0.333 2.394e-04     1.000      [ -0.652,  0.652]<br>beta[2]  3.8056e-05 0.545 6.980e-05     1.000      [ -1.069,  1.069]<br>beta[3]  1.6184e-03 0.312 5.194e-03     0.996      [ -0.609,  0.612]<br>                              Distribution                              <br>====================================================================<br>               coef    std err        t      P&gt;|t|  95.0% Conf. Int.<br>--------------------------------------------------------------------<br>nu           7.7912      0.362   21.531 8.018e-103 [  7.082,  8.500]<br>====================================================================<br><br>Covariance estimator: robust</pre><p>Let’s plot the residuals again</p><pre>tsplot(res.resid, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yGasCrAn3o0DhSU_f_KvYA.png" /></figure><p>The plots looks like a realisation of a discrete white noise process, indicating a good fit. Let’s plot a square of residuals to be sure</p><pre>tsplot(res.resid**2, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5IIGQ3lQGirskcxrOFUZSg.png" /></figure><p>We have what looks like a realisation of a discrete white noise process, indicating that we have “explained” the serial correlation present in the squared residuals with an appropriate mixture of ARIMA(p,d,q) and GARCH(p,q).</p><h3>Next Steps — Sample Trading Strategy</h3><p>We are now at the point in our time series analysis where we have studied ARIMA and GARCH, allowing us to fit a combination of these models to a stock market index, and to determine if we have achieved a good fit or not.</p><p>The next step is to actually produce forecasts of future daily returns values from this combination and use it to create a basic trading strategy for the S&amp;P500.</p><pre>import auquanToolbox.dataloader as dl</pre><pre>end = &#39;2016-11-30&#39;<br>start = &#39;2000-01-01&#39;<br>symbols = [&#39;SPX&#39;]<br>data = dl.load_data_nologs(&#39;nasdaq&#39;, symbols ,<br>                           start, end)[&#39;ADJ CLOSE&#39;]<br># log returns<br>lrets = np.log(data/data.shift(1)).dropna()</pre><h3>Strategy Overview</h3><p>Let’s try to create a simple strategy using our knowledge so far about ARIMA and GARCH models. The idea of this strategy is as below:</p><ul><li>Fit an ARIMA and GARCH model everyday on log of S&amp;P 500 returns for previous <strong><em>T</em></strong> days</li><li>Use the combined model to make a prediction for the next day’s return</li><li>If the prediction is positive, buy the stock and if negative, short the stock at today’s close</li><li>If the prediction is the same as the previous day then do nothing</li></ul><h3>Strategy Implementation</h3><p>Let’s start by choosing an appropriate window <strong><em>T o</em></strong>f previous days we are going to use to make a prediction. We are going to use <strong><em>T = 252 (1 year)</em></strong>, but this parameter should be optimised in order to improve performance or reduce drawdown.</p><pre>windowLength = 252</pre><p>We will now attempt to generate a trading signal for <em>length(data)- T </em>days</p><pre>foreLength = len(lrets) - windowLength<br>signal = 0*lrets[-foreLength:]</pre><p>To backtest our strategy, let’s loop through every day in the trading data and fit an appropriate ARIMA and GARCH model to the rolling window of length 252. We’ve defined the functions to fit ARIMA and GARCH above (Given that we try 32 separate ARIMA fits and fit a GARCH model, for each day, the indicator can take a long time to generate)</p><pre>for d in range(foreLength):<br>    <br>    # create a rolling window by selecting <br>    # values between d+1 and d+T of S&amp;P500 returns<br>    <br>    TS = lrets[(1+d):(windowLength+d)] <br>    <br>    # Find the best ARIMA fit <br>    # set d = 0 since we&#39;ve already taken log return of the series<br>    res_tup = _get_best_model(TS)<br>    order = res_tup[1]<br>    model = res_tup[2]<br>    <br>    #now that we have our ARIMA fit, we feed this to GARCH model<br>    p_ = order[0]<br>    o_ = order[1]<br>    q_ = order[2]<br>    <br>    am = arch_model(model.resid, p=p_, o=o_, q=q_, dist=&#39;StudentsT&#39;)<br>    res = am.fit(update_freq=5, disp=&#39;off&#39;)<br>    <br>    # Generate a forecast of next day return using our fitted model<br>    out = res.forecast(horizon=1, start=None, align=&#39;origin&#39;)<br>    <br>    #Set trading signal equal to the sign of forecasted return<br>    # Buy if we expect positive returns, sell if negative<br>      <br>    signal.iloc[d] = np.sign(out.mean[&#39;h.1&#39;].iloc[-1])</pre><p><strong>Note: </strong><em>The backtest is doesn&#39;t take commission or slippage into account, hence the performance achieved in a real trading system would be lower than what you see here.</em></p><h3>Strategy Results</h3><p>Now that we have generated our signals, we need to compare its performance to ‘<em>Buy and Hold</em>’: what would our returns be if we simply bought the S&amp;P 500 at the start of our backtest period.</p><pre>returns = pd.DataFrame(index = signal.index, <br>                       columns=[&#39;Buy and Hold&#39;, &#39;Strategy&#39;])<br>returns[&#39;Buy and Hold&#39;] = lrets[-foreLength:]<br>returns[&#39;Strategy&#39;] = signal[&#39;SPX&#39;]*returns[&#39;Buy and Hold&#39;]</pre><pre>eqCurves = pd.DataFrame(index = signal.index, <br>                       columns=[&#39;Buy and Hold&#39;, &#39;Strategy&#39;])<br>eqCurves[&#39;Buy and Hold&#39;]=returns[&#39;Buy and Hold&#39;].cumsum()+1<br>eqCurves[&#39;Strategy&#39;] = returns[&#39;Strategy&#39;].cumsum()+1</pre><pre>eqCurves[&#39;Strategy&#39;].plot(figsize=(10,8))<br>eqCurves[&#39;Buy and Hold&#39;].plot()<br>plt.legend()<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/877/1*n2A8nArK17y8zvydEKtz7A.png" /><figcaption>Long/Short SPX strategy based GARCH + ARIMA model from 2000–2016</figcaption></figure><p>We find the model does outperform a naive Buy and Hold strategy. However, the model doesn’t perform well all the time, you can see majority of the gains have happened during short durations in 2000–2001 and 2008. It seems there are certain market conditions when the model does exceedingly well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/591/1*fGt9yD-V4GcJXEsPo51ghg.png" /><figcaption>Long/Short SPX strategy based GARCH + ARIMA model from 2000–2003</figcaption></figure><p>In periods of high volatility, or when S&amp;P 500 had periods of ‘<em>sell-off’ , such as 2000–2002 or the crash of 2008–09, </em>the strategy does extremely well, possibly because our GARCH model captures the conditional volatility well. During periods of uptrend in S&amp;P500, such as the bull run from 2002–2007 the model performs on par with S&amp;P 500.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/610/1*1TwYcwsFpDZODuevVWfxiQ.png" /><figcaption>Long/Short SPX strategy based GARCH + ARIMA model from 2003–2007 bull period</figcaption></figure><p>In the current bull run from 2009, the model has performed poorly compared to S&amp;P 500. The index behaved like what looks to be more a stochastic trend, the model performance suffered in this duration.</p><p>There are some caveats here: We don’t account for slippages or trading costs here, which would significantly eat into profits. Also, we’ve performed a backtest on a stock market index and not a tradeable instrument. Ideally, we should perform the same modelling and backtest on S&amp;P500 futures or a Exchange Traded Fund (ETF) like SPY .</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/591/1*-6ZJBDCrdJe-Q-60dl7Hgg.png" /><figcaption>Long/Short SPX strategy based GARCH + ARIMA model during crash of 2008–09</figcaption></figure><p>This strategy can be easily applied to other stock market indices, other regions, equities or other asset classes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/591/1*QK6Wi_EtIaIxP8Bp-lfBoA.png" /><figcaption>Long/Short SPX strategy based GARCH + ARIMA model from 2009-present</figcaption></figure><p>You should try researching other instruments, playing with window parameters and see if you can make improvements on the results presented here. Other improvements to the strategy could include buying/selling only if predicted returns are more or less than a certain threshold, incorporating variance of prediction into the strategy etc.</p><p>If you do find interesting strategies, participate in our competition, <a href="http://quant-quest.auquan.com/">QuantQuest</a> and earn profit shares on your strategies!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=822f87f1d755" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/time-series-analysis-for-finance-arch-garch-models-822f87f1d755">Time Series Analysis for Financial Data VI— GARCH model and predicting SPX returns</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Time Series Analysis for Financial Data V — ARIMA Models]]></title>
            <link>https://medium.com/auquan/time-series-analysis-for-finance-arima-models-acb5e39999df?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/acb5e39999df</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[math]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[mathematics]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Thu, 07 Dec 2017 16:01:01 GMT</pubDate>
            <atom:updated>2017-12-13T22:15:56.486Z</atom:updated>
            <content:encoded><![CDATA[<p>Download IPython Notebook <a href="https://github.com/Auquan/Tutorials/blob/master/Time%20Series%20Analysis%20-%203.ipynb">here</a>.</p><p>In the <a href="https://medium.com/auquan/time-series-analysis-for-finance-arma-models-21695e14c999">previous posts in this series</a>, we combined the Autoregressive models and Moving Average models to produce Auto Regressive Moving Average(ARMA) models. We found that we were still unable to fully explain autocorrelation or obtain residuals that are discrete white noise.</p><p>Let’s further extend this discussion of merging AR and MA models to Auto Regressive Integrated Moving Average(ARIMA) models and see what we get.</p><h3>Autoregressive Integrated Moving Average Models — ARIMA(p, d, q)</h3><p>ARIMA is a natural extension to the class of ARMA models — they can reduce a non-stationary series to a stationary series using a sequence of differences.</p><p>We’ve seen that many of our TS are not stationary, however they can be made stationary by differencing. We saw an example of this in <a href="https://medium.com/auquan/time-series-analysis-for-financial-data-part-1-stationarity-autocorrelation-and-white-noise-1a1cc2fb23f2">part 1 of the post</a> when we took the first difference of non-stationary Guassian random walk and proved that it equals stationary white noise.</p><p>ARIMA essentially performs same function, but does so repeatedly, <em>d</em> times, in order to reduce a non-stationary series to a stationary one.</p><blockquote>A time series <strong><em>x(t)</em></strong><em>, </em>is integrated of order <em>d</em> if differencing the series d times results in a discrete white noise series.</blockquote><p>A time series <em>x(t)</em> is <em>ARIMA(p,d,q)</em> model if the series is differenced <em>d</em> times, and it then follows an <em>ARMA(p,q)</em> process.</p><p>Let’s simulate an ARIMA(2,1,1) model, with alphas equal to [0.5,-0.25] and beta = [-0.5] . We will fit an ARIMA model to our simulated data, attempt to recover the parameters.</p><pre><em># Simulate an ARIMA(2,1,1) model <br># alphas=[0.5,-0.25] <br># betas=[-0.5]</em><br><br>max_lag = 30<br><br>n = int(5000)<br>burn = 2000<br><br>alphas = np.array([0.5,-0.25])<br>betas = np.array([-0.5])<br><br>ar = np.r_[1, -alphas]<br>ma = np.r_[1, betas]<br><br>arma11 = smt.arma_generate_sample(ar=ar, ma=ma, nsample=n, burnin=burn)<br>arima111 = arma11.cumsum()<br>_ = tsplot(arima111, lags=max_lag)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/712/1*Mw6AP8yk_2GGQCnz_Kzn8g.png" /><figcaption><em>ARIMA(2,1,1) model</em></figcaption></figure><pre><em># Fit ARIMA(p, d, q) model</em><br><em># pick best order and final model based on aic</em><br><br>best_aic = np.inf <br>best_order = None<br>best_mdl = None<br><br>pq_rng = range(5) <em># [0,1,2,3]</em><br>d_rng = range(2) <em># [0,1]</em><br><strong>for</strong> i <strong>in</strong> pq_rng:<br>    <strong>for</strong> d <strong>in</strong> d_rng:<br>        <strong>for</strong> j <strong>in</strong> pq_rng:<br>            <strong>try</strong>:<br>                tmp_mdl = smt.ARIMA(arima111, <br>                                    order=(i,d,j)).fit(method=&#39;mle&#39;,<br>                                    trend=&#39;nc&#39;)<br>                tmp_aic = tmp_mdl.aic<br>                <strong>if</strong> tmp_aic &lt; best_aic:<br>                    best_aic = tmp_aic<br>                    best_order = (i, d, j)<br>                    best_mdl = tmp_mdl<br>            <strong>except</strong>: <strong>continue</strong><br><br><br><strong>print</strong>(&#39;aic: <strong>%6.2f</strong> | order: <strong>%s</strong>&#39;%(best_aic, best_order))<br><br><em># ARIMA model resid plot</em><br>_ = tsplot(best_mdl.resid, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/712/1*xHqf1rPHVJ3ch6rlkdEyzw.png" /><figcaption>Residuals of ARIMA(2,1,1) fit</figcaption></figure><p><strong>aic: 14227.34 | order: (2, 1, 1)</strong></p><p>As expected, we predict a ARIMA(2,1,1) model and the residuals looking like a realisation of discrete white noise:</p><pre>sms.diagnostic.acorr_ljungbox(best_mdl.resid, lags=[20], boxpierce=False)</pre><pre><strong>from</strong> <strong>statsmodels.stats.stattools</strong> <strong>import</strong> jarque_bera</pre><pre>score, pvalue, _, _ = jarque_bera(mdl.resid)</pre><pre><strong>if</strong> pvalue &lt; 0.10:<br>    <strong>print</strong> &#39;The residuals may not be normally distributed.&#39;<br><strong>else</strong>:<br>    <strong>print</strong> &#39;The residuals seem normally distributed.&#39;</pre><pre>(array([ 13.88378716]), array([ 0.83633895]))<br>The residuals seem normally distributed.</pre><p>We perform the Ljung-Box test and find the p-value is significantly larger than 0.05 and as such we can state that there is strong evidence for discrete white noise being a good fit to the residuals. Hence, the ARIMA(2,1,1) model is a good fit, as expected.</p><h3>Modelling SPX returns</h3><p>Let’s now iterate through a non-trivial number of combinations of (p, d, q) orders, to find the best ARIMA model to fit SPX returns. We use the AIC to evaluate each model. The lowest AIC wins.</p><pre><em># Fit ARIMA(p, d, q) model to SPX log returns</em><br><em># pick best order and final model based on aic</em><br><br>best_aic = np.inf <br>best_order = None<br>best_mdl = None<br><br>pq_rng = range(5) <em># [0,1,2,3]</em><br>d_rng = range(2) <em># [0,1]</em><br><strong>for</strong> i <strong>in</strong> pq_rng:<br>    <strong>for</strong> d <strong>in</strong> d_rng:<br>        <strong>for</strong> j <strong>in</strong> pq_rng:<br>            <strong>try</strong>:<br>                tmp_mdl = smt.ARIMA(lrets.SPX, <br>                          order=(i,d,j)).fit(method=&#39;mle&#39;,<br>                          trend=&#39;nc&#39;)<br>                tmp_aic = tmp_mdl.aic<br>                <strong>if</strong> tmp_aic &lt; best_aic:<br>                    best_aic = tmp_aic<br>                    best_order = (i, d, j)<br>                    best_mdl = tmp_mdl<br>            <strong>except</strong>: <strong>continue</strong><br><br><br><strong>print</strong>(&#39;aic: {:6.2f} | order: {}&#39;.format(best_aic, best_order))<br><br><em># ARIMA model resid plot</em><br>_ = tsplot(best_mdl.resid, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/712/1*J9OUMYYRcuThtR-KFda_9g.png" /><figcaption>Residuals of Modelling SPX returns from 2007–2015 as ARIMA(3,0,2) model</figcaption></figure><pre>aic: -11515.95 | order: (3, 0, 2)</pre><p>Note that the best model has a differencing of 0. This is expected because we already took the first difference of log prices to calculate the stock returns. The result is essentially identical to the <a href="https://medium.com/auquan/time-series-analysis-for-finance-arma-models-21695e14c999">ARMA(3, 2) model we fit in the previous post.</a> Clearly this ARIMA model has not explained the conditional volatility in the series either! The ljung box test below also shows a pvalue of less than 0.05</p><pre>sms.diagnostic.acorr_ljungbox(best_mdl.resid, lags=[20], boxpierce=False</pre><p><strong>(array([ 39.20689685]), array([ 0.00628326]))</strong></p><h3>Excluding periods of Conditional Volatility</h3><p>Let’s now try the same model on SPX data from 2010–2016</p><pre>end = &#39;2016-01-01&#39;<br>start = &#39;2010-01-01&#39;<br>symbols = [&#39;SPX&#39;]<br>data = dl.load_data_nologs(&#39;nasdaq&#39;, symbols , start, end)[&#39;ADJ CLOSE&#39;]<br><em># log returns</em><br>lrets = np.log(data/data.shift(1)).dropna()</pre><pre><em># Fit ARIMA(p, d, q) model to SPX log returns</em><br><em># pick best order and final model based on aic</em></pre><pre>best_aic = np.inf <br>best_order = None<br>best_mdl = None</pre><pre>pq_rng = range(5) <em># [0,1,2,3]</em><br>d_rng = range(2) <em># [0,1]</em><br><strong>for</strong> i <strong>in</strong> pq_rng:<br>    <strong>for</strong> d <strong>in</strong> d_rng:<br>        <strong>for</strong> j <strong>in</strong> pq_rng:<br>            <strong>try</strong>:<br>                tmp_mdl = smt.ARIMA(lrets.SPX, <br>                order=(i,d,j)).fit(method=&#39;mle&#39;, trend=&#39;nc&#39;)<br>                tmp_aic = tmp_mdl.aic<br>                <strong>if</strong> tmp_aic &lt; best_aic:<br>                    best_aic = tmp_aic<br>                    best_order = (i, d, j)<br>                    best_mdl = tmp_mdl<br>            <strong>except</strong>: <strong>continue</strong><br></pre><pre><strong>print</strong>(&#39;aic: {:6.2f} | order: {}&#39;.format(best_aic, best_order))</pre><pre><em># ARIMA model resid plot</em><br>_ = tsplot(best_mdl.resid, lags=30)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/712/1*ogotNbz5gIe-jUN0kg937Q.png" /><figcaption>Residuals of Modelling SPX returns from 2010–2016 as ARIMA(3,0,3) model</figcaption></figure><pre>aic: -9622.34 | order: (3, 0, 3)</pre><p>Our residuals look much closer to white noise! How did our model suddenly improve?</p><pre>sms.diagnostic.acorr_ljungbox(best_mdl.resid, lags=[20], boxpierce=False)</pre><pre><strong>from</strong> <strong>statsmodels.stats.stattools</strong> <strong>import</strong> jarque_bera<br><br>score, pvalue, _, _ = jarque_bera(mdl.resid)<br><br><strong>if</strong> pvalue &lt; 0.10:<br>    <strong>print</strong> &#39;The residuals may not be normally distributed.&#39;<br><strong>else</strong>:<br>    <strong>print</strong> &#39;The residuals seem normally distributed.&#39;</pre><pre>(array([ 18.93350068]), array([ 0.52615227]))</pre><pre>The residuals seem normally distributed.</pre><p>The p-value of our test is now greater than 0.05!</p><p>We deliberately truncated the S&amp;P500 data to start from 2010 onwards, which conveniently excludes the volatile periods around 2007–2008. Hence we have excluded a large portion of the S&amp;P500 where we had excessive volatility clustering. This impacts the serial correlation of the series and hence has the effect of making the series seem “more stationary” than it has been in the past.</p><blockquote><em>This is a very important point</em>. When analysing time series we need to be extremely careful of conditionally <a href="https://en.wikipedia.org/wiki/Heteroscedasticity">heteroscedastic</a> series, such as stock market indexes. In quantitative finance, trying to determine periods of differing volatility is often known as “regime detection”. It is one of the harder tasks to achieve!</blockquote><h3>Time Series Forecasting</h3><p>Finally, we are able to do what we actually set out to do! We have at least accumulated enough knowledge to make a simple forecast of future returns. We use statmodels forecast() method — we need to provide the number of time steps to predict, and a decimal for the alpha argument to specify the confidence intervals. The default setting is 95% confidence. For 99% set alpha equal to 0.01.</p><pre><em># Create a 21 day forecast of SPY returns with 95%, 99% CI</em></pre><pre>n_steps = 21<br><br>f, err95, ci95 = best_mdl.forecast(steps=n_steps) <em># 95% CI</em><br>_, err99, ci99 = best_mdl.forecast(steps=n_steps, alpha=0.01) <em># 99% </em><br><br>idx = pd.date_range(data.index[-1], periods=n_steps, freq=&#39;D&#39;)<br>fc_95 = pd.DataFrame(np.column_stack([f, ci95]), index=idx, columns=<br>                     [&#39;forecast&#39;, &#39;lower_95&#39;, &#39;upper_95&#39;])<br>fc_99 = pd.DataFrame(np.column_stack([ci99]), index=idx, columns=<br>                     [&#39;lower_99&#39;, &#39;upper_99&#39;])<br>fc_all = fc_95.combine_first(fc_99)<br>fc_all.head()</pre><pre>           |forecast | lower_95  |  lower_99 | upper_95 | upper_99 |<br>--------------------------------------------------------------------<br>2015-12-31 |-0.00079 | -0.020347 | -0.026490 | 0.018754 | 0.024897 |<br>2016-01-01 |0.000004 | -0.019563 | -0.025712 | 0.019572 | 0.025721 |<br>2016-01-02 |0.000358 | -0.019215 | -0.025365 | 0.019931 | 0.026081 |<br>2016-01-03 |0.000667 | -0.018968 | -0.025138 | 0.020302 | 0.026472 |<br>2016-01-04 |0.000586 | -0.019051 | -0.025222 | 0.020223 | 0.026394 |</pre><pre># Plot 21 day forecast for SPX returns</pre><pre>plt.style.use(&#39;bmh&#39;)<br>fig = plt.figure(figsize=(15,10))<br>ax = plt.gca()</pre><pre>ts = lrets.SPX.iloc[-500:].copy()<br>ts.plot(ax=ax, label=&#39;SPX Returns&#39;)<br># in sample prediction<br>pred = best_mdl.predict(ts.index[0], ts.index[-1])<br>pred.plot(ax=ax, style=&#39;r-&#39;, label=&#39;In-sample prediction&#39;)</pre><pre>styles = [&#39;b-&#39;, &#39;0.2&#39;, &#39;0.75&#39;, &#39;0.2&#39;, &#39;0.75&#39;]<br>fc_all.plot(ax=ax, style=styles)</pre><pre>plt.fill_between(fc_all.index, fc_all.lower_95, <br>                 fc_all.upper_95, color=&#39;gray&#39;, alpha=0.7)<br>plt.fill_between(fc_all.index, fc_all.lower_99, <br>                 fc_all.upper_99, color=&#39;gray&#39;, alpha=0.2)<br>plt.title(&#39;{} Day SPX Return Forecast\nARIMA{}&#39;.format(n_steps,<br>                 best_order))</pre><pre>plt.legend(loc=&#39;best&#39;, fontsize=10)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/885/1*23bGsJzszyQ9DswcH1K0Vg.png" /><figcaption>SPX returns forecasted by ARIMA model</figcaption></figure><p>Now that we have the ability to fit and forecast models such as ARIMA, we’re very close to being able to create strategy indicators for trading.</p><p>You can already start analysing different time series, like difference between prices of two correlated stocks, and try the above models to check for stationarity. Once you find a model that fits the series well enough to leave white noise like residuals, you can start using that model for forecasting future values. And that’s really all there is to forecasting!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=acb5e39999df" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/time-series-analysis-for-finance-arima-models-acb5e39999df">Time Series Analysis for Financial Data V — ARIMA Models</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Time Series Analysis for Financial Data IV— ARMA Models]]></title>
            <link>https://medium.com/auquan/time-series-analysis-for-finance-arma-models-21695e14c999?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/21695e14c999</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[math]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[mathematics]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Thu, 07 Dec 2017 15:31:01 GMT</pubDate>
            <atom:updated>2017-12-19T15:13:24.413Z</atom:updated>
            <content:encoded><![CDATA[<p>Download IPython Notebook <a href="https://github.com/Auquan/Tutorials/blob/master/Time%20Series%20Analysis%20-%203.ipynb">here</a>.</p><p>In the <a href="https://medium.com/auquan/time-series-analysis-ii-auto-regressive-models-d0cb1a8a7c43">previous posts in this series</a>, we talked about Auto-Regressive Models and Moving Average Models and found that both these models only partially explained the log-returns of stock prices.</p><p>We now combine the Autoregressive models and Moving Average models to produce more sophisticated models — Auto Regressive Moving Average(ARMA) and Auto Regressive Integrated Moving Average(ARIMA) models.</p><h3>Auto Regressive Moving Average(ARMA) Models</h3><p>ARMA model is simply the merger between AR(p) and MA(q) models:</p><ul><li>AR(p) models try to explain the momentum and mean reversion effects often observed in trading markets (market participant effects).</li><li>MA(q) models try to capture the shock effects observed in the white noise terms. These shock effects could be thought of as unexpected events affecting the observation process e.g. Surprise earnings, wars, attacks, etc.</li></ul><p>ARMA model attempts to capture both of these aspects when modelling financial time series. ARMA model does not take into account volatility clustering, a key empirical phenomena of many financial time series which we will discuss later.</p><p>ARMA(1,1) model is:</p><blockquote>x(t) = a*x(t-1) + b*e(t-1) + e(t)</blockquote><p>is e(t) white noise with E[e(t)] = 0</p><p>An ARMA model will often require fewer parameters than an AR(p) or MA(q) model alone. That is, it is redundant in its parameters.</p><p>Let’s try to simulate an ARMA(2, 2) process with given parameters, then fit an ARMA(2, 2) model and see if it can correctly estimate those parameters. Set alphas equal to [0.5,-0.25] and betas equal to [0.5,-0.3].</p><pre><strong>import</strong> <strong>pandas</strong> <strong>as</strong> <strong>pd</strong><br><strong>import</strong> <strong>numpy</strong> <strong>as</strong> <strong>np</strong><br><br><strong>import</strong> <strong>statsmodels.tsa.api</strong> <strong>as</strong> <strong>smt</strong><br><strong>import</strong> <strong>statsmodels.api</strong> <strong>as</strong> <strong>sm</strong><br><strong>import</strong> <strong>scipy.stats</strong> <strong>as</strong> <strong>scs</strong><br><strong>import</strong> <strong>statsmodels.stats</strong> <strong>as</strong> <strong>sms</strong><br><br><strong>import</strong> <strong>matplotlib.pyplot</strong> <strong>as</strong> <strong>plt</strong><br>%matplotlib inline</pre><pre><strong>def</strong> tsplot(y, lags=None, figsize=(10, 8), style=&#39;bmh&#39;):<br>    <strong>if</strong> <strong>not</strong> isinstance(y, pd.Series):<br>        y = pd.Series(y)<br>    <strong>with</strong> plt.style.context(style):    <br>        fig = plt.figure(figsize=figsize)<br>        layout = (3, 2)<br>        ts_ax = plt.subplot2grid(layout, (0, 0), colspan=2)<br>        acf_ax = plt.subplot2grid(layout, (1, 0))<br>        pacf_ax = plt.subplot2grid(layout, (1, 1))<br>        qq_ax = plt.subplot2grid(layout, (2, 0))<br>        pp_ax = plt.subplot2grid(layout, (2, 1))<br>        <br>        y.plot(ax=ts_ax)<br>        ts_ax.set_title(&#39;Time Series Analysis Plots&#39;)<br>        smt.graphics.plot_acf(y, lags=lags, ax=acf_ax, alpha=0.05)<br>        smt.graphics.plot_pacf(y, lags=lags, ax=pacf_ax, alpha=0.05)<br>        sm.qqplot(y, line=&#39;s&#39;, ax=qq_ax)<br>        qq_ax.set_title(&#39;QQ Plot&#39;)        <br>        scs.probplot(y, sparams=(y.mean(), y.std()), plot=pp_ax)<br><br>        plt.tight_layout()<br>    <strong>return</strong></pre><pre><em># Simulate an ARMA(2, 2) model<br># alphas=[0.5,-0.25]<br># betas=[0.5,-0.3]</em></pre><pre>max_lag = 30<br><br>n = int(5000) <em># lots of samples to help estimates</em><br>burn = int(n/10) <em># number of samples to discard before fit</em><br><br>alphas = np.array([0.5, -0.25])<br>betas = np.array([0.5, -0.3])<br>ar = np.r_[1, -alphas]<br>ma = np.r_[1, betas]<br><br>arma22 = smt.arma_generate_sample(ar=ar, ma=ma, nsample=n, burnin=burn)<br>_ = tsplot(arma22, lags=max_lag)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/712/1*H1Gkixt8vG_3Rj9jtOmwOg.png" /><figcaption>ARMA(2,2) process</figcaption></figure><pre>mdl = smt.ARMA(arma22, order=(2, 2)).fit(<br>    maxlag=max_lag, method=&#39;mle&#39;, trend=&#39;nc&#39;, burnin=burn)<br><strong>print</strong>(mdl.summary())</pre><pre>                          ARMA Model Results                              <br>====================================================================<br>Dep. Variable:              y   No. Observations:         5000<br>Model:             ARMA(2, 2)   Log Likelihood       -7054.211<br>Method:                   mle   S.D. of innovations      0.992<br>Date:        Mon, 27 Feb 2017   AIC                  14118.423<br>Time:                21:27:58   BIC                  14151.009<br>Sample:                     0   HQIC                 14129.844<br>                                                                              <br>====================================================================<br>               coef  std err        z    P&gt;|z|    [0.025      0.975]<br>--------------------------------------------------------------------<br>ar.L1.y      0.5476    0.058    9.447    0.000     0.434       0.661<br>ar.L2.y     -0.2566    0.015  -17.288    0.000    -0.286      -0.228<br>ma.L1.y      0.4548    0.060    7.622    0.000     0.338       0.572<br>ma.L2.y     -0.3432    0.055   -6.284    0.000    -0.450      -0.236<br>                                    Roots                                    <br>====================================================================<br>               Real         Imaginary         Modulus      Frequency<br>--------------------------------------------------------------------<br>AR.1          1.0668         -1.6609j          1.9740        -0.1591<br>AR.2          1.0668         +1.6609j          1.9740         0.1591<br>MA.1         -1.1685         +0.0000j          1.1685         0.5000<br>MA.2          2.4939         +0.0000j          2.4939         0.0000<br>--------------------------------------------------------------------</pre><p>If you run the above code a few times, you may notice that the confidence intervals for some coefficients may not actually contain the original parameter value. This outlines the danger of attempting to fit models to data, even when we know the true parameter values!</p><p>However, for trading purposes we just need to have a predictive power that exceeds chance and produces enough profit above transaction costs, in order to be profitable in the long run.</p><p><strong>So how do we decide the values of <em>p</em> and <em>q</em> ?</strong></p><p>We exapnd on the method described in previous sheet. To fit data to an ARMA model, we use the <a href="https://en.wikipedia.org/wiki/Akaike_information_criterion">Akaike Information Criterion (AIC)</a>across a subset of values for <em>p,q</em> to find the model with minimum AIC and then apply the <a href="https://en.wikipedia.org/wiki/Ljung%E2%80%93Box_test">Ljung-Box test</a> to determine if a good fit has been achieved, for particular values of <em>p,q</em>. If the p-value of the test is greater the required significance, we can conclude that the residuals are independent and white noise.</p><pre><em># Simulate an ARMA(3, 2) model <br># alphas=[0.5,-0.4,0.25] <br># betas=[0.5,-0.3]</em><br><br>max_lag = 30<br><br>n = int(5000)<br>burn = 2000<br><br>alphas = np.array([0.5, -0.4, 0.25])<br>betas = np.array([0.5, -0.3])<br><br>ar = np.r_[1, -alphas]<br>ma = np.r_[1, betas]<br><br>arma32 = smt.arma_generate_sample(ar=ar, ma=ma, nsample=n, burnin=burn)<br>_ = tsplot(arma32, lags=max_lag)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/703/1*lX0PXtHx3edrzTlhFdnRsg.png" /><figcaption>ARMA(3,2) model</figcaption></figure><pre><em># pick best order by aic </em><br><em># smallest aic value wins</em><br>best_aic = np.inf <br>best_order = None<br>best_mdl = None<br><br>rng = range(5)<br><strong>for</strong> i <strong>in</strong> rng:<br>    <strong>for</strong> j <strong>in</strong> rng:<br>        <strong>try</strong>:<br>            tmp_mdl = smt.ARMA(arma32,<br>                      order=(i, j)).fit(method=&#39;mle&#39;, trend=&#39;nc&#39;)<br>            tmp_aic = tmp_mdl.aic<br>            <strong>if</strong> tmp_aic &lt; best_aic:<br>                best_aic = tmp_aic<br>                best_order = (i, j)<br>                best_mdl = tmp_mdl<br>        <strong>except</strong>: <strong>continue</strong><br><br><br><strong>print</strong>(&#39;aic: <strong>%6.2f</strong> | order: <strong>%s</strong>&#39;%(best_aic, best_order))</pre><p><strong>aic: 14110.88 | order: (3, 2)</strong></p><pre>sms.diagnostic.acorr_ljungbox(best_mdl.resid, lags=[20], boxpierce=False)</pre><p><strong>(array([ 11.602271]), array([ 0.92908567]))</strong></p><p>Notice that the p-value is greater than 0.05, which states that the residuals are independent at the 95% level and thus an ARMA(3,2) model provides a good model fit (ofcourse, we knew that).</p><p>Let’s also check if the model residuals are indeed white noise</p><pre>_ = tsplot(best_mdl.resid, lags=max_lag)</pre><pre><strong>from</strong> <strong>statsmodels.stats.stattools</strong> <strong>import</strong> jarque_bera<br><br>score, pvalue, _, _ = jarque_bera(mdl.resid)<br><br><strong>if</strong> pvalue &lt; 0.10:<br>    <strong>print</strong> &#39;The residuals may not be normally distributed.&#39;<br><strong>else</strong>:<br>    <strong>print</strong> &#39;The residuals seem normally distributed.&#39;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/712/1*QuoyFoIbEipDoCMFiRPJ0g.png" /><figcaption>Residuals after finding best fit for ARMA(3,2)</figcaption></figure><pre>The residuals seem normally distributed.</pre><p>Finally, let’s fit an ARMA model to SPX returns.</p><pre><strong>import</strong> <strong>auquanToolbox.dataloader</strong> <strong>as</strong> <strong>dl</strong></pre><pre># download data<br>end = &#39;2015-01-01&#39;<br>start = &#39;2007-01-01&#39;<br>symbols = [&#39;SPX&#39;,&#39;DOW&#39;,&#39;AAPL&#39;,&#39;MSFT&#39;]<br>data = dl.load_data_nologs(&#39;nasdaq&#39;, symbols , start, end)[&#39;ADJ CLOSE&#39;]<br><em># log returns</em><br>lrets = np.log(data/data.shift(1)).dropna()</pre><pre><em># Fit ARMA model to SPY returns</em><br><br>best_aic = np.inf <br>best_order = None<br>best_mdl = None<br><br>rng = range(5) <em># [0,1,2,3,4,5]</em><br><strong>for</strong> i <strong>in</strong> rng:<br>    <strong>for</strong> j <strong>in</strong> rng:<br>        <strong>try</strong>:<br>            tmp_mdl = smt.ARMA(lrets.SPX, order=(i, j)).fit(<br>                      method=&#39;mle&#39;, trend=&#39;nc&#39;)<br>            tmp_aic = tmp_mdl.aic<br>            <strong>if</strong> tmp_aic &lt; best_aic:<br>                best_aic = tmp_aic<br>                best_order = (i, j)<br>                best_mdl = tmp_mdl<br>        <strong>except</strong>: <strong>continue</strong><br><br><br><strong>print</strong>(&#39;aic: {:6.2f} | order: {}&#39;.format(best_aic, best_order))<br><br>_ = tsplot(best_mdl.resid, lags=max_lag)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/712/1*wNyHTA2w1qC0cEsM9EbA-g.png" /><figcaption>Residuals after fitting ARMA(3,2) to SPX returns from 2007–2015</figcaption></figure><pre>aic: -11515.95 | order: (3, 2)</pre><p>The best fitting model has ARMA(3,2). Notice that there are some significant peaks, especially at higher lags. This is indicative of a poor fit. Let’s perform a Ljung-Box test to see if we have statistical evidence for this:</p><pre>sms.diagnostic.acorr_ljungbox(best_mdl.resid, lags=[20], boxpierce=False)</pre><p><strong>(array([ 39.20681465]), array([ 0.00628341]))</strong></p><p>As we suspected, the p-value is less that 0.05 and as such we cannot say that the residuals are a realisation of discrete white noise. Hence there is additional autocorrelation in the residuals that is not explained by the fitted ARMA(3,2) model. This is obvious from the plot of residuals as well, we can see areas of obvious conditional volatility (heteroskedasticity) that the model has not captured.</p><p>In the <a href="https://medium.com/auquan/time-series-analysis-for-finance-arima-models-acb5e39999df">next post, we will take this concept of merging AR and MA models even further and discuss ARIMA models</a>.</p><p>We will also finally talk about how everything we’ve learned so far can be used for forecasting future values of any time series. Stay tuned!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=21695e14c999" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/time-series-analysis-for-finance-arma-models-21695e14c999">Time Series Analysis for Financial Data IV— ARMA Models</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Application of Machine Learning Techniques to Trading]]></title>
            <link>https://medium.com/auquan/https-medium-com-auquan-machine-learning-techniques-trading-b7120cee4f05?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/b7120cee4f05</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[trading]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[learning]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Wed, 01 Nov 2017 16:41:27 GMT</pubDate>
            <atom:updated>2018-01-12T11:52:18.015Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="http://auquan.com/">Auquan</a> recently concluded another version of <a href="http://quant-quest.auquan.com/">QuantQuest</a>, and this time, we had a lot of people attempt Machine Learning with our problems. It was good learning for both us and them (hopefully!). This post is inspired by our observations of some common caveats and pitfalls during the competition when trying to apply ML techniques to trading problems.</p><p>IF you haven’t read our previous posts, we recommend going through <a href="https://medium.com/auquan/beginners-guide-to-quantitative-trading-ii-developing-automated-trading-systems-4c967e544f34">our guide on building automated systems</a> and <a href="https://medium.com/auquan/developing-trading-strategies-4fc71b41d64b">A Systematic Approach to Developing Trading Strategies</a> before this post.</p><h3>Creating a Trade Strategy</h3><p>The final output of a trading strategy should answer the following questions:</p><ul><li><em>DIRECTION:</em> identify if an asset is cheap/expensive/fair value</li><li><em>ENTRY TRADE:</em> if an asset is cheap/expensive, should you buy/sell it</li><li><em>EXIT TRADE:</em> if an asset is fair priced and if we hold a position in that asset(bought or sold it earlier), should you exit that position</li><li><em>PRICE RANGE:</em> which price (or range) to make this trade at</li><li><em>QUANTITY:</em> Amount of capital to trade(example shares of a stock)</li></ul><p>Machine Learning can be used to answer each of these questions, but for the rest of this post, we will focus on answering the first, Direction of trade.</p><h3>Strategy Approach</h3><p>There can be two types of approaches to building strategies, model based or data mining. These are essentially opposite approaches. In <em>model-based strategy building</em>, we start with a model of a market inefficiency, construct a mathematical representation(eg price, returns) and test it’s validity in the long term. This model is usually a simplified representation of the true complex model and it’s long term significance and stability need to verified. Common trend-following, mean reversion, arbitrage strategies fall in this category.</p><p>On the other hand, we first look for price patterns and attempt to fit an algorithm to it in<em> data mining approach</em>. What causes these patterns is not important, only that patterns identified will continue to repeat in the future. This is a blind approach and we need rigorous checks to identify real patterns from random patterns. Trial-and-error TA, candle patterns, regression on a large number of features fall in this category.</p><p>Clearly, Machine Learning lends itself easily to data mining approach. Let’s look into how we can use ML to create a trade signal by data mining.</p><p>You can follow along the steps in this model using <a href="https://github.com/Auquan/Tutorials/blob/master/Simple%20ML%20Strategies%20to%20generate%20Trading%20Signal.ipynb"><strong><em>this IPython notebook</em></strong></a>. The code samples use <a href="https://bitbucket.org/auquan/auquantoolbox">Auquan’s python based free and open source toolbox</a>. You can install it via pip: `<em>pip install -U auquan_toolbox`</em>. We use scikit learn for ML models. Install it using<em> `pip install -U scikit-learn`.</em></p><h3>Using ML to create a Trading Strategy Signal — Data Mining</h3><p>Before we begin, a sample ML problem setup looks like below</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/879/1*GMf5ff3gSLGZtXiFN-aTcQ.jpeg" /><figcaption>Sample ML problem setup</figcaption></figure><p>We create features which could have some predictive power (X), a target variable that we’d like to predict(Y) and use historical data to train a ML model that can predict Y as close as possible to the actual value. Finally, we use this model to make predictions on new data where Y is unknown. This leads to our first step:</p><h3>Step 1 — Setup your problem</h3><blockquote>What are you trying to predict? What is a good prediction? How do you evaluate</blockquote><p>In our framework above, what is Y?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/908/1*A_l0s1-aR2VaPYgyZ1p2jQ.jpeg" /><figcaption>What are you trying to predict?</figcaption></figure><p>Are you predicting <em>Price at a future time, future Return/Pnl, Buy/Sell Signal, Optimizing Portfolio Allocation, try Efficient Execution etc?</em><br>Let’s say we’re trying to predict price at the next time stamp. In that case, Y(t) = Price(t+1). Now we can complete our framework with historical data</p><blockquote><em>Note Y(t) will only be known during a backtest, but when using our model live, we won’t know Price(t+1) at time t. We make a prediction Y(Predicted,t) using our model and compare it with actual value only at time t+1. This means </em><strong><em>you cannot use Y as a feature in your predictive model.</em></strong></blockquote><p>Once we know our target, Y, we can also decide how to evaluate our predictions. This is important to distinguish between different models we will try on our data. Choose a metric that is a good indicator of our model efficiency based on the problem we are solving. For example, if we are predicting price, we can use the <a href="https://en.wikipedia.org/wiki/Root-mean-square_deviation">Root Mean Square Error</a> as a metric. Some common metrics(RMSE, logloss, variance score etc) are pre-coded in Auquan’s toolbox and available under features.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/930/1*Z0KPJ_0xarp4RviawBBQ5A.jpeg" /><figcaption>ML frame for predicting future price</figcaption></figure><p>For demonstration, we’re going to use a problem from <a href="http://quant-quest.auquan.com/problem/qq2p1">QuantQuest(Problem 1)</a>. We are going to create a prediction model that predicts future expected value of basis, where:</p><blockquote>basis = Price of Stock — Price of Future</blockquote><blockquote>basis(t)=S(t)−F(t)</blockquote><blockquote>Y(t) = future expected value of basis = Average(basis(t+1),basis(t+2),basis(t+3),basis(t+4),basis(t+5))</blockquote><p>Since this is a regression problem, we will evaluate the model on RMSE. We’ll also use Total Pnl as an evaluation criterion</p><p><em>Our Objective: Create a model so that predicted value is as close as possible to Y</em></p><h3>Step 2: Collect Reliable Data</h3><blockquote>Collect and clean data that helps you solve the problem at hand</blockquote><p>You need to think about what data will have predictive power for the target variable Y? If we were predicting Price, you could use Stock Price Data, Stock Trade Volume Data, Fundamental Data, Price and Volume Data of Correlated stocks, an Overall Market indicator like Stock Index Level, Price of other correlated assets etc.</p><p>You will need to setup data access for this data, and make sure your data is accurate, free of errors and solve for missing data(quite common). Also ensure your data is unbiased and adequately represents all market conditions (example equal number of winning and losing scenarios) to avoid bias in your model. You may also need to clean your data for dividends, stock splits, rolls etc.</p><p>If you’re using Auquan’s Toolbox, we provide access to free data from <a href="https://bitbucket.org/auquan/auquantoolbox/wiki/Home#markdown-header-getting-data-datasource">Google, Yahoo, NSE and Quandl</a>. We also pre-clean the data for dividends, stock splits and rolls and load it in a format that rest of the toolbox understands.</p><p>For our demo problem, we are using the following data for a dummy stock ‘MQK’ at minute intervals for trading days over one month(~8000 data points): <em>Stock Bid Price, Ask Price, Bid Volume, Ask Volume Future Bid Price, Ask Price, Bid Volume, Ask Volume, StockVWAP, Future VWAP. </em>This data is already cleaned for Dividends, Splits, Rolls.</p><pre># Load the data<br>from backtester.dataSource.quant_quest_data_source import QuantQuestDataSource</pre><pre>cachedFolderName = &#39;/Users/chandinijain/Auquan/qq2solver-data/historicalData/&#39;<br>dataSetId = &#39;trainingData1&#39;</pre><pre>instrumentIds = [&#39;MQK&#39;]<br>ds = QuantQuestDataSource(cachedFolderName=cachedFolderName,<br>                                    dataSetId=dataSetId,<br>                                    instrumentIds=instrumentIds)</pre><pre>def loadData(ds):<br>    data = None<br>    for key in ds.getBookDataByFeature().keys():<br>        if data is None:<br>            data = pd.DataFrame(np.nan, index = ds.getBookDataByFeature()[key].index, columns=[])<br>        data[key] = ds.getBookDataByFeature()[key]<br>    data[&#39;Stock Price&#39;] =  ds.getBookDataByFeature()[&#39;stockTopBidPrice&#39;] + ds.getBookDataByFeature()[&#39;stockTopAskPrice&#39;] / 2.0<br>    data[&#39;Future Price&#39;] = ds.getBookDataByFeature()[&#39;futureTopBidPrice&#39;] + ds.getBookDataByFeature()[&#39;futureTopAskPrice&#39;] / 2.0<br>    data[&#39;Y(Target)&#39;] = ds.getBookDataByFeature()[&#39;basis&#39;].shift(-5)<br>    del data[&#39;benchmark_score&#39;]<br>    del data[&#39;FairValue&#39;]<br>    return data</pre><pre>data = loadData(ds)</pre><p>Auquan’s Toolbox has downloaded and loaded the data into a dictionary of dataframes for you. We now need to prepare the data in a format we like. The function ds.getBookDataByFeature() returns a dictionary of dataframes, one dataframe per feature. We create a new data dataframe for the stock with all the features.</p><h3><strong>Step 3: Split Data</strong></h3><blockquote>Create Training, Cross-Validation and Test Datasets from the data</blockquote><p><strong>This is an extremely important step! </strong>Before we proceed any further, we should split our data into training data to train your model and test data to evaluate model performance. Recommended split: 60–70% training and 30–40% test</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2NPbRwyZTm4KHiYxja8faw.png" /><figcaption>Split Data into Training and Test Data</figcaption></figure><p>Since training data is used to evaluate model parameters, your model will likely be overfit to training data and training data metrics will be misleading about model performance. If you do not keep any separate test data and use all your data to train, you will not know how well or badly your model performs on new unseen data. <strong>This is one of the major reasons why well trained ML models fail on live data </strong>— people train on all available data and get excited by training data metrics, but the model fails to make any meaningful predictions on live data that it wasn’t trained on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mf65xh4fCTNsaYXgPIjjaQ.png" /><figcaption>Split Data into Training, Validation and Test Data</figcaption></figure><p>There is a problem with this method. If we repeatedly train on training data, evaluate performance on test data and optimise our model till we are happy with performance we have implicitly made test data a part of training data. Eventually our model may perform well for this set of training and test data, but there is no guarantee that it will predict well on new data.</p><p>To solve for this we can create a separate validation data set. Now you can train on training data, evaluate performance on validation data, optimise till you are happy with performance, and finally test on test data. This way the test data stays untainted and we don’t use any information from test data to improve our model.</p><p>Remember once you do check performance on test data don’t go back and try to optimise your model further. If you find that your model does not give good results discard that model altogether and start fresh. Recommended split could be 60% training data, 20% validation data and 20% test data.</p><p>For our problem we have three datasets available, we will use one as training set, second as validation set and the third as our test set.</p><pre># Training Data<br>dataSetId =  &#39;trainingData1&#39;<br>ds_training = QuantQuestDataSource(cachedFolderName=cachedFolderName,<br>                                    dataSetId=dataSetId,<br>                                    instrumentIds=instrumentIds)</pre><pre>training_data = loadData(ds_training)</pre><pre># Validation Data<br>dataSetId =  &#39;trainingData2&#39;<br>ds_validation = QuantQuestDataSource(cachedFolderName=cachedFolderName,<br>                                    dataSetId=dataSetId,<br>                                    instrumentIds=instrumentIds)<br>validation_data = loadData(ds_validation)</pre><pre># Test Data<br>dataSetId =  &#39;trainingData3&#39;<br>ds_test = QuantQuestDataSource(cachedFolderName=cachedFolderName,<br>                                    dataSetId=dataSetId,<br>                                    instrumentIds=instrumentIds)<br>out_of_sample_test_data = loadData(ds_test)</pre><p>To each of these, we add the target variable <em>Y,</em> defined as average of next five values of basis</p><pre>def prepareData(data, period):<br>    data[&#39;Y(Target)&#39;] = data[&#39;basis&#39;].rolling(period).mean().shift(-period)<br>    if &#39;FairValue&#39; in data.columns:<br>        del data[&#39;FairValue&#39;]<br>    data.dropna(inplace=True)</pre><pre>period = 5<br>prepareData(training_data, period)<br>prepareData(validation_data, period)<br>prepareData(out_of_sample_test_data, period)</pre><h3><strong>Step 4: Feature Engineering</strong></h3><blockquote>Analyze behavior of your data and Create features that have predictive power</blockquote><p>Now comes the real engineering. The golden rule of feature selection is that the predictive power should come from primarily from the features and not from the model. You will find that the choice of features has a far greater impact on performance than the choice of model. Some pointers for feature selection:</p><ul><li>Don’t randomly choose a very large set of features without exploring relationship with target variable</li><li>Little or no relationship with target variable will likely lead to overfitting</li><li>Your features might be highly correlated with each other, in that case a fewer number of features will explain the target just as well</li><li>I generally create a few features that make intuitive sense, look at correlation of target variable with those features, as well as their inter correlation to decide what to use</li><li>You could also try ranking candidate features according to <a href="https://en.wikipedia.org/wiki/Maximal_information_coefficient">Maximal Information Coefficient (MIC)</a>, performing <a href="https://medium.com/towards-data-science/a-one-stop-shop-for-principal-component-analysis-5582fb7e0a9c">Principal Component Analysis(PCA)</a> and other methods</li></ul><h4>Feature Transformation/Normalization:</h4><p>ML models tend to perform well with normalization. However, normalization is tricky when working with time series data because future range of data is unknown. Your data could fall out of bounds of your normalization leading to model errors. Still you could try to enforce some degree of stationarity:</p><ul><li><em>Scaling: divide features by standard deviation or interquartile range</em></li><li><em>Centering: subtract historical mean from current value</em></li><li><em>Normalization: both of the above (x — mean)/stdev over lookback period</em></li><li><em>Regular normalization: standardize data to the range -1 to +1 over lookback period (x-min)/(max-min) and re-center</em></li></ul><p>Note since we are using historical rolling mean, standard deviation, max or min over lookback period, the same normalized value of feature will mean different actual value at different times. For example, if the current value of feature is 5 with a rolling 30-period mean of 4.5, this will transform to 0.5 after centering. Later if the rolling 30-period mean changes to 3, a value of 3.5 will transform to 0.5. This may be a cause of errors in your model; hence normalization is tricky and you have to figure what actually improves performance of your model(if at all).</p><p>If you are using our toolbox, it already comes with a set of<a href="https://bitbucket.org/auquan/auquantoolbox/wiki/Home#markdown-header-available-feature-guide"> pre coded features</a> for you to explore.</p><p>For this first iteration in our problem, we create a large number of features, using a mix of parameters. Later we will try to see if can reduce the number of features</p><pre>def difference(dataDf, period):<br>    return dataDf.sub(dataDf.shift(period), fill_value=0)</pre><pre>def ewm(dataDf, halflife):<br>    return dataDf.ewm(halflife=halflife, ignore_na=False,<br>                      min_periods=0, adjust=True).mean()</pre><pre>def rsi(data, period):<br>    data_upside = data.sub(data.shift(1), fill_value=0)<br>    data_downside = data_upside.copy()<br>    data_downside[data_upside &gt; 0] = 0<br>    data_upside[data_upside &lt; 0] = 0<br>    avg_upside = data_upside.rolling(period).mean()<br>    avg_downside = - data_downside.rolling(period).mean()<br>    rsi = 100 - (100 * avg_downside / (avg_downside + avg_upside))<br>    rsi[avg_downside == 0] = 100<br>    rsi[(avg_downside == 0) &amp; (avg_upside == 0)] = 0</pre><pre>return rsi</pre><pre>def create_features(data):<br>    basis_X = pd.DataFrame(index = data.index, columns =  [])<br>    <br>    basis_X[&#39;mom3&#39;] = difference(data[&#39;basis&#39;],4)<br>    basis_X[&#39;mom5&#39;] = difference(data[&#39;basis&#39;],6)<br>    basis_X[&#39;mom10&#39;] = difference(data[&#39;basis&#39;],11)<br>    <br>    basis_X[&#39;rsi15&#39;] = rsi(data[&#39;basis&#39;],15)<br>    basis_X[&#39;rsi10&#39;] = rsi(data[&#39;basis&#39;],10)<br>    <br>    basis_X[&#39;emabasis3&#39;] = ewm(data[&#39;basis&#39;],3)<br>    basis_X[&#39;emabasis5&#39;] = ewm(data[&#39;basis&#39;],5)<br>    basis_X[&#39;emabasis7&#39;] = ewm(data[&#39;basis&#39;],7)<br>    basis_X[&#39;emabasis10&#39;] = ewm(data[&#39;basis&#39;],10)</pre><pre>    basis_X[&#39;basis&#39;] = data[&#39;basis&#39;]<br>    basis_X[&#39;vwapbasis&#39;] = data[&#39;stockVWAP&#39;]-data[&#39;futureVWAP&#39;]<br>    <br>    basis_X[&#39;swidth&#39;] = data[&#39;stockTopAskPrice&#39;] -<br>                        data[&#39;stockTopBidPrice&#39;]<br>    basis_X[&#39;fwidth&#39;] = data[&#39;futureTopAskPrice&#39;] -<br>                        data[&#39;futureTopBidPrice&#39;]<br>    <br>    basis_X[&#39;btopask&#39;] = data[&#39;stockTopAskPrice&#39;] -<br>                         data[&#39;futureTopAskPrice&#39;]<br>    basis_X[&#39;btopbid&#39;] = data[&#39;stockTopBidPrice&#39;] -<br>                         data[&#39;futureTopBidPrice&#39;]<br><br>    basis_X[&#39;totalaskvol&#39;] = data[&#39;stockTotalAskVol&#39;] -<br>                             data[&#39;futureTotalAskVol&#39;]<br>    basis_X[&#39;totalbidvol&#39;] = data[&#39;stockTotalBidVol&#39;] -<br>                             data[&#39;futureTotalBidVol&#39;]<br>    <br>    basis_X[&#39;emabasisdi7&#39;] = basis_X[&#39;emabasis7&#39;] -<br>                             basis_X[&#39;emabasis5&#39;] + <br>                             basis_X[&#39;emabasis3&#39;]<br>    <br>    basis_X = basis_X.fillna(0)<br>    <br>    basis_y = data[&#39;Y(Target)&#39;]<br>    basis_y.dropna(inplace=True)<br>    <br>    print(&quot;Any null data in y: %s, X: %s&quot;<br>            %(basis_y.isnull().values.any(), <br>             basis_X.isnull().values.any()))<br>    print(&quot;Length y: %s, X: %s&quot;<br>            %(len(basis_y.index), len(basis_X.index)))<br>    <br>    return basis_X, basis_y</pre><pre>basis_X_train, basis_y_train = create_features(training_data)<br>basis_X_test, basis_y_test = create_features(validation_data)</pre><h3><strong>Step 5: Model Selection</strong></h3><blockquote>Choose an appropriate statistical/ML model based on chosen problem</blockquote><p>The choice of model will depend on the way the problem is framed. Are you solving a <a href="https://medium.com/towards-data-science/machine-learning-101-supervised-unsupervised-reinforcement-beyond-f18e722069bc">supervised (every point X in feature matrix maps to a target variable Y ) or unsupervised learning problem</a>(there is no given mapping, model tries to learn unknown patterns)? Are you solving a <a href="https://medium.com/simple-ai/classification-versus-regression-intro-to-machine-learning-5-5566efd4cb83">regression (predict the actual price at a future time) or a classification problem</a> (predict only the direction of price(increase/decrease) at a future time).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lSfJvjNQgg5RaqPGwfMbRA.png" /><figcaption>Supervised v/s unsupervised learning</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/949/1*OpLAEAbtAc_kUa0EGjAhDw.png" /><figcaption>Regression v/s classification</figcaption></figure><p>Some common supervised learning algorithms to get you started are:</p><ul><li><a href="https://medium.com/towards-data-science/simple-and-multiple-linear-regression-in-python-c928425168f9">Linear Regression (Parametric, regression)</a></li><li><a href="https://medium.com/towards-data-science/building-a-logistic-regression-in-python-step-by-step-becd4d56c9c8">Logistic Regression (Parametric, classification)</a></li><li><a href="https://medium.com/machine-learning-101/k-nearest-neighbors-classifier-1c1ff404d265">K Nearest Neighbors (Instance based, regression)</a></li><li><a href="https://blog.statsbot.co/support-vector-machines-tutorial-c1618e635e93">SVM, SVR (Parametric, classification and regression)</a></li><li><a href="https://medium.com/towards-data-science/decision-trees-and-random-forests-for-classification-and-regression-pt-1-dbb65a458df">Decision Trees</a></li><li>Decision Forests</li></ul><p>I recommend starting with a simple model, for example linear or logistic regression and building up to more sophisticated models from there if needed. Also recommend reading the Math behind the model instead of blindly using it as a black box.</p><h3>Step 6: Train, Validate and Optimize (Repeat steps 4–6)</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xBpjiWA-2_1gShBsyMr9QQ.png" /><figcaption>Train and Optimize your model using Training and Validation Datasets</figcaption></figure><p>Now you’re ready to finally build your model. At this stage, you really just iterate over models and model parameters. Train your model on training data, measure it’s performance on validation data, and go back, optimize, re-train and evaluate again. If you’re unhappy with a model’s performance, try using a different model. You loop over this stage multiple times till you finally have a model that you’re happy with.</p><blockquote><strong>Only when you have a model who’s performance you like, proceed to the next step.</strong></blockquote><p>For our demo problem, let’s start with a simple linear regression</p><pre>from sklearn import linear_model<br>from sklearn.metrics import mean_squared_error, r2_score</pre><pre>def linear_regression(basis_X_train, basis_y_train,<br>                      basis_X_test,basis_y_test):<br>    <br>    regr = linear_model.LinearRegression()<br>    # Train the model using the training sets<br>    regr.fit(basis_X_train, basis_y_train)<br>    # Make predictions using the testing set<br>    basis_y_pred = regr.predict(basis_X_test)</pre><pre>    # The coefficients<br>    print(&#39;Coefficients: \n&#39;, regr.coef_)<br>    <br>    # The mean squared error<br>    print(&quot;Mean squared error: %.2f&quot;<br>          % mean_squared_error(basis_y_test, basis_y_pred))<br>    <br>    # Explained variance score: 1 is perfect prediction<br>    print(&#39;Variance score: %.2f&#39; % r2_score(basis_y_test,<br>                                            basis_y_pred))</pre><pre>    # Plot outputs<br>    plt.scatter(basis_y_pred, basis_y_test,  color=&#39;black&#39;)<br>    plt.plot(basis_y_test, basis_y_test, color=&#39;blue&#39;, linewidth=3)</pre><pre>    plt.xlabel(&#39;Y(actual)&#39;)<br>    plt.ylabel(&#39;Y(Predicted)&#39;)</pre><pre>    plt.show()<br>    <br>    return regr, basis_y_pred</pre><pre>_, basis_y_pred = linear_regression(basis_X_train, basis_y_train, <br>                                    basis_X_test,basis_y_test)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/402/1*8FFqumm-kXpiVNflGmClOQ.png" /><figcaption>Linear Regression with no normalization</figcaption></figure><pre>(&#39;Coefficients: \n&#39;, array([ -1.0929e+08, 4.1621e+07, 1.4755e+07, 5.6988e+06, -5.656e+01, -6.18e-04, -8.2541e-05,4.3606e-02, -3.0647e-02, 1.8826e+07, 8.3561e-02, 3.723e-03, -6.2637e-03, 1.8826e+07, 1.8826e+07, 6.4277e-02, 5.7254e-02, 3.3435e-03, 1.6376e-02, -7.3588e-03, -8.1531e-04, -3.9095e-02, 3.1418e-02, 3.3321e-03, -1.3262e-06, -1.3433e+07, 3.5821e+07, 2.6764e+07, -8.0394e+06, -2.2388e+06, -1.7096e+07]))</pre><pre>Mean squared error: 0.02<br>Variance score: 0.96</pre><p>Look at the model coeffecients. We can’t really compare them or tell which ones are important since they all belong to different scale. Let’s try normalization to conform them to same scale and also enforce some stationarity.</p><pre>def normalize(basis_X, basis_y, period):<br>    basis_X_norm = (basis_X - basis_X.rolling(period).mean())/<br>                    basis_X.rolling(period).std()<br>    basis_X_norm.dropna(inplace=True)<br>    basis_y_norm = (basis_y - <br>                    basis_X[&#39;basis&#39;].rolling(period).mean())/<br>                    basis_X[&#39;basis&#39;].rolling(period).std()<br>    basis_y_norm = basis_y_norm[basis_X_norm.index]<br>    <br>    return basis_X_norm, basis_y_norm</pre><pre>norm_period = 375<br>basis_X_norm_test, basis_y_norm_test = normalize(basis_X_test,basis_y_test, norm_period)<br>basis_X_norm_train, basis_y_norm_train = normalize(basis_X_train, basis_y_train, norm_period)</pre><pre>regr_norm, basis_y_pred = linear_regression(basis_X_norm_train, basis_y_norm_train, basis_X_norm_test, basis_y_norm_test)</pre><pre>basis_y_pred = basis_y_pred * basis_X_test[&#39;basis&#39;].rolling(period).std()[basis_y_norm_test.index] + basis_X_test[&#39;basis&#39;].rolling(period).mean()[basis_y_norm_test.index]</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/398/1*OO7bTVf9xKzgwWJ6Seh7tg.png" /><figcaption>Linear Regression with normalization</figcaption></figure><pre>Mean squared error: 0.05<br>Variance score: 0.90</pre><p>The model doesn’t improve on the previous model, but it’s not much worse either. And now we can actually compare coefficients to see which ones are actually important.</p><p>Let’s look at the coefficients</p><pre>for i in range(len(basis_X_train.columns)):<br>    print(&#39;%.4f, %s&#39;%(regr_norm.coef_[i], basis_X_train.columns[i]))</pre><blockquote>19.8727, emabasis4<br>-9.2015, emabasis5<br>8.8981, emabasis7<br>-5.5692, emabasis10<br>-0.0036, rsi15<br>-0.0146, rsi10<br>0.0196, mom10<br>-0.0035, mom5<br>-7.9138, basis<br>0.0062, swidth<br>0.0117, fwidth<br>2.0883, btopask<br>2.0311, btopbid<br>0.0974, bavgask<br>0.0611, bavgbid<br>0.0007, topaskvolratio<br>0.0113, topbidvolratio<br>-0.0220, totalaskvolratio<br>0.0231, totalbidvolratio</blockquote><p>We can clearly see that some features have a much higher coeffecient compared to others, and probably have more predictive power.</p><p>Let’s also look at correlation between different features.</p><pre>import seaborn</pre><pre>c = basis_X_train.corr()<br>plt.figure(figsize=(10,10))<br>seaborn.heatmap(c, cmap=&#39;RdYlGn_r&#39;, mask = (np.abs(c) &lt;= 0.8))<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/617/1*9BwyXJtDl9D5Vz_kte1_8Q.png" /><figcaption>Correlation between features</figcaption></figure><p>The areas of dark red indicate highly correlated variables. Let’s create/modify some features again and try to improve our model.</p><p>For example, I can easily discard features like <strong><em>emabasisdi7</em></strong> that are just a linear combination of other features</p><pre>def create_features_again(data):<br>    basis_X = pd.DataFrame(index = data.index, columns =  [])<br>    basis_X[&#39;mom10&#39;] = difference(data[&#39;basis&#39;],11)</pre><pre>    basis_X[&#39;emabasis2&#39;] = ewm(data[&#39;basis&#39;],2)<br>    basis_X[&#39;emabasis5&#39;] = ewm(data[&#39;basis&#39;],5)<br>    basis_X[&#39;emabasis10&#39;] = ewm(data[&#39;basis&#39;],10)</pre><pre>    basis_X[&#39;basis&#39;] = data[&#39;basis&#39;]<br>    basis_X[&#39;totalaskvolratio&#39;] = (data[&#39;stockTotalAskVol&#39;]<br>                                 - data[&#39;futureTotalAskVol&#39;])/<br>                                   100000<br>    basis_X[&#39;totalbidvolratio&#39;] = (data[&#39;stockTotalBidVol&#39;]<br>                                 - data[&#39;futureTotalBidVol&#39;])/<br>                                   100000</pre><pre>    basis_X = basis_X.fillna(0)<br>    <br>    basis_y = data[&#39;Y(Target)&#39;]<br>    basis_y.dropna(inplace=True)</pre><pre>    return basis_X, basis_y</pre><pre>basis_X_test, basis_y_test = create_features_again(validation_data)<br>basis_X_train, basis_y_train = create_features_again(training_data)<br>_, basis_y_pred = linear_regression(basis_X_train, basis_y_train, basis_X_test,basis_y_test)</pre><pre>basis_y_regr = basis_y_pred.copy()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/501/1*zhoocIEsasYuC0nnPgcpgQ.png" /></figure><pre>(&#39;Coefficients: &#39;, array([ 0.03246139,<br>0.49780982, -0.22367172,  0.20275786,  0.50758852,<br>-0.21510795, 0.17153884]))</pre><pre>Mean squared error: 0.02<br>Variance score: 0.96</pre><p>See, our model performance does not change, and we only need a few features to explain our target variable. I recommend playing with more features above, trying new combinations etc to see what can improve our model.</p><p>We can also try more sophisticated models to see if change of model may improve performance</p><h4>K Nearest Neighbours</h4><pre>from sklearn import neighbors<br>n_neighbors = 5</pre><pre>model = neighbors.KNeighborsRegressor(n_neighbors, weights=&#39;distance&#39;)<br>model.fit(basis_X_train, basis_y_train)<br>basis_y_pred = model.predict(basis_X_test)<br>basis_y_knn = basis_y_pred.copy()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/501/1*rt4FN_jWmT7sWdi6regqbQ.png" /></figure><h4>SVR</h4><pre>from sklearn.svm import SVR</pre><pre>model = SVR(kernel=&#39;rbf&#39;, C=1e3, gamma=0.1)</pre><pre>model.fit(basis_X_train, basis_y_train)<br>basis_y_pred = model.predict(basis_X_test)<br>basis_y_svr = basis_y_pred.copy()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/504/1*ESRa2h4e-4-v6NpAYwpZBA.png" /></figure><h4>Decision Trees</h4><pre>model=ensemble.ExtraTreesRegressor()<br>model.fit(basis_X_train, basis_y_train)<br>basis_y_pred = model.predict(basis_X_test)<br>basis_y_trees = basis_y_pred.copy()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/501/1*tRrpfqV0VU3njXeQTw5oUQ.png" /></figure><h3>Step 7: Backtest on Test Data</h3><blockquote>Check for performance of Real Out of Sample Data</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xmVugGjonqyHr7D6woflYA.png" /><figcaption>Backtest performance on (yet untouched) Test Dataset</figcaption></figure><p>This is the moment of truth. We run our final, optimized model from last step on that Test Data that we had kept aside at the start and did not touch yet.</p><p>This provides you with realistic expectation of how your model is expected to perform on new and unseen data when you start trading live. Hence, it is necessary to ensure you have a clean dataset that you haven’t used to train or validate your model.</p><p>If you don’t like the results of your backtest on test data, discard the model and start again. <strong>DO NOT</strong> go back and re-optimize your model, this will lead to over fitting! (Also recommend to create a new test data set, since this one is now tainted; in discarding a model, we implicitly know something about the dataset).</p><p>For backtesting, we use Auquan’s Toolbox</p><pre>import backtester<br>from backtester.features.feature import Feature<br>from backtester.trading_system import TradingSystem<br>from backtester.sample_scripts.fair_value_params import FairValueTradingParams</pre><pre>class Problem1Solver():</pre><pre>def getTrainingDataSet(self):<br>        return &quot;trainingData1&quot;</pre><pre>def getSymbolsToTrade(self):<br>        return [&#39;MQK&#39;]</pre><pre>def getCustomFeatures(self):<br>        return {&#39;my_custom_feature&#39;: MyCustomFeature}</pre><pre>def getFeatureConfigDicts(self):<br>                            <br>        expma5dic = {&#39;featureKey&#39;: &#39;emabasis5&#39;,<br>                 &#39;featureId&#39;: &#39;exponential_moving_average&#39;,<br>                 &#39;params&#39;: {&#39;period&#39;: 5,<br>                              &#39;featureName&#39;: &#39;basis&#39;}}<br>        expma10dic = {&#39;featureKey&#39;: &#39;emabasis10&#39;,<br>                 &#39;featureId&#39;: &#39;exponential_moving_average&#39;,<br>                 &#39;params&#39;: {&#39;period&#39;: 10,<br>                              &#39;featureName&#39;: &#39;basis&#39;}}                     <br>        expma2dic = {&#39;featureKey&#39;: &#39;emabasis3&#39;,<br>                 &#39;featureId&#39;: &#39;exponential_moving_average&#39;,<br>                 &#39;params&#39;: {&#39;period&#39;: 3,<br>                              &#39;featureName&#39;: &#39;basis&#39;}}<br>        mom10dic = {&#39;featureKey&#39;: &#39;mom10&#39;,<br>                 &#39;featureId&#39;: &#39;difference&#39;,<br>                 &#39;params&#39;: {&#39;period&#39;: 11,<br>                              &#39;featureName&#39;: &#39;basis&#39;}}<br>        <br>        return [expma5dic,expma2dic,expma10dic,mom10dic]    <br>    <br>    def getFairValue(self, updateNum, time, instrumentManager):<br>        # holder for all the instrument features<br>        lbInstF = instrumentManager.getlookbackInstrumentFeatures()<br>        mom10 = lbInstF.getFeatureDf(&#39;mom10&#39;).iloc[-1]<br>        emabasis2 = lbInstF.getFeatureDf(&#39;emabasis2&#39;).iloc[-1]<br>        emabasis5 = lbInstF.getFeatureDf(&#39;emabasis5&#39;).iloc[-1]<br>        emabasis10 = lbInstF.getFeatureDf(&#39;emabasis10&#39;).iloc[-1] <br>        basis = lbInstF.getFeatureDf(&#39;basis&#39;).iloc[-1]<br>        totalaskvol = lbInstF.getFeatureDf(&#39;stockTotalAskVol&#39;).iloc[-1] - lbInstF.getFeatureDf(&#39;futureTotalAskVol&#39;).iloc[-1]<br>        totalbidvol = lbInstF.getFeatureDf(&#39;stockTotalBidVol&#39;).iloc[-1] - lbInstF.getFeatureDf(&#39;futureTotalBidVol&#39;).iloc[-1]<br>        <br>        coeff = [ 0.03249183, 0.49675487, -0.22289464, 0.2025182, 0.5080227, -0.21557005, 0.17128488]<br>        newdf[&#39;MQK&#39;] = coeff[0] * mom10[&#39;MQK&#39;] + coeff[1] * emabasis2[&#39;MQK&#39;] +\<br>                      coeff[2] * emabasis5[&#39;MQK&#39;] + coeff[3] * emabasis10[&#39;MQK&#39;] +\<br>                      coeff[4] * basis[&#39;MQK&#39;] + coeff[5] * totalaskvol[&#39;MQK&#39;]+\<br>                      coeff[6] * totalbidvol[&#39;MQK&#39;]<br>                    <br>        newdf.fillna(emabasis5,inplace=True)<br>        return newdf</pre><pre>problem1Solver = Problem1Solver()<br>tsParams = FairValueTradingParams(problem1Solver)<br>tradingSystem = TradingSystem(tsParams)<br>tradingSystem.startTrading(onlyAnalyze=False, <br>                           shouldPlot=True,<br>                           makeInstrumentCsvs=False)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yr3fjVr44Xpw0AKqbQeOgg.png" /><figcaption>Backtest Results, Pnl in USD (Pnl doesn’t account for transaction costs and other fees)</figcaption></figure><h3>Step 8: Other ways to improve model</h3><blockquote>Rolling Validation, Ensemble Learning, Bagging, Boosting</blockquote><p>Besides collecting more data, creating better features or trying more models, there’s a few things you can try to train your model better.</p><p>1. Rolling Validation</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YNgA39KuiMcyKnwPVa83cw.png" /><figcaption>Rolling Validation</figcaption></figure><p>Market conditions rarely stay same. Let’s say you have data for a year and you use Jan-August to train and Sep-Dec to test your model, you might end up training over a very specific set of market conditions. Maybe there was no market volatility for first half of the year and some extreme news caused markets to move a lot in September, your model will not learn this pattern and give you junk results.</p><p>It might be better to try a <a href="http://scikit-learn.org/stable/modules/cross_validation.html">walk forward rolling validation</a> — train over Jan-Feb, validate over March, re-train over Apr-May, validate over June and so on.</p><p>2. Ensemble Learning</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/917/1*NTeiNKgipcm7voFSiauh0Q.jpeg" /><figcaption>Ensemble Learning</figcaption></figure><p>Some models may work well in prediction certain scenarios and other in prediction other scenarios. Or a model may be extremely overfitting in a certain scenario. One way of reducing error and overfitting both is to use an ensemble of different model. Your prediction is the average of predictions made by many model, with errors from different models likely getting cancelled out or reduced. Some common ensemble methods are Bagging and Boosting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/916/1*IQb3FWQzfBbwpxyv-mMhcQ.jpeg" /><figcaption>Bagging</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JGNvUPjDr85Px03elh87xQ.png" /><figcaption>Boosting</figcaption></figure><p>To keep this post short, I will skip these methods, but you can read more <a href="http://scikit-learn.org/stable/modules/ensemble.html">about them here.</a></p><p>Let’s try an ensemble method for our problem</p><pre>basis_y_pred_ensemble = (basis_y_trees + basis_y_svr +<br>                         basis_y_knn + basis_y_regr)/4</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/504/1*ESRa2h4e-4-v6NpAYwpZBA.png" /></figure><blockquote>Mean squared error: 0.02<br>Variance score: 0.95</blockquote><p>All the code for the above steps is available in this IPython notebook. You can read more below:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/38233cc1928120274e3a6b170c89d320/href">https://medium.com/media/38233cc1928120274e3a6b170c89d320/href</a></iframe><h3>That was quite a lot of information. Let’s do a quick Recap:</h3><ul><li>Frame your problem</li><li>Collect reliable Data and clean Data</li><li>Split Data into Training, Validation and Test sets</li><li>Create Features and Analyze Behavior</li><li>Choose an appropriate training model based on Behavior</li><li>Use Training Data to train your model to make predictions</li><li>Check performance on validation set and re-optimize</li><li>Verify final performance on Test Set</li></ul><p>Phew! But that’s not it. You only have a solid prediction model now. Remember what we actually wanted from our strategy? You still have to:</p><ul><li>Develop Signal to identify trade direction based on prediction model</li><li>Develop Strategy to identify Entry/Exit Points</li><li>Execution System to identify Sizing and Price</li></ul><p>And then you can finally send this order to your broker, and make your automated trade!</p><p><strong>Important Note on Transaction Costs</strong>: Why are the next steps important? Your model tells you when your chosen asset is a buy or sell. It however doesn’t take into account fees/transaction costs/available trading volumes/stops etc. Transaction costs very often turn profitable trades into losers. For example, an asset with an expected $0.05 increase in price is a buy, but if you have to pay $0.10 to make this trade, you will end up with a net loss of -$0.05. Our own great looking profit chart above actually looks like this after you account for broker commissions, exchange fees and spreads:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e24mumYUyLY6GAu99etG8w.png" /><figcaption>Backtest Results after transaction fees and spreads, Pnl in USD</figcaption></figure><p>Transaction fees and spreads take up more than 90% of our Pnl! We will discuss these in detail in a follow-up post.</p><p>Finally, let’s look at some common pitfalls.</p><h3>DO’s and DONT’s</h3><ul><li><strong>AVOID OVERFITTING AT ALL COSTS!</strong></li><li>Don’t retrain after every datapoint: This was a common mistake people made in QuantQuest. If your model needs re-training after every datapoint, it’s probably not a very good model. That said, it will need to be retrained periodically, just at a reasonable frequency (example retraining at the end of every week if making intraday predictions)</li><li>Avoid biases, especially lookahead bias: This is another reason why models don’t work — Make sure you are not using any information from the future. Mostly this means, <strong>don’t use the target variable, Y as a feature in your model. </strong>This is available to you during a backtest but won’t be available when you run your model live, making your model useless.</li><li>Be wary of data mining bias: Since we are trying a bunch of models on our data to see if anything fits, without an inherent reason behind it fits, make sure you run rigorous tests to separate random patterns from real patterns which are likely to occur in the future. For example what might seem like an upward trending pattern explained well by a linear regression may turn out to be a small part of a larger random walk!</li></ul><h3>Avoid Overfitting</h3><p>This is so important, I feel the need to mention it again.</p><ul><li>Overfitting is the most dangerous pitfall of a trading strategy</li><li>A complex algorithm may perform wonderfully on a backtest but fails miserably on new unseen data —this algorithm has not really uncovered any trend in data and no real predictive power. It is just fit very well to the data it has seen</li><li>Keep your systems as simple as possible. If you find yourself needing a large number of complex features to explain your data, you are likely over fitting</li><li>Divide your available data into training and test data and always validate performance on Real Out of Sample data before using your model to trade live.</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fq8iMS8dENjQ%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dq8iMS8dENjQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fq8iMS8dENjQ%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/a66b79b7cfcbefb0da5e60a7acf71973/href">https://medium.com/media/a66b79b7cfcbefb0da5e60a7acf71973/href</a></iframe><p><strong>Webinar Video</strong>: If you prefer listening to reading and would like to see a video version of this post, you can watch this webinar link instead.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b7120cee4f05" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/https-medium-com-auquan-machine-learning-techniques-trading-b7120cee4f05">Application of Machine Learning Techniques to Trading</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Time Series Analysis for Financial Data III— Moving Average Models]]></title>
            <link>https://medium.com/auquan/time-series-analysis-for-financial-data-iii-moving-average-models-cccf027f264e?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/cccf027f264e</guid>
            <category><![CDATA[math]]></category>
            <category><![CDATA[mathematics]]></category>
            <category><![CDATA[finance]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Wed, 13 Sep 2017 04:51:02 GMT</pubDate>
            <atom:updated>2017-09-13T04:52:48.116Z</atom:updated>
            <content:encoded><![CDATA[<p>Download IPython Notebook <a href="https://github.com/Auquan/Tutorials/blob/master/Time%20Series%20Analysis%20-%202.ipynb">here</a>.</p><p>In the <a href="https://medium.com/auquan/time-series-analysis-ii-auto-regressive-models-d0cb1a8a7c43">second post in this series</a>, we talked about Auto-Regressive Models — models which only depend on past data of the system. We saw that these models only partially explained the log-returns of stock prices.</p><p>We turn to another model, the Moving Average model to see if they perform better on our data.</p><h3>Moving Average Models</h3><p>MA(q) models are very similar to AR(p) models. MA(q) model is a linear combination of past error terms as opposed to a linear combination of past observations like the AR(p) model. The motivation for the MA model is that we can explain “shocks” in the error process directly by fitting a model to the error terms. The first order model, MA(1) is:</p><blockquote><em>x(t) = b*e(t-1) + e(t)</em></blockquote><p>where b is the coefficient and e is the error term.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/608b024f44561493dfd5429486329043/href">https://medium.com/media/608b024f44561493dfd5429486329043/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cccf027f264e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/time-series-analysis-for-financial-data-iii-moving-average-models-cccf027f264e">Time Series Analysis for Financial Data III— Moving Average Models</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Time Series Analysis for Financial Data II — Auto-Regressive Models]]></title>
            <link>https://medium.com/auquan/time-series-analysis-ii-auto-regressive-models-d0cb1a8a7c43?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/d0cb1a8a7c43</guid>
            <category><![CDATA[math]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[mathematics]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[finance]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Wed, 13 Sep 2017 04:11:50 GMT</pubDate>
            <atom:updated>2017-09-13T05:05:49.940Z</atom:updated>
            <content:encoded><![CDATA[<p>Download the iPython notebook <a href="https://github.com/Auquan/Tutorials/blob/master/Time%20Series%20Analysis%20-%202.ipynb">here</a></p><p>In the <a href="https://medium.com/auquan/time-series-analysis-for-financial-data-part-1-stationarity-autocorrelation-and-white-noise-1a1cc2fb23f2">first post on Time Series Analysis</a>, we talked about the basics of time series analysis - Staionarity and AutoCorrelation. We also talked about simple time series models, White Noise and Random Walks.</p><p>In this post, we take the concept forward and introduce a more sophisticated time series model, namely Auto Regressive(AR) model.</p><h3>AutoRegressive Models</h3><p>The autoregressive model is simply an extension of the random walk. It is a regression model which depends linearly on the previous terms. An order 1 regression model, AR(1) is:</p><blockquote><em>x(t) = a*x(t-1) + w</em></blockquote><p>where a is the auto-regressive coefficient and w is the white noise term. In simple words, the current value only depends on the previous value of the system. Note that an AR(1) model with <em>a</em> set equal to 1 is a random walk!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a6040958fbc10be68994c67fb11ce144/href">https://medium.com/media/a6040958fbc10be68994c67fb11ce144/href</a></iframe><p>In the <a href="https://medium.com/auquan/time-series-analysis-for-financial-data-iii-moving-average-models-cccf027f264e">next post</a>, we will talk about another class of models, Moving Average models (not to be confused with simple moving average — a rolling measure of average).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d0cb1a8a7c43" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/time-series-analysis-ii-auto-regressive-models-d0cb1a8a7c43">Time Series Analysis for Financial Data II — Auto-Regressive Models</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Measuring Momentum for Momentum Models: Simple Trading Strategies Part 3]]></title>
            <link>https://medium.com/auquan/measuring-momentum-for-momentum-models-simple-trading-strategies-part-3-a8d105d74729?source=rss-2a45747180c6------2</link>
            <guid isPermaLink="false">https://medium.com/p/a8d105d74729</guid>
            <category><![CDATA[trading-strategy]]></category>
            <category><![CDATA[stock-market]]></category>
            <category><![CDATA[investing]]></category>
            <category><![CDATA[trading]]></category>
            <category><![CDATA[finance]]></category>
            <dc:creator><![CDATA[Auquan]]></dc:creator>
            <pubDate>Tue, 05 Sep 2017 06:16:00 GMT</pubDate>
            <atom:updated>2017-09-05T06:16:00.549Z</atom:updated>
            <content:encoded><![CDATA[<p>In this series, we cover some basic trading strategies that can help you get started with developing your own automated trading systems.</p><p>Download IPython Notebook <a href="https://github.com/Auquan/Tutorials/blob/master/Measuring%20Momentum.ipynb">here</a>.</p><p>In <a href="https://medium.com/auquan/momentum-simple-trading-strategies-part-2-188cf464ffcf">our previous post</a>, we talked about the concept of Momentum: extrapolating from existing trends. Momentum strategies assume that stocks which are going up will continue to go up and stocks which are going down will continue going down, and buy and sell accordingly.</p><p>The obvious question is, how do we determine or measure this momentum? In this post, we will talk about different ways to measure momentum.</p><p>You will notice that some of the signals used for momentum trading could well be used as inverse signals in a mean reversion system as well. This should be expected, momentum and mean reversion are opposite strategies. This is also why it is very important to check for which kind of behavior might be present in your data before you actually try developing a strategy based on that.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c2d0ac3e3cde009e5448fd1078c46eb5/href">https://medium.com/media/c2d0ac3e3cde009e5448fd1078c46eb5/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a8d105d74729" width="1" height="1" alt=""><hr><p><a href="https://medium.com/auquan/measuring-momentum-for-momentum-models-simple-trading-strategies-part-3-a8d105d74729">Measuring Momentum for Momentum Models: Simple Trading Strategies Part 3</a> was originally published in <a href="https://medium.com/auquan">auquan</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>