# Intraday Stock Mean Reversion Trading Backtest in Python With Short Selling

Carrying on from the last post which outlined an intra-day mean reversion stock trading strategy, I just wanted to expand on that by adapting the backtest to allow short selling too. So as well as buying stocks that have gapped down, we will be allowing the strategy to short sell stocks that have gapped up.

I was interested as to how that would effect our returns and Sharpe Ratio; generally speaking there is more market inefficiency on the short side of the market for various reasons (including for example the inability of large pension funds to short sell stocks due to investment mandate restrictions among other things) .

This increased market inefficiency should theoretically lead to higher returns for those market participants who are able to take advantage of the short selling opportunities.

I’ll begin by backtesting across the same stock universe we used in the last post – the NSYE stock list. The list can be downloaded by clicking the link below:

So let’s begin the code:

```#import the relevant modules
import pandas as pd
import numpy as np
import requests
from math import sqrt
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
%matplotlib inline

#read the stock tickers and names into a DataFrame

stocks_list = []

#iterate through stock list and append tickers into our empty list
for symbol in stocks['Symbol']:
stocks_list.append(symbol)

#create empty list to hold our return series DataFrame for each stock
frames = []

for stock in stocks_list:

try:

#create column to hold our 90 day rolling standard deviation
df['Stdev'] = df['Close'].rolling(window=90).std()

#create a column to hold our 20 day moving average
df['Moving Average'] = df['Close'].rolling(window=20).mean()

#create a column which holds a TRUE value if the gap down from previous day's low to next
#day's open is larger than the 90 day rolling standard deviation
df['Buy1'] = (df['Open'] - df['Low'].shift(1)) < -df['Stdev']
#create a column which holds a TRUE value if the opening price of the stock is above the 20 day moving average
df['Buy2'] = df['Open'] > df['Moving Average']

#create a column that holds a TRUE value if both buy criteria are also TRUE

#create a column which holds a TRUE value if the gap up from previous day's high to next
#day's open is larger than the 90 day rolling standard deviation
df['Sell1'] = (df['Open'] - df['High'].shift(1)) > df['Stdev']

#create a column which holds a TRUE value if the opening price of the stock is below the 20 day moving average
df['Sell2'] = df['Open'] < df['Moving Average']

#create a column that holds a TRUE value if both sell criteria are also TRUE
df['SELL'] = df['Sell1'] & df['Sell2']

#calculate daily % return series for stock
df['Pct Change'] = (df['Close'] - df['Open']) / df['Open']

#create a strategy return series by using the daily stock returns mutliplied by 1 if we are long and -1 if we are short
df['Rets'] = np.where(df['SELL'],-df['Pct Change'], df['Rets'])

#append the strategy return series to our list
frames.append(df['Rets'])

except:
pass

#concatenate the individual DataFrames held in our list- and do it along the column axis
masterFrame = pd.concat(frames,axis=1)

#create a column to hold the sum of all the individual daily strategy returns
masterFrame['Total'] = masterFrame.sum(axis=1)

#fill 'NaNs' with zeros to allow our "count" function below to work properly
masterFrame.fillna(0,inplace=True)

#create a column that hold the count of the number of stocks that were traded each day
#we minus one from it so that we dont count the "Total" column we added as a trade.
masterFrame['Count'] = (masterFrame != 0).sum(axis=1) - 1

#create a column that divides the "total" strategy return each day by the number of stocks traded that day to get equally weighted return.
masterFrame['Return'] = masterFrame['Total'] / masterFrame['Count']

#plot the strategy returns
masterFrame['Return'].dropna().cumsum().plot()
```

This last line produces the below chart

with a Sharpe Ratio of

```#risk free element excluded for simplicity
(masterFrame['Return'].mean() *252) / (masterFrame['Return'].std() * (sqrt(252)))
```
```1.147048581128388
```

and an annual return of

```days = (masterFrame.index[-1] - masterFrame.index).days

(masterFrame['Return'].dropna().cumsum()[-1]+1)**(365.0/days) - 1
```
```0.12087198786174858
```

Ok so the Sharpe Ratio has decreased from just over 2, to 1.15 when we add the short selling ability, and the annual return has increased from around 8.8% to 12%.

I guess it depends on which statistic is more important to you, straight return or risk adjusted return. I’ll leave that one up to you!

Just for a bit of fun, let’s run this strategy across another investment universe of stocks, this time the London Stock Exchange (LSE) tickers.

This backtest generates the following results:

With a Sharpe Ratio of 1.289 and an annual return of 25.21%…well that’s a pretty hefty return!

If you are running the backtest for yourself, remember that there are around 6000 stocks in that list so it can take a little while to complete the backtest, just be patient.

Although the results of the LSE backtest look very promising, I think if anyone tried to actually trade it for real they would quickly find out that many of the stocks the strategy short sells, wouldn’t actually be available for short sale in the live market and the transaction costs would be high enough to eat into a large proportion of the returns.

Anyway, it’s still fun to investigate these things and look for ideas to at least begin our search for a profitable strategy.

Until next time folks…

#### You may also like

August 4, 2017 - 5:27 pm

Hello there,

Hope all is well! So I am trying to run this same code, however receiving a very odd error. Specifically:

Traceback (most recent call last):
File “C:/Users/Sam/PycharmProjects/Test/.ipynb_checkpoints/Intraday Mean Reversion Algorithm.py”, line 100, in
masterFrame = pd.concat(frames, axis=1)
File “C:\Users\Sam\Anaconda64Best\lib\site-packages\pandas\core\reshape\concat.py”, line 206, in concat
copy=copy)
File “C:\Users\Sam\Anaconda64Best\lib\site-packages\pandas\core\reshape\concat.py”, line 239, in __init__
raise ValueError(‘No objects to concatenate’)

ValueError: No objects to concatenate

Process finished with exit code 1

Any idea what could be going on? As always, thank you very much for the assistance. Love your work. My full code is identical and as follows:

*****************

FULL CODE

****************

# The long-short strategy:

#1) Select all stocks near the market open whose returns from their previous day’s lows to today’s opens are lower or higher than one standard deviation.
# The standard deviation is computed using the daily close-to-close returns of the last 90 days. These are stocks that “gapped down” or “gapped up”

#2) Narrow down this list of stocks by requiring that their open prices be higher than or lower than the 20-day moving average of the closing prices.

#3) Liquidate the positions at the market close.

import pandas as pd
import numpy as np
from math import sqrt
import requests
from math import sqrt
import matplotlib.pyplot as plt
plt.style.use(‘seaborn-whitegrid’)

# will be running this back-test using the NYSE stock universe which contains 3159 stocks and saved as .txt file

# make sure the NYSE.txt file is in the same folder as your python script file

# set up our empty list to hold the stock tickers
stocks_list = []

# iterate through the pandas dataframe of tickers and append them to our empty list
for symbol in stocks[‘Symbol’]:
stocks_list.append(symbol)

# The logic of our approach is as follows:

# 1. We will iterate through the list of stock tickers
# 2. Each time we will download the relevant price data into a DataFrame
# 3. Then add a couple of columns to help us create signals as to when our two criteria are met (gap down or gap up of larger than 1 90 day rolling standard deviation,
# WITH an opening price above or below the 20 day moving average).
# 4. We will then use these signals to create our return series for that stock, and then store that information by appending each stocks return series to a list.
# 5. Finally we will concatenate all those return series into a master DataFrame and calculate our overall daily return.

# create empty list to hold our return series DataFrame for each stock
frames = []

for stock in stocks_list:

try:

# create column to hold the 90 day rolling standard deviation
df[‘Stdev’] = df[‘Close’].rolling(window=90).std()

# create a column to hold our 20 day moving average
df[‘Moving Average’] = df[‘Close’].rolling(window=20).mean()

# create a column which holds a TRUE value if the gap down from previous day’s low to next
# day’s open is larger than the 90 day rolling standard deviation
df[‘Buy1’] = (df[‘Open’] – df[‘Low’].shift(1)) df[‘Moving Average’]

# create a column that holds a TRUE value if both buy criteria are also TRUE

# create a column which holds a TRUE value if the gap up from previous day’s high to next
# day’s open is larger than the 90 day rolling standard deviation
df[‘Sell1’] = (df[‘Open’] – df[‘High’].shift(1)) > df[‘Stdev’]

# create a column which holds a TRUE value if the opening price of the stock is below the 20 day moving average
df[‘Sell2’] = df[‘Open’] < df['Moving Average']

# create a column that holds a TRUE value if both sell criteria are also TRUE
df['SELL'] = df['Sell1'] & df['Sell2']

# calculate daily % return series for stock
df['Pct Change'] = (df['Close'] – df['Open']) / df['Open']

# create a strategy return series by using the daily stock returns mutliplied by 1 if we are long and -1 if we are short
df['Rets'] = np.where(df['BUY'], df['Pct Change'], 0)
df['Rets'] = np.where(df['SELL'], -df['Pct Change'], df['Rets'])

# append the strategy return series to our list
frames.append(df['Rets'])

except:
pass

# concatenate the individual DataFrames held in our list- and do it along the column axis
masterFrame = pd.concat(frames, axis=1)

# create a column to hold the sum of all the individual daily strategy returns
masterFrame['Total'] = masterFrame.sum(axis=1)

# fill 'NaNs' with zeros to allow our "count" function below to work properly
masterFrame.fillna(0, inplace=True)

# create a column that hold the count of the number of stocks that were traded each day
# we minus one from it so that we dont count the "Total" column we added as a trade.
masterFrame['Count'] = (masterFrame != 0).sum(axis=1) – 1

# create a column that divides the "total" strategy return each day by the number of stocks traded that day to get equally weighted return.
masterFrame['Return'] = masterFrame['Total'] / masterFrame['Count']

# plot the strategy returns, this line of code plots the chart
masterFrame['Return'].dropna().cumsum().plot()

# with a Sharpe Ratio of
#risk free element excluded for simplicity
(masterFrame['Return'].mean() *252) / (masterFrame['Return'].std() * (sqrt(252)))

# and an annual return of
days = (masterFrame.index[-1] – masterFrame.index).days
(masterFrame['Return'].dropna().cumsum()[-1] + 1) ** (365.0 / days) – 1

August 4, 2017 - 9:00 pm

Finally code finished up (albeit took a while!)

However, an error message was spit out. Specifically:

Traceback (most recent call last):
File “C:/Users/Sam/PycharmProjects/Test/.ipynb_checkpoints/Intraday Mean Reversion Algorithm.py”, line 122, in

(masterFrame[‘Return’].mean() *252) / (masterFrame[‘Return’].std() * (sqrt(252)))

ZeroDivisionError: float division by zero

Process finished with exit code 1

Any idea what could be the cause? Thanks in advance!

Best,

Sam

August 5, 2017 - 5:41 am

It’s failing at this line:

masterFrame = pd.concat(frames, axis=1)

Saying there is nothing to concatenate – the previous code is in a “try/except” loop so it will “try” to execute the code and if it can not, it will just pass on to the next loop – it looks like there is an error with your code in the “try” loop meaning it is just skipping (“passing”) everything, not creating any DataFrames of data and then failing when trying to concatenate as no DataFrames exist.

There must be an error in the code held within the “try” section.

August 5, 2017 - 5:47 pm

Hell there! Thanks for prompt response as always. Actually got it to run ok. The primary problem I am running into is this:

Specifically, error occurring:

Traceback (most recent call last):
File “C:/Users/Sam/PycharmProjects/Test/.ipynb_checkpoints/Intraday Mean Reversion Algorithm.py”, line 122, in
(masterFrame[‘Return’].mean() *252) / (masterFrame[‘Return’].std() * (sqrt(252)))

ZeroDivisionError: float division by zero

Process finished with exit code 1

Any idea what could be the cause? Thanks in advance!
Best,
Sam

September 9, 2017 - 2:10 am

@s666

Hello again!

Where in the code is the exit strategy on this? Meaning, when a trade is put on (short or long) how long does one wait or what signal does one get to close out the position and lock a profit?

August 4, 2017 - 6:18 pm

Nevermind looks like I got it to run. Was a random typo!

However, is it supposed to take abnormally long? I am only trying to run it for the past 2 days since 8/2/2017 to see if I can get output, and its still running the code (its been like 10 min already). Let me know your thoughts………..

August 4, 2017 - 9:01 pm

Finally code finished up (albeit took a while!)
However, an error message was spit out. Specifically:
Traceback (most recent call last):
File “C:/Users/Sam/PycharmProjects/Test/.ipynb_checkpoints/Intraday Mean Reversion Algorithm.py”, line 122, in
(masterFrame[‘Return’].mean() *252) / (masterFrame[‘Return’].std() * (sqrt(252)))
ZeroDivisionError: float division by zero
Process finished with exit code 1
Any idea what could be the cause? Thanks in advance!
Best,
Sam

August 7, 2017 - 1:24 pm

Hi Sam, I never timed my code but I think the bottleneck us not necessarily the date range over which the code is run, as the operations on the DataFrames are vectorised so should be lightning fast…I think the time is taken up by the call to the Google finance API – remember we are asking for data for 3159 stocks. Even is each call only takes a fifth of a second, that’s still (0.2 x 3159) / 60 = 10.53 minutes.

The error message you are getting is a “division by zero” error – dividing anything by zero gives the answer “undefined” (in maths in general I mean, not in Python) – so the line of code:

(masterFrame[‘Return’].mean() *252) / (masterFrame[‘Return’].std() * (sqrt(252)))

is trying to divide by zero – so the denominator equates to zero. This means yoru P&L stream has zero standard deviation – which says to me that your P&L stream is actually empty.

That’s not surprising if you are running is only over 2-3 days – I guess no stocks met the criteria over such a small time frame.

Run it again with a much longer time frame – you will find the code doesn’t take much longer to run, and you will get results which avoids the “division by zero” error.

August 13, 2017 - 2:34 am

Been getting this error here, can’t seem to find the issue. Any help would be appreciated, thanks.

Traceback (most recent call last):
File “C:\Users\Jackk\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\indexes\base.py”, line 2442, in get_loc
return self._engine.get_loc(key)
File “pandas\_libs\index.pyx”, line 132, in pandas._libs.index.IndexEngine.get_loc (pandas\_libs\index.c:5280)
File “pandas\_libs\index.pyx”, line 154, in pandas._libs.index.IndexEngine.get_loc (pandas\_libs\index.c:5126)
File “pandas\_libs\hashtable_class_helper.pxi”, line 1210, in pandas._libs.hashtable.PyObjectHashTable.get_item (pandas\_libs\hashtable.c:20523)
File “pandas\_libs\hashtable_class_helper.pxi”, line 1218, in pandas._libs.hashtable.PyObjectHashTable.get_item (pandas\_libs\hashtable.c:20477)
KeyError: ‘Symbol’

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “C:/Users/Jackk/PycharmProjects/untitled/Mean Reversion Backtest.py”, line 16, in
for symbol in stocks[‘Symbol’]:
File “C:\Users\Jackk\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\frame.py”, line 1964, in __getitem__
return self._getitem_column(key)
File “C:\Users\Jackk\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\frame.py”, line 1971, in _getitem_column
return self._get_item_cache(key)
File “C:\Users\Jackk\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\generic.py”, line 1645, in _get_item_cache
values = self._data.get(item)
File “C:\Users\Jackk\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\internals.py”, line 3590, in get
loc = self.items.get_loc(item)
File “C:\Users\Jackk\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\indexes\base.py”, line 2444, in get_loc
return self._engine.get_loc(self._maybe_cast_indexer(key))
File “pandas\_libs\index.pyx”, line 132, in pandas._libs.index.IndexEngine.get_loc (pandas\_libs\index.c:5280)
File “pandas\_libs\index.pyx”, line 154, in pandas._libs.index.IndexEngine.get_loc (pandas\_libs\index.c:5126)
File “pandas\_libs\hashtable_class_helper.pxi”, line 1210, in pandas._libs.hashtable.PyObjectHashTable.get_item (pandas\_libs\hashtable.c:20523)
File “pandas\_libs\hashtable_class_helper.pxi”, line 1218, in pandas._libs.hashtable.PyObjectHashTable.get_item (pandas\_libs\hashtable.c:20477)
KeyError: ‘Symbol’

August 13, 2017 - 7:35 am

Ok so you are getting the error:

```File “C:/Users/Jackk/PycharmProjects/untitled/Mean Reversion Backtest.py”, line 16, in
for symbol in stocks[‘Symbol’]:
```

This is happening as apparently the DataFrame “stocks” doesn’t have a column named “Symbol”.

If you run the code:

```print(stocks.head())
```

What columns do you see appear in the DataFrame?

August 13, 2017 - 11:21 pm

Yup, that was the issue. I originally tried extracting the tickers from a pickle file and that was the issue since it was nothing but tickers and no header. I dumped all the tickers in a csv with a “Symbol” header and everything seems to be working.

Thanks for the quick response, I really appreciate it.

September 7, 2017 - 3:40 pm

Hello Sir!
Could you tell me how to read the NYSE txt file into python? I typed “stocks = pd.read_csv(‘NYSE.txt’, delimiter=”\t”)”, but it keeps telling me that the file does not exist.

September 7, 2017 - 6:17 pm

Hi Tony – it seems like you havn’t actually downloaded the .txt file and saved it in the same folder as your python script – to download it, just click the blue button in the body of the blog post labelled “NYSE Stock List” and that will automatically download the text file for you – you just need to transfer it to the correct folder and your code should work.

September 9, 2017 - 2:21 am

@s666 nevemind! see that its intraday so position is out before close. got it thanks =D

November 28, 2017 - 1:52 pm

@s666 Would you like to have a chat? Maybe we can work on something together 🙂

September 11, 2017 - 11:58 pm

Hello @s666,

Hope all is well. I am attempting to mimic the structure of this algorithm for a strategy but keep getting a very weird error! Being:

ValueError: No objects to concatenate

Looks like this is in relation to the line of your code that is right after exiting out of the for loop, being:

# append the strategy return series to our list
frames.append(df[‘Rets’])

Curious on what could be going wrong? The only difference for my code is I did not read it from a csv file like you did. Instead I created an array of 30 stocks as you will see, then used webdata reader for the information. Not sure whether it is related to me doing this instead of reading from csv like you did. Wouldnt think it would be at least.

Any how, my code is below. Please let me know if you can spot the cause of this error when you have a moment. Thanks as always and hope all is well with you!

Best,

Sam

***********************************************************
CODE SHOWN BELOW
***********************************************************

# SMA, Mean Reversion Algorithm Combined with ROC, CCI, and Ease of Movement

import pandas as pd
import numpy as np
from math import sqrt
import requests
from math import sqrt
import matplotlib.pyplot as plt
plt.style.use(‘seaborn-whitegrid’)

# extract all Dow 30 stock symbols
stocks = [‘AXP’,’AAPL’,’BA’,’CAT’,’CSCO’,’CVX’,’DD’, ‘DIS’,’GE’,’GS’,’HD’,’IBM’,’INTC’,’JNJ’,’KO’,’JPM’,’MCD’,’MMM’,’MRK’,’MSFT’,’NKE’,’PFE’,’PG’,’TRV’,’UNH’,’UTX’,’V’,’VZ’,’WMT’,’XOM’]

# set up our empty list to hold the stock tickers
stocks_list = []

# iterate through the pandas dataframe of tickers and append them to our empty list
stocks_list.append(stocks)

# create empty list to hold our return series DataFrame for each stock
frames = []

for stock in stocks:

try:

# create a column to hold our 20 day moving average
df[‘Moving Average’] = df[‘Close’].rolling(window=20).mean()

threshold = 1.5 # threshold value, or absolute deviation from the SMA for the signal generation is defined for mean reversion

df[‘distance’] = df[‘Close’] – df[‘Moving Average’] # the distance is calculated for every point in time for mean reversion

df[‘SMA1’] = df[‘Close’].rolling(20).mean() # calculate SMA1
df[‘SMA2’] = df[‘Close’].rolling(50).mean() # calculate SMA2

# DEFINE TECHNICAL INDICATORS (3 of them, being CCI, Rate of Change, and Ease of Movement)

# COMMODITY CHANNEL INDEX ###############################################################################################################################

def CCI(df, ndays):
TP = (df[‘High’] + df[‘Low’] + df[‘Close’]) / 3 # takes average of day’s high, low, and close first
CCI = pd.Series((TP – pd.rolling_mean(TP, ndays)) / (0.015 * pd.rolling_std(TP, ndays)),
# subtract rolling mean of 20 days from the average then divide by standard deviation of the price
name=’CCI’)
df = df.join(CCI)
return df

# Compute the Commodity Channel Index(CCI) for NIFTY based on the 20-day Moving average
n = 20
NIFTY_CCI = CCI(df, n)
CCI = NIFTY_CCI[‘CCI’]

# Plotting the Price Series chart and the Commodity Channel index below
fig = plt.figure(figsize=(7, 5))
ax.set_xticklabels([])
plt.plot(df[‘Close’], lw=1)
plt.title(‘Price Chart’)
plt.ylabel(‘Close Price’)
plt.grid(True)
plt.plot(CCI, ‘k’, lw=0.75, linestyle=’-‘, label=’CCI’)
plt.legend(loc=2, prop={‘size’: 9.5})
plt.ylabel(‘CCI values’)
plt.grid(True)
plt.setp(plt.gca().get_xticklabels(), rotation=30)

#### RATE OF CHANGE#####################################################################################################################################

# The Rate of Change (ROC) is a technical indicator that measures the percentage change between the most recent price and the price
# “n” day’s ago. The indicator fluctuates around the zero line.

# If the ROC is rising, it gives a bullish signal, while a falling ROC gives a bearish signal. One can compute ROC based on different
# periods in order to gauge the short-term momentum or the long-term momentum.

# Estimation

# ROC = [(Close price today – Close price “n” day’s ago) / Close price “n” day’s ago))]

# Rate of Change (ROC)
def ROC(df, n):
N = df[‘Close’].diff(n)
D = df[‘Close’].shift(n)
ROC = pd.Series(N / D, name=’Rate of Change’)
data = df.join(ROC)
return data

# Compute the 5-period Rate of Change
n = 5
NIFTY_ROC = ROC(df, n)
ROC = NIFTY_ROC[‘Rate of Change’]

# Plotting the Price Series chart and the Ease Of Movement below
fig = plt.figure(figsize=(7, 5))
ax.set_xticklabels([])
plt.plot(df[‘Close’], lw=1)
plt.title(‘Price Chart’)
plt.ylabel(‘Close Price’)
plt.grid(True)
plt.plot(ROC, ‘k’, lw=0.75, linestyle=’-‘, label=’ROC’)
plt.legend(loc=2, prop={‘size’: 9})
plt.ylabel(‘ROC values’)
plt.grid(True)
plt.setp(plt.gca().get_xticklabels(), rotation=30)

#### EASE OF MOVEMENT ##################################################################################################################################

# EVM indicates the ease with which the prices rise or fall taking into account the volume of the security.
# A price rise on a low volume means prices advanced with relative ease, and there was little selling pressure.
# Positive EVM values imply that the market is moving higher with ease, while negative values indicate an easy decline.

# Estimation

# To calculate the EMV we first calculate the Distance moved. It is given by:

# Distance moved = ((Current High + Current Low)/2 – (Prior High + Prior Low)/2)
# We then compute the Box ratio which uses the volume and the high-low range:

# Box ratio = (Volume / 100,000,000) / (Current High – Current Low)

# EMV = Distance moved / Box ratio

# To compute the n-period EMV we take the n-period simple moving average of the 1-period EMV

# A sustained positive Ease of Movement together with a rising market confirms a bullish trend,
# while a negative Ease of Movement values with falling prices confirms a bearish trend.

# Example code: 14-day Ease of Movement (EMV)

# Ease of Movement
def EVM(df, ndays):
dm = ((df[‘High’] + df[‘Low’]) / 2) – ((df[‘High’].shift(1) + df[‘Low’].shift(1)) / 2)
br = (df[‘Volume’] / 100000000) / (df[‘High’] – df[‘Low’])
EVM = dm / br
EVM_MA = pd.Series(pd.rolling_mean(EVM, ndays), name=’EVM’)
data = df.join(EVM_MA)
return data

# Compute the 14-day Ease of Movement
n = 14
MU_EVM = EVM(df, n)
EVM = MU_EVM[‘EVM’]

# Plotting the Price Series chart and the Ease Of Movement below
fig = plt.figure(figsize=(7, 5))
ax.set_xticklabels([])
plt.plot(df[‘Close’], lw=1)
plt.title(‘Price Chart’)
plt.ylabel(‘Close Price’)
plt.grid(True)
plt.plot(EVM, ‘k’, lw=0.75, linestyle=’-‘, label=’EVM(14)’)
plt.legend(loc=2, prop={‘size’: 9})
plt.ylabel(‘EVM values’)
plt.grid(True)
plt.setp(plt.gca().get_xticklabels(), rotation=30)

# IDENTIFY LONG AND SHORT POSITIONS ###############################################################################################################################################################

# create a column that holds a TRUE value if both buy criteria are also TRUE
df[‘SELL’] = np.where((df[‘distance’] > threshold)
& (df[‘SMA1’] < df['SMA2']) & (EVM < 0) & (ROC < 0) & (CCI < -100), -1,
np.nan) # if the distance is greater than the threshold value, AND the SMA1 < SMA2, go short i.e. set -1 in the new column position; otherwise set NaN

# create a column that holds a TRUE value if both sell criteria are also TRUE
np.nan) # if the distance is less than the threshold value, AND the SMA1 > SMA2, go long i.e. set 1 in the new column position; otherwise set NaN

df[‘NEUTRAL’] = np.where(df[‘distance’] *
df[‘distance’].shift(1) < 0, 0, df[
'position']) # if there is a change in the sign of the distance value go market neutral i.e. set 0 , otherwise keep position unchanged

# calculate daily % return series for stock
df['Pct Change'] = (df['Close'] – df['Open']) / df['Open']

# create a strategy return series by using the daily stock returns mutliplied by 1 if we are long and -1 if we are short
df['Rets'] = np.where(df['BUY'], df['Pct Change'], 0)
df['Rets'] = np.where(df['SELL'], -df['Pct Change'], df['Rets'])

# append the strategy return series to our list
frames.append(df['Rets'])

except:
pass

############ CALCULATE STATISTICS #########################################################################################################

# concatenate the individual DataFrames held in our list- and do it along the column axis
masterFrame = pd.concat(frames, axis=1)

# create a column to hold the sum of all the individual daily strategy returns
masterFrame['Total'] = masterFrame.sum(axis=1)

# fill 'NaNs' with zeros to allow our "count" function below to work properly
masterFrame.fillna(0, inplace=True)

# create a column that hold the count of the number of stocks that were traded each day
# we minus one from it so that we dont count the "Total" column we added as a trade.
masterFrame['Count'] = (masterFrame != 0).sum(axis=1) – 1

# create a column that divides the "total" strategy return each day by the number of stocks traded that day to get equally weighted return.
masterFrame['Return'] = masterFrame['Total'] / masterFrame['Count']

# plot the strategy returns, this line of code plots the chart
masterFrame['Return'].dropna().cumsum().plot()

# with a Sharpe Ratio of
#risk free element excluded for simplicity
(masterFrame['Return'].mean() *252) / (masterFrame['Return'].std() * (sqrt(252)))
print("Sharpe Ratio")
print((masterFrame['Return'].mean() *252) / (masterFrame['Return'].std() * (sqrt(252))))

# and an annual return of
days = (masterFrame.index[-1] – masterFrame.index).days
(masterFrame['Return'].dropna().cumsum()[-1] + 1) ** (365.0 / days) – 1
print("Annual Return")
print((masterFrame['Return'].dropna().cumsum()[-1] + 1) ** (365.0 / days) – 1)

September 12, 2017 - 4:27 pm

Hi Sam, its really difficult for me to see what is going on here as there is no indentation in your code – that’s the way WordPress formats it unfortunately. If you can email me your code file directly, i will take a look through – you have my email address as I emailed you previously. Cheers

September 12, 2017 - 4:48 pm

Hello,

Sounds good I agree with you. No problem, will email you….

November 15, 2017 - 6:55 am

Hey Sir, I have one question. I want to know how we decide the amount of coins to be sell on buy on each day. For e.g. by applying some techinique can i get output of portfolio like [0.1,0.3 ; -0.5,0.1]. Which means(for portfolio of 2 stocks) on day 1 i will buy 10% of 1st stock and buy 30% of other stock and on second day i will sell 50% of one stock and buy 10% other stock. Can you give me some idea about this sir please.
{"ft_lang_label":"__label__en","ft_lang_prob":0.77908176,"math_prob":0.83986133,"size":30195,"snap":"2020-24-2020-29","text_gpt3_token_len":7969,"char_repetition_ratio":0.13805439,"word_repetition_ratio":0.31883726,"special_character_ratio":0.29302865,"punctuation_ratio":0.12629545,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9827298,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2020-07-05T20:31:05Z\",\"WARC-Record-ID\":\"<urn:uuid:7b888906-f3e0-4a6d-bef2-e994d1a2d5b2>\",\"Content-Length\":\"164988\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:f8dd5871-e67f-491a-ab31-bb1f0dec43f0>\",\"WARC-Concurrent-To\":\"<urn:uuid:aa55ef34-d5e2-4860-846d-9b17e8a50629>\",\"WARC-IP-Address\":\"104.24.100.133\",\"WARC-Target-URI\":\"https://www.pythonforfinance.net/2017/02/21/intraday-stock-mean-reversion-trading-backtest-in-python-with-short-selling/\",\"WARC-Payload-Digest\":\"sha1:4BFCKF4R3QDZSOD43OZ3MJGL2S3O2YWY\",\"WARC-Block-Digest\":\"sha1:HFCXUOTJKEB4OVSGLSS55I2IOIORBCV5\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2020/CC-MAIN-2020-29/CC-MAIN-2020-29_segments_1593655888561.21_warc_CC-MAIN-20200705184325-20200705214325-00442.warc.gz\"}"}