Build a Stock Trading Signal Bot

Build a Python bot that generates buy/sell signals from real stock data.

Introduction

30 Second Summary

Every day, millions of traders stare at stock charts trying to decide when to buy and sell. Professional quants skip the guesswork entirely by using algorithms and data to make those decisions.

In this project, you will build a Python trading signal bot that fetches real Apple stock data, calculates technical indicators, and backtests a multi-indicator strategy against 4 years of price history to show you exactly how it would have performed.

What You'll Build

A Python script that downloads live market data, computes technical analysis indicators like RSI and SMA, generates buy/sell signals, and runs a full historical backtest to measure actual performance.

By the end of this project, you'll have:

  • A working trading bot that fetches real stock data and generates buy/sell signals using multiple technical indicators.
  • A performance report showing win rate, total return, Sharpe ratio, and max drawdown for your strategy.
  • An interactive HTML chart visualizing every trade entry and exit overlaid on the price history.
  • Secret Mission: Adapt your bot to trade crypto and optimize strategy parameters for better performance.

Are there any prerequisites?

None. This guide walks you through installing Python, VS Code, and all required libraries from scratch. No prior trading or programming experience is assumed.

Fetch Historical Stock Data

Your environment is ready with Python, VS Code, and all the libraries installed. Now it's time to pull real stock market data into your project.

Every trading bot needs historical price data to analyze. In this step, you'll use yfinance to download 4 years of Apple (AAPL) stock data and prepare it for analysis.

In this step, get ready to:
  • Download 4 years of AAPL stock data using yfinance.
  • Clean and verify the data is ready for analysis.
Download Apple stock data

yfinance connects to Yahoo Finance and pulls historical price data for any stock ticker. You'll fetch Open, High, Low, Close, and Volume (OHLCV) data for Apple's stock.

  • Create a new file called trading_bot.py in your project folder by clicking the new file icon in VS Code's Explorer sidebar.
  • Add the following imports and data fetch code to trading_bot.py:
import pandas as pd
import yfinance as yf
import pandas_ta_classic as ta
from backtesting import Backtest, Strategy


# --- Fetch Historical Stock Data ---
print("Downloading AAPL stock data...")
ticker = yf.Ticker("AAPL")
df = ticker.history(start="2022-01-01", end="2026-07-01")

What does this code do?

The imports at the top bring in all the libraries you installed in Step 1. You won't use pandas_ta_classic or backtesting yet, but importing them now confirms everything is wired up correctly.

yf.Ticker("AAPL") creates a connection to Apple's stock data. ticker.history(start="2022-01-01", end="2026-07-01") downloads daily price data for that entire date range into a pandas DataFrame.

  • Save trading_bot.py.
  • Run the script in your terminal by entering this command:
python trading_bot.py
  • You should see Downloading AAPL stock data... printed to the terminal, and the script completes without errors.

Seeing an error about yfinance or the ticker?

  • If you see ModuleNotFoundError, your libraries did not install correctly. Run pip install yfinance again in the integrated terminal.
  • If you see a network-related error, check your internet connection. yfinance needs to reach Yahoo Finance to download data.
  • Help me debug this
Clean and summarize the data

The raw data from Yahoo Finance includes extra columns like Dividends and Stock Splits that you don't need for technical analysis. Next, you'll select only the columns that matter and print a summary to confirm everything looks right.

  • In trading_bot.py, add this code below the ticker.history() line:
df = df[["Open", "High", "Low", "Close", "Volume"]]
print(f"Downloaded {len(df)} trading days of data")
print(f"Date range: {df.index[0].strftime('%Y-%m-%d')} to {df.index[-1].strftime('%Y-%m-%d')}")
print(f"Price range: ${df['Close'].min():.2f} - ${df['Close'].max():.2f}")
print(df.tail())

What does this code do?

  • df = df[["Open", "High", "Low", "Close", "Volume"]] keeps only the five OHLCV columns and drops everything else.
  • len(df) tells you how many trading days are in your dataset.
  • df.index[0] and df.index[-1] give the first and last dates, confirming your full date range loaded.
  • df.tail() prints the last 5 rows so you can eyeball the most recent prices.
  • Save trading_bot.py and run the script again:
python trading_bot.py
  • You should see output showing roughly 800-1100 trading days, a date range starting from 2022, a price range for AAPL, and a table of the last 5 rows with Open, High, Low, Close, and Volume columns.

Not seeing any data or getting an empty DataFrame?

  • If len(df) shows 0, the date range may not have returned data. Double-check that your start date is 2022-01-01 and end date is 2026-07-01.
  • If you see a KeyError about a column name, make sure the column names in your list match exactly: Open, High, Low, Close, Volume (capitalized).
  • Help me debug this

What is OHLCV?

OHLCV stands for Open, High, Low, Close, Volume. These are the five standard data points recorded for each trading day.

Open is the price when the market opened that day. High and Low are the highest and lowest prices reached during the day. Close is the final price when the market closed. Volume is how many shares were traded.

✔️ Awesome, I've got everything!

Great. Double check you've saved trading_bot.py before moving on.

ⓧ I'd like to double check the full code

Here's what your complete trading_bot.py should look like at this point:

import pandas as pd
import yfinance as yf
import pandas_ta_classic as ta
from backtesting import Backtest, Strategy


# --- Fetch Historical Stock Data ---
print("Downloading AAPL stock data...")
ticker = yf.Ticker("AAPL")
df = ticker.history(start="2022-01-01", end="2026-07-01")
df = df[["Open", "High", "Low", "Close", "Volume"]]
print(f"Downloaded {len(df)} trading days of data")
print(f"Date range: {df.index[0].strftime('%Y-%m-%d')} to {df.index[-1].strftime('%Y-%m-%d')}")
print(f"Price range: ${df['Close'].min():.2f} - ${df['Close'].max():.2f}")
print(df.tail())

You now have real Apple stock data loaded and ready to analyze. Next up, you'll calculate technical analysis indicators that reveal hidden patterns in this price data.

Calculate Technical Indicators

Your DataFrame is loaded with 4 years of Apple price history. Raw prices alone only tell you where the stock has been.

To generate trading signals, you need technical indicators that reveal patterns hiding inside the price data. In this step, you'll calculate indicators that measure trend direction and momentum, then clean the data so it's ready for your strategy.

In this step, get ready to:
  • Calculate Simple Moving Averages (SMA), RSI, and MACD indicators on your price data.
  • Remove incomplete rows so the data is clean for backtesting.
Calculate SMA, RSI, and MACD indicators

You're about to add four indicators to your DataFrame. Each one reveals a different dimension of price behavior that raw OHLCV data doesn't show on its own.

  • Add the indicator calculations to trading_bot.py by pasting the following code at the bottom of your file, below the print(df.tail()) line:
# --- Calculate Technical Indicators ---
df.ta.sma(length=20, append=True)
df.ta.sma(length=50, append=True)
df.ta.rsi(length=14, append=True)
df.ta.macd(append=True)

# Show all new columns
print(f"\nColumns after indicators: {df.columns.tolist()}")

What do these indicators measure?

SMA (Simple Moving Average) calculates the average closing price over N days. You're computing a 20-day "fast" SMA and a 50-day "slow" SMA. When the fast SMA is above the slow SMA, the stock is in an uptrend.

RSI (Relative Strength Index) measures how fast prices are rising or falling on a scale of 0 to 100. Below 30 means the stock is "oversold" (potentially undervalued). Above 70 means "overbought" (potentially overvalued).

MACD (Moving Average Convergence Divergence) shows the relationship between two exponential moving averages. It helps confirm whether a trend is gaining or losing strength.

  • Save trading_bot.py.
  • Run your script in the terminal by running:
python trading_bot.py
  • You should see a list of column names printed that includes SMA_20, SMA_50, RSI_14, MACD_12_26_9, MACDh_12_26_9, and MACDs_12_26_9.

Not seeing the indicator columns?

  • Make sure import pandas_ta_classic as ta is at the top of your file. The df.ta extension only works once this import runs.
  • Check that append=True is included in each indicator call. Without it, the columns won't be added to your DataFrame.

Still stuck? Help me debug why my indicators aren't appearing in the DataFrame columns.

Drop rows with missing values

Indicators need a "warmup" period of historical data before they can produce values. A 50-day SMA, for example, can't calculate until at least 50 days of data have passed. Those early rows show NaN (Not a Number) for the indicators that haven't accumulated enough data yet.

  • Add the following code at the bottom of trading_bot.py to remove incomplete rows and verify the cleaned data:
# Drop NaN rows (indicators need warmup period)
df.dropna(inplace=True)
print(f"Data ready for backtesting: {len(df)} rows")
print(df[["Close", "SMA_20", "SMA_50", "RSI_14"]].tail())

What does this code do?

  • df.dropna(inplace=True) removes every row that has at least one NaN value. This trims the first ~50 rows where the slower indicators haven't warmed up yet.
  • The final print shows the last 5 rows of your cleaned data with the Close price alongside the three key indicators, confirming everything has real numeric values.
  • Save trading_bot.py and run it again:
python trading_bot.py
  • At the bottom of your terminal output, you should see a row count message (something like Data ready for backtesting: XXX rows) followed by a table showing numeric values for Close, SMA_20, SMA_50, and RSI_14.

Seeing an error about NaN or empty DataFrame?

  • If you see 0 rows after dropping NaN, check that your indicator lines use append=True. Without it, the columns are never added and dropna() has nothing to work with.
  • Make sure the indicator calculations appear BEFORE the df.dropna() line. If you drop NaN first, the indicators have no data to compute from.

Need more help? Help me figure out why my DataFrame is empty after dropping NaN values.

✔️ Awesome, I've got everything!

Great. Double check you've saved trading_bot.py before moving on.

ⓧ I'd like to double check the full code

Here's the complete trading_bot.py so far:

import pandas as pd
import yfinance as yf
import pandas_ta_classic as ta
from backtesting import Backtest, Strategy


# --- Fetch Historical Stock Data ---
print("Downloading AAPL stock data...")
ticker = yf.Ticker("AAPL")
df = ticker.history(start="2022-01-01", end="2026-07-01")
df = df[["Open", "High", "Low", "Close", "Volume"]]
print(f"Downloaded {len(df)} trading days of data")
print(f"Date range: {df.index[0].strftime('%Y-%m-%d')} to {df.index[-1].strftime('%Y-%m-%d')}")
print(f"Price range: ${df['Close'].min():.2f} - ${df['Close'].max():.2f}")
print(df.tail())


# --- Calculate Technical Indicators ---
df.ta.sma(length=20, append=True)
df.ta.sma(length=50, append=True)
df.ta.rsi(length=14, append=True)
df.ta.macd(append=True)

# Show all new columns
print(f"\nColumns after indicators: {df.columns.tolist()}")

# Drop NaN rows (indicators need warmup period)
df.dropna(inplace=True)
print(f"Data ready for backtesting: {len(df)} rows")
print(df[["Close", "SMA_20", "SMA_50", "RSI_14"]].tail())

Your data now has trend and momentum signals baked in. Next up, you'll build the actual trading strategy that reads these indicators and decides when to buy and sell.

Build the Signal Strategy

Your DataFrame is loaded with clean indicator data. Now it's time to turn those numbers into actual trading decisions.

In this step, you'll define a strategy class that reads the RSI and SMA values on each trading day and decides whether to buy, sell, or hold. Backtesting.py provides a Strategy base class that handles the mechanics of simulating trades. Your job is to define the rules.

In this step, get ready to:
  • Define a MultiIndicatorStrategy class and register indicators for charting.
  • Implement buy and sell signal logic using RSI and SMA thresholds.
Define the strategy class and register indicators

Backtesting.py strategies are Python classes that inherit from Strategy. You define two methods: init (which sets up your indicators) and next (which runs on every trading day to make decisions).

  • In trading_bot.py, scroll to the bottom of the file (below the last print statement).
  • Add the strategy class definition with its init method by copying this code:

# --- Define the Trading Strategy ---
class MultiIndicatorStrategy(Strategy):
    rsi_lower = 30
    rsi_upper = 70

    def init(self):
        self.rsi = self.I(lambda x: x, self.data.RSI_14, name="RSI_14")
        self.sma_fast = self.I(lambda x: x, self.data.SMA_20, name="SMA_20")
        self.sma_slow = self.I(lambda x: x, self.data.SMA_50, name="SMA_50")

What does this code do?

The class-level variables rsi_lower = 30 and rsi_upper = 70 define the RSI thresholds for your buy and sell signals. Defining them at the class level (not inside a method) makes them tunable parameters that the optimizer can adjust later.

The self.I(lambda x: x, self.data.RSI_14) pattern registers a pre-computed indicator column for plotting on the final chart. The lambda x: x is an identity function because the indicator values are already calculated in the DataFrame. Without self.I(), the indicators would still work for trading logic but wouldn't appear on the chart.

Why use class-level variables for thresholds?

Professional quant strategies separate parameters from logic. By putting rsi_lower and rsi_upper at the class level, you can test different values without rewriting your strategy. Backtesting.py's optimizer specifically looks for class-level variables to sweep through during parameter optimization.

You won't see new output yet. The next method in the next substep will use these registered indicators for trading logic.

Implement the buy and sell logic

The next method runs once for every trading day in your data. On each day, it checks the current indicator values and decides: buy, sell, or do nothing.

  • Inside the MultiIndicatorStrategy class, directly below the init method, add the next method:
    def next(self):
        # Buy signal: RSI oversold AND uptrend confirmed
        if (self.rsi[-1] < self.rsi_lower and
                self.sma_fast[-1] > self.sma_slow[-1]):
            if not self.position:
                self.buy()

        # Sell signal: RSI overbought OR downtrend
        elif (self.rsi[-1] > self.rsi_upper or
              self.sma_fast[-1] < self.sma_slow[-1]):
            if self.position:
                self.sell()

What does the buy and sell logic mean?

The buy condition requires TWO things to be true at the same time: RSI must be below 30 (the stock is oversold, meaning it dropped a lot and might bounce back) AND the 20-day SMA must be above the 50-day SMA (confirming the overall trend is still upward). Both conditions together reduce false signals.

The sell condition triggers if EITHER condition is true: RSI goes above 70 (overbought, the stock ran up too much) OR the 20-day SMA drops below the 50-day SMA (the uptrend reversed). Selling is more aggressive than buying because protecting gains matters more than catching every opportunity.

The if not self.position and if self.position checks prevent duplicate orders. You only buy if you don't already hold stock, and only sell if you do.

  • Save trading_bot.py.
  • Run the script to confirm no errors by running:
python trading_bot.py

You should see the same data output as before (download info, column names, row count, indicator values). No new output appears because the strategy class is defined but not yet called. The important thing is that no error messages appear.

Seeing an IndentationError?

Make sure the next method is indented at the same level as init (one level inside the class). Python uses indentation to determine what belongs inside a class or function.

Also check that the lines inside next use consistent indentation. Mixing tabs and spaces causes this error even when code looks correct visually.

Why is my strategy class causing an indentation error?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the full trading_bot.py up to this point:

import pandas as pd
import yfinance as yf
import pandas_ta_classic as ta
from backtesting import Backtest, Strategy


# --- Fetch Historical Stock Data ---
print("Downloading AAPL stock data...")
ticker = yf.Ticker("AAPL")
df = ticker.history(start="2022-01-01", end="2026-07-01")
df = df[["Open", "High", "Low", "Close", "Volume"]]
print(f"Downloaded {len(df)} trading days of data")
print(f"Date range: {df.index[0].strftime('%Y-%m-%d')} to {df.index[-1].strftime('%Y-%m-%d')}")
print(f"Price range: ${df['Close'].min():.2f} - ${df['Close'].max():.2f}")
print(df.tail())


# --- Calculate Technical Indicators ---
df.ta.sma(length=20, append=True)
df.ta.sma(length=50, append=True)
df.ta.rsi(length=14, append=True)
df.ta.macd(append=True)

# Show all new columns
print(f"\nColumns after indicators: {df.columns.tolist()}")

# Drop NaN rows (indicators need warmup period)
df.dropna(inplace=True)
print(f"Data ready for backtesting: {len(df)} rows")
print(df[["Close", "SMA_20", "SMA_50", "RSI_14"]].tail())


# --- Define the Trading Strategy ---
class MultiIndicatorStrategy(Strategy):
    rsi_lower = 30
    rsi_upper = 70

    def init(self):
        self.rsi = self.I(lambda x: x, self.data.RSI_14, name="RSI_14")
        self.sma_fast = self.I(lambda x: x, self.data.SMA_20, name="SMA_20")
        self.sma_slow = self.I(lambda x: x, self.data.SMA_50, name="SMA_50")

    def next(self):
        # Buy signal: RSI oversold AND uptrend confirmed
        if (self.rsi[-1] < self.rsi_lower and
                self.sma_fast[-1] > self.sma_slow[-1]):
            if not self.position:
                self.buy()

        # Sell signal: RSI overbought OR downtrend
        elif (self.rsi[-1] > self.rsi_upper or
              self.sma_fast[-1] < self.sma_slow[-1]):
            if self.position:
                self.sell()

Your strategy is fully defined. Next up, you'll run a backtest to see how this strategy actually performs against 4 years of real stock data.

Backtest and Analyze Performance

Your trading strategy is defined and ready to trade. Now it's time to put it to the test against real historical data and find out if your buy/sell logic actually makes money.

Backtesting simulates your strategy on past price data, tracking every trade as if you made it in real time. In this step, you'll run the simulation and get hard numbers on performance.

In this step, get ready to:
  • Run a backtest simulation on 4 years of AAPL price history.
  • Print a performance report with win rate, return, and risk metrics.
  • Generate an interactive HTML chart showing every trade.
Run the backtest and print a performance report

The Backtest class from Backtesting.py takes your data and strategy, then replays the price history day by day. Your strategy's next() method fires on each bar, placing trades exactly as your logic dictates.

  • In trading_bot.py, scroll to the bottom of the file (below the MultiIndicatorStrategy class) and add this code:
# --- Run the Backtest ---
bt = Backtest(df, MultiIndicatorStrategy, commission=0.002, exclusive_orders=True)
stats = bt.run()

# Print performance report
print("\n" + "=" * 50)
print("BACKTEST RESULTS")
print("=" * 50)
print(f"Total Return: {stats['Return [%]']:.2f}%")
print(f"Buy & Hold Return: {stats['Buy & Hold Return [%]']:.2f}%")
print(f"Win Rate: {stats['Win Rate [%]']:.2f}%")
print(f"Number of Trades: {stats['# Trades']}")
print(f"Sharpe Ratio: {stats['Sharpe Ratio']:.2f}")
print(f"Max Drawdown: {stats['Max. Drawdown [%]']:.2f}%")
print(f"Avg Trade: {stats['Avg. Trade [%]']:.2f}%")
print("=" * 50)

# Generate interactive chart
bt.plot()

What does this code do?

Backtest(df, MultiIndicatorStrategy, commission=0.002, exclusive_orders=True) creates a simulation engine. The commission=0.002 means 0.2% fee per trade, mimicking real brokerage costs. The exclusive_orders=True parameter ensures only one position is open at a time, keeping the simulation simple.

stats = bt.run() replays every trading day in your data, executing your strategy's buy/sell logic. It returns a results object containing all the performance metrics.

The print statements extract the most important metrics from stats using their exact dictionary keys. bt.plot() generates an interactive HTML chart using Bokeh and opens it in your default browser.

  • Save trading_bot.py.
  • Run your script by entering this command in the terminal:
python trading_bot.py
  • After the data downloads and indicators calculate, you should see a formatted report in the terminal showing your Total Return, Win Rate, Sharpe Ratio, and other metrics.

What do these metrics mean?

  • Total Return: the percentage gain or loss your strategy produced over the entire period.
  • Buy & Hold Return: what you would have earned by simply buying AAPL at the start and holding. This is your benchmark.
  • Win Rate: the percentage of trades that were profitable. A rate of 45-55% is typical for indicator strategies.
  • Sharpe Ratio: measures return per unit of risk. Above 1.0 is considered decent. Below 0 means the risk outweighed the reward.
  • Max Drawdown: the largest peak-to-trough drop in your portfolio value. This tells you the worst losing streak.

Script throwing an error?

  • If you see KeyError, check that the dictionary keys in your print statements match exactly (including spaces and brackets). For example, Return [%] has a space before the bracket.
  • If you see NameError: name 'Backtest' is not defined, confirm your import at the top of the file reads from backtesting import Backtest, Strategy.
  • If the script hangs on data download, your internet connection may have dropped. Wait a moment and try again.

Still stuck? Help me debug my backtest script error

Examine the interactive chart

When the script finishes, an interactive HTML chart opens in your browser. This is the visual payoff for everything you've built.

  • Switch to the browser window that opened automatically.
  • Look at the top panel. This is the price chart with your SMA_20 and SMA_50 overlays drawn on top of the AAPL price.
  • Find the triangle markers on the price chart. Green triangles mark buy entries and red triangles mark sell exits.
  • Scroll down to see the equity curve (your portfolio value over time) and the drawdown curve (how far below the peak your portfolio dropped at each point).

What should I compare?

The key comparison is your strategy's Total Return vs the Buy & Hold Return. If Buy & Hold is higher, it means simply holding the stock beat your active trading strategy. This is extremely common with basic indicator strategies and is an honest result.

Professional quants use backtesting to eliminate bad strategies quickly. A strategy that underperforms buy-and-hold is one you would never deploy with real money.

✔️ Awesome, I've got everything!

Great. Double check you've saved trading_bot.py and that your chart opened successfully in the browser.

ⓧ I'd like to double check the full code

Here is the complete trading_bot.py file. Compare your code to this:

import pandas as pd
import yfinance as yf
import pandas_ta_classic as ta
from backtesting import Backtest, Strategy


# --- Fetch Historical Stock Data ---
print("Downloading AAPL stock data...")
ticker = yf.Ticker("AAPL")
df = ticker.history(start="2022-01-01", end="2026-07-01")
df = df[["Open", "High", "Low", "Close", "Volume"]]
print(f"Downloaded {len(df)} trading days of data")
print(f"Date range: {df.index[0].strftime('%Y-%m-%d')} to {df.index[-1].strftime('%Y-%m-%d')}")
print(f"Price range: ${df['Close'].min():.2f} - ${df['Close'].max():.2f}")
print(df.tail())


# --- Calculate Technical Indicators ---
df.ta.sma(length=20, append=True)
df.ta.sma(length=50, append=True)
df.ta.rsi(length=14, append=True)
df.ta.macd(append=True)

# Show all new columns
print(f"\nColumns after indicators: {df.columns.tolist()}")

# Drop NaN rows (indicators need warmup period)
df.dropna(inplace=True)
print(f"Data ready for backtesting: {len(df)} rows")
print(df[["Close", "SMA_20", "SMA_50", "RSI_14"]].tail())


# --- Define the Trading Strategy ---
class MultiIndicatorStrategy(Strategy):
    rsi_lower = 30
    rsi_upper = 70

    def init(self):
        self.rsi = self.I(lambda x: x, self.data.RSI_14, name="RSI_14")
        self.sma_fast = self.I(lambda x: x, self.data.SMA_20, name="SMA_20")
        self.sma_slow = self.I(lambda x: x, self.data.SMA_50, name="SMA_50")

    def next(self):
        # Buy signal: RSI oversold AND uptrend confirmed
        if (self.rsi[-1] < self.rsi_lower and
                self.sma_fast[-1] > self.sma_slow[-1]):
            if not self.position:
                self.buy()

        # Sell signal: RSI overbought OR downtrend
        elif (self.rsi[-1] > self.rsi_upper or
              self.sma_fast[-1] < self.sma_slow[-1]):
            if self.position:
                self.sell()


# --- Run the Backtest ---
bt = Backtest(df, MultiIndicatorStrategy, commission=0.002, exclusive_orders=True)
stats = bt.run()

# Print performance report
print("\n" + "=" * 50)
print("BACKTEST RESULTS")
print("=" * 50)
print(f"Total Return: {stats['Return [%]']:.2f}%")
print(f"Buy & Hold Return: {stats['Buy & Hold Return [%]']:.2f}%")
print(f"Win Rate: {stats['Win Rate [%]']:.2f}%")
print(f"Number of Trades: {stats['# Trades']}")
print(f"Sharpe Ratio: {stats['Sharpe Ratio']:.2f}")
print(f"Max Drawdown: {stats['Max. Drawdown [%]']:.2f}%")
print(f"Avg Trade: {stats['Avg. Trade [%]']:.2f}%")
print("=" * 50)

# Generate interactive chart
bt.plot()

Secret mission

Crypto Trading and Strategy Optimization

Your bot works on stocks, but what about the wild world of crypto? In this extension, you'll point your strategy at Bitcoin, compare how it performs in a completely different market, then use Backtesting.py's built-in optimizer to automatically find the best RSI thresholds.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing cloud costs or subscriptions to worry about. Decide whether to keep your resources for future experimentation, or clean them up to free disk space.

Resources you used:

  • Project folder containing trading_bot.py and generated HTML chart files.
  • Python packages: yfinance, pandas-ta-classic, backtesting (and their dependencies).
  • Python 3.12 installed on your system.
  • VS Code with the Python extension.

Keep everything running

No action needed. Choose this if you plan to keep experimenting with your trading bot, testing new indicators, or trying different tickers.

  • Your project folder, Python packages, and tools all stay in place.
  • You can re-run trading_bot.py at any time to get fresh market data and updated backtest results.

Pause - I'll come back to this later

Close VS Code and your terminal to free up memory. Your files and packages remain installed for when you return.

  • Close VS Code.
  • Close any open terminal windows.
  • Your project folder and installed packages stay on disk and will work next time you open them.

Delete - I don't want to use this again

Remove all project files and uninstall the Python packages you installed for this project.

  • Delete your project folder (the one containing trading_bot.py) by running this command in Command Prompt:
  • Replace the path below with the actual location of your project folder.
rmdir /s /q "%USERPROFILE%\Desktop\your-project-folder"
  • Uninstall the Python packages by running:
pip uninstall -y yfinance pandas-ta-classic backtesting
  • If you also want to remove Python 3.12, open Settings, then Apps, then Installed apps. Search for Python 3.12 and click Uninstall.
  • If you want to remove VS Code, search for Visual Studio Code in the same Installed apps list and click Uninstall.

Keep Python and VS Code?

Python and VS Code are general-purpose tools useful for many projects beyond this one. If you plan to do more coding in the future, consider keeping them installed even if you delete the trading bot files.

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully functional stock trading signal bot that fetches real market data, applies professional-grade technical analysis, and proves its performance with hard numbers.

You've learned how to:

  • Built a Python trading bot that fetches real market data using yfinance and generates buy/sell signals with three technical indicators (RSI, SMA, and MACD).
  • Backtested a multi-indicator strategy on 4 years of historical data with realistic commission costs, producing honest performance metrics including win rate, Sharpe ratio, and max drawdown.
  • Generated interactive HTML charts visualizing every trade entry and exit overlaid on the price history, equity curve, and drawdown.
  • Secret Mission: Applied the bot to crypto (BTC-USD) and used parameter optimization to find the best RSI thresholds for improved performance.

Ready to quiz yourself?