A hands-on guide to creating an unconventional trading strategy by combining multiple indicators in Python
The trading arena is being flooded with a massive amount of people aiming to make a fortune out of the stock market. So to make yourself stand out of this huge crowd and hold a strong edge on the market, it is essential to innovate new ideas that are unconventional and not utilized in general. For example, since the introduction of the technical indicators, almost everyone has started using them but to be successful it is necessary to be creative with them rather than going with the traditional strategies. That’s exactly what we are going to do today.
In this article, we will use Python to create an innovative trading strategy by combining two powerful indicators which are the Stochastic Oscillator and the Moving Average Convergence/Divergence (MACD) indicator with the ultimate aim of eradicating false signals as much as possible and achieve better results. Without further ado, let’s dive into the article!
Stochastic Oscillator
Stochastic Oscillator is a momentum-based leading indicator that is widely used to identify whether the market is in the state of overbought or oversold. This leads to our next question. What is overbought and oversold in a concerning market? A stock is said to be overbought when the market’s trend seems to be extremely bullish and bound to consolidate. Similarly, a stock reaches an oversold region when the market’s trend seems to be extremely bearish and has the tendency to bounce.
The values of the Stochastic Oscillator always lie between 0 to 100 due to its normalization function. The general overbought and oversold levels are considered as 70 and 30 respectively but it could vary from one person to another. The Stochastic Oscillator comprises two main components:
%K Line: This line is the most important and core component of the Stochastic Oscillator indicator. It is otherwise known as the Fast Stochastic indicator. The sole purpose of this line is to express the current state of the market (overbought or oversold). This line is calculated by subtracting the lowest price the stock has reached over a specified number of periods from the closing price of the stock and this difference is then divided by the value calculated by subtracting the lowest price the stock has reached over a specified number of periods from the highest stock price. The final value is arrived at by multiplying the value calculated from the above-mentioned steps by 100. The way to calculate the %K line with the most popular setting of 14 as the number of periods can be represented as follows:
%K = 100 * ((14 DAY CLOSING PRICE - 14 DAY LOWEST PRICE) - (14 DAY HIGHEST PRICE - 14 DAY LOWEST PRICE))
%D Line: Otherwise known as the Slow Stochastic Indicator, is nothing but the moving average of the %K line for a specified period. It is also known as the smooth version of the %K line as the line graph of the %D line will look smoother than the %K line. The standard setting of the %D line is 3 as the number of periods.
That’s the whole process of calculating the components of the Stochastic Oscillator. Now, let’s analyze a chart where Apple’s closing price data is plotted along with its Stochastic Oscillator calculated with 14 and 3 as the lookback periods of the %K line and % D line respectively to build a solid understanding of the indicator and how it’s being used.
The plot is sub-divided into two panels: The upper panel and the lower panel. The upper panel represents the line plot of the closing price of Apple. The lower panel comprises the components of the Stochastic Oscillator. Being a leading indicator, the stochastic oscillator cannot be plotted alongside the closing price as the values of the indicator and the closing price vary a lot. So, it is plotted apart from the closing price (below the closing price in our case).
The components %K line and %D line we discussed before are plotted in blue and orange respectively. You can also notice two additional black dotted lines above and below the %K and %D line. It is an additional component of the Stochastic Oscillator known as Bands. These bands are used to highlight the region of overbought and oversold. If both the %K and %D line crosses above the upper band, then the stock is considered to be overbought. Likewise, when both the %K and %D line crosses below the lower band, the stock is considered to be oversold.
MACD
Before moving on to MACD, it is essential to know what Exponential Moving Average (EMA) means. EMA is a type of Moving Average (MA) that automatically allocates greater weighting (nothing but importance) to the most recent data point and lesser weighting to data points in the distant past. For example, a question paper would consist of 10% of one mark questions, 40% of three mark questions, and 50% of long answer questions. From this example, you can observe that we are assigning unique weights to each section of the question paper based on the importance level (probably long answer questions are given more importance than the one mark questions).
Now, MACD is a trend-following leading indicator that is calculated by subtracting two Exponential Moving Averages (one with longer and the other shorter periods). There are three notable components in a MACD indicator.
MACD Line: This line is the difference between two given Exponential Moving Averages. To calculate the MACD line, one EMA with a longer period known as slow length and another EMA with a shorter period known as fast length is calculated. The most popular length of the fast and slow is 12, 26 respectively. The final MACD line values can be arrived at by subtracting the slow length EMA from the fast length EMA. The formula to calculate the MACD line can be represented as follows:
MACD LINE = FAST LENGTH EMA - SLOW LENGTH EMA
Signal Line: This line is the Exponential Moving Average of the MACD line itself for a given period of time. The most popular period to calculate the Signal line is 9. As we are averaging out the MACD line itself, the Signal line will be smoother than the MACD line.
Histogram: As the name suggests, it is a histogram purposely plotted to reveal the difference between the MACD line and the Signal line. It is a great component to be used to identify trends. The formula to calculate the Histogram can be represented as follows:
HISTOGRAM = MACD LINE - SIGNAL LINE
Now that we have an understanding of what MACD exactly is. Let’s analyze a chart of MACD to build intuitions about the indicator.
There are two panels in this plot: the top panel is the plot of Apple’s close prices, and the bottom panel is a series of plots of the calculated MACD components. Let’s break and see each and every component.
The first and most visible component in the bottom panel is obviously the plot of the calculated Histogram values. You can notice that the plot turns red whenever the market shows a negative trend and turns green whenever the market reveals a positive trend. This feature of the Histogram plot becomes very handy when it comes to identifying the trend of the market. The Histogram plot spreads larger whenever the difference between the MACD line and the Signal line is huge and it is noticeable that the Histogram plot shrinks at times representing the difference between the two of the other components is comparatively smaller.
The next two components are the MACD line and the Signal line. The MACD line is the grey-colored line plot that shows the difference between the slow length EMA and the fast length EMA of Apple’s stock prices. Similarly, the blue-colored line plot is the Signal line that represents the EMA of the MACD line itself. Like we discussed before, the Signal line seems to be more of a smooth-cut version of the MACD line because it is calculated by averaging out the values of the MACD line itself.
Trading Strategy
Now that we have built some basic intuitions on both Stochastic Oscillator and MACD indicators. Let’s discuss the trading strategy that we are going to implement in this article. The strategy is pretty straightforward.
We go long (buy the stock) if the %K and %D line goes below 30, and the MACD and Signal line are lesser than -2. Similarly, we go short (sell the stock) if the %K and %D line crosses above 70, and both the components of the MACD indicator are greater than 2. Our trading strategy can be represented as follows:
IF %K < 30 AND %D < 30 AND MACD.L < -2 AND SIGNAL.L < -2 ==> BUY
IF %K > 70 AND %D > 70 AND MACD.L > 2 AND SIGNAL.L > 2 ==> SELL
That’s it! This concludes our theory part and let’s move on to the programming part where we will use Python to first build the indicators from scratch, construct the discussed trading strategy, backtest the strategy on Apple stock data, and finally compare the results with that of SPY ETF. Let’s do some coding! Before moving on, a note on disclaimer: This article’s sole purpose is to educate people and must be considered as an information piece but not as investment advice or so.
Implementation in Python
The coding part is classified into various steps as follows:
1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. Stochastic Oscillator Calculation
4. MACD Calculation
5. Creating the Trading Strategy
6. Creating our Position
7. Backtesting
8. SPY ETF Comparison
We will be following the order mentioned in the above list and buckle up your seat belts to follow every upcoming coding part.
Step-1: Importing Packages
Importing the required packages into the python environment is a non-skippable step. The primary packages are going to be Pandas to work with data, NumPy to work with arrays and for complex functions, Matplotlib for plotting purposes, and Requests to make API calls. The secondary packages are going to be Math for mathematical functions and Termcolor for font customization (optional).
Python Implementation:
# IMPORTING PACKAGES
import pandas as pd
import numpy as np
import requests
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl
plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20,10)
Now that we have imported all the required packages into our python. Let’s pull the historical data of Apple with Twelve Data’s API endpoint.
Step-2: Extracting Stock Data from Twelve Data
In this step, we are going to pull the historical stock data of Apple using an API endpoint provided by twelvedata.com. Before that, a note on twelvedata.com: Twelve Data is one of the leading market data providers having an enormous amount of API endpoints for all types of market data. It is very easy to interact with the APIs provided by Twelve Data and has one of the best documentation ever. Also, ensure that you have an account on twelvedata.com, only then, you will be able to access your API key (vital element to extract data with an API).
Python Implementation:
# EXTRACTING STOCK DATA
def get_historical_data(symbol, start_date):
api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df
aapl = get_historical_data('AAPL', '2010-01-01')
aapl.tail()
Output:
Code Explanation: The first thing we did is to define a function named ‘get_historical_data’ that takes the stock’s symbol (‘symbol’) and the starting date of the historical data (‘start_date’) as parameters. Inside the function, we are defining the API key and the URL and stored them into their respective variable. Next, we are extracting the historical data in JSON format using the ‘get’ function and stored it into the ‘raw_df’ variable. After doing some processes to clean and format the raw JSON data, we are returning it in the form of a clean Pandas dataframe. Finally, we are calling the created function to pull the historic data of Apple from the starting of 2010 and stored it into the ‘aapl’ variable.
Step-3: Stochastic Oscillator Calculation
In this step, we are going to calculate the components of the Stochastic Oscillator by following the methods and formula we discussed before.
Python Implementation:
# STOCHASTIC OSCILLATOR CALCULATION
def get_stoch_osc(high, low, close, k_lookback, d_lookback):
lowest_low = low.rolling(k_lookback).min()
highest_high = high.rolling(k_lookback).max()
k_line = ((close - lowest_low) / (highest_high - lowest_low)) * 100
d_line = k_line.rolling(d_lookback).mean()
return k_line, d_line
aapl['%k'], aapl['%d'] = get_stoch_osc(aapl['high'], aapl['low'], aapl['close'], 14, 3)
aapl.tail()
Output:
Code Explanation: We are first defining a function named ‘get_stoch_osc’ that takes a stock’s high (‘high’), low (‘low’), closing price data (‘close’), and the lookback periods of the %K line (‘k_lookback’) and the %D line (‘d_lookback’) respectively as parameters. Inside the function, we are first calculating the lowest low and highest high data points for a specified number of periods using the ‘rolling’, ‘min’, and ‘max’ functions provided by the Pandas packages and stored the values into the ‘lowest_low’, and ‘highest_high’ variables.
Then comes the %K line calculation where we are substituting the formula into our code and stored the readings into the ‘k_line’ variable, followed by that, we are calculating the %D line which is nothing but taking the SMA of the %K line readings for a specified number of periods. Finally, we are returning the values and calling the function to store Apple’s Stochastic Oscillator readings with 14 and 3 as the lookback periods of the %K and %D line respectively.
Step-4: MACD Calculation
In this step, we are going to calculate all the components of the MACD indicator from the extracted historical data of Apple.
Python Implementation:
# MACD CALCULATION
def get_macd(price, slow, fast, smooth):
exp1 = price.ewm(span = fast, adjust = False).mean()
exp2 = price.ewm(span = slow, adjust = False).mean()
macd = pd.DataFrame(exp1 - exp2).rename(columns = {'close':'macd'})
signal = pd.DataFrame(macd.ewm(span = smooth, adjust = False).mean()).rename(columns = {'macd':'signal'})
hist = pd.DataFrame(macd['macd'] - signal['signal']).rename(columns = {0:'hist'})
return macd, signal, hist
aapl['macd'] = get_macd(aapl['close'], 26, 12, 9)[0]
aapl['macd_signal'] = get_macd(aapl['close'], 26, 12, 9)[1]
aapl['macd_hist'] = get_macd(aapl['close'], 26, 12, 9)[2]
aapl = aapl.dropna()
aapl.tail()
Output:
Code Explanation: Firstly, we are defining a function named ‘get_macd’ that takes the stock’s price (‘prices’), length of the slow EMA (‘slow’), length of the fast EMA (‘fast’), and the period of the Signal line (‘smooth’).
Inside the function, we are first calculating the fast and slow length EMAs using the ‘ewm’ function provided by Pandas and stored them into the ‘ema1’ and ‘ema2’ variables respectively.
Next, we calculated the values of the MACD line by subtracting the slow length EMA from the fast length EMA and stored it into the ‘macd’ variable in the form of a Pandas dataframe. Followed by that, we defined a variable named ‘signal’ to store the values of the Signal line calculated by taking the EMA of the MACD line’s values (‘macd’) for a specified number of periods.
Then, we calculated the Histogram values by subtracting the MACD line’s values (‘macd’) from the Signal line’s values (‘signal’) and stored them into the ‘hist’ variable. Finally, we returning all the calculated values and called the created function to store Apple’s MACD components.
Step-5: Creating the Trading Strategy:
In this step, we are going to implement the discussed Stochastic Oscillator and Moving Average Convergence/Divergence (MACD) combined trading strategy in python.
Python Implementation:
# TRADING STRATEGY
def implement_stoch_macd_strategy(prices, k, d, macd, macd_signal):
buy_price = []
sell_price = []
stoch_macd_signal = []
signal = 0
for i in range(len(prices)):
if k[i] < 30 and d[i] < 30 and macd[i] < -2 and macd_signal[i] < -2:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
stoch_macd_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
stoch_macd_signal.append(0)
elif k[i] > 70 and d[i] > 70 and macd[i] > 2 and macd_signal[i] > 2:
if signal != -1 and signal != 0:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
stoch_macd_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
stoch_macd_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
stoch_macd_signal.append(0)
return buy_price, sell_price, stoch_macd_signal
buy_price, sell_price, stoch_macd_signal = implement_stoch_macd_strategy(aapl['close'], aapl['%k'], aapl['%d'], aapl['macd'], aapl['macd_signal'])
Code Explanation: First, we are defining a function named ‘bb_stoch_strategy’ which takes the stock prices (‘prices’), %K line readings (‘k’), %D line readings (‘d’), MACD line (‘macd’), and the Signal line (‘macd_signal’) as parameters.
Inside the function, we are creating three empty lists (buy_price, sell_price, and stoch_macd_signal) in which the values will be appended while creating the trading strategy.
After that, we are implementing the trading strategy through a for-loop. Inside the for-loop, we are passing certain conditions, and if the conditions are satisfied, the respective values will be appended to the empty lists. If the condition to buy the stock gets satisfied, the buying price will be appended to the ‘buy_price’ list, and the signal value will be appended as 1 representing to buy the stock. Similarly, if the condition to sell the stock gets satisfied, the selling price will be appended to the ‘sell_price’ list, and the signal value will be appended as -1 representing to sell the stock. Finally, we are returning the lists appended with values. Then, we are calling the created function and stored the values into their respective variables.
Step-6: Creating our Position
In this step, we are going to create a list that indicates 1 if we hold the stock or 0 if we don’t own or hold the stock.
Python Implementation:
# POSITION
position = []
for i in range(len(stoch_macd_signal)):
if stoch_macd_signal[i] > 1:
position.append(0)
else:
position.append(1)
for i in range(len(aapl['close'])):
if stoch_macd_signal[i] == 1:
position[i] = 1
elif stoch_macd_signal[i] == -1:
position[i] = 0
else:
position[i] = position[i-1]
close_price = aapl['close']
k_line = aapl['%k']
d_line = aapl['%d']
macd_line = aapl['macd']
signal_line = aapl['macd_signal']
stoch_macd_signal = pd.DataFrame(stoch_macd_signal).rename(columns = {0:'stoch_macd_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'stoch_macd_position'}).set_index(aapl.index)
frames = [close_price, k_line, d_line, macd_line, signal_line, stoch_macd_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)
strategy
Output:
Code Explanation: First, we are creating an empty list named ‘position’. We are passing two for-loops, one is to generate values for the ‘position’ list to just match the length of the ‘signal’ list. The other for-loop is the one we are using to generate actual position values.
Inside the second for-loop, we are iterating over the values of the ‘signal’ list, and the values of the ‘position’ list get appended concerning which condition gets satisfied. The value of the position remains 1 if we hold the stock or remains 0 if we sold or don’t own the stock. Finally, we are doing some data manipulations to combine all the created lists into one dataframe.
From the output being shown, we can see that in the first row our position in the stock has remained 1 (since there isn’t any change in the trading signal) but our position suddenly turned to 0 as we sold the stock when the trading signal represents a buy signal (-1). Our position will remain -1 until some changes in the trading signal occur. Now it’s time to do implement some backtesting processes!
Step-7: Backtesting
Before moving on, it is essential to know what backtesting is. Backtesting is the process of seeing how well our trading strategy has performed on the given stock data. In our case, we are going to implement a backtesting process for our Stochastic Oscillator and MACD combined trading strategy over the Apple stock data.
Python Implementation:
# BACKTESTING
aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
stoch_macd_strategy_ret = []
for i in range(len(aapl_ret)):
try:
returns = aapl_ret['returns'][i] * strategy['stoch_macd_position'][i]
stoch_macd_strategy_ret.append(returns)
except:
pass
stoch_macd_strategy_ret_df = pd.DataFrame(stoch_macd_strategy_ret).rename(columns = {0:'stoch_macd_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value / aapl['close'][0])
stoch_macd_investment_ret = []
for i in range(len(stoch_macd_strategy_ret_df['stoch_macd_returns'])):
returns = number_of_stocks * stoch_macd_strategy_ret_df['stoch_macd_returns'][i]
stoch_macd_investment_ret.append(returns)
stoch_macd_investment_ret_df = pd.DataFrame(stoch_macd_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(stoch_macd_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret / investment_value) * 100)
print(cl('Profit gained from the STOCH MACD strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the STOCH MACD strategy : {}%'.format(profit_percentage), attrs = ['bold']))
Output:
Profit gained from the STOCH MACD strategy by investing $100k in AAPL : 1189853.94
Profit percentage of the STOCH MACD strategy : 1189%
Code Explanation: First, we are calculating the returns of the Apple stock using the ‘diff’ function provided by the NumPy package and we have stored it as a dataframe into the ‘aapl_ret’ variable. Next, we are passing a for-loop to iterate over the values of the ‘aapl_ret’ variable to calculate the returns we gained from our trading strategy, and these returns values are appended to the ‘stoch_macd_strategy_ret’ list. Next, we are converting the ‘stoch_macd_strategy_ret’ list into a dataframe and stored it into the ‘stoch_macd_strategy_ret_df’ variable.
Next comes the backtesting process. We are going to backtest our strategy by investing a hundred thousand USD into our trading strategy. So first, we are storing the amount of investment into the ‘investment_value’ variable. After that, we are calculating the number of Apple stocks we can buy using the investment amount. You can notice that I’ve used the ‘floor’ function provided by the Math package because, while dividing the investment amount by the closing price of Apple stock, it spits out an output with decimal numbers. The number of stocks should be an integer but not a decimal number. Using the ‘floor’ function, we can cut out the decimals. Remember that the ‘floor’ function is way more complex than the ‘round’ function. Then, we are passing a for-loop to find the investment returns followed by some data manipulation tasks.
Finally, we are printing the total return we got by investing a hundred thousand into our trading strategy and it is revealed that we have made an approximate profit of one million, one hundred and ninety thousand USD in around ten-and-a-half years with a profit percentage of 1189%. That’s great! Now, let’s compare our returns with SPY ETF (an ETF designed to track the S&P 500 stock market index) returns.
Step-8: SPY ETF Comparison
This step is optional but it is highly recommended as we can get an idea of how well our trading strategy performs against a benchmark (SPY ETF). In this step, we will extract the SPY ETF data using the ‘get_historical_data’ function we created and compare the returns we get from the SPY ETF with our trading strategy returns on Apple.
You might have observed that in all of my algorithmic trading articles, I’ve compared the strategy results not with the S&P 500 market index itself but with the SPY ETF and this is because most of the stock data providers (like Twelve Data) don’t provide the S&P 500 index data. So, I have no other choice than to go with the SPY ETF. If you’re fortunate to get the S&P 500 market index data, it is recommended to use it for comparison rather than any ETF.
Python Implementation:
# SPY ETF COMPARISON
def get_benchmark(start_date, investment_value):
spy = get_historical_data('SPY', start_date)['close']
benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})
investment_value = investment_value
number_of_stocks = floor(investment_value/spy[0])
benchmark_investment_ret = []
for i in range(len(benchmark['benchmark_returns'])):
returns = number_of_stocks*benchmark['benchmark_returns'][i]
benchmark_investment_ret.append(returns)
benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
return benchmark_investment_ret_df
benchmark = get_benchmark('2010-01-01', 100000)
investment_value = 100000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)
print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('STOCH MACD Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))
Output:
Benchmark profit by investing $100k : 287249.77
Benchmark Profit percentage : 287%
STOCH MACD Strategy profit is 902% higher than the Benchmark Profit
Code Explanation: The code used in this step is almost similar to the one used in the previous backtesting step but, instead of investing in Apple, we are investing in SPY ETF by not implementing any trading strategies. From the output, we can see that our trading strategy has outperformed the SPY ETF by 902%. That’s awesome!
Final Thoughts!
After an overwhelming process of crushing both theory and coding parts, we have successfully learned what the Stochastic Oscillator and the Moving Average Convergence/Divergence is all about, combined these two indicators using Python to create a trading strategy and surpassed the SPY ETF’s performance.
This article is just a glimpse of how programming is being applied to finance and especially the stock market these days. From this, we can say that in order to hold a strong edge on the market, it is essential for one to master the art of programming and other similar technical concepts.
If you wonder where to learn these kinds of stuff, don’t worry! I’ve got your back. The answer would be Quantra which is an e-learning platform dedicated to using Python for finance. They offer everything you need, for example, Machine Learning and Deep Learning for Finance, Algorithmic Trading with Python, Sentiment Analysis for Stock Trading using Python, and so on. Without having a second thought on your mind, hop on to Quantra and start craving your path into this world.
Full code:
# IMPORTING PACKAGES
import pandas as pd
import numpy as np
import requests
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl
plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20,10)
# EXTRACTING STOCK DATA
def get_historical_data(symbol, start_date):
api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df
aapl = get_historical_data('AAPL', '2010-01-01')
aapl.tail()
# STOCHASTIC OSCILLATOR CALCULATION
def get_stoch_osc(high, low, close, k_lookback, d_lookback):
lowest_low = low.rolling(k_lookback).min()
highest_high = high.rolling(k_lookback).max()
k_line = ((close - lowest_low) / (highest_high - lowest_low)) * 100
d_line = k_line.rolling(d_lookback).mean()
return k_line, d_line
aapl['%k'], aapl['%d'] = get_stoch_osc(aapl['high'], aapl['low'], aapl['close'], 14, 3)
aapl.tail()
# STOCHASTIC OSCILLATOR PLOT
plot_data = aapl[aapl.index >= '2020-01-01']
ax1 = plt.subplot2grid((14,1), (0,0), rowspan = 7, colspan = 1)
ax2 = plt.subplot2grid((15,1), (9,0), rowspan = 6, colspan = 1)
ax1.plot(plot_data['close'], linewidth = 2.5)
ax1.set_title('AAPL STOCK PRICES')
ax2.plot(plot_data['%k'], color = 'deepskyblue', linewidth = 1.5, label = '%K')
ax2.plot(plot_data['%d'], color = 'orange', linewidth = 1.5, label = '%D')
ax2.axhline(70, color = 'black', linewidth = 1, linestyle = '--')
ax2.axhline(30, color = 'black', linewidth = 1, linestyle = '--')
ax2.set_title(f'AAPL STOCH 14,3')
ax2.legend(loc = 'right')
plt.show()
# MACD CALCULATION
def get_macd(price, slow, fast, smooth):
exp1 = price.ewm(span = fast, adjust = False).mean()
exp2 = price.ewm(span = slow, adjust = False).mean()
macd = pd.DataFrame(exp1 - exp2).rename(columns = {'close':'macd'})
signal = pd.DataFrame(macd.ewm(span = smooth, adjust = False).mean()).rename(columns = {'macd':'signal'})
hist = pd.DataFrame(macd['macd'] - signal['signal']).rename(columns = {0:'hist'})
return macd, signal, hist
aapl['macd'] = get_macd(aapl['close'], 26, 12, 9)[0]
aapl['macd_signal'] = get_macd(aapl['close'], 26, 12, 9)[1]
aapl['macd_hist'] = get_macd(aapl['close'], 26, 12, 9)[2]
aapl = aapl.dropna()
aapl.tail()
# MACD PLOT
plot_data = aapl[aapl.index >= '2020-01-01']
def plot_macd(prices, macd, signal, hist):
ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(prices, linewidth = 2.5)
ax1.set_title('AAPL STOCK PRICES')
ax2.plot(macd, color = 'grey', linewidth = 1.5, label = 'MACD')
ax2.plot(signal, color = 'skyblue', linewidth = 1.5, label = 'SIGNAL')
ax2.set_title('AAPL MACD 26,12,9')
for i in range(len(prices)):
if str(hist[i])[0] == '-':
ax2.bar(prices.index[i], hist[i], color = '#ef5350')
else:
ax2.bar(prices.index[i], hist[i], color = '#26a69a')
plt.legend(loc = 'lower right')
plot_macd(plot_data['close'], plot_data['macd'], plot_data['macd_signal'], plot_data['macd_hist'])
# TRADING STRATEGY
def implement_stoch_macd_strategy(prices, k, d, macd, macd_signal):
buy_price = []
sell_price = []
stoch_macd_signal = []
signal = 0
for i in range(len(prices)):
if k[i] < 30 and d[i] < 30 and macd[i] < -2 and macd_signal[i] < -2:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
stoch_macd_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
stoch_macd_signal.append(0)
elif k[i] > 70 and d[i] > 70 and macd[i] > 2 and macd_signal[i] > 2:
if signal != -1 and signal != 0:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
stoch_macd_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
stoch_macd_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
stoch_macd_signal.append(0)
return buy_price, sell_price, stoch_macd_signal
buy_price, sell_price, stoch_macd_signal = implement_stoch_macd_strategy(aapl['close'], aapl['%k'], aapl['%d'], aapl['macd'], aapl['macd_signal'])
# TRADING SIGNALS PLOT
plt.plot(aapl['close'])
plt.plot(aapl.index, buy_price, marker = '^', markersize = 10, color = 'green')
plt.plot(aapl.index, sell_price, marker = 'v', markersize = 10, color = 'r')
# POSITION
position = []
for i in range(len(stoch_macd_signal)):
if stoch_macd_signal[i] > 1:
position.append(0)
else:
position.append(1)
for i in range(len(aapl['close'])):
if stoch_macd_signal[i] == 1:
position[i] = 1
elif stoch_macd_signal[i] == -1:
position[i] = 0
else:
position[i] = position[i-1]
close_price = aapl['close']
k_line = aapl['%k']
d_line = aapl['%d']
macd_line = aapl['macd']
signal_line = aapl['macd_signal']
stoch_macd_signal = pd.DataFrame(stoch_macd_signal).rename(columns = {0:'stoch_macd_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'stoch_macd_position'}).set_index(aapl.index)
frames = [close_price, k_line, d_line, macd_line, signal_line, stoch_macd_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)
strategy.head()
strategy[-75:-70]
# BACKTESTING
aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
stoch_macd_strategy_ret = []
for i in range(len(aapl_ret)):
try:
returns = aapl_ret['returns'][i] * strategy['stoch_macd_position'][i]
stoch_macd_strategy_ret.append(returns)
except:
pass
stoch_macd_strategy_ret_df = pd.DataFrame(stoch_macd_strategy_ret).rename(columns = {0:'stoch_macd_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value / aapl['close'][0])
stoch_macd_investment_ret = []
for i in range(len(stoch_macd_strategy_ret_df['stoch_macd_returns'])):
returns = number_of_stocks * stoch_macd_strategy_ret_df['stoch_macd_returns'][i]
stoch_macd_investment_ret.append(returns)
stoch_macd_investment_ret_df = pd.DataFrame(stoch_macd_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(stoch_macd_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret / investment_value) * 100)
print(cl('Profit gained from the STOCH MACD strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the STOCH MACD strategy : {}%'.format(profit_percentage), attrs = ['bold']))
# SPY ETF COMPARISON
def get_benchmark(start_date, investment_value):
spy = get_historical_data('SPY', start_date)['close']
benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})
investment_value = investment_value
number_of_stocks = floor(investment_value/spy[0])
benchmark_investment_ret = []
for i in range(len(benchmark['benchmark_returns'])):
returns = number_of_stocks*benchmark['benchmark_returns'][i]
benchmark_investment_ret.append(returns)
benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
return benchmark_investment_ret_df
benchmark = get_benchmark('2010-01-01', 100000)
investment_value = 100000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)
print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('STOCH MACD Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))
Elaborate one. Good
Sometime back you explained stochastic indicator with Bollinger, now with MACD which is well detailed by you. Good work Nikil.
Paramasivan
Good One Nikhil