The A-Z of Coding a Trading Strategy. A Python Series
A comprehensive tutorial to coding your first trading strategy.

Introduction
Creating a personal trading strategy is becoming more and more popular amongst at-home traders and/or Python enthusiasts. This article will go through the complete lifecycle of a trading strategy: starting from procuring market data, computing technical indicators, generating trading signals and finally assessing the efficacy of the strategy.
We will use $ETH prices in USD from Kraken’s API and build two strategies based on the Moving Average Convergence/Divergence (MACD) and the Awesome Oscillator (AO).
By the end of this article, one should have a robust foundation to build on, and the capability to test its own trading strategies and assess their potential.
That said, it’s critical to note that the strategies we are deploying here are simplified versions designed for demonstrative purposes. There are additional factors that a pragmatic trading strategy ought to consider, such as trading costs, market influence, risk management, and more. Moreover, tangible trading conditions, like existing portfolio holdings, can significantly influence the outcome of a strategy. Although we’ll use real market data, keep in mind that this article is a tutorial, not financial advice, and aims at enhancing our Python skills.
With this in mind, let’s create our own trading strategy!
Part 1: Fetching historical data from Kraken
We first import our required libraries and then initialize our public API. While we are fetching data from Kraken, note that the “ccxt” Python library can be used to fetch data from multiple exchanges like Binance. Be aware that symbol and timeframe formats may vary between exchanges, consequently you might need to check “ccxt” documentation before fetching new data from other exchanges.
We define the symbol and the timeframe. We are looking for Ethereum “$ETH” traded in USD on Kraken “ETH/USD” and daily data.
“fetch_ohlcv” retrieves the OHLCV (Open, High, Low, Close, Volume) data for the symbol and timeframe specified: OHLCV data is fundamental for creating a strategy or conducting various types of technical analysis.
import ccxt
import pandas as pd
# Kraken API
exchange = ccxt.kraken()
# We define the symbol and timeframe
symbol = 'ETH/USD'
timeframe = '1d'
# We fetch OHLCV data
ohlcv = exchange.fetch_ohlcv(symbol, timeframe)
# We first convert to DataFrame then to Timestamp for a more readable format
df = pd.DataFrame(ohlcv, columns=['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume'])
df['Timestamp'] = pd.to_datetime(df['Timestamp'], unit='ms')
df.set_index('Timestamp', inplace=True)
# We now have our DataFrame
df
Here, let’s keep in mind that our DataFrame “df” has 720 entries. 1 entry represents 1 day. It’s important to determine the number of days of historical data available for technical indicators and backtesting purposes.
Part 2: Calculating the MACD and the AO
We now calculate the MACD and AO from our fetch data. The definitions below come from Investopedia and IG International. You will find the links in the ressources section.
“The Moving Average Convergence/Divergence (MACD) is a trend-following momentum indicator that shows the relationship between two exponential moving averages (EMAs) of a security’s price. The MACD line is calculated by subtracting the 26-period EMA from the 12-period EMA.
The result of that calculation is the MACD line. A nine-day EMA of the MACD line is called the signal line, which is then plotted on top of the MACD line, which can function as a trigger for buy or sell signals.” — Investopedia
For the MACD, we compute the MACD Line, the Signal Line, and the MACD Histogram in order to visualize it completely and develop a proper strategy.
“The Awesome Oscillator (AO) is a market momentum indicator which compares recent market movements to historic market movements. It uses a zero line in the centre, either side of which price movements are plotted according to a comparison of two different moving averages.” — IG International
For the AO, we compute a histogram anchored around a zero line.
We calculate both indicators using the “ta” Python library.
# Calculate MACD Line, Signal Line and MACD Histogram
macd = ta.trend.MACD(df['Close'])
df['macd_line'] = macd.macd() # MACD Line
df['macd_signal'] = macd.macd_signal() # Signal Line
df['macd_hist'] = macd.macd_diff() # MACD Histogram (MACD Line - Signal Line)
# Calculate Awesome Oscillator
ao = ta.momentum.AwesomeOscillatorIndicator(df['High'], df['Low'])
df['ao'] = ao.awesome_oscillator() # Awesome Oscillator
df
Part 3: Dropping the NaN values from our DataFrame
This is a brief but crucial part of the tutorial. Given that the data for MACD and AO can’t be calculated due to the absence of historical prices before 2021–05–13, we drop all the NaN values from our DataFrame “df”. Always understand an indicator before adding it to your strategy!
# Drop NaN values
df.dropna(inplace=True)
df.info()We now have prices for 687 days: from the 2021–06–15 to the 2023–05–02. Note that if you reproduce this code at home, you will fetch data that will have a different timeframe from this article (since Kraken only allows you to get data from the last 720 days, there will be a lag). But not to worry, it’s still the same process!
Part 4: Plotting the ETH prices and the indicators
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
fig, ax = plt.subplots(3, figsize=(12,12), dpi=200)
# Plot ETH prices
ax[0].plot(df.index, df['Close'], label='ETH/USD')
ax[0].set_title('ETH/USD Prices - Last 687 days')
ax[0].legend()
ax[0].set_xlim([df.index.min(), df.index.max()])
# Plot MACD Line, Signal Line and Histogram
ax[1].plot(df.index, df['macd_line'], label='MACD Line') # Plot MACD Line and Signal Line
ax[1].plot(df.index, df['macd_signal'], label='Signal Line') # Plot MACD Line and Signal Line
ax[1].bar(df.index, df['macd_hist'], alpha=0.5, label='MACD Histogram') # Plot MACD Histogram as bar plot
ax[1].set_title('MACD')
ax[1].legend()
ax[1].set_xlim([df.index.min(), df.index.max()])
# Plot Awesome Oscillator
ax[2].bar(df.index, df['ao'], alpha=0.5, label='Awesome Oscillator', color='gray')
ax[2].set_title('Awesome Oscillator')
ax[2].legend()
ax[2].set_xlim([df.index.min(), df.index.max()])
# Set date format
date_format = mdates.DateFormatter('%b %y')
ax[0].xaxis.set_major_formatter(date_format)
ax[1].xaxis.set_major_formatter(date_format)
ax[2].xaxis.set_major_formatter(date_format)
# Show the plot
plt.tight_layout()
plt.show()
Part 5: Generating trading signals
We’ll define two strategies based on the MACD and AO signals.
Strategy 1: Buy when both MACD and AO are positive, sell when they’re both negative.
Strategy 2: Buy when MACD crosses above the signal line and AO crosses above zero, sell when MACD crosses below the signal line and AO crosses below zero.
import numpy as np
# Strategy 1
# Generating the buy "signal1"
df['signal1'] = np.where((df['macd_line'] > df['macd_signal']) & (df['ao'] > 0), 1, 0)
# Generating the "sell_signal1"
df['sell_signal1'] = np.where((df['macd_line'] < df['macd_signal']) & (df['ao'] < 0), 1, 0)
# Strategy 2
# Generating the buy "signal2"
df['signal2'] = np.where((df['macd_line'] > df['macd_signal']) & (df['macd_line'].shift() < df['macd_signal'].shift()) & (df['ao'] > 0) & (df['ao'].shift() < 0), 1, 0)
# Generating the "sell_signal2"
df['sell_signal2'] = np.where((df['macd_line'] < df['macd_signal']) & (df['macd_line'].shift() > df['macd_signal'].shift()) & (df['ao'] < 0) & (df['ao'].shift() > 0), 1, 0)
# Counting the buy/sell signals
num_signals1 = df['signal1'].sum()
num_sells1 = df['sell_signal1'].sum()
num_signals2 = df['signal2'].sum()
num_sells2 = df['sell_signal2'].sum()
print(f"Number of buy signals in Strategy 1: {num_signals1}")
print(f"Number of sell signals in Strategy 1: {num_sells1}")
print(f"Number of buy signals in Strategy 2: {num_signals2}")
print(f"Number of sell signals in Strategy 2: {num_sells2}")In summary, both strategies rely on the MACD Line, Signal Line, and AO value to generate buy and sell signals. Strategy 1 is based on simple intersections of the MACD Line and Signal Line, along with the AO value being above or below zero. Strategy 2, on the other hand, focuses on the crossing points of the MACD Line and Signal Line, as well as the crossing points of the AO value above and below zero.
Strategy 2 is more sensitive because it is looking for the points at which the MACD Line crosses the Signal Line and when the AO value crosses zero. These crossing points typically represent changes in market momentum, which is why the strategy is said to be sensitive to changes in momentum.
Part 6: Plotting the signals on the graph
fig, ax = plt.subplots(4, figsize=(12, 30))
# Plot ETH prices
ax[0].plot(df.index, df['Close'], label='ETH/USD')
ax[0].scatter(df[df['signal1'] == 1].index, df[df['signal1'] == 1]['Close'], color='g', marker='^', label='Buy Signal - Strategy 1')
ax[0].scatter(df[df['sell_signal1'] == 1].index, df[df['sell_signal1'] == 1]['Close'], color='r', marker='v', label='Sell Signal - Strategy 1')
ax[0].scatter(df[df['signal2'] == 1].index, df[df['signal2'] == 1]['Close'], color='b', marker='o', label='Buy Signal - Strategy 2', alpha=0.7)
ax[0].scatter(df[df['sell_signal2'] == 1].index, df[df['sell_signal2'] == 1]['Close'], color='orange', marker='x', label='Sell Signal - Strategy 2', alpha=0.7)
ax[0].set_title('ETH/USD Prices - Last 687 days')
ax[0].legend()
ax[0].set_xlim([df.index.min(), df.index.max()])
# Plot MACD Line, Signal Line and Histogram
ax[1].plot(df.index, df['macd_line'], label='MACD Line')
ax[1].plot(df.index, df['macd_signal'], label='Signal Line')
ax[1].bar(df.index, df['macd_hist'], alpha=0.5, label='MACD Histogram')
ax[1].set_title('MACD')
ax[1].legend()
ax[1].set_xlim([df.index.min(), df.index.max()])
# Plot Awesome Oscillator
ax[2].bar(df.index, df['ao'], alpha=0.5, label='Awesome Oscillator', color='gray')
ax[2].set_title('Awesome Oscillator')
ax[2].legend()
ax[2].set_xlim([df.index.min(), df.index.max()])
# Let's also set the appropriate date format
date_format = mdates.DateFormatter('%b %y')
for a in ax:
a.xaxis.set_major_formatter(date_format)
# Plot
plt.tight_layout()
plt.show()
Part 7: Backtesting
To evaluate our strategies, we calculate the daily returns based on the generated buy and sell signals: we calculate the daily returns as the percentage change in the closing price.
Then, we calculate the returns for each strategy by multiplying the daily returns with the respective signals (shifted by one day to avoid forward-looking bias). Cumulative returns are then calculated by cumulatively multiplying the daily returns (plus one); it simulates the compounding of returns as if you reinvested the proceeds of each trade.
Strategy 2 generated only 1 buy and 2 sell signals. We won’t be backtesting this strategy.
We recommend using the “quantstats” Python library for detailed reports.
import quantstats as qs
# We calculate the daily returns
df['returns'] = df['Close'].pct_change()
# Strategy 1 returns
df['strategy1_returns'] = df['returns'] * df['signal1'].shift()
df['strategy1_cum_returns'] = (1 + df['strategy1_returns']).cumprod()
# Drop the NaN values
df.dropna(inplace=True)
# Let's print the report for Strategy 1
print("Strategy 1:")
qs.reports.full(df['strategy1_returns'])

Quantstats gives us the EOY returns, the distribution of monthly returns, daily returns, the rolling volatility (6-months), the rolling sharpe (6-months), the rolling sortino (6-months), and so on.
Here, we only got 720 historical prices from Kraken’s API and are currently using 687 for backtesting. It’s enough for our tutorial but keep in mind that when conducting a backtest you need to have data that includes all market conditions (bull markets, bear markets, periods of high volatility, periods of low volatility); therefore sometimes much more than 687 days! Indeed, a commonly recommended minimum for many strategies would be at least 5–10 years.
Also, in more advanced backtesting, you would ideally simulate the strategy as closely as possible to how it would be implemented in real trading conditions. Like keeping track of whether you’re currently holding a position or not. It would also include transaction costs, slippage, and other practical considerations.
And it’s done. We hope you now have a robust foundation to build new strategies and assess their potential!
Thanks for reading and stay tuned for more articles in this Python series!
Ressources:
Brian Dolan, “MACD Indicator Explained, with Formula, Examples, and Limitations”, Investopedia, March 15, 2023.
IG International, “A trader’s guide to using the awesome oscillator”, IG International, Updated Monthly.
QuantStats Library, QuantStats Documentation, last checked on May 10, 2023.
Ta Library, Ta Documentation, last checked on April 28, 2023.






