Creating a Moving Average Cross Trading Strategy in Python
Coding a Moving Average Strategy From Scratch in Python
The most basic trend-following strategy is the cross between different-period moving averages as it can signal a potential change in the overall trend. Intuitively, it is a good strategy but we always need to back our intuitions with numbers.
I have released a new book called “Contrarian Trading Strategies in Python”. It features a lot of advanced contrarian indicators and strategies with a GitHub page dedicated to the continuously updated code. If you are interested, you could buy the PDF version directly through a PayPal payment of 9.99 EUR.
Please include your email in the note before paying so that you receive it on the right address. Also, once you receive it, make sure to download it through google drive.
Moving averages help us confirm and ride the trend. They are the most known technical indicator and this is because of their simplicity and their proven track record of adding value to the analyses. We can use them to find support and resistance levels, stops and targets, and to understand the underlying trend. This versatility makes them an indispensable tool in our trading arsenal.
As the name suggests, this is your plain simple mean that is used everywhere in statistics and basically any other part in our lives. It is simply the total values of the observations divided by the number of observations. Mathematically speaking, it can be written down as:
The code for the moving average can be written down as the following:
# The function to add a number of columns inside an array def adder(Data, times): for i in range(1, times + 1): new_col = np.zeros((len(Data), 1), dtype = float) Data = np.append(Data, new_col, axis = 1) return Data
# The function to delete a number of columns starting from an index def deleter(Data, index, times): for i in range(1, times + 1): Data = np.delete(Data, index, axis = 1) return Data # The function to delete a number of rows from the beginning def jump(Data, jump): Data = Data[jump:, ] return Data
# Example of adding 3 empty columns to an array my_ohlc_array = adder(my_ohlc_array, 3)
# Example of deleting the 2 columns after the column indexed at 3 my_ohlc_array = deleter(my_ohlc_array, 3, 2)
# Example of deleting the first 20 rows my_ohlc_array = jump(my_ohlc_array, 20)
# Remember, OHLC is an abbreviation of Open, High, Low, and Close and it refers to the standard historical data file def ma(data, lookback, close, where): data = adder(data, 1) for i in range(len(data)): try: data[i, where] = (data[i - lookback + 1:i + 1, close].mean()) except IndexError: pass data = jump(data, lookback) return data
The below states that the moving average function will be called on the array named my_data for a lookback period of 200, on the column indexed at 3 (closing prices in an OHLC array). The moving average values will then be put in the column indexed at 4 which is the one we have added using the adder function.
my_data = ma(my_data, 200, 3, 4)
Creating the Cross Strategy
Two well-known references exist in the moving average jargon:
The Golden Cross: This occurs whenever the short-term moving average crosses the long-term moving average from the below to the above as a sign of a bullish continuation.
The Death Cross: This occurs whenever the short-term moving average crosses the long-term moving average from the above to the below as a sign of a bearish continuation.
Therefore, the idea of our strategy is to code the golden and death crosses and. However, one question arises, which periods should we select? 50 and 200? 10 and 30? 100 and 500? There is a huge number of combinations that can be made and at the risk of overfitting, they change from one asset to another. It is also based on the trader’s preference, in our case we will stick to a 50 and a 100-period lookback.
def signal(data, short_ma, long_ma, buy, sell): data = adder(data, 10) for i in range(len(data)): if data[i, short_ma] > data[i, long_ma] and data[i - 1, short_ma] < data[i - 1, long_ma]: data[i, buy] = 1 if data[i, short_ma] < data[i, long_ma] and data[i - 1, short_ma] > data[i - 1, long_ma]: data[i, sell] = -1 return data
# Assuming we ran the above code, we will have an OHLC data with two extra columns for the moving average as written below my_data = ma(my_data, short_ma, 3, 4) my_data = ma(my_data, long_ma, 3, 5)
# Using the signal function signal(my_data, 4, 5, 6, 7)
The issue with this type of strategies is the huge lag as evidenced above by the buying of the tops and the selling of the bottoms.
To sum up, what I am trying to do is to simply contribute to the world of objective technical analysis which is promoting more transparent techniques and strategies that need to be back-tested before being implemented. This way, technical analysis will get rid of the bad reputation of being subjective and scientifically unfounded.
I recommend you always follow the the below steps whenever you come across a trading technique or strategy:
Have a critical mindset and get rid of any emotions.
Back-test it using real life simulation and conditions.
If you find potential, try optimizing it and running a forward test.
Always include transaction costs and any slippage simulation in your tests.
Always include risk management and position sizing in your tests.
Finally, even after making sure of the above, stay careful and monitor the strategy because market dynamics may shift and make the strategy unprofitable.