Algotradium

Backtesting · Python Tutorial Python Backtesting

Simple Python Backtesting Tutorial for Beginners

Simple Python backtesting tutorial example using SMA crossover

This simple Python backtesting tutorial will teach you how to build and test your first trading strategy using Python. Python backtesting is one of the most important skills in algorithmic trading. In this tutorial, you'll learn how to backtest a trading strategy with Python using the backtesting.py library from scratch.Imagine you have a trading idea: "When the 20-day moving average crosses above the 50-day moving average, buy. When the opposite happens, sell." It sounds simple, but did it actually work in the past? Is it profitable even after accounting for trading fees? The answer to these questions comes not from guesswork but from scientific backtesting. In this article, we'll take you step-by-step from implementing a simple idea in Python to a fully tested strategy. After reading this, you'll be able to define almost any indicator-based strategy in Python and backtest it across any market and symbol in just a few minutes.

What You'll Learn in This Simple Python Backtesting Tutorial

We'll cover four main phases:

  • Setup: Installing essential Python libraries
  • Data Collection: Downloading high-quality historical price data
  • Initial Implementation: Coding a simple SMA Crossover strategy and running your first backtest
  • Enhancement: Adding Stop Loss, Take Profit, and combining with RSI for a more powerful strategy

By the end, you'll have a working Python file that can test your final strategy on any asset and produce a comprehensive report.


Step 1: Set Up Your Environment for This Simple Python Backtesting Tutorial

Before we start, we need to prepare our tools. Python is the best choice for this due to its rich libraries and active community.

Choosing the Right Python Backtesting Library

When learning Python backtesting, one of the first decisions you'll make is choosing a backtesting framework. While several Python libraries are available, backtesting.py library has become one of the most popular choices because it combines simplicity with powerful features. Whether you're testing a basic moving average crossover or a sophisticated algorithmic trading strategy, the library provides everything needed to simulate trades, evaluate performance, and visualize results.Compared with more complex frameworks, backtesting.py is lightweight, easy to learn, and requires only a small amount of code to create professional trading strategies. It integrates naturally with popular Python libraries such as pandas, NumPy, and matplotlib, making it an excellent choice for beginners as well as experienced quantitative traders.Another advantage of backtesting.py library is its interactive reporting system. After running a strategy, the library automatically generates detailed performance statistics, including total return, maximum drawdown, win rate, Sharpe ratio, and an interactive equity curve. These reports make it much easier to analyze a strategy before risking real capital.Throughout this tutorial, we'll use backtesting.py to build, test, and improve a complete Python backtesting workflow that you can later adapt to stocks, cryptocurrencies, forex, or any other financial market.

Installing Specialized Backtesting Libraries

Python alone isn't enough. We need libraries that handle heavy financial and analytical tasks for us.

Essential Libraries and Their Purpose

LibraryPurpose
backtesting.pyCore backtesting engine: strategy building, trade simulation, and reporting
pandas & numpyData processing and fast numerical calculations
pandas-taTechnical indicators
yfinanceFree historical market data from Yahoo Finance
matplotlibCharting price, indicators, and trade results
pip install backtesting pandas numpy pandas-ta yfinance matplotlib

Verifying the Installation

Let's make sure everything is installed correctly. Save and run the code below in a Python file (e.g., test_install.py).

import backtesting
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib

print("✅ All libraries imported successfully!")
print("backtesting.py version:", backtesting.__version__)
print("pandas version:", pd.__version__)

Important Note on Data Access

Sometimes, using the yfinance library may be restricted in certain regions. If you can't download data this way, you can use sample CSV files provided in our Telegram or Eita channels, or use the built-in test data from the backtesting library:

from backtesting.test import EURUSD, BTCUSD

You can also use the ccxt library for cryptocurrency data:

import ccxt
exchange = ccxt.kucoin()
ohlcv = exchange.fetch_ohlcv('BTC/USDT', timeframe='4h')
# Convert to DataFrame with proper column names

Step 2: Collect Historical Data for Python Backtesting

Historical data is the fuel for our backtest engine. Without accurate, complete data, any result is meaningless.

Downloading Data with yfinance

The yfinance library provides easy access to decades of historical data for thousands of stocks and ETFs.

# File: download_data.py
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt

# 1. Set download parameters
SYMBOL = "BTC-USD"
START_DATE = "2020-01-01"
END_DATE = "2025-12-01"
INTERVAL = "1d"

print(f"Downloading data for {SYMBOL} from {START_DATE} to {END_DATE}...")

# 2. Download data
df = yf.download(
    tickers=SYMBOL,
    start=START_DATE,
    end=END_DATE,
    interval=INTERVAL,
    progress=False
)

print(f"\n✅ Download complete. {len(df)} rows received.")
print("Date range:", df.index[0].strftime('%Y-%m-%d'), "to", df.index[-1].strftime('%Y-%m-%d'))

# 3. Save to CSV for later use
csv_filename = f"{SYMBOL}_historical.csv"
df.to_csv(csv_filename)
print(f"\n💾 Data saved to '{csv_filename}'")

Understanding the Data Structure

The downloaded data is a pandas DataFrame with these columns:

  • Open: First price of the day
  • High: Highest price of the day
  • Low: Lowest price of the day
  • Close: Last price of the day
  • Adj Close: Adjusted close price accounting for dividends and splits
  • Volume: Number of shares traded

For backtesting, we typically use Adj Close for more realistic analysis.


Step 3: Now it's time to build the first strategy in this simple Python backtesting tutorial.

Now it's time to bring our trading idea to life. We'll use the backtesting.py library, which provides a clean and efficient framework for this.

Understanding the Strategy Structure in Python backtesting library backtesting.py

Every strategy is a Python class that inherits from the base Strategy class. It has two key methods:

  • init(): Defines indicators and one-time calculations
  • next(): The brain of the strategy — called for every candle (e.g., every day). Here we define our buy and sell logic.

Complete Code for the Simple SMA Crossover Strategy

# File: simple_sma_crossover.py
import pandas as pd
import yfinance as yf
from backtesting import Backtest, Strategy
from backtesting.lib import crossover

class SimpleSMACrossover(Strategy):
    # Strategy parameters
    n1 = 20
    n2 = 50

    def init(self):
        close_prices = self.data.Close
        self.sma_short = self.I(
            lambda x: pd.Series(x).rolling(self.n1).mean(),
            close_prices, name=f'SMA{self.n1}'
        )
        self.sma_long = self.I(
            lambda x: pd.Series(x).rolling(self.n2).mean(),
            close_prices, name=f'SMA{self.n2}'
        )

    def next(self):
        # Buy signal: no position and SMA short crosses above SMA long
        if not self.position and crossover(self.sma_short, self.sma_long):
            self.buy()

        # Sell signal: in a position and SMA long crosses above SMA short
        elif self.position and crossover(self.sma_long, self.sma_short):
            self.position.close()

if __name__ == "__main__":
    # Download data
    data = yf.download("BTC-USD", start="2020-01-01", end="2025-12-01")

    # Handle MultiIndex columns from yfinance
    if isinstance(data.columns, pd.MultiIndex):
        data.columns = [col[0] for col in data.columns]

    # Create and run backtest
    bt = Backtest(data, SimpleSMACrossover,
                  cash=1000000,
                  commission=.001,
                  exclusive_orders=True)

    output = bt.run()

    # Display key results
    print("\n" + "="*50)
    print("📊 Backtest Results")
    print("="*50)
    print(f"Total Return: {output['Return [%]']:.2f}%")
    print(f"Final Equity: ${output['Equity Final [$]']:.2f}")
    print(f"Max Drawdown: {output['Max. Drawdown [%]']:.2f}%")
    print(f"Total Trades: {output['# Trades']}")
    print(f"Win Rate: {output['Win Rate [%]']:.2f}%")

    # Plot interactive charts
    bt.plot()
Python backtesting SMA crossover strategy results using backtesting.py
Backtest results shown in an html tab

Key Tips for Your First Backtest

  • Check the terminal output: Trade signals and dates are printed — great for debugging
  • Explore the interactive chart: View SMA lines, buy/sell arrows, equity curve, and drawdown
  • Watch Max Drawdown: A number above 20-25% is usually a warning sign

Pro Tip for Crypto: Use FractionalBacktest instead of Backtest for cryptocurrencies, as it allows fractional asset purchases.


Step 4: Upgrading with Stop Loss and Take Profit

Our simple strategy only uses SMA signals. But a professional trader always manages risk. Let's add two key rules:

  • Stop Loss: Exit with a small loss if the price moves against us
  • Take Profit: Lock in profits when the price moves in our favor

Strategy Code with SL/TP

# File: sma_crossover_with_sltp.py
import pandas as pd
from backtesting import Strategy
from backtesting.lib import crossover, FractionalBacktest

class SMACrossWithSLTP(Strategy):
    n1 = 20
    n2 = 50
    stop_loss_pct = 0.08   # 8% stop loss
    take_profit_pct = 3.5    # 350% take profit

    def init(self):
        close = self.data.Close
        self.sma_short = self.I(lambda x: pd.Series(x).rolling(self.n1).mean(), close)
        self.sma_long = self.I(lambda x: pd.Series(x).rolling(self.n2).mean(), close)

    def next(self):
        price = self.data.Close[-1]

        if not self.position:
            if crossover(self.sma_short, self.sma_long):
                sl = price * (1 - self.stop_loss_pct)
                tp = price * (1 + self.take_profit_pct)
                self.buy(sl=sl, tp=tp)

        elif self.position and crossover(self.sma_long, self.sma_short):
            self.position.close()

Comparing Results: Simple vs. SL/TP Strategy

When you run the comparison, you'll typically see:

  • Lower Max Drawdown: Losses are cut short
  • Higher Win Rate: More trades close profitably
  • Lower Total Return: Some big winners may be cut short — this is the price of risk management

The goal is to find the right balance between return and risk.

Comparison of a simple strategy with risk-management one
Performance comparison between simple and SL/TP strategies

Step 5: Building a Combined Advanced Strategy (SMA + RSI)

Now let's make our strategy smarter. The RSI indicator helps us identify overbought and oversold conditions. We can use it to filter our SMA signals.

New Logic: Buy only when: 1) SMA gives a buy signal AND 2) RSI is below 30 (oversold). This means the market is correcting and a trend reversal is more likely.

Calculating RSI in backtesting.py

# File: advanced_strategy.py
import pandas as pd
import numpy as np
from backtesting import Backtest, Strategy
from backtesting.lib import crossover

def calculate_rsi(series: pd.Series, period: int = 14) -> pd.Series:
    delta = series.diff()
    gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
    rs = gain / loss
    return 100 - (100 / (1 + rs))

class AdvancedSMARSIStrategy(Strategy):
    sma_short = 20
    sma_long = 50
    rsi_period = 14
    rsi_oversold = 30
    rsi_overbought = 70
    stop_loss = 0.06
    take_profit = 0.12

    def init(self):
        close = self.data.Close
        self.sma_fast = self.I(lambda x: pd.Series(x).rolling(self.sma_short).mean(), close)
        self.sma_slow = self.I(lambda x: pd.Series(x).rolling(self.sma_long).mean(), close)
        self.rsi = self.I(lambda x: calculate_rsi(pd.Series(x), self.rsi_period), close)

    def next(self):
        price = self.data.Close[-1]
        rsi_val = self.rsi[-1] if not np.isnan(self.rsi[-1]) else 50

        if not self.position:
            if crossover(self.sma_fast, self.sma_slow) and rsi_val < self.rsi_oversold:
                sl = price * (1 - self.stop_loss)
                tp = price * (1 + self.take_profit)
                self.buy(sl=sl, tp=tp)

        elif self.position:
            if crossover(self.sma_slow, self.sma_fast) or rsi_val > self.rsi_overbought:
                self.position.close()

Practical Project: Final Backtest with Comprehensive Report

Now we have all the pieces. Let's run our final combined strategy on some data and generate a professional report.

Congratulations! You've completed this simple Python backtesting tutorial and now know how to build, test, and improve trading strategies using Python. The strategy includes moving averages, RSI filtering, stop loss, take profit, and a professional performance report.

Complete Project Code

# File: final_backtest_project.py
import pandas as pd
import numpy as np
import yfinance as yf
from datetime import datetime
from backtesting import Strategy
from backtesting.lib import crossover, FractionalBacktest

# RSI calculation function
def calculate_rsi(series, period=14):
    delta = series.diff()
    gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
    rs = gain / loss
    return 100 - (100 / (1 + rs))

# Final combined strategy
class FinalCombinedStrategy(Strategy):
    sma_short = 20
    sma_long = 60
    rsi_period = 14
    rsi_oversold = 35
    rsi_overbought = 75
    stop_loss = 0.05
    take_profit = 0.15

    def init(self):
        close = self.data.Close
        self.sma_fast = self.I(lambda x: pd.Series(x).rolling(self.sma_short).mean(), close)
        self.sma_slow = self.I(lambda x: pd.Series(x).rolling(self.sma_long).mean(), close)
        self.rsi = self.I(lambda x: calculate_rsi(pd.Series(x), self.rsi_period), close)

    def next(self):
        price = self.data.Close[-1]
        rsi_val = self.rsi[-1]

        if not self.position:
            if crossover(self.sma_fast, self.sma_slow) and rsi_val < self.rsi_oversold:
                sl = price*(1-self.stop_loss)
                tp = price*(1+self.take_profit)
                self.buy(sl=sl, tp=tp)

        elif self.position:
            if crossover(self.sma_slow, self.sma_fast) or rsi_val > self.rsi_overbought:
                self.position.close()

# Main execution
if __name__ == "__main__":
    TICKER = "BTC-USD"
    YEARS = 5
    end_date = datetime.now().strftime("%Y-%m-%d")
    start_date = (datetime.now() - pd.DateOffset(years=YEARS)).strftime("%Y-%m-%d")

    print(f"Symbol: {TICKER}")
    print(f"Period: {start_date} to {end_date} ({YEARS} years)")

    data = yf.download(TICKER, start=start_date, end=end_date, progress=False)

    # Clean up columns
    if isinstance(data.columns, pd.MultiIndex):
        data.columns = [col[0] for col in data.columns]

    bt = FractionalBacktest(data, FinalCombinedStrategy,
                           fractional_unit=1e-06,
                           cash=1000,
                           commission=.002,
                           margin=1,
                           exclusive_orders=True)

    results = bt.run()

    # Print comprehensive report
    print("\n" + "#"*70)
    print("Final Backtest Report")
    print("#"*70)

    print("\n1. 📊 Overall Performance")
    print("-"*40)
    print(f"   Total Return: {results['Return [%]']:+.2f}%")
    print(f"   Final Equity: ${results['Equity Final [$]']:.2f}")

    print("\n2. 🛡️ Risk Metrics")
    print("-"*40)
    print(f"   Max Drawdown: {results['Max. Drawdown [%]']:.2f}%")
    print(f"   Sharpe Ratio: {results.get('Sharpe Ratio', 'N/A'):.2f}")

    print("\n3. 🔄 Trade Statistics")
    print("-"*40)
    print(f"   Total Trades: {results['# Trades']}")
    if results['# Trades'] > 0:
        print(f"   Win Rate: {results['Win Rate [%]']:.1f}%")
        print(f"   Profit Factor: {results['Profit Factor']:.2f}")

    bt.plot()
Python backtesting final project performance report and equity curve
Complete backtest report showing all key metrics

Backtesting on the Algotradium Platform

The Algotradium platform was designed to simplify and make accessible the backtesting and strategy-building process. It offers a web-based solution that removes many obstacles found in traditional platforms.

Step 1: Register and Log In

Simply open your web browser and go to the Algotradium platform. You do not need a broker account — just a simple registration on the platform itself.

Algotradium login page
Complete backtest report showing all key metrics

Step 2: Define Your Strategy (Strategy Builder)

Algotradium offers two powerful ways to define strategies:

Indicator-Based (No Coding)

  • Use a wide library of technical indicators (MA, RSI, MACD, etc.)
  • Fully adjustable indicator parameters
  • Create complex entry/exit conditions using operators (cross above/below, greater/less than, etc.)

Python Coding

  • For advanced users, code strategies in Python
  • Access to popular libraries like pandas-ta for technical indicators
  • Define custom indicators for unlimited creative possibilities
Algotradium strategy creation forms: no-code (left) and Python (right)
Complete backtest report showing all key metrics

Step 3: Build Your First Python Backtest Strategy

After saving your strategy, go to the Backtests section and click "Add Backtest".

1 Select Strategy

Choose your previously defined strategy

2 Select Market & Symbol

Choose market (Forex, Crypto, Gold/Silver) and specific symbol

3 Select Timeframe

Choose timeframe from 1 minute to 1 day

4 Set Date Range

Specify exact start and end dates

5 Execute Test

Click "Create Backtest" and then "Run Backtest"

Setting backtest parameters in Algotradium
Setting backtest paremeters

Advanced Analysis and Reports on Algotradium

A) Comprehensive Performance Metrics

Algotradium calculates and displays a wide range of performance metrics:

Sharpe
Sharpe Ratio
Sortino
Sortino Ratio
Drawdown
Max & Avg Drawdown
Win Rate
Win Rate
  • Sharpe & Sortino Ratios: Risk-adjusted return analysis
  • Max & Average Drawdown: Understand depth of temporary losses
  • Drawdown Duration: Time needed to recover from losses
  • Win Rate & Number of Trades: Strategy efficiency and activity
  • Total Return & Annual Return: Measure profitability
  • Buy & Hold Return: Compare your strategy to simple buy-and-hold
  • Profit Factor & Expectancy: System's inherent profitability
Complete backtest reposrt and strategy performance metrics in Algotradium
Complete backtest report showing all key metrics

B) Advanced Charts

Price Chart with Entry/Exit Markers

Main price chart with clear markers showing entry (green arrow) and exit (red arrow) points for each trade.

Equity Curve Comparison

Two curves on one chart: Equity Curve (account growth) and Buy & Hold Curve for easy comparison.

PnL Bar Chart

Bar chart showing profit/loss of each trade in chronological order — easily identify winning and losing sequences.

Return and trade charts of the backtest results in Algotradium
Return and trade charts of the backtest results in Algotradium

C) Save and Restore Results

A unique feature of Algotradium is the ability to save every backtest result. You can store results from different tests and revisit or compare them anytime.

D) CSV Export

For deeper analysis, download complete trade reports as CSV files to open in Excel or Google Sheets for advanced statistical analysis.

Returns bar chart with exportable data in Algotradium
Returns bar chart with exportable data in Algotradium

Summary and Next Steps

What You've Learned

Congratulations! You now have the ability to backtest a trading strategy from scratch. You've not only tested a simple strategy but also enhanced it with risk management rules and additional filters to create a more robust trading system.

What's Next?

In the next article, we'll cover "How to Optimize a Trading Strategy with Python". We'll learn how to automatically find the best parameters for your strategy using heatmaps and validation techniques to avoid overfitting.

Important Final Warning

Backtest results do not guarantee future profitability. Markets are dynamic and conditions change. Always:

  • Start with capital you can afford to lose
  • Test your strategy in live markets with very small positions (Forward Test)
  • Maintain strict risk management and diversification

The goal of backtesting is to increase the probability of success and reduce unnecessary risk, not to guarantee certain profits.

Want to continue learning? Read our comprehensive guide: Python Backtesting: The Complete Guide (2026) .

Frequently Asked Questions (FAQ)

What is a simple Python backtesting tutorial?

A simple Python backtesting tutorial teaches beginners how to test trading strategies on historical market data using Python. It explains how to install the required libraries, download market data, create trading rules, run a backtest, and evaluate the results before risking real money.

Popular Python backtesting libraries such as backtesting.py, Backtrader, and vectorbt make this process much easier by handling trade execution, portfolio management, and performance reporting automatically.

Which Python library is best for backtesting?

There is no single "best" Python backtesting library because the ideal choice depends on your experience and project requirements.

  • backtesting.py is excellent for beginners and intermediate traders because it is simple, lightweight, and generates interactive reports.
  • Backtrader offers many advanced features but has a steeper learning curve.
  • vectorbt is designed for high-performance quantitative research and large-scale strategy optimization.

For most educational projects and personal strategy development, backtesting.py provides one of the best balances between simplicity, flexibility, and performance.

Is backtesting.py free?

Yes. backtesting.py is a free and open-source Python library released under an open-source license. Anyone can install it using pip and use it for educational, research, or commercial projects.

Because it is open source, the community continuously improves the library by fixing bugs and adding new features. It also integrates well with popular Python packages such as pandas, NumPy, matplotlib, and yfinance.

Can I backtest cryptocurrency strategies with Python?

Absolutely. Python is widely used for cryptocurrency strategy development and backtesting. Historical crypto data can be downloaded from services like Yahoo Finance, Binance, KuCoin, or other exchanges through APIs such as CCXT.

For cryptocurrencies, many traders use fractional position sizing because digital assets can usually be purchased in small fractions. Libraries like backtesting.py support this through FractionalBacktest, making crypto simulations more realistic.

Does backtesting guarantee future profits?

No. Backtesting never guarantees future profitability. Historical performance only shows how a strategy would have behaved under past market conditions. Financial markets constantly evolve, meaning a profitable strategy today may stop working tomorrow.

To improve confidence in a strategy, traders should combine backtesting with out-of-sample testing, walk-forward analysis, and forward testing on live markets using small position sizes. Proper risk management remains essential regardless of historical results.

How much historical data is enough for Python backtesting?

The amount of historical data depends on your trading strategy and timeframe. Swing trading strategies usually require several years of daily data, while intraday strategies may need millions of minute-level records. In general, using multiple market conditions—including bull, bear, and sideways markets—produces more reliable backtesting results.

Can Python backtesting be used for stocks, forex, and crypto?

Yes. Python backtesting works with virtually any financial market as long as historical OHLCV data is available. The same strategy framework can be adapted to stocks, ETFs, forex, cryptocurrencies, commodities, indices, and futures by simply loading the appropriate historical dataset.

What is the difference between backtesting and paper trading?

Backtesting evaluates a trading strategy using historical market data, while paper trading tests the strategy in real-time market conditions without risking real money. Backtesting is faster because it simulates years of data in minutes, whereas paper trading reflects current market behavior and execution conditions.

backtesting Python trading strategy SMA crossover stop loss take profit RSI yfinance algorithmic trading technical analysis risk management backtesting.py cryptocurrency Forex