The trading arena is experiencing an influx of individuals striving to capitalize on the stock market. To stand out in this vast landscape and gain a competitive edge, innovation is key. Developing unconventional ideas that deviate from common practices is essential for success. For instance, although technical indicators have become widely adopted, achieving success demands creative utilization rather than adhering solely to traditional strategies. Today, we embark on a journey to do just that.

In this article, we will utilize Python to create an innovative trading strategy by synergizing two potent indicators: the Stochastic Oscillator and the Moving Average Convergence/Divergence (MACD) indicator. Our objective is to minimize false signals as much as possible, thereby enhancing overall performance. Without further delay, let’s dive into the content!

Register & Get Data

Stochastic Oscillator

The Stochastic Oscillator is a momentum-based leading indicator extensively employed to identify overbought and oversold conditions in the market. But what do these terms signify? Overbought refers to a stock in an extremely bullish market, likely to consolidate. Conversely, oversold indicates a stock in an extremely bearish market, with potential for a rebound.

The Stochastic Oscillator values range between 0 and 100 due to normalization. Commonly, overbought and oversold levels are set at 70 and 30 respectively, although these thresholds can vary. The Stochastic Oscillator consists of two primary components:

  • %K Line: Also known as the Fast Stochastic indicator, this line is pivotal. It assesses the current market state (overbought or oversold). The %K line is calculated by subtracting the lowest price over a specific period from the closing price and dividing the result by the difference between the highest and lowest prices over the same period. The outcome is then multiplied by 100. With a popular setting of 14 periods, the %K line calculation is as follows:

%K = 100 * ((14 DAY CLOSING PRICE - 14 DAY LOWEST PRICE) - (14 DAY HIGHEST PRICE - 14 DAY LOWEST PRICE))

  • %D Line: Referred to as the Slow Stochastic Indicator, this line is the moving average of the %K line over a designated period. It serves as the smoothed version of %K, appearing less erratic. A standard period for %D is 3.

This summarizes the Stochastic Oscillator components. Now, let’s examine a chart featuring Apple’s closing price data along with the Stochastic Oscillator calculated using a 14-day period for %K and a 3-day period for %D. This will facilitate a better grasp of the indicator and its practical application.

The chart consists of two panels: the upper panel displays Apple’s closing price line plot, while the lower panel showcases the Stochastic Oscillator components. As a leading indicator, the Stochastic Oscillator cannot be plotted alongside the closing price due to substantial value differences. Therefore, it’s plotted separately (below the closing price).

The %K and %D lines are depicted in blue and orange respectively. Additionally, you’ll notice black dotted lines above and below the %K and %D lines – these are the Bands. These bands highlight overbought and oversold regions. A crossing of both %K and %D above the upper band indicates overbought conditions. Conversely, when both %K and %D cross below the lower band, oversold conditions are identified.

MACD

Before delving into MACD, let’s clarify Exponential Moving Average (EMA). EMA is a type of Moving Average that assigns greater weight to recent data points and lesser weight to distant ones. For instance, in a question paper consisting of 10% one-mark questions, 40% three-mark questions, and 50% long-answer questions, unique weights are assigned based on importance. Similarly, EMA assigns varying weights to data points based on their relevance.

MACD, a trend-following leading indicator, is calculated by subtracting two EMAs (one with longer and one with shorter periods). Three key components constitute a MACD indicator:

  • MACD Line: This line signifies the difference between two EMAs. It involves one EMA with a longer period (slow length) and another with a shorter period (fast length). Common settings are 12 and 26 for fast and slow lengths respectively. Calculation is simply the subtraction of slow length EMA from fast length EMA:
MACD LINE = FAST LENGTH EMA - SLOW LENGTH EMA
  • Signal Line: Also known as the Slow Stochastic Indicator, this is the EMA of the MACD line over a specified period, typically 9. As an average of the MACD line, the Signal line is smoother.
  • Histogram: Representing the difference between the MACD line and the Signal line, the Histogram aids in trend identification. Its calculation is straightforward:

HISTOGRAM = MACD LINE - SIGNAL LINE

With a grasp of MACD’s essence, let’s analyze a MACD chart to foster intuition about the indicator.

The chart features two panels: the upper panel depicts Apple’s closing prices, while the lower panel presents a series of MACD components. Each component is worth inspecting.

The most prominent aspect in the lower panel is the Histogram plot. This plot turns red during negative trends and green during positive ones, offering a visual trend indicator. The spread of the Histogram plot correlates with the difference between MACD and Signal lines. The plot enlarges when this difference is substantial, and contracts when it’s relatively smaller.

The other components are the MACD line and the Signal line. The gray line denotes the MACD line, representing the disparity between slow-length and fast-length EMAs of Apple’s stock prices. The blue line, or Signal line, depicts the EMA of the MACD line. The Signal line is smoother due to averaging of MACD line values.

Register & Get Data

Trading Strategy

Now, having established foundational insights into both the Stochastic Oscillator and MACD, let’s address the trading strategy we’ll implement. The strategy is simple and aims to capitalize on overbought and oversold conditions while minimizing false signals.

We buy (go long) if both %K and %D lines fall below 30, and if both MACD and Signal lines are less than -2. Similarly, we sell (go short) if %K and %D lines cross above 70, and if both MACD and Signal line values exceed 2. The strategy can be formalized 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

This strategy concludes our theoretical segment. Let’s transition to the practical side – the programming. We’ll use Python to build the indicators from scratch, implement the discussed strategy, conduct backtesting on Apple stock data, and ultimately compare outcomes with the SPY ETF. Let’s embark on the coding journey!

Before we proceed, a disclaimer: The purpose of this article is educational, and the strategies presented should be considered as an information piece but not as investment advice.

Implementation in Python

The coding process is divided into several steps as outlined below:

1. Importing Packages
2. Extracting Stock Data using EODHD API
3. Stochastic Oscillator Calculation
4. MACD Calculation
5. Creating the Trading Strategy
6. Creating our Position
7. Backtesting
8. SPY ETF Comparison

We will follow the sequence mentioned above to guide you through each coding segment.

Step-1: Importing Packages

Importing the necessary packages into the Python environment is a crucial initial step. The primary packages to be employed include Pandas for data manipulation, NumPy for array handling and complex operations, Matplotlib for plotting, and Requests for API calls. Additionally, Math is utilized for mathematical functions, and Termcolor offers optional font customization.

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)

Once we’ve imported all the required packages, we can proceed to retrieve historical data for Apple using EODHD’s OHLC split-adjusted data API endpoint.

Step-2: Extracting Stock Data using EODHD

In this step, we’ll retrieve Apple’s historical stock data through OHLC split-adjusted API endpoint provided by EODHD. Please note that EOD Historical Data provides reliable financial APIs, encompassing diverse market data, from historical records to economic and financial news data. It’s essential to possess an EODHD account to access the secret API key, a crucial element for data extraction through the API.

Python Implementation:

# EXTRACTING STOCK DATA

def get_historical_data(symbol, start_date):
    api_key = 'YOUR API KEY'
    api_url = f'https://eodhistoricaldata.com/api/technical/{symbol}?order=a&fmt=json&from={start_date}&function=splitadjusted&api_token={api_key}'
    raw_df = requests.get(api_url).json()
    df = pd.DataFrame(raw_df)
    df.date = pd.to_datetime(df.date)
    df = df.set_index('date')
    return df

aapl = get_historical_data('AAPL', '2010-01-01')
aapl.tail()

Output:

Code Explanation: We begin by defining a function named ‘get_historical_data’. This function requires the stock symbol (‘symbol’) and the starting date for historical data (‘start_date’) as parameters. Within the function, we define the API key and URL, storing them in respective variables. Subsequently, we retrieve historical data in JSON format using the ‘get’ function, storing it in the ‘raw_df’ variable. After processing the raw JSON data to achieve a clean Pandas dataframe, we return it. Finally, we call this function to fetch Apple’s historical data from the start of 2010, storing it in the ‘aapl’ variable.

Step-3: Stochastic Oscillator Calculation

In this phase, we calculate the components of the Stochastic Oscillator using the methods and formulas discussed earlier.

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 define a function called ‘get_stoch_osc’. This function takes high (‘high’), low (‘low’), closing price (‘close’), %K line lookback (‘k_lookback’), and %D line lookback (‘d_lookback’) as parameters. Inside the function, we compute the lowest low and highest high values over a specified period using Pandas’ ‘rolling’, ‘min’, and ‘max’ functions. These values are stored in ‘lowest_low’ and ‘highest_high’ variables, respectively.

Next, we compute the %K line and %D line values according to their formulas, saving them in ‘k_line’ and ‘d_line’ variables. Finally, we return these values, applying the function to obtain Apple’s Stochastic Oscillator readings using lookback periods of 14 for %K and 3 for %D.

Step-4: MACD Calculation

This step focuses on calculating the components of the MACD indicator using Apple’s extracted historical data.

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: We define a function named ‘get_macd’, which accepts stock prices (‘prices’), slow EMA length (‘slow’), fast EMA length (‘fast’), and Signal line period (‘smooth’) as inputs.

Inside the function, we compute the slow and fast EMAs using Pandas’ ‘ewm’ function, storing them in ’ema1′ and ’ema2′ variables. We then calculate the MACD line by subtracting the slow EMA from the fast EMA and store it in the ‘macd’ variable. The Signal line is computed by taking the EMA of the MACD line values for a specific period, stored in the ‘signal’ variable. The Histogram values are derived from the difference between the MACD line and the Signal line, stored in the ‘hist’ variable. Finally, all calculated values are returned, and the function is called to acquire Apple’s MACD components.

Step-5: Creating the Trading Strategy:

In this stage, we implement the combined trading strategy involving the Stochastic Oscillator and Moving Average Convergence/Divergence (MACD) indicators using 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: We define a function named ‘bb_stoch_strategy’, which takes stock prices (‘prices’), %K line values (‘k’), %D line values (‘d’), MACD line (‘macd’), and Signal line (‘macd_signal’) as inputs.

Within the function, we create empty lists (‘buy_price’, ‘sell_price’, and ‘stoch_macd_signal’) to which values will be appended while implementing the trading strategy.

We then employ a for-loop to execute the trading strategy. Based on specified conditions, values are 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 buying 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.The function returns lists with appended values. We call this function to store the values in their respective variables.

Step-6: Creating our Position

This step involves creating a list indicating whether we hold the stock (1) or not (0).

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: We begin by creating an empty list called ‘position’. We employ two for-loops: the first to generate values for the ‘position’ list to match the length of the ‘signal’ list, and the second to generate actual position values.

Inside the second for-loop, we iterate through the ‘signal’ list values. Depending on conditions, we append values to the ‘position’ list. The position remains 1 when holding the stock or 0 when not owning the stock. Finally, we manipulate data to combine the lists into a single dataframe.

From the displayed output, it’s evident that our stock position remains 1 initially, indicating no change in the trading signal. However, the position shifts to 0 when we sell the stock due to a buy signal (-1). The position remains -1 until the trading signal changes. Now, let’s proceed to implement backtesting.

Step-7: Backtesting

Before continuing, it’s crucial to understand backtesting. This process involves evaluating the performance of our trading strategy on the given stock data. In this case, we’ll backtest the combined Stochastic Oscillator and MACD trading strategy on Apple’s 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
stoch_macd_investment_ret = []

for i in range(len(stoch_macd_strategy_ret_df['stoch_macd_returns'])):
    number_of_stocks = floor(investment_value/aapl['close'][i])
    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 : 313585.35
Profit percentage of the STOCH MACD strategy : 313%

Code Explanation: Firstly, we calculate returns for Apple stock using NumPy’s ‘diff’ function, storing them as a dataframe in ‘aapl_ret’. We then use a for-loop to calculate returns obtained from our trading strategy, appending them to ‘stoch_macd_strategy_ret’ list. This list is converted into a dataframe and stored in ‘stoch_macd_strategy_ret_df’.

Next, we initiate backtesting by investing $100,000 in our strategy. Investment value is stored in ‘investment_value’. Using a for-loop, we calculate investment returns and perform data manipulations.

Finally, we print the total return obtained by investing $100,000 in our trading strategy. The output shows that an approximate profit of $300,000 was generated in around thirteen and a half years, with a profit percentage of 313%. Impressive! Now, let’s compare these returns with the SPY ETF.

Step-8: SPY ETF Comparison

This optional step involves comparing our trading strategy’s performance against a benchmark (SPY ETF). We’ll extract SPY ETF data using the ‘get_historical_data’ function and compare its returns with those of our trading strategy on Apple.

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
    benchmark_investment_ret = []
    
    for i in range(len(benchmark['benchmark_returns'])):
        number_of_stocks = floor(investment_value/spy[0])
        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 : 159095.09
Benchmark Profit percentage : 159%
STOCH MACD Strategy profit is 154% higher than the Benchmark Profit

Code Explanation: This code segment is similar to the previous backtesting step, except that we invest in the SPY ETF without implementing any trading strategy. The output indicates that our trading strategy has outperformed the SPY ETF by 154%. Excellent!

Register & Get Data

Final Thoughts!

After an extensive exploration of both theoretical concepts and coding, we’ve successfully comprehended the Stochastic Oscillator and Moving Average Convergence/Divergence indicators. Using Python, we’ve combined these indicators to formulate a trading strategy that has outperformed the SPY ETF.

This article offers a glimpse into how programming is applied in finance, particularly in the stock market. It underscores the importance of mastering programming and technical concepts to gain a competitive edge in the market.