Combining Bollinger Bands and Stochastic Oscillator to create a Killer Trading Strategy in Python

A detailed guide to boosting the strategy performance by using two powerful technical indicators



Technical indicators are great and effective but all of them never fail to have one specific drawback which is revealing false trading signals and trading stocks accordingly might lead to catastrophic results. To tackle this problem, it is best to introduce two different technical indicators whose characteristics vary from one and another to the trading strategy, and by doing so, we could filter the false trading signals from the authentic ones.


In this article, we will try building a trading strategy with two technical indicators which are Bollinger Bands, and Stochastic Oscillator in Python. We will also backtest the strategy and compare the returns with those of the SPY ETF (an ETF designed specifically to track the movement of the S&P 500 market index). Without further ado, let’s dive into the article!


Bollinger Bands


Before jumping on to explore Bollinger Bands, it is essential to know what Simple Moving Average (SMA) is. Simple Moving Average is nothing but the average price of a stock given a specified period of time. Now, Bollinger Bands are trend lines plotted above and below the SMA of the given stock at a specific standard deviation level. To understand Bollinger Bands better, have a look at the following chart that represents the Bollinger Bands of the Apple stock calculated with SMA 20.



Bollinger Bands are great to observe the volatility of a given stock over a period of time. The volatility of a stock is observed to be lower when the space or distance between the upper and lower band is less. Similarly, when the space or distance between the upper and lower band is more, the stock has a higher level of volatility. While observing the chart, you can observe a trend line named ‘MIDDLE BB 20’ which is nothing but SMA 20 of the Apple stock. The formula to calculate both upper and lowers bands of stock are as follows:



UPPER_BB = STOCK SMA + SMA STANDARD DEVIATION * 2
LOWER_BB = STOCK SMA - SMA STANDARD DEVIATION * 2


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 80 and 20 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.


Trading strategy


Now that we have built some basic understanding of what both the technical indicators are all about. Let’s proceed to the trading strategy we are going to implement in this article using Bollinger Bands and the Stochastic Oscillator.


Our trading strategy will reveal a buy signal if the previous day readings of the components of the Stochastic Oscillator are above 30, current-day components’ readings are below 30, and the last closing price is below the lower band. Similarly, a sell signal is revealed whenever the Stochastic Oscillator’s components’ readings are below 70, current components’ readings are above 70, and the last closing price is above the upper band. The strategy can be represented as follows:



IF PREV_ST_COM > 30 AND CUR_ST_COMP < 30 AND CL < LOWER_BB ==> BUY
IF PREV_ST_COM > 70 AND CUR_ST_COMP < 70 AND CL < UPPER_BB ==> SELL

where,
PRE_ST_COM = Previous Day Stochastic Oscillator components' readings
CUR_ST_COM = Current Day Stochastic Oscillator components' readings
CL = Last Closing Price
LOWER_BB = Current Day Lower Band reading
UPPER_BB = Current Day Upper Band reading


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. Bollinger Bands Calculation
4. Stochastic Oscillator 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 requests
import numpy as np
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl
plt.rcParams[‘figure.figsize’] = (20, 10)
plt.style.use(‘fivethirtyeight’)


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: Bollinger Bands calculation


In this step, we are going to calculate the components of the Bollinger Bands by following the methods and formula we discussed before.


Python Implementation:



# BOLLINGER BANDS CALCULATION

def sma(data, lookback):
    sma = data.rolling(lookback).mean()
    return sma

def get_bb(data, lookback):
    std = data.rolling(lookback).std()
    upper_bb = sma(data, lookback) + std * 2
    lower_bb = sma(data, lookback) - std * 2
    middle_bb = sma(data, lookback)
    return upper_bb, lower_bb, middle_bb

aapl['upper_bb'], aapl['middle_bb'], aapl['lower_bb'] = get_bb(aapl['close'], 20)
aapl = aapl.dropna()
aapl.tail()


Output:



Code Explanation: The above can be classified into two parts: SMA calculation, and the Bollinger Bands calculation.


SMA calculation: Firstly, we are defining a function named ‘sma’ that takes the stock prices (‘data’), and the number of periods (‘lookback’) as the parameters. Inside the function, we are using the ‘rolling’ function provided by the Pandas package to calculate the SMA for the given number of periods. Finally, we are storing the calculated values into the ‘sma’ variable and returned them.


Bollinger Bands calculation: We are first defining a function named ‘get_bb’ that takes the stock prices (‘data’), and the number of periods as parameters (‘lookback’). Inside the function, we are using the ‘rolling’ and the ‘std’ function to calculate the standard deviation of the given stock data and stored the calculated standard deviation values into the ‘std’ variable. Next, we are calculating Bollinger Bands values using their respective formulas, and finally, we are returning the calculated values. We are storing the Bollinger Bands values into our ‘aapl’ dataframe using the created ‘bb’ function.


Step-4: 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-5: Creating the trading strategy


In this step, we are going to implement the discussed Bollinger Bands and Stochastic Oscillator trading strategy in python.


Python Implementation:



# TRADING STRATEGY

def bb_stoch_strategy(prices, k, d, upper_bb, lower_bb):
    buy_price = []
    sell_price = []
    bb_stoch_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if k[i-1] > 30 and d[i-1] > 30 and k[i] < 30 and d[i] < 30 and prices[i] < lower_bb[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                bb_stoch_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                bb_stoch_signal.append(0)
        elif k[i-1] < 70 and d[i-1] < 70 and k[i] > 70 and d[i] > 70 and prices[i] > upper_bb[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                bb_stoch_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                bb_stoch_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            bb_stoch_signal.append(0)
    
    sell_price[-1] = prices[-1]
    bb_stoch_signal[-1] = -1
    return buy_price, sell_price, bb_stoch_signal

buy_price, sell_price, bb_stoch_signal = bb_stoch_strategy(aapl['close'], aapl['%k'], aapl['%d'], aapl['upper_bb'], aapl['lower_bb'])


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’), Upper Bollinger band (‘upper_bb’), and the Lower Bollinger band (‘lower_bb’) as parameters.


Inside the function, we are creating three empty lists (buy_price, sell_price, and bb_stoch_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-7: 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(bb_stoch_signal)):
    if bb_stoch_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if bb_stoch_signal[i] == 1:
        position[i] = 1
    elif bb_stoch_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
k = aapl['%k']
d = aapl['%d']
upper_bb = aapl['upper_bb'] 
lower_bb = aapl['lower_bb']
close_price = aapl['close']
bb_stoch_signal = pd.DataFrame(bb_stoch_signal).rename(columns = {0:'bb_stoch_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'bb_stoch_position'}).set_index(aapl.index)

frames = [close_price, k, d, upper_bb, lower_bb, bb_stoch_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy.tail()


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 four rows 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 Bollinger Bands Stochastic Oscillator trading strategy over the Apple stock data.


Python Implementation:



# BACKTESTING

aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
bb_stoch_strategy_ret = []

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['bb_stoch_position'][i]
    bb_stoch_strategy_ret.append(returns)
    
bb_stoch_strategy_ret_df = pd.DataFrame(bb_stoch_strategy_ret).rename(columns = {0:'bb_stoch_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
bb_stoch_investment_ret = []

for i in range(len(bb_stoch_strategy_ret_df['bb_stoch_returns'])):
    returns = number_of_stocks*bb_stoch_strategy_ret_df['bb_stoch_returns'][i]
    bb_stoch_investment_ret.append(returns)

bb_stoch_investment_ret_df = pd.DataFrame(bb_stoch_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(bb_stoch_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the BB STOCH strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the BB STOCH strategy : {}%'.format(profit_percentage), attrs = ['bold']))


Output:



Profit gained from the BB STOCH strategy by investing $100k in AAPL : 1713231.15
Profit percentage of the BB STOCH strategy : 1713%


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 RVI trading strategy, and these returns values are appended to the ‘bb_stoch_strategy_ret’ list. Next, we are converting the ‘bb_stoch_strategy_ret’ list into a dataframe and stored it into the ‘bb_stoch_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 and seven hundred thousand USD in around ten-and-a-half years with a profit percentage of 1713%. 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 Bollinger Bands Stochastic Oscillator 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('BB STOCH Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))


Output:



Benchmark profit by investing $100k : 284127.48
Benchmark Profit percentage : 284%
BB STOCH Strategy profit is 1429% 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 Bollinger Bands Stochastic Oscillator trading strategy has outperformed the SPY ETF by 1429%. That’s awesome!


Final Thoughts!


After an exhaustive process of crushing both theory and programming parts, we have successfully built a trading strategy with two technical indicators which actually make some good returns.


In my opinion, considering two technical indicators to build a trading strategy is the most fundamental and important step in technical analysis and we have to make sure that we do it the right way. This will help us from overcoming one of the huge obstacles while using technical indicators which are false signals and that’s great because making our trades aligning with non-authentic signals would lead to bizarre results and ultimately leads to depleting the capital on a whole.


Talking about improvements, one way to improve this article and take it to the next level would be creating a trading strategy that is realistic in nature. In this article, our program would buy the stocks if the corresponding condition gets satisfied and the same for selling the stock but when implemented in the real-world market, the scenario would be different.


For example, for each trade being made in the real-world market, some fraction of the capital would be transferred to the broker as commission, but in our strategy, we did not consider factors like this and even has some impact on our returns. So, constructing our trading strategy in realistic ways might help us getting more practical insights.


With that being said, you’ve reached the end of the article. If you forgot to follow any of the coding parts, don’t worry. I’ve provided the full source at the end. Hope you learned something new and useful from this article.


Full code:



# IMPORTING PACKAGES

import pandas as pd
import requests
import numpy as np
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl

plt.rcParams['figure.figsize'] = (20, 10)
plt.style.use('fivethirtyeight')

# 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()

# BOLLINGER BANDS CALCULATION

def sma(data, lookback):
    sma = data.rolling(lookback).mean()
    return sma

def get_bb(data, lookback):
    std = data.rolling(lookback).std()
    upper_bb = sma(data, lookback) + std * 2
    lower_bb = sma(data, lookback) - std * 2
    middle_bb = sma(data, lookback)
    return upper_bb, lower_bb, middle_bb

aapl['upper_bb'], aapl['middle_bb'], aapl['lower_bb'] = get_bb(aapl['close'], 20)
aapl = aapl.dropna()
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()

# PLOTTING THE DATA

plot_data = aapl[aapl.index >= '2020-01-01']

plt.plot(plot_data['close'], linewidth = 2.5)
plt.plot(plot_data['upper_bb'], label = 'UPPER BB 20', linestyle = '--', linewidth = 1, color = 'black')
plt.plot(plot_data['middle_bb'], label = 'MIDDLE BB 20', linestyle = '--', linewidth = 1.2