10 KiB
This notebook demonstrates backtesting a trading strategy using Backtrader and OpenBB SDK for data acquisition. It outlines creating a trading strategy and running backtests to simulate performance over historical data. The code includes setting up a backtest, downloading data, defining strategy logic, and assessing results with performance metrics. This is useful for evaluating trading strategies' robustness and optimizing them for better risk-adjusted returns.
import datetime as dt import pandas as pd from openbb_terminal.sdk import openbb import quantstats as qs import backtrader as bt
Define a function to fetch stock data from OpenBB SDK and convert it for Backtrader use
def openbb_data_to_bt_data(symbol, start_date, end_date): """Fetch and convert stock data for Backtrader Parameters ---------- symbol : str Stock symbol to fetch start_date : str Start date for data in 'YYYY-MM-DD' format end_date : str End date for data in 'YYYY-MM-DD' format Returns ------- bt.feeds.YahooFinanceCSVData Formatted data for Backtrader """ # Fetch stock data from OpenBB SDK df = openbb.stocks.load(symbol, start_date=start_date, end_date=end_date) # Save data to a CSV file fn = f"{symbol.lower()}.csv" df.to_csv(fn) # Return data formatted for Backtrader return bt.feeds.YahooFinanceCSVData( dataname=fn, fromdate=dt.datetime.strptime(start_date, '%Y-%m-%d'), todate=dt.datetime.strptime(end_date, '%Y-%m-%d') )
Determine the last day of a given month
def last_day_of_month(any_day): """Calculate the last day of the month Parameters ---------- any_day : datetime.date Any date within the target month Returns ------- int Last day of the month """ # Move to the next month and then back to the last day of the current month next_month = any_day.replace(day=28) + dt.timedelta(days=4) return (next_month - dt.timedelta(days=next_month.day)).day
Define a trading strategy to execute monthly flows
class MonthlyFlows(bt.Strategy): """Strategy to trade based on monthly flows Parameters ---------- end_of_month : int, optional Day of the month to start buying (default is 23) start_of_month : int, optional Day of the month to start selling (default is 7) Attributes ---------- order : bt.Order or None Current order in the market dataclose : bt.LineSeries Closing prices of the data feed """ params = ( ("end_of_month", 23), ("start_of_month", 7), ) def __init__(self): self.order = None self.dataclose = self.datas[0].close def notify_order(self, order): """Handle order notifications""" # No more orders self.order = None def next(self): """Execute strategy logic for each step in the backtest""" # Get today's date, day of month, and last day of current month dt_ = self.datas[0].datetime.date(0) dom = dt_.day ldm = last_day_of_month(dt_) # If an order is pending, exit if self.order: return # Check if we are in the market if not self.position: # We're in the first week of the month, sell if dom <= self.params.start_of_month: self.order = self.order_target_percent(target=-1) print(f"Created SELL of {self.order.size} at {self.data_close[0]} on day {dom}") # We're in the last week of the month, buy if dom >= self.params.end_of_month: self.order = self.order_target_percent(target=1) print(f"Created BUY of {self.order.size} {self.data_close[0]} on day {dom}") # We are not in the market else: # If we're long if self.position.size > 0: if not self.params.end_of_month <= dom <= ldm: print(f"Created CLOSE of {self.position.size} at {self.data_close[0]} on day {dom}") self.order = self.order_target_percent(target=0.0) # If we're short if self.position.size < 0: if not 1 <= dom <= self.params.start_of_month: print(f"Created CLOSE of {self.position.size} at {self.data_close[0]} on day {dom}") self.order = self.order_target_percent(target=0.0)
Run the strategy using Backtrader
data = openbb_data_to_bt_data("TLT", start_date="2002-01-01", end_date="2022-06-30")
cerebro = bt.Cerebro(stdstats=False) cerebro.adddata(data) cerebro.broker.setcash(1000.0) cerebro.addstrategy(MonthlyFlows) cerebro.addobserver(bt.observers.Value) cerebro.addanalyzer(bt.analyzers.Returns, _name="returns") cerebro.addanalyzer(bt.analyzers.TimeReturn, _name="time_return")
backtest_result = cerebro.run()
Convert backtest results into a pandas DataFrame
returns_dict = backtest_result[0].analyzers.time_return.get_analysis() returns_df = ( pd.DataFrame( list(returns_dict.items()), columns=["date", "return"] ) .set_index("date") )
Fetch benchmark data for comparison
bench = openbb.stocks.load("TLT", start_date="2002-01-01", end_date="2022-06-30")["Adj Close"]
Assess results using QuantStats metrics
qs.reports.metrics( returns_df, benchmark=bench, mode="full" )
PyQuant News is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to get started with Python for quant finance. For educational purposes. Not investment advise. Use at your own risk.
