daily update
This commit is contained in:
339
to_explore/pyquantnews/28_FlowEffectsBacktrader.ipynb
Normal file
339
to_explore/pyquantnews/28_FlowEffectsBacktrader.ipynb
Normal file
@ -0,0 +1,339 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "04574bc2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8c027594",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3bee1431",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import datetime as dt\n",
|
||||
"import pandas as pd\n",
|
||||
"from openbb_terminal.sdk import openbb\n",
|
||||
"import quantstats as qs\n",
|
||||
"import backtrader as bt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c0e61059",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to fetch stock data from OpenBB SDK and convert it for Backtrader use"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "132f6e66",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def openbb_data_to_bt_data(symbol, start_date, end_date):\n",
|
||||
" \"\"\"Fetch and convert stock data for Backtrader\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" symbol : str\n",
|
||||
" Stock symbol to fetch\n",
|
||||
" start_date : str\n",
|
||||
" Start date for data in 'YYYY-MM-DD' format\n",
|
||||
" end_date : str\n",
|
||||
" End date for data in 'YYYY-MM-DD' format\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" bt.feeds.YahooFinanceCSVData\n",
|
||||
" Formatted data for Backtrader\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Fetch stock data from OpenBB SDK\n",
|
||||
" df = openbb.stocks.load(symbol, start_date=start_date, end_date=end_date)\n",
|
||||
"\n",
|
||||
" # Save data to a CSV file\n",
|
||||
" fn = f\"{symbol.lower()}.csv\"\n",
|
||||
" df.to_csv(fn)\n",
|
||||
" \n",
|
||||
" # Return data formatted for Backtrader\n",
|
||||
" return bt.feeds.YahooFinanceCSVData(\n",
|
||||
" dataname=fn,\n",
|
||||
" fromdate=dt.datetime.strptime(start_date, '%Y-%m-%d'),\n",
|
||||
" todate=dt.datetime.strptime(end_date, '%Y-%m-%d')\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6c3c0564",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Determine the last day of a given month"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cc17ebfc",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def last_day_of_month(any_day):\n",
|
||||
" \"\"\"Calculate the last day of the month\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" any_day : datetime.date\n",
|
||||
" Any date within the target month\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" int\n",
|
||||
" Last day of the month\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Move to the next month and then back to the last day of the current month\n",
|
||||
" next_month = any_day.replace(day=28) + dt.timedelta(days=4)\n",
|
||||
" return (next_month - dt.timedelta(days=next_month.day)).day"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "64b04705",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a trading strategy to execute monthly flows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0729cd4a",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class MonthlyFlows(bt.Strategy):\n",
|
||||
" \"\"\"Strategy to trade based on monthly flows\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" end_of_month : int, optional\n",
|
||||
" Day of the month to start buying (default is 23)\n",
|
||||
" start_of_month : int, optional\n",
|
||||
" Day of the month to start selling (default is 7)\n",
|
||||
" \n",
|
||||
" Attributes\n",
|
||||
" ----------\n",
|
||||
" order : bt.Order or None\n",
|
||||
" Current order in the market\n",
|
||||
" dataclose : bt.LineSeries\n",
|
||||
" Closing prices of the data feed\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" params = (\n",
|
||||
" (\"end_of_month\", 23),\n",
|
||||
" (\"start_of_month\", 7),\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" def __init__(self):\n",
|
||||
" self.order = None\n",
|
||||
" self.dataclose = self.datas[0].close\n",
|
||||
" \n",
|
||||
" def notify_order(self, order):\n",
|
||||
" \"\"\"Handle order notifications\"\"\"\n",
|
||||
" \n",
|
||||
" # No more orders\n",
|
||||
" self.order = None \n",
|
||||
" \n",
|
||||
" def next(self):\n",
|
||||
" \"\"\"Execute strategy logic for each step in the backtest\"\"\"\n",
|
||||
" \n",
|
||||
" # Get today's date, day of month, and last day of current month\n",
|
||||
" dt_ = self.datas[0].datetime.date(0)\n",
|
||||
" dom = dt_.day\n",
|
||||
" ldm = last_day_of_month(dt_)\n",
|
||||
" \n",
|
||||
" # If an order is pending, exit\n",
|
||||
" if self.order:\n",
|
||||
" return\n",
|
||||
" \n",
|
||||
" # Check if we are in the market\n",
|
||||
" if not self.position:\n",
|
||||
" \n",
|
||||
" # We're in the first week of the month, sell\n",
|
||||
" if dom <= self.params.start_of_month:\n",
|
||||
" self.order = self.order_target_percent(target=-1)\n",
|
||||
" print(f\"Created SELL of {self.order.size} at {self.data_close[0]} on day {dom}\")\n",
|
||||
" \n",
|
||||
" # We're in the last week of the month, buy\n",
|
||||
" if dom >= self.params.end_of_month:\n",
|
||||
" self.order = self.order_target_percent(target=1)\n",
|
||||
" print(f\"Created BUY of {self.order.size} {self.data_close[0]} on day {dom}\")\n",
|
||||
" \n",
|
||||
" # We are not in the market\n",
|
||||
" else:\n",
|
||||
" \n",
|
||||
" # If we're long\n",
|
||||
" if self.position.size > 0:\n",
|
||||
" if not self.params.end_of_month <= dom <= ldm:\n",
|
||||
" print(f\"Created CLOSE of {self.position.size} at {self.data_close[0]} on day {dom}\")\n",
|
||||
" self.order = self.order_target_percent(target=0.0)\n",
|
||||
" \n",
|
||||
" # If we're short\n",
|
||||
" if self.position.size < 0:\n",
|
||||
" if not 1 <= dom <= self.params.start_of_month:\n",
|
||||
" print(f\"Created CLOSE of {self.position.size} at {self.data_close[0]} on day {dom}\")\n",
|
||||
" self.order = self.order_target_percent(target=0.0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e8fbbc83",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Run the strategy using Backtrader"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b47c7a71",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = openbb_data_to_bt_data(\"TLT\", start_date=\"2002-01-01\", end_date=\"2022-06-30\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d53cbfee",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cerebro = bt.Cerebro(stdstats=False)\n",
|
||||
"cerebro.adddata(data)\n",
|
||||
"cerebro.broker.setcash(1000.0)\n",
|
||||
"cerebro.addstrategy(MonthlyFlows)\n",
|
||||
"cerebro.addobserver(bt.observers.Value)\n",
|
||||
"cerebro.addanalyzer(bt.analyzers.Returns, _name=\"returns\")\n",
|
||||
"cerebro.addanalyzer(bt.analyzers.TimeReturn, _name=\"time_return\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "94ea3665",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"backtest_result = cerebro.run()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "257cc57c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Convert backtest results into a pandas DataFrame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "27a1ec64",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns_dict = backtest_result[0].analyzers.time_return.get_analysis()\n",
|
||||
"returns_df = (\n",
|
||||
" pd.DataFrame(\n",
|
||||
" list(returns_dict.items()),\n",
|
||||
" columns=[\"date\", \"return\"]\n",
|
||||
" )\n",
|
||||
" .set_index(\"date\")\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7b6e5887",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Fetch benchmark data for comparison"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "86c540be",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"bench = openbb.stocks.load(\"TLT\", start_date=\"2002-01-01\", end_date=\"2022-06-30\")[\"Adj Close\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3c39d605",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Assess results using QuantStats metrics"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2dec6aa2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"qs.reports.metrics(\n",
|
||||
" returns_df,\n",
|
||||
" benchmark=bench,\n",
|
||||
" mode=\"full\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5b590090",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> 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 <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Reference in New Issue
Block a user