Backtesting a Stochastic, RSI, MACD Cryptocurrency Trading Strategy Using Python

·

Cryptocurrency trading has evolved from speculative guesswork into a data-driven discipline, where algorithmic strategies and technical indicators play a pivotal role. One of the most effective ways to evaluate a trading idea is through backtesting—applying your strategy to historical data to assess its performance. In this guide, we’ll walk through building and backtesting a powerful trading strategy using Stochastic, RSI, and MACD indicators in Python.

This multi-indicator approach combines momentum, trend confirmation, and overbought/oversold signals to generate high-probability trade entries and exits—ideal for volatile assets like Bitcoin, Ethereum, and Binance Coin.


Understanding the Indicators

Before diving into code, let’s clarify how each indicator contributes to the strategy.

1. Stochastic Oscillator – Detecting Reversals

The Stochastic oscillator helps identify potential price reversals by comparing a cryptocurrency’s closing price to its price range over a given period. It consists of two lines: %K (fast line) and %D (slow, signal line).

👉 Discover how real-time data can improve your trading signals with advanced tools.

While many traders use Stochastic alone for entry signals, doing so often leads to false triggers—especially in strong trends. That’s why we use it here only as a reversal filter, not the sole decision-maker.

2. Relative Strength Index (RSI) – Confirming Trend Direction

Although commonly used for overbought/oversold detection, we repurpose the RSI as a trend confirmation tool.

By aligning trades with the RSI trend direction, we avoid counter-trend entries that typically underperform.

3. MACD – Measuring Momentum

The Moving Average Convergence Divergence (MACD) tracks changes in momentum by analyzing the relationship between two moving averages. A standard signal occurs when the MACD line crosses above or below its signal line.

However, in choppy or sideways markets, MACD can generate misleading crossovers. To increase reliability, we require MACD confirmation only when aligned with Stochastic and RSI signals.

This layered validation ensures trades are taken only when multiple forces—reversal potential, trend alignment, and momentum—are working in unison.


Building the Strategy in Python

To implement this strategy, we’ll use widely available libraries: yfinance for data retrieval, pandas for analysis, and matplotlib for visualization.

Step 1: Install and Import Required Libraries

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

If you're using Jupyter Notebook:

!pip install yfinance matplotlib pandas numpy

Step 2: Fetch Cryptocurrency Price Data

We'll pull BTC-USD data at 30-minute intervals:

ticker = 'BTC-USD'
data = yf.download(ticker, interval='30m', period='60d')  # Last 60 days
df = pd.DataFrame(data)

You can replace 'BTC-USD' with 'ETH-USD', 'BNB-USD', or any other major pair supported by Yahoo Finance.

Step 3: Calculate Technical Indicators

Stochastic Oscillator

def calculate_stochastic(df, k_period=14, d_period=3):
    low_min = df['Low'].rolling(window=k_period).min()
    high_max = df['High'].rolling(window=k_period).max()
    df['%K'] = 100 * ((df['Close'] - low_min) / (high_max - low_min))
    df['%D'] = df['%K'].rolling(window=d_period).mean()
    return df

RSI

def calculate_rsi(df, period=14):
    delta = df['Close'].diff()
    gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
    rs = gain / loss
    df['RSI'] = 100 - (100 / (1 + rs))
    return df

MACD

def calculate_macd(df, fast=12, slow=26, signal=9):
    exp1 = df['Close'].ewm(span=fast).mean()
    exp2 = df['Close'].ewm(span=slow).mean()
    df['MACD'] = exp1 - exp2
    df['MACD_signal'] = df['MACD'].ewm(span=signal).mean()
    return df

Apply all three:

df = calculate_stochastic(df)
df = calculate_rsi(df)
df = calculate_macd(df)

Generating Buy/Sell Signals

Now we define conditions for valid trades:

We create a function to detect these patterns:

def generate_signals(df, lags=4):
    # Stochastic oversold/overbought check over past 'lags' periods
    df['Stoch_Oversold'] = np.where((df['%K'] < 25) & (df['%D'] < 25), 1, 0)
    df['Stoch_Overbought'] = np.where((df['%K'] > 75) & (df['%D'] > 75), 1, 0)

    # Rolling check for recent signal
    df['Buy_Signal_Stoch'] = df['Stoch_Oversold'].rolling(lags).sum()
    df['Sell_Signal_Stoch'] = df['Stoch_Overbought'].rolling(lags).sum()

    # MACD crossover
    df['MACD_Cross_Up'] = np.where(df['MACD'] > df['MACD_signal'], 1, 0)
    df['MACD_Cross_Down'] = np.where(df['MACD'] < df['MACD_signal'], 1, 0)

    # Final buy/sell conditions
    df['Buy'] = np.where(
        (df['Buy_Signal_Stoch'] > 0) &
        (df['RSI'] > 50) &
        (df['MACD_Cross_Up'] == 1), 1, 0)

    df['Sell'] = np.where(
        (df['Sell_Signal_Stoch'] > 0) &
        (df['RSI'] < 50) &
        (df['MACD_Cross_Down'] == 1), 1, 0)

    return df

Executing Trades and Calculating Returns

We extract entry and exit points:

Buying_dates = []
Selling_dates = []

for i in range(len(df)):
    if df['Buy'].iloc[i] == 1:
        Buying_dates.append(df.index[i])
    elif df['Sell'].iloc[i] == 1 and len(Buying_dates) > len(Selling_dates):
        Selling_dates.append(df.index[i])

Avoid overlapping trades:

actuals = pd.DataFrame({'Buying_dates': Buying_dates, 'Selling_dates': Selling_dates})
actuals = actuals[actuals.Buying_dates < actuals.Selling_dates]
actuals = actuals[actuals.Buying_dates > actuals.Selling_dates.shift(1)]

Calculate returns per trade:

def calculate_returns(df, actuals):
    returns = []
    for i, row in actuals.iterrows():
        buy_price = df.loc[row.Buying_dates, 'Open']
        sell_price = df.loc[row.Selling_dates, 'Open']
        returns.append((sell_price - buy_price) / buy_price * 100)
    return returns

trade_returns = calculate_returns(df, actuals)
mean_return = np.mean(trade_returns)
cumulative_return = (np.prod([1 + r/100 for r in trade_returns]) - 1) * 100

print(f"Mean Return per Trade: {mean_return:.2f}%")
print(f"Cumulative Return: {cumulative_return:.2f}%")

Backtest Results Across Major Cryptocurrencies

AssetNumber of TradesAvg Return/TradeCumulative Return
BTC-USD62.43%15.39%
ETH-USD51.16%5.90%
BNB-USD61.34%7.30%

Bitcoin delivered the strongest results during the tested period—likely due to higher liquidity and clearer trend behavior.

👉 See how professional traders refine strategies using live market analytics.


Frequently Asked Questions (FAQ)

Q: Can this strategy work on lower timeframes like 5-minute charts?
A: Yes, but increased volatility may lead to more false signals. You should optimize parameters like RSI period or add volume filters to improve accuracy.

Q: Why not use RSI for overbought/oversold levels instead of trend confirmation?
A: Because combining two similar signals (e.g., Stochastic and RSI both detecting overbought levels) increases redundancy. Using RSI as a trend filter diversifies the logic and reduces whipsaws.

Q: How do I avoid overlapping trades?
A: Ensure each buy is followed by a sell before the next buy. The condition Buying_dates > Selling_dates.shift(1) prevents simultaneous open positions.

Q: Is this strategy suitable for altcoins with low volume?
A: No. Low-volume coins suffer from wide bid-ask spreads and manipulation risk. Stick to major pairs like BTC, ETH, or BNB for reliable execution.

Q: What improvements can I make?
A: Add stop-loss logic, position sizing rules, or integrate volume-weighted indicators. You can also test walk-forward optimization for robustness.


Key Takeaways and Next Steps

This multi-indicator cryptocurrency trading strategy leverages the strengths of Stochastic (reversal detection), RSI (trend filtering), and MACD (momentum confirmation) to generate disciplined trading signals.

Core keywords naturally integrated: backtesting cryptocurrency strategy, Python trading bot, Stochastic RSI MACD strategy, crypto backtesting with Python, algorithmic trading indicators, technical analysis in crypto.

While initial results are promising—especially on BTC—the strategy must be stress-tested across different market regimes (bullish, bearish, sideways). Consider expanding the model with risk management modules or deploying it on paper trading platforms before going live.

👉 Start applying your backtested strategies in real-time markets safely and efficiently.