Note: This tutorial assumes that you have basic knowledge of Python programming and have set up a Python environment with the necessary libraries installed (pandas, numpy, requests, ccxt, etc.).
- 1. Introduction
- Step 1: Fetching historical data
- Step 2: Implementing the moving average crossover strategy
- Step 3: Backtesting the strategy
- Step 4: Visualizing the backtest results
- Conclusion
1. Introduction
Backtesting is the process of testing a trading strategy using historical data to determine its performance and potential profitability. In this tutorial, we will discuss how to backtest a simple moving average crossover strategy using Python.
Step 1: Fetching historical data
First, let’s fetch historical price data from a cryptocurrency exchange using the ccxt library. We will use daily data for this example.
import ccxt
import pandas as pd
exchange = ccxt.binance()
symbol = 'BTC/USDT'
timeframe = '1d'
limit = 1000
ohlcv_data = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume']
df = pd.DataFrame(ohlcv_data, columns=columns)
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
Step 2: Implementing the moving average crossover strategy
The moving average crossover strategy generates a buy signal when a shorter-term moving average crosses above a longer-term moving average and a sell signal when the shorter-term moving average crosses below the longer-term moving average.
short_window = 50
long_window = 200
df['short_mavg'] = df['close'].rolling(window=short_window).mean()
df['long_mavg'] = df['close'].rolling(window=long_window).mean()
df['signal'] = 0
df.loc[df['short_mavg'] > df['long_mavg'], 'signal'] = 1
df.loc[df['short_mavg'] < df['long_mavg'], 'signal'] = -1
Step 3: Backtesting the strategy
To backtest the strategy, we will simulate trades based on the generated signals and calculate the total return over the backtest period.
1. Simulating trades
initial_balance = 10000
balance = initial_balance
position = 0
trades = []
for index, row in df.iterrows():
if row['signal'] == 1 and position == 0:
# Buy signal
position = balance / row['close']
balance = 0
trades.append((index, 'buy', row['close']))
elif row['signal'] == -1 and position > 0:
# Sell signal
balance = position * row['close']
position = 0
trades.append((index, 'sell', row['close']))
# Liquidate remaining position at the end of the backtest
if position > 0:
balance = position * df.iloc[-1]['close']
trades.append((df.index[-1], 'sell', df.iloc[-1]['close']))
# Calculate total return
total_return = (balance - initial_balance) / initial_balance
2: Analyzing the backtest results
To evaluate the performance of the strategy, we can calculate various performance metrics, such as the number of trades, win rate, average profit per trade, and annualized return.
import numpy as np
trade_df = pd.DataFrame(trades, columns=['timestamp', 'action', 'price'])
trade_df['profit'] = trade_df['price'].pct_change().shift(-1)
trade_df = trade_df[:-1] # Remove the last liquidation trade
number_of_trades = len(trade_df)
winning_trades = trade_df[trade_df['profit'] > 0]
losing_trades = trade_df[trade_df['profit'] < 0]
win_rate = len(winning_trades) / number_of_trades
average_profit = trade_df['profit'].mean()
annualized_return = (1 + total_return) ** (365 / len(df)) - 1
print("Number of trades:", number_of_trades)
print("Win rate:", win_rate)
print("Average profit per trade:", average_profit)
print("Annualized return:", annualized_return)
Step 4: Visualizing the backtest results
To better understand the backtest results, we can create a simple plot of the historical price data, moving averages, and trade signals using matplotlib.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(15, 8))
# Historical price data and moving averages
ax.plot(df.index, df['close'], label='Close', alpha=0.5)
ax.plot(df.index, df['short_mavg'], label=f'Short MA ({short_window})', linestyle='--')
ax.plot(df.index, df['long_mavg'], label=f'Long MA ({long_window})', linestyle='--')
# Buy and sell signals
buy_signals = trade_df[trade_df['action'] == 'buy']
sell_signals = trade_df[trade_df['action'] == 'sell']
ax.scatter(buy_signals['timestamp'], buy_signals['price'], marker='^', color='g', label='Buy')
ax.scatter(sell_signals['timestamp'], sell_signals['price'], marker='v', color='r', label='Sell')
ax.legend(loc='best')
ax.set_title('Moving Average Crossover Backtest')
plt.show()
Conclusion
In this tutorial, we have demonstrated how to backtest a simple moving average crossover strategy using historical data and Python. Backtesting is an essential step in the development of any trading strategy, as it allows you to evaluate the strategy’s performance and make any necessary adjustments before deploying it in live trading. Keep in mind that past performance is not always indicative of future results, and you should always consider other factors such as risk management, position sizing, and diversification when designing and implementing a trading strategy.
