Python Quantitative Backtesting

Backtesting is the process of simulating and evaluating a trading strategy on historical market data.

In quantitative finance and algorithmic trading, backtesting is a key step used to evaluate the performance of a trading strategy on past market behavior.

Through backtesting, traders can understand how their strategies perform under different market conditions, and optimize and improve them.

Backtesting typically includes the following steps:

  • Define the trading strategy:Determine the rules for when to buy, sell, or hold positions. This may involve various strategies such as technical indicators, moving average strategies, trend following, arbitrage, and more.

  • Obtain historical data:Retrieve past market data, including prices, trading volumes, and other information for financial instruments such as stocks, futures, and foreign exchange.

  • Simulate trading:Based on the defined strategy, simulate executing trades on historical data. This includes determining when to buy or sell, and calculating the gains and losses of each trade.

  • Calculate performance metrics:Based on the backtest results, calculate various performance metrics such as annualized return, maximum drawdown, Sharpe ratio, etc., to evaluate the strategy's performance.

  • Optimize the strategy:If the backtest results are unsatisfactory, traders can optimize the strategy by adjusting parameters or modifying rules, and then re-run the backtest.

  • Look-ahead bias testing:A key issue in backtesting is preventing the leakage of future data. Look-ahead bias testing ensures that only a portion of historical data is used when designing and evaluating a strategy, to simulate the situation in actual trading where only known information can be used.

Next, here is example code for backtesting a simple moving average crossover strategy:

Example

import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt

# Get stock data
symbol = "600519.SS"  # Stock code for Moutai
start_date = "2019-01-01"
end_date = "2021-01-01"

data = yf.download(symbol, start=start_date, end=end_date)

# Calculate moving average
data['SMA_50'] = data['Close'].rolling(window=50).mean()
data['SMA_200'] = data['Close'].rolling(window=200).mean()

# Initialize crossover signal column
data['Signal'] = 0

# Calculate crossover signals
data.loc[data['SMA_50'] > data['SMA_200'], 'Signal'] = 1
data.loc[data['SMA_50'] < data['SMA_200'], 'Signal'] = -1

# Calculate daily returns
data['Daily_Return'] = data['Close'].pct_change()

# Calculate the return of the strategy signal (shift(1) is used to avoid look-ahead bias)
data['Strategy_Return'] = data['Signal'].shift(1) * data['Daily_Return']

# Calculate cumulative returns
data['Cumulative_Return'] = (1 + data['Strategy_Return']).cumprod()

# Plot cumulative return curve
plt.figure(figsize=(10, 6))
plt.plot(data['Cumulative_Return'], label='Strategy Cumulative Return', color='b')
plt.plot(data['Close'] / data['Close'].iloc[0], label='Stock Cumulative Return', color='g')
plt.title("Cumulative Return of Strategy vs. Stock")
plt.xlabel("Date")
plt.ylabel("Cumulative Return")
plt.legend()
plt.show()

Executing the above code produces the following output:

Other extensions