In cryptocurrency trading, developing a welltested strategy is crucial for managing volatility and capitalizing on opportunities. Backtesting, which involves simulating a strategy’s performance using historical data, is a powerful method for assessing its potential effectiveness.
This tutorial will walk you through the process of backtesting crypto trading strategies with Python, covering everything from setting up your environment to evaluating the results.
Prerequisites
To follow along, you’ll need to set up a few essential tools and resources:
 Connect the Google Colab app to your Google Drive account.
 Obtain your CoinGecko API key and save to Google Colab Secrets.
 Retrieve your CoinGecko API key from Google Colab Secrets.
After setting up, you should be able to open the Google Colab notebook for this tutorial.
Access your CoinGecko API key from Google Colab Secrets as follows:
Install Required Libraries
Install the required Python libraries before proceeding with backtesting. This tutorial uses custom versions of the following libraries to ensure stability and avoid potential compatibility issues with future updates:

bt:
A flexible Python library designed for backtesting and analyzing quantitative trading strategies. 
pycgapi
: An unofficial Python wrapper for the CoinGecko API, allowing easy access to cryptocurrency market data.
Run the following code cell to install the required libraries:
With the required libraries installed, the next step is to import them into your notebook.
Initialize API Client
To begin fetching cryptocurrency data, start by initializing the CoinGecko API client. Set the pro_api
flag according to whether you’re using the Demo or Pro (paid) API key.
Next, create an instance of the CoinGecko API client using your API key and the pro_api
setting. It’s advisable to check the API server status to ensure it’s operational before proceeding with data retrieval.
Define Your Investment Universe
The ‘investment universe’ refers to the list of cryptocurrencies that represent the assets you plan to analyze and trade, as part of your crypto trading strategy.
Leverage the code below to search for any cryptocurrency by name, and return a DataFrame with details such as the token’s id
, name
, symbol
, and market_cap_rank
. This information is crucial as the id
will be used for further data retrieval from the CoinGecko API. Confirm the id
of your chosen token to ensure accurate data access in the following steps.
After confirming the id
of the cryptocurrencies you want to include in your backtesting, you can define your investment universe by specifying those id
s in a list. In the following code cell, we create a list of selected cryptocurrencies by their id
s, which will be used throughout the backtesting process.
How to Get Historical Crypto Data
To perform effective backtesting, you need to gather historical data for each cryptocurrency in your investment universe. Access historical crypto data including price, market capitalization, and trading volume with the following Python code, which retreves the data from the CoinGecko API and organizes it into DataFrames for easy access and analysis.
The data retrieved is structured as follows:
By examining the data, you can ensure it is complete and ready for use in your strategy testing.
Process Data for Analysis and Backtesting
With the historical data in hand, the next step is to process it for analysis and backtesting. This involves a few key steps:
 Normalization: Adjust the prices so that each asset starts at $100, allowing for easy comparison across different cryptocurrencies.
 Daily Returns: Calculate the daily returns to capture daytoday changes in value.
 Cumulative Returns: Compute the cumulative returns to track the overall growth or decline from the initial starting point.
This processed data is essential for evaluating and comparing the performance of assets in your investment universe.
Normalize and Calculate Returns
Plot Normalized Cumulative Returns
The following code plots the normalized cumulative returns for the selected cryptocurrencies, allowing you to visually compare their performance over time.
Define Crypto Trading Strategies
In this section, we’ll define 5 different trading strategies: Buyandhold, equalweighted, randomweighted, risk parity and custom marketweighted strategy.
Buyandhold Strategy
The following code creates a simple buyandhold strategy for Bitcoin using the bt
library. This strategy performs the following actions:
 RunOnce: Executes the strategy a single time.
 SelectThese: Selects Bitcoin as the asset to include in the strategy.
 WeighEqually: Assigns equal weight to the selected asset.
 Rebalance: Rebalances the portfolio according to the specified weights.
You can build upon this basic structure to develop more complex strategies tailored to your investment universe.
Similarly, ths code defines a buyandhold strategy for Ethereum, using the same approach of running once, selecting Ethereum, assigning equal weight, and rebalancing the portfolio.
Equalweighted Strategy
The following code defines an equalweighted strategy that runs on a monthly basis. This strategy selects all assets in your investment universe, assigns equal weight to each, and rebalances the portfolio monthly to maintain those equal weights.
Randomweighted Strategy
The code below implements a randomweighted strategy that is executed on a monthly basis. This strategy randomly selects assets from your investment universe, assigns random weights to the selected assets, and rebalances the portfolio monthly according to these random weights.
Risk Parity Strategy
The following code outlines a risk parity strategy, also known as equal risk contribution (ERC). This strategy begins after a specified lookback period and runs on a monthly basis. It selects all assets in your investment universe and uses a riskbased approach to weight them, aiming for equal risk contribution across the portfolio.
The strategy employs the LedoitWolf shrinkage method to estimate the covariance matrix, enhancing the stability of the risk estimates. The portfolio is rebalanced monthly to maintain the desired risk allocation.
Meanvariance Optimization (MVO) Strategy
The meanvariance optimization (MVO) strategy starts after a 3month lookback period and runs on a monthly basis. It selects all assets in the investment universe and uses meanvariance optimization to determine the portfolio weights, aiming to maximize returns for a given level of risk. The strategy employs the LedoitWolf shrinkage method to estimate the covariance matrix and sets constraints on the weights, allowing each asset to have a weight between 0.0 and 0.5. The portfolio is rebalanced monthly to maintain the optimized allocation.
Custom Marketweighted Strategy
Finally, we define a custom marketweighted strategy using the bt
library. The strategy dynamically adjusts the portfolio weights based on the market capitalization of each asset. A custom algorithm, WeighMW
, is implemented to calculate these weights. This algorithm:
 Initializes with historical market capitalizations and a list of valid tickers.
 Filters the universe of assets to ensure only valid tickers are considered for rebalancing.
 Calculates Weights by normalizing the market capitalizations of the selected assets, assigning higher weights to assets with larger market caps.
The strategy runs on a monthly basis, selects all available assets, applies the marketcapbased weights, and rebalances the portfolio accordingly.
How to Backtest Crypto Strategies
With your strategies defined, the next step is to create backtests for each crypto trading strategy. Backtesting allows you to simulate the performance of these strategies over historical data, providing insights into their effectiveness. Using normalized historical prices as input data, the following code creates backtests for the these strategies:

Buy and Hold Strategies (Bitcoin and Ethereum): These strategies involve purchasing a cryptocurrency and holding it over the entire period without making any changes to the portfolio, offering a straightforward comparison of longterm returns.

EqualWeighted Strategy: This strategy allocates an equal percentage of the total portfolio to each cryptocurrency, giving each asset the same initial influence on the portfolio.

MarketWeighted Strategy: In this approach, the portfolio weights are adjusted according to the market capitalization of each cryptocurrency, giving more weight to larger market cap assets.

Risk Parity Strategy: This strategy aims to balance the portfolio’s risk by allocating weights based on the risk contribution of each asset, striving for equal risk distribution across all holdings.

MeanVariance Strategy: This advanced strategy uses meanvariance optimization to maximize returns for a given level of risk, applying constraints to ensure a balanced and diversified portfolio.
To facilitate easy lookup and access to the backtest results, the following code creates a dictionary that maps each strategy’s name to its corresponding backtest object. This allows you to quickly retrieve and analyze the backtest results by simply referencing the strategy name.
Run Backtests and Obtain Results
With the backtests created, the next step is to run them and obtain the results.
100%██████████ 1/1 [00:00<00:00, 1.00it/s] 100%██████████ 1/1 [00:00<00:00, 2.05it/s] 100%██████████ 1/1 [00:01<00:00, 1.37s/it] 100%██████████ 1/1 [00:01<00:00, 1.54s/it] 100%██████████ 1/1 [00:02<00:00, 2.95s/it] 100%██████████ 1/1 [00:07<00:00, 7.12s/it]
To analyze the performance of all the strategies together, you can run them simultaneously and compile the results.
100%██████████ 6/6 [00:00<00:00, 15837.52it/s]
Establish a RiskFree Rate
To accurately assess the performance of your strategies, particularly in the context of riskadjusted returns, you need to establish a riskfree rate. The riskfree rate represents the return on an investment with no risk of financial loss, typically represented by government bonds.
In the following code, we:
 Determine the start and end dates of the historical data.
 Fetch the historical average riskfree rate for this period using the yield on the 10year U.S. Treasury Note (^TNX).
 Calculate the average riskfree rate over the given period and print it for reference.
[*********************100%***********************] 1 of 1 completed
Average RiskFree Rate: 2.44%
Once the riskfree rate is determined, it should be applied to each set of backtest results. This step is essential for calculating riskadjusted performance metrics such as the Sharpe ratio. The following code sets the riskfree rate for each of the backtest results, ensuring that these calculations accurately reflect the riskfree benchmark.
Plot Backtest Results
Visualizing the performance of your backtested strategies is crucial for understanding how they compare over time. The following code generates a plot of the portfolio values for each strategy, using a logarithmic scale on the yaxis to capture the wide range of values more effectively. This plot allows you to see how different strategies have performed, relative to one another, from the start of the backtest period to the present.
The chart shows how each strategy’s portfolio value evolved over time, providing a clear comparison of their performance. The logarithmic scale helps to manage the wide variation in portfolio values, making it easier to observe relative changes across all strategies.
Review Strategy Performance
Evaluating the performance of each strategy is key to understanding how well they have met their objectives. The following code displays a comprehensive set of performance metrics for all the strategies you have backtested. These metrics include total return, riskadjusted returns (such as Sharpe and Sortino ratios), drawdowns, and volatility measures, among others.
Assess Current Portfolio Weights
Understanding the current portfolio weights of each strategy is crucial for evaluating how different strategies allocate assets based on their underlying principles. The following code defines a function that combines the latest security weightings from multiple backtest results into a single DataFrame. This allows you to easily compare the asset allocations across different strategies.
Analyze Historical Portfolio Weights
Analyzing the historical portfolio weights of a strategy provides valuable insights into how the strategy allocates assets over time. This can reveal the strategy’s behavior under different market conditions and help you understand its longterm investment approach.
First, the available strategies are listed so you can choose which one to analyze:
Next, you select the strategy you want to examine. In this example, we focus on the "MarketWeighted" strategy:
Finally, the code generates a plot showing the historical portfolio weights of the selected strategy. This visualization allows you to observe how the strategy adjusted its holdings over time:
The resulting plot, as shown above, displays the weights of each asset in the portfolio across the backtest period. This is particularly useful for understanding how the strategy’s allocation shifts in response to market changes and the impact of these shifts on the portfolio’s overall performance.
Perform a Hypothesis Test
To evaluate the robustness of your chosen strategy, you can perform a hypothesis test by comparing it against randomly generated portfolios. This process involves simulating multiple random portfolios and assessing how your strategy’s performance compares to these benchmarks.
Similarly, choose the strategy you want to test, from the list of available strategies.
For this example, we’ll test the “Risk Parity” strategy, but you can replace it with any other strategy from the list:
Generate Multiple Random Portfolios (Monte Carlo Simulation)
Run a Monte Carlo simulation to generate multiple random portfolios, which will serve as a benchmark for your strategy. The simulation is set to run 1,000 iterations, but you can adjust the nsim parameter to increase or decrease the number of simulations. The following code may may take some time to run especially for higher nsim
values. In this example, it took ~12 minutes to run 1,000 random portfolio simulations. I found 1,000 to be enough simulations to be statisticaly reliable but not so many that it took forever to run.
100%██████████ 1000/1000 [11:48<00:00, 1.41it/s]
After running the Monte Carlo simulation, the next step is to statistically evaluate whether your chosen strategy outperforms the randomly generated portfolios. This is done using a onesample ttest, which compares the Sortino ratio of your strategy against the distribution of Sortino ratios from the random portfolios.
First, set the significance level (alpha
), which determines the threshold for rejecting the null hypothesis:
The code then extracts the Daily Sortino ratio for your investment strategy and the random portfolios:
Run a Onetailed Test
A onesample ttest (onetailed) is performed to determine if your strategy’s Sortino ratio is significantly higher than that of the random portfolios:
Result: The portfolio outperformed the sample of randomly generated portfolios. Tstatistic: 48.1620 Pvalue (onetailed): 0.0000 Significance Level: 0.05
Visualizing the Results
To visualize the results, a histogram of the Sortino ratios for the random portfolios is plotted alongside the Sortino ratio of your strategy and the critical Sortino ratio threshold for significance:
This plot allows you to visually compare your strategy’s performance against the random benchmarks. If your strategy’s Sortino ratio exceeds the critical threshold, it indicates that the strategy has significantly outperformed the random portfolios at the specified confidence level.
Limitations and Risks in Backtesting Strategies
Backtesting is a powerful tool for evaluating portfolio strategies, but it has inherent limitations and risks, including:
 Data Quality: Results depend on the accuracy of historical data.
 Overfitting: Strategies might perform well on historical data but fail in future markets due to being overly tailored to past conditions.
 Unforeseen Events: Backtests cannot predict the impact of unexpected market events or shifts in investor sentiment.
While backtesting provides valuable insights, these limitations mean it should be used cautiously and supplemented with other analyses.
Conclusion
Backtesting is a crucial step in developing and refining cryptocurrency trading strategies, offering insights into potential performance by simulating past market conditions. This tutorial has guided you through the entire process, from setting up your environment to evaluating strategy performance with Python. While backtesting provides valuable data, it is important to acknowledge its limitations, such as data accuracy, idealized assumptions, and the risk of overfitting. To navigate the unpredictable world of cryptocurrency trading, complement your backtesting efforts with realtime monitoring, ongoing analysis, and robust risk management practices.
Disclaimer
The information provided in this tutorial, including but not limited to text, code examples, and any other material, is for educational and informational purposes only. It is not intended as, and should not be construed as, financial advice, investment recommendations, or an endorsement of any particular security, strategy, or investment product. The tutorial discusses concepts related to backtesting trading strategies in the context of cryptocurrency markets using Python. The examples and strategies outlined are provided to illustrate the application of backtesting techniques in analyzing historical cryptocurrency data and do not constitute advice on investing or trading in cryptocurrencies or any other assets. The strategies and examples presented are purely hypothetical and do not guarantee future performance or success. Investing and trading in cryptocurrencies involve significant risk, including the potential loss of principal. Market conditions, economic factors, and the volatile nature of cryptocurrencies can affect investment outcomes. Readers are strongly encouraged to conduct their own research and consult with a qualified financial advisor or investment professional before making any investment decisions. The author and publisher of this tutorial are not responsible for any financial losses or damages resulting from the application of the information provided.