Skip to content

Instantly share code, notes, and snippets.

@mohitkh7
Last active April 21, 2025 15:46
Show Gist options
  • Select an option

  • Save mohitkh7/5a11d0cf9740be36800dce94c1fc3d34 to your computer and use it in GitHub Desktop.

Select an option

Save mohitkh7/5a11d0cf9740be36800dce94c1fc3d34 to your computer and use it in GitHub Desktop.
PyCon24 AlgoTrading.ipynb
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/mohitkh7/5a11d0cf9740be36800dce94c1fc3d34/pycon24-algotrading.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SsgZmxYXpQUA"
},
"source": [
"# Python-Powered Algorithmic Trading: From Theory to Practice"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "i1dATioXroHH"
},
"source": [
"## Introduction\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "V_RumFUErnoO"
},
"source": [
"### Algorithmic Trading\n",
"\n",
"Algorithmic trading uses computer algorithms to automatically execute trades based on predefined criteria, such as price, volume, or timing.\n",
"\n",
"It aims to optimize trading strategies, minimize human error, and capitalize on market opportunities quickly.\n",
"\n",
"By analyzing vast amounts of data and executing trades at high speeds, algo trading enhances efficiency and can lead to more precise and profitable trading decisions.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "HBB1krXxCYlh"
},
"source": [
"## Installation"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "220-Kz9E5isH"
},
"source": [
"### Configurations"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "T_ZPvG4KPsXB"
},
"outputs": [],
"source": [
"# Set Configurations\n",
"\n",
"# Matplotlib configuration\n",
"%matplotlib inline\n",
"\n",
"# Suppress future warning messages\n",
"import warnings\n",
"warnings.simplefilter(\"ignore\", category=FutureWarning)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9ns-qZczCeDw"
},
"source": [
"### TA-Lib C Module\n",
"\n",
"[TA-Lib](https://ta-lib.org/) (Technical Analysis Library) is widely used by developers in finance and trading for performing technical analysis on market data.\n",
"\n",
"- Provides over 200+ indicators such as ADX, MACD, RSI, Stochastic, Bollinger Bands, and more.\n",
"- Supports candlestick pattern recognition for identifying trading signals.\n",
"- Released in 2001, TA-Lib implements well-established algorithms that are still popular over 20 years later.\n",
"- Provides Open-source API for Python, but requires installation of a C module dependency for proper functionality.\n",
"\n",
"Installation:\n",
"To install the required C module for TA-Lib, specific commands are needed.\n",
"For installation in Google Colab, refer to a [Stackoverflow answer](https://stackoverflow.com/questions/49648391/how-to-install-ta-lib-in-google-colab) that provides step-by-step guidance."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "U7nVxRVmI0Kw",
"outputId": "395c3fb8-b99d-4bb9-b8ec-ea9222389422"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Selecting previously unselected package libta-lib0.\n",
"(Reading database ... 123599 files and directories currently installed.)\n",
"Preparing to unpack libta.deb ...\n",
"Unpacking libta-lib0 (0.4.0-oneiric1) ...\n",
"Selecting previously unselected package ta-lib0-dev.\n",
"Preparing to unpack ta.deb ...\n",
"Unpacking ta-lib0-dev (0.4.0-oneiric1) ...\n",
"Setting up libta-lib0 (0.4.0-oneiric1) ...\n",
"Setting up ta-lib0-dev (0.4.0-oneiric1) ...\n",
"Processing triggers for man-db (2.10.2-1) ...\n",
"Processing triggers for libc-bin (2.35-0ubuntu3.4) ...\n",
"/sbin/ldconfig.real: /usr/local/lib/libtbbbind.so.3 is not a symbolic link\n",
"\n",
"/sbin/ldconfig.real: /usr/local/lib/libur_adapter_level_zero.so.0 is not a symbolic link\n",
"\n",
"/sbin/ldconfig.real: /usr/local/lib/libtbbbind_2_0.so.3 is not a symbolic link\n",
"\n",
"/sbin/ldconfig.real: /usr/local/lib/libur_loader.so.0 is not a symbolic link\n",
"\n",
"/sbin/ldconfig.real: /usr/local/lib/libtbbmalloc_proxy.so.2 is not a symbolic link\n",
"\n",
"/sbin/ldconfig.real: /usr/local/lib/libtbb.so.12 is not a symbolic link\n",
"\n",
"/sbin/ldconfig.real: /usr/local/lib/libtbbbind_2_5.so.3 is not a symbolic link\n",
"\n",
"/sbin/ldconfig.real: /usr/local/lib/libur_adapter_opencl.so.0 is not a symbolic link\n",
"\n",
"/sbin/ldconfig.real: /usr/local/lib/libtbbmalloc.so.2 is not a symbolic link\n",
"\n"
]
}
],
"source": [
"url = 'https://launchpad.net/~mario-mariomedina/+archive/ubuntu/talib/+files'\n",
"!wget $url/libta-lib0_0.4.0-oneiric1_amd64.deb -qO libta.deb\n",
"!wget $url/ta-lib0-dev_0.4.0-oneiric1_amd64.deb -qO ta.deb\n",
"!dpkg -i libta.deb ta.deb"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "nB-GOTc-C5GX"
},
"source": [
"### TA-Lib Python Package\n",
"\n",
"The original Python bindings for TA-Lib were created using **SWIG** (Simplified Wrapper and Interface Generator). However, these bindings can be challenging to install and are not as optimized in terms of performance as they could be.\n",
"\n",
"To address these limitations, we will use a different [Python package](https://github.com/TA-Lib/ta-lib-python) that serves as a **Cython-based wrapper** for TA-Lib. Cython provides a more streamlined and efficient way to interface with the underlying C library, making it easier to install and faster in execution compared to the SWIG-based implementation.\n",
"\n",
"This alternative package ensures better performance and ease of use, making it a preferred choice for developers working with TA-Lib in Python."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "RnxlHoCMC4sG",
"outputId": "5c418e86-f643-4ed1-be9b-603232103756"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Collecting TA-Lib\n",
" Downloading TA-Lib-0.4.32.tar.gz (368 kB)\n",
"\u001b[?25l \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/368.5 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K \u001b[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[91m╸\u001b[0m\u001b[90m━\u001b[0m \u001b[32m358.4/368.5 kB\u001b[0m \u001b[31m14.3 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m368.5/368.5 kB\u001b[0m \u001b[31m9.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[?25h Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n",
" Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n",
" Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
"Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from TA-Lib) (1.26.4)\n",
"Building wheels for collected packages: TA-Lib\n",
" Building wheel for TA-Lib (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
" Created wheel for TA-Lib: filename=TA_Lib-0.4.32-cp310-cp310-linux_x86_64.whl size=2063404 sha256=4a50807dedfc87b4f2168885d6fbe1fd8db3c019a5ff8feba00b3af1c50c5854\n",
" Stored in directory: /root/.cache/pip/wheels/c3/21/bd/ca95eb09997c2a18fce271b98b10ffa9fcafbaa161be864dd7\n",
"Successfully built TA-Lib\n",
"Installing collected packages: TA-Lib\n",
"Successfully installed TA-Lib-0.4.32\n"
]
}
],
"source": [
"!pip install TA-Lib"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "rpAM5RfOHjpn"
},
"source": [
"### Yfinance\n",
"[yfinance](https://github.com/ranaroussi/yfinance) provides a Pythonic and efficient way to download market data, making it easy to access historical and real-time financial information."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "f1la4Te3O7vv",
"outputId": "b03c278a-75e2-45d0-b9c5-9f72a765a0b6"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Requirement already satisfied: yfinance in /usr/local/lib/python3.10/dist-packages (0.2.43)\n",
"Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from yfinance) (2.1.4)\n",
"Requirement already satisfied: numpy>=1.16.5 in /usr/local/lib/python3.10/dist-packages (from yfinance) (1.26.4)\n",
"Requirement already satisfied: requests>=2.31 in /usr/local/lib/python3.10/dist-packages (from yfinance) (2.32.3)\n",
"Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.10/dist-packages (from yfinance) (0.0.11)\n",
"Requirement already satisfied: lxml>=4.9.1 in /usr/local/lib/python3.10/dist-packages (from yfinance) (4.9.4)\n",
"Requirement already satisfied: platformdirs>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from yfinance) (4.3.4)\n",
"Requirement already satisfied: pytz>=2022.5 in /usr/local/lib/python3.10/dist-packages (from yfinance) (2024.2)\n",
"Requirement already satisfied: frozendict>=2.3.4 in /usr/local/lib/python3.10/dist-packages (from yfinance) (2.4.4)\n",
"Requirement already satisfied: peewee>=3.16.2 in /usr/local/lib/python3.10/dist-packages (from yfinance) (3.17.6)\n",
"Requirement already satisfied: beautifulsoup4>=4.11.1 in /usr/local/lib/python3.10/dist-packages (from yfinance) (4.12.3)\n",
"Requirement already satisfied: html5lib>=1.1 in /usr/local/lib/python3.10/dist-packages (from yfinance) (1.1)\n",
"Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.10/dist-packages (from beautifulsoup4>=4.11.1->yfinance) (2.6)\n",
"Requirement already satisfied: six>=1.9 in /usr/local/lib/python3.10/dist-packages (from html5lib>=1.1->yfinance) (1.16.0)\n",
"Requirement already satisfied: webencodings in /usr/local/lib/python3.10/dist-packages (from html5lib>=1.1->yfinance) (0.5.1)\n",
"Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->yfinance) (2.8.2)\n",
"Requirement already satisfied: tzdata>=2022.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->yfinance) (2024.1)\n",
"Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests>=2.31->yfinance) (3.3.2)\n",
"Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests>=2.31->yfinance) (3.10)\n",
"Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.31->yfinance) (2.0.7)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests>=2.31->yfinance) (2024.8.30)\n"
]
}
],
"source": [
"!pip install yfinance"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1_Ken_6VHbzC"
},
"source": [
"### Backtrader\n",
"[Backtrader](https://github.com/mementum/backtrader) is a popular open-source Python library designed for backtesting trading strategies.\n",
"\n",
"It allows traders and developers to test and analyze their trading ideas using historical data before applying them in real-world markets."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "ZaXIxvuLLMqu",
"outputId": "474b5eeb-3493-42af-dcb0-b129223af76e"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Collecting backtrader\n",
" Downloading backtrader-1.9.78.123-py2.py3-none-any.whl.metadata (6.8 kB)\n",
"Downloading backtrader-1.9.78.123-py2.py3-none-any.whl (419 kB)\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m419.5/419.5 kB\u001b[0m \u001b[31m8.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[?25hInstalling collected packages: backtrader\n",
"Successfully installed backtrader-1.9.78.123\n",
"Requirement already satisfied: backtrader[plotting] in /usr/local/lib/python3.10/dist-packages (1.9.78.123)\n",
"Requirement already satisfied: matplotlib in /usr/local/lib/python3.10/dist-packages (from backtrader[plotting]) (3.7.1)\n",
"Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (1.3.0)\n",
"Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (0.12.1)\n",
"Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (4.53.1)\n",
"Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (1.4.7)\n",
"Requirement already satisfied: numpy>=1.20 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (1.26.4)\n",
"Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (24.1)\n",
"Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (10.4.0)\n",
"Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (3.1.4)\n",
"Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib->backtrader[plotting]) (2.8.2)\n",
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib->backtrader[plotting]) (1.16.0)\n"
]
}
],
"source": [
"!pip install backtrader\n",
"!pip install backtrader[plotting]"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "xWiY06oCJGC0"
},
"source": [
"### Import Packages"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fgMY4-NLPa4K"
},
"outputs": [],
"source": [
"import talib\n",
"import yfinance\n",
"import backtrader as bt\n",
"import pytz\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import plotly.express as px\n",
"import plotly.graph_objects as go"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "TeQBJgYvJ87Y"
},
"source": [
"## Get Instrument Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "VxQ65cGpQrsh"
},
"outputs": [],
"source": [
"def get_instrument_data(symbol: str, period=\"1mo\", interval=\"1d\") -> pd.DataFrame:\n",
" \"\"\"\n",
" Fetches historical instrument data for a given symbol within a specified period.\n",
"\n",
" Args:\n",
" symbol (str): The instrument ticker symbol (e.g., 'AAPL' for Apple).\n",
" period (str): Valid periods: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max\n",
" interval (str): Valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo\n",
" Intraday data cannot extend last 60 days\n",
"\n",
" Returns:\n",
" pd.DataFrame: DataFrame with columns: 'Open', 'Close', 'High', 'Low' and 'Volume'.\n",
" \"\"\"\n",
" # Create a Ticker object for the given symbol\n",
" ticker = yfinance.Ticker(symbol)\n",
"\n",
" # Fetch historical data\n",
" instrument_data_df = ticker.history(period, interval, auto_adjust=False,\n",
" rounding=True, actions=False)\n",
"\n",
" return instrument_data_df\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "L6hSJifsSRHK"
},
"outputs": [],
"source": [
"data = get_instrument_data(\"^NSEI\", \"1y\", \"1d\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3D1N8gUGPI_e"
},
"source": [
"### Ticker Symbols\n",
"\n",
"\n",
"| Category | Instrument | Symbol |\n",
"|--------------|--------------|-------------|\n",
"| Stock | RELIANCE | RELIANCE.NS |\n",
"| | HDFC | HDFCBANK.NS |\n",
"| | | |\n",
"| Index | Nifty50 | ^NSEI |\n",
"| | Bank Nifty | ^NSEBANK |\n",
"| | | |\n",
"| Crypto | Bitcoin | BTC-INR |\n",
"| | Ethereum | ETH-INR |\n",
"| | | |\n",
"\n",
"\n",
"You can search for the symbol of any instrument on [Yahoo's website](https://finance.yahoo.com/lookup/).\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5Gkn-NKuSuS0"
},
"source": [
"### Preview Stock Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 237
},
"id": "IQz4-aLiL4V1",
"outputId": "b4d821ce-7944-48a2-9bc4-bd42f5b9a14b"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" Open High Low Close Adj Close \\\n",
"Date \n",
"2024-09-16 00:00:00+05:30 25406.65 25445.70 25336.20 25383.75 25383.75 \n",
"2024-09-17 00:00:00+05:30 25416.90 25441.65 25352.25 25418.55 25418.55 \n",
"2024-09-18 00:00:00+05:30 25402.40 25482.20 25285.55 25377.55 25377.55 \n",
"2024-09-19 00:00:00+05:30 25487.05 25611.95 25376.05 25415.80 25415.80 \n",
"2024-09-20 00:00:00+05:30 25525.95 25692.70 25426.60 25654.20 25654.20 \n",
"\n",
" Volume \n",
"Date \n",
"2024-09-16 00:00:00+05:30 168700 \n",
"2024-09-17 00:00:00+05:30 216000 \n",
"2024-09-18 00:00:00+05:30 215700 \n",
"2024-09-19 00:00:00+05:30 314500 \n",
"2024-09-20 00:00:00+05:30 0 "
],
"text/html": [
"\n",
" <div id=\"df-1d6ca0e3-1d87-4361-9652-cf4c7d78f466\" class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Open</th>\n",
" <th>High</th>\n",
" <th>Low</th>\n",
" <th>Close</th>\n",
" <th>Adj Close</th>\n",
" <th>Volume</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Date</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>2024-09-16 00:00:00+05:30</th>\n",
" <td>25406.65</td>\n",
" <td>25445.70</td>\n",
" <td>25336.20</td>\n",
" <td>25383.75</td>\n",
" <td>25383.75</td>\n",
" <td>168700</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2024-09-17 00:00:00+05:30</th>\n",
" <td>25416.90</td>\n",
" <td>25441.65</td>\n",
" <td>25352.25</td>\n",
" <td>25418.55</td>\n",
" <td>25418.55</td>\n",
" <td>216000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2024-09-18 00:00:00+05:30</th>\n",
" <td>25402.40</td>\n",
" <td>25482.20</td>\n",
" <td>25285.55</td>\n",
" <td>25377.55</td>\n",
" <td>25377.55</td>\n",
" <td>215700</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2024-09-19 00:00:00+05:30</th>\n",
" <td>25487.05</td>\n",
" <td>25611.95</td>\n",
" <td>25376.05</td>\n",
" <td>25415.80</td>\n",
" <td>25415.80</td>\n",
" <td>314500</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2024-09-20 00:00:00+05:30</th>\n",
" <td>25525.95</td>\n",
" <td>25692.70</td>\n",
" <td>25426.60</td>\n",
" <td>25654.20</td>\n",
" <td>25654.20</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <div class=\"colab-df-buttons\">\n",
"\n",
" <div class=\"colab-df-container\">\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-1d6ca0e3-1d87-4361-9652-cf4c7d78f466')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
"\n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
" <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
" </svg>\n",
" </button>\n",
"\n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" .colab-df-buttons div {\n",
" margin-bottom: 4px;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-1d6ca0e3-1d87-4361-9652-cf4c7d78f466 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-1d6ca0e3-1d87-4361-9652-cf4c7d78f466');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
"\n",
"\n",
"<div id=\"df-1ede2df4-dd9a-4466-beb3-2142f078f707\">\n",
" <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-1ede2df4-dd9a-4466-beb3-2142f078f707')\"\n",
" title=\"Suggest charts\"\n",
" style=\"display:none;\">\n",
"\n",
"<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <g>\n",
" <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
" </g>\n",
"</svg>\n",
" </button>\n",
"\n",
"<style>\n",
" .colab-df-quickchart {\n",
" --bg-color: #E8F0FE;\n",
" --fill-color: #1967D2;\n",
" --hover-bg-color: #E2EBFA;\n",
" --hover-fill-color: #174EA6;\n",
" --disabled-fill-color: #AAA;\n",
" --disabled-bg-color: #DDD;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-quickchart {\n",
" --bg-color: #3B4455;\n",
" --fill-color: #D2E3FC;\n",
" --hover-bg-color: #434B5C;\n",
" --hover-fill-color: #FFFFFF;\n",
" --disabled-bg-color: #3B4455;\n",
" --disabled-fill-color: #666;\n",
" }\n",
"\n",
" .colab-df-quickchart {\n",
" background-color: var(--bg-color);\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: var(--fill-color);\n",
" height: 32px;\n",
" padding: 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-quickchart:hover {\n",
" background-color: var(--hover-bg-color);\n",
" box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: var(--button-hover-fill-color);\n",
" }\n",
"\n",
" .colab-df-quickchart-complete:disabled,\n",
" .colab-df-quickchart-complete:disabled:hover {\n",
" background-color: var(--disabled-bg-color);\n",
" fill: var(--disabled-fill-color);\n",
" box-shadow: none;\n",
" }\n",
"\n",
" .colab-df-spinner {\n",
" border: 2px solid var(--fill-color);\n",
" border-color: transparent;\n",
" border-bottom-color: var(--fill-color);\n",
" animation:\n",
" spin 1s steps(1) infinite;\n",
" }\n",
"\n",
" @keyframes spin {\n",
" 0% {\n",
" border-color: transparent;\n",
" border-bottom-color: var(--fill-color);\n",
" border-left-color: var(--fill-color);\n",
" }\n",
" 20% {\n",
" border-color: transparent;\n",
" border-left-color: var(--fill-color);\n",
" border-top-color: var(--fill-color);\n",
" }\n",
" 30% {\n",
" border-color: transparent;\n",
" border-left-color: var(--fill-color);\n",
" border-top-color: var(--fill-color);\n",
" border-right-color: var(--fill-color);\n",
" }\n",
" 40% {\n",
" border-color: transparent;\n",
" border-right-color: var(--fill-color);\n",
" border-top-color: var(--fill-color);\n",
" }\n",
" 60% {\n",
" border-color: transparent;\n",
" border-right-color: var(--fill-color);\n",
" }\n",
" 80% {\n",
" border-color: transparent;\n",
" border-right-color: var(--fill-color);\n",
" border-bottom-color: var(--fill-color);\n",
" }\n",
" 90% {\n",
" border-color: transparent;\n",
" border-bottom-color: var(--fill-color);\n",
" }\n",
" }\n",
"</style>\n",
"\n",
" <script>\n",
" async function quickchart(key) {\n",
" const quickchartButtonEl =\n",
" document.querySelector('#' + key + ' button');\n",
" quickchartButtonEl.disabled = true; // To prevent multiple clicks.\n",
" quickchartButtonEl.classList.add('colab-df-spinner');\n",
" try {\n",
" const charts = await google.colab.kernel.invokeFunction(\n",
" 'suggestCharts', [key], {});\n",
" } catch (error) {\n",
" console.error('Error during call to suggestCharts:', error);\n",
" }\n",
" quickchartButtonEl.classList.remove('colab-df-spinner');\n",
" quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
" }\n",
" (() => {\n",
" let quickchartButtonEl =\n",
" document.querySelector('#df-1ede2df4-dd9a-4466-beb3-2142f078f707 button');\n",
" quickchartButtonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
" })();\n",
" </script>\n",
"</div>\n",
"\n",
" </div>\n",
" </div>\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"summary": "{\n \"name\": \"data\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"Date\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2024-09-16 00:00:00+05:30\",\n \"max\": \"2024-09-20 00:00:00+05:30\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2024-09-17 00:00:00+05:30\",\n \"2024-09-20 00:00:00+05:30\",\n \"2024-09-18 00:00:00+05:30\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Open\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 55.5816449018912,\n \"min\": 25402.4,\n \"max\": 25525.95,\n \"num_unique_values\": 5,\n \"samples\": [\n 25416.9,\n 25525.95,\n 25402.4\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"High\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 112.10153544889546,\n \"min\": 25441.65,\n \"max\": 25692.7,\n \"num_unique_values\": 5,\n \"samples\": [\n 25441.65,\n 25692.7,\n 25482.2\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Low\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 51.84935631230111,\n \"min\": 25285.55,\n \"max\": 25426.6,\n \"num_unique_values\": 5,\n \"samples\": [\n 25352.25,\n 25426.6,\n 25285.55\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Close\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 115.64432865471667,\n \"min\": 25377.55,\n \"max\": 25654.2,\n \"num_unique_values\": 5,\n \"samples\": [\n 25418.55,\n 25654.2,\n 25377.55\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Adj Close\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 115.64432865471667,\n \"min\": 25377.55,\n \"max\": 25654.2,\n \"num_unique_values\": 5,\n \"samples\": [\n 25418.55,\n 25654.2,\n 25377.55\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Volume\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 115264,\n \"min\": 0,\n \"max\": 314500,\n \"num_unique_values\": 5,\n \"samples\": [\n 216000,\n 0,\n 215700\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {},
"execution_count": 17
}
],
"source": [
"data.tail()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "vw3-NzfFURMF"
},
"source": [
"## Visualize Stock Data"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "VEauubeTVXIu"
},
"source": [
"### Line Chart\n",
"A line chart displays the stock's closing price over time, offering a clear view of overall trends. It is a simple yet effective way to track price movements and identify upward or downward trends."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "PQ_kI9GUVGYI"
},
"outputs": [],
"source": [
"def display_line_chart(data):\n",
" fig = px.line(data, x=data.index, y=\"Close\")\n",
" fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 542
},
"id": "vuHo1mthVCyR",
"outputId": "e7e1c8e2-29b4-4045-fa29-c6f379de0dc0"
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/html": [
"<html>\n",
"<head><meta charset=\"utf-8\" /></head>\n",
"<body>\n",
" <div> <script src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG\"></script><script type=\"text/javascript\">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: \"STIX-Web\"}});}</script> <script type=\"text/javascript\">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>\n",
" <script charset=\"utf-8\" src=\"https://cdn.plot.ly/plotly-2.24.1.min.js\"></script> <div id=\"001a7462-30bb-4c6a-a922-9f98a5db653e\" class=\"plotly-graph-div\" style=\"height:525px; width:100%;\"></div> <script type=\"text/javascript\"> window.PLOTLYENV=window.PLOTLYENV || {}; if (document.getElementById(\"001a7462-30bb-4c6a-a922-9f98a5db653e\")) { Plotly.newPlot( \"001a7462-30bb-4c6a-a922-9f98a5db653e\", [{\"hovertemplate\":\"Date=%{x}\\u003cbr\\u003eClose=%{y}\\u003cextra\\u003e\\u003c\\u002fextra\\u003e\",\"legendgroup\":\"\",\"line\":{\"color\":\"#636efa\",\"dash\":\"solid\"},\"marker\":{\"symbol\":\"circle\"},\"mode\":\"lines\",\"name\":\"\",\"orientation\":\"v\",\"showlegend\":false,\"x\":[\"2024-08-20T00:00:00+05:30\",\"2024-08-21T00:00:00+05:30\",\"2024-08-22T00:00:00+05:30\",\"2024-08-23T00:00:00+05:30\",\"2024-08-26T00:00:00+05:30\",\"2024-08-27T00:00:00+05:30\",\"2024-08-28T00:00:00+05:30\",\"2024-08-29T00:00:00+05:30\",\"2024-08-30T00:00:00+05:30\",\"2024-09-02T00:00:00+05:30\",\"2024-09-03T00:00:00+05:30\",\"2024-09-04T00:00:00+05:30\",\"2024-09-05T00:00:00+05:30\",\"2024-09-06T00:00:00+05:30\",\"2024-09-09T00:00:00+05:30\",\"2024-09-10T00:00:00+05:30\",\"2024-09-11T00:00:00+05:30\",\"2024-09-12T00:00:00+05:30\",\"2024-09-13T00:00:00+05:30\",\"2024-09-16T00:00:00+05:30\",\"2024-09-17T00:00:00+05:30\",\"2024-09-18T00:00:00+05:30\",\"2024-09-19T00:00:00+05:30\",\"2024-09-20T00:00:00+05:30\"],\"xaxis\":\"x\",\"y\":[24698.85,24770.2,24811.5,24823.15,25010.6,25017.75,25052.35,25151.95,25235.9,25278.7,25279.85,25198.7,25145.1,24852.15,24936.4,25041.1,24918.45,25388.9,25356.5,25383.75,25418.55,25377.55,25415.8,25654.2],\"yaxis\":\"y\",\"type\":\"scatter\"}], {\"template\":{\"data\":{\"histogram2dcontour\":[{\"type\":\"histogram2dcontour\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"choropleth\":[{\"type\":\"choropleth\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"histogram2d\":[{\"type\":\"histogram2d\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"heatmap\":[{\"type\":\"heatmap\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"heatmapgl\":[{\"type\":\"heatmapgl\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"contourcarpet\":[{\"type\":\"contourcarpet\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"contour\":[{\"type\":\"contour\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"surface\":[{\"type\":\"surface\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"mesh3d\":[{\"type\":\"mesh3d\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"scatter\":[{\"fillpattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2},\"type\":\"scatter\"}],\"parcoords\":[{\"type\":\"parcoords\",\"line\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterpolargl\":[{\"type\":\"scatterpolargl\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"bar\":[{\"error_x\":{\"color\":\"#2a3f5f\"},\"error_y\":{\"color\":\"#2a3f5f\"},\"marker\":{\"line\":{\"color\":\"#E5ECF6\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"bar\"}],\"scattergeo\":[{\"type\":\"scattergeo\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterpolar\":[{\"type\":\"scatterpolar\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"histogram\":[{\"marker\":{\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"histogram\"}],\"scattergl\":[{\"type\":\"scattergl\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatter3d\":[{\"type\":\"scatter3d\",\"line\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}},\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattermapbox\":[{\"type\":\"scattermapbox\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterternary\":[{\"type\":\"scatterternary\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattercarpet\":[{\"type\":\"scattercarpet\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"carpet\":[{\"aaxis\":{\"endlinecolor\":\"#2a3f5f\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"minorgridcolor\":\"white\",\"startlinecolor\":\"#2a3f5f\"},\"baxis\":{\"endlinecolor\":\"#2a3f5f\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"minorgridcolor\":\"white\",\"startlinecolor\":\"#2a3f5f\"},\"type\":\"carpet\"}],\"table\":[{\"cells\":{\"fill\":{\"color\":\"#EBF0F8\"},\"line\":{\"color\":\"white\"}},\"header\":{\"fill\":{\"color\":\"#C8D4E3\"},\"line\":{\"color\":\"white\"}},\"type\":\"table\"}],\"barpolar\":[{\"marker\":{\"line\":{\"color\":\"#E5ECF6\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"barpolar\"}],\"pie\":[{\"automargin\":true,\"type\":\"pie\"}]},\"layout\":{\"autotypenumbers\":\"strict\",\"colorway\":[\"#636efa\",\"#EF553B\",\"#00cc96\",\"#ab63fa\",\"#FFA15A\",\"#19d3f3\",\"#FF6692\",\"#B6E880\",\"#FF97FF\",\"#FECB52\"],\"font\":{\"color\":\"#2a3f5f\"},\"hovermode\":\"closest\",\"hoverlabel\":{\"align\":\"left\"},\"paper_bgcolor\":\"white\",\"plot_bgcolor\":\"#E5ECF6\",\"polar\":{\"bgcolor\":\"#E5ECF6\",\"angularaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"radialaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"}},\"ternary\":{\"bgcolor\":\"#E5ECF6\",\"aaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"baxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"caxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"}},\"coloraxis\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}},\"colorscale\":{\"sequential\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"sequentialminus\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"diverging\":[[0,\"#8e0152\"],[0.1,\"#c51b7d\"],[0.2,\"#de77ae\"],[0.3,\"#f1b6da\"],[0.4,\"#fde0ef\"],[0.5,\"#f7f7f7\"],[0.6,\"#e6f5d0\"],[0.7,\"#b8e186\"],[0.8,\"#7fbc41\"],[0.9,\"#4d9221\"],[1,\"#276419\"]]},\"xaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\",\"title\":{\"standoff\":15},\"zerolinecolor\":\"white\",\"automargin\":true,\"zerolinewidth\":2},\"yaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\",\"title\":{\"standoff\":15},\"zerolinecolor\":\"white\",\"automargin\":true,\"zerolinewidth\":2},\"scene\":{\"xaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2},\"yaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2},\"zaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2}},\"shapedefaults\":{\"line\":{\"color\":\"#2a3f5f\"}},\"annotationdefaults\":{\"arrowcolor\":\"#2a3f5f\",\"arrowhead\":0,\"arrowwidth\":1},\"geo\":{\"bgcolor\":\"white\",\"landcolor\":\"#E5ECF6\",\"subunitcolor\":\"white\",\"showland\":true,\"showlakes\":true,\"lakecolor\":\"white\"},\"title\":{\"x\":0.05},\"mapbox\":{\"style\":\"light\"}}},\"xaxis\":{\"anchor\":\"y\",\"domain\":[0.0,1.0],\"title\":{\"text\":\"Date\"}},\"yaxis\":{\"anchor\":\"x\",\"domain\":[0.0,1.0],\"title\":{\"text\":\"Close\"}},\"legend\":{\"tracegroupgap\":0},\"margin\":{\"t\":60}}, {\"responsive\": true} ).then(function(){\n",
" \n",
"var gd = document.getElementById('001a7462-30bb-4c6a-a922-9f98a5db653e');\n",
"var x = new MutationObserver(function (mutations, observer) {{\n",
" var display = window.getComputedStyle(gd).display;\n",
" if (!display || display === 'none') {{\n",
" console.log([gd, 'removed!']);\n",
" Plotly.purge(gd);\n",
" observer.disconnect();\n",
" }}\n",
"}});\n",
"\n",
"// Listen for the removal of the full notebook cells\n",
"var notebookContainer = gd.closest('#notebook-container');\n",
"if (notebookContainer) {{\n",
" x.observe(notebookContainer, {childList: true});\n",
"}}\n",
"\n",
"// Listen for the clearing of the current output cell\n",
"var outputEl = gd.closest('.output');\n",
"if (outputEl) {{\n",
" x.observe(outputEl, {childList: true});\n",
"}}\n",
"\n",
" }) }; </script> </div>\n",
"</body>\n",
"</html>"
]
},
"metadata": {}
}
],
"source": [
"display_line_chart(data)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3IXlZT5WU4wZ"
},
"source": [
"### Candlestick Chart\n",
"A candlestick chart visually represents the stock's open, high, low, and close prices for each time period. It is commonly used in technical analysis to detect patterns and trends in market behavior."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "NaEFeOy8Vfjw"
},
"outputs": [],
"source": [
"def display_candlestick_chart(data):\n",
" candlestick_data = go.Candlestick(x=data.index,\n",
" open=data['Open'],\n",
" high=data['High'],\n",
" low=data['Low'],\n",
" close=data['Close'])\n",
" fig = go.Figure(data=[candlestick_data])\n",
" fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 542
},
"id": "bW5q69dUV0Vz",
"outputId": "6f19c68f-e35a-402f-fbf1-e8971720d15e"
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/html": [
"<html>\n",
"<head><meta charset=\"utf-8\" /></head>\n",
"<body>\n",
" <div> <script src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG\"></script><script type=\"text/javascript\">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: \"STIX-Web\"}});}</script> <script type=\"text/javascript\">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>\n",
" <script charset=\"utf-8\" src=\"https://cdn.plot.ly/plotly-2.24.1.min.js\"></script> <div id=\"145ef464-cb1a-4793-929c-d266e1676217\" class=\"plotly-graph-div\" style=\"height:525px; width:100%;\"></div> <script type=\"text/javascript\"> window.PLOTLYENV=window.PLOTLYENV || {}; if (document.getElementById(\"145ef464-cb1a-4793-929c-d266e1676217\")) { Plotly.newPlot( \"145ef464-cb1a-4793-929c-d266e1676217\", [{\"close\":[24698.85,24770.2,24811.5,24823.15,25010.6,25017.75,25052.35,25151.95,25235.9,25278.7,25279.85,25198.7,25145.1,24852.15,24936.4,25041.1,24918.45,25388.9,25356.5,25383.75,25418.55,25377.55,25415.8,25654.2],\"high\":[24734.3,24787.95,24867.35,24858.4,25043.8,25073.1,25129.6,25192.9,25268.35,25333.65,25321.7,25216.0,25275.45,25168.75,24957.5,25130.5,25113.7,25433.35,25430.5,25445.7,25441.65,25482.2,25611.95,25692.7],\"low\":[24607.2,24654.5,24784.45,24771.65,24874.7,24973.65,24964.65,24998.5,25199.4,25235.5,25235.8,25083.8,25127.75,24801.3,24753.15,24896.8,24885.15,24941.45,25292.45,25336.2,25352.25,25285.55,25376.05,25426.6],\"open\":[24648.9,24680.55,24863.4,24845.4,24906.1,25024.8,25030.8,25035.3,25249.7,25333.6,25313.4,25089.95,25250.5,25093.7,24823.4,24999.4,25034.0,25059.65,25430.45,25406.65,25416.9,25402.4,25487.05,25525.95],\"x\":[\"2024-08-20T00:00:00+05:30\",\"2024-08-21T00:00:00+05:30\",\"2024-08-22T00:00:00+05:30\",\"2024-08-23T00:00:00+05:30\",\"2024-08-26T00:00:00+05:30\",\"2024-08-27T00:00:00+05:30\",\"2024-08-28T00:00:00+05:30\",\"2024-08-29T00:00:00+05:30\",\"2024-08-30T00:00:00+05:30\",\"2024-09-02T00:00:00+05:30\",\"2024-09-03T00:00:00+05:30\",\"2024-09-04T00:00:00+05:30\",\"2024-09-05T00:00:00+05:30\",\"2024-09-06T00:00:00+05:30\",\"2024-09-09T00:00:00+05:30\",\"2024-09-10T00:00:00+05:30\",\"2024-09-11T00:00:00+05:30\",\"2024-09-12T00:00:00+05:30\",\"2024-09-13T00:00:00+05:30\",\"2024-09-16T00:00:00+05:30\",\"2024-09-17T00:00:00+05:30\",\"2024-09-18T00:00:00+05:30\",\"2024-09-19T00:00:00+05:30\",\"2024-09-20T00:00:00+05:30\"],\"type\":\"candlestick\"}], {\"template\":{\"data\":{\"histogram2dcontour\":[{\"type\":\"histogram2dcontour\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"choropleth\":[{\"type\":\"choropleth\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"histogram2d\":[{\"type\":\"histogram2d\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"heatmap\":[{\"type\":\"heatmap\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"heatmapgl\":[{\"type\":\"heatmapgl\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"contourcarpet\":[{\"type\":\"contourcarpet\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"contour\":[{\"type\":\"contour\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"surface\":[{\"type\":\"surface\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"mesh3d\":[{\"type\":\"mesh3d\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"scatter\":[{\"fillpattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2},\"type\":\"scatter\"}],\"parcoords\":[{\"type\":\"parcoords\",\"line\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterpolargl\":[{\"type\":\"scatterpolargl\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"bar\":[{\"error_x\":{\"color\":\"#2a3f5f\"},\"error_y\":{\"color\":\"#2a3f5f\"},\"marker\":{\"line\":{\"color\":\"#E5ECF6\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"bar\"}],\"scattergeo\":[{\"type\":\"scattergeo\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterpolar\":[{\"type\":\"scatterpolar\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"histogram\":[{\"marker\":{\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"histogram\"}],\"scattergl\":[{\"type\":\"scattergl\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatter3d\":[{\"type\":\"scatter3d\",\"line\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}},\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattermapbox\":[{\"type\":\"scattermapbox\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterternary\":[{\"type\":\"scatterternary\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattercarpet\":[{\"type\":\"scattercarpet\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"carpet\":[{\"aaxis\":{\"endlinecolor\":\"#2a3f5f\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"minorgridcolor\":\"white\",\"startlinecolor\":\"#2a3f5f\"},\"baxis\":{\"endlinecolor\":\"#2a3f5f\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"minorgridcolor\":\"white\",\"startlinecolor\":\"#2a3f5f\"},\"type\":\"carpet\"}],\"table\":[{\"cells\":{\"fill\":{\"color\":\"#EBF0F8\"},\"line\":{\"color\":\"white\"}},\"header\":{\"fill\":{\"color\":\"#C8D4E3\"},\"line\":{\"color\":\"white\"}},\"type\":\"table\"}],\"barpolar\":[{\"marker\":{\"line\":{\"color\":\"#E5ECF6\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"barpolar\"}],\"pie\":[{\"automargin\":true,\"type\":\"pie\"}]},\"layout\":{\"autotypenumbers\":\"strict\",\"colorway\":[\"#636efa\",\"#EF553B\",\"#00cc96\",\"#ab63fa\",\"#FFA15A\",\"#19d3f3\",\"#FF6692\",\"#B6E880\",\"#FF97FF\",\"#FECB52\"],\"font\":{\"color\":\"#2a3f5f\"},\"hovermode\":\"closest\",\"hoverlabel\":{\"align\":\"left\"},\"paper_bgcolor\":\"white\",\"plot_bgcolor\":\"#E5ECF6\",\"polar\":{\"bgcolor\":\"#E5ECF6\",\"angularaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"radialaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"}},\"ternary\":{\"bgcolor\":\"#E5ECF6\",\"aaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"baxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"caxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"}},\"coloraxis\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}},\"colorscale\":{\"sequential\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"sequentialminus\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"diverging\":[[0,\"#8e0152\"],[0.1,\"#c51b7d\"],[0.2,\"#de77ae\"],[0.3,\"#f1b6da\"],[0.4,\"#fde0ef\"],[0.5,\"#f7f7f7\"],[0.6,\"#e6f5d0\"],[0.7,\"#b8e186\"],[0.8,\"#7fbc41\"],[0.9,\"#4d9221\"],[1,\"#276419\"]]},\"xaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\",\"title\":{\"standoff\":15},\"zerolinecolor\":\"white\",\"automargin\":true,\"zerolinewidth\":2},\"yaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\",\"title\":{\"standoff\":15},\"zerolinecolor\":\"white\",\"automargin\":true,\"zerolinewidth\":2},\"scene\":{\"xaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2},\"yaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2},\"zaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2}},\"shapedefaults\":{\"line\":{\"color\":\"#2a3f5f\"}},\"annotationdefaults\":{\"arrowcolor\":\"#2a3f5f\",\"arrowhead\":0,\"arrowwidth\":1},\"geo\":{\"bgcolor\":\"white\",\"landcolor\":\"#E5ECF6\",\"subunitcolor\":\"white\",\"showland\":true,\"showlakes\":true,\"lakecolor\":\"white\"},\"title\":{\"x\":0.05},\"mapbox\":{\"style\":\"light\"}}}}, {\"responsive\": true} ).then(function(){\n",
" \n",
"var gd = document.getElementById('145ef464-cb1a-4793-929c-d266e1676217');\n",
"var x = new MutationObserver(function (mutations, observer) {{\n",
" var display = window.getComputedStyle(gd).display;\n",
" if (!display || display === 'none') {{\n",
" console.log([gd, 'removed!']);\n",
" Plotly.purge(gd);\n",
" observer.disconnect();\n",
" }}\n",
"}});\n",
"\n",
"// Listen for the removal of the full notebook cells\n",
"var notebookContainer = gd.closest('#notebook-container');\n",
"if (notebookContainer) {{\n",
" x.observe(notebookContainer, {childList: true});\n",
"}}\n",
"\n",
"// Listen for the clearing of the current output cell\n",
"var outputEl = gd.closest('.output');\n",
"if (outputEl) {{\n",
" x.observe(outputEl, {childList: true});\n",
"}}\n",
"\n",
" }) }; </script> </div>\n",
"</body>\n",
"</html>"
]
},
"metadata": {}
}
],
"source": [
"display_candlestick_chart(data)"
]
},
{
"cell_type": "markdown",
"source": [
"## Instantiating Cerebro\n",
"**Cerebro** is the cornerstone of backtrader because it serves as a central point for:\n",
"\n",
"- Gathering all inputs (Data Feeds),\n",
"- Gathering actors (Stratgegies),\n",
"- Gathering critics (Analyzers)\n",
"- Execute the backtesting/or live data feeding/trading\n",
"- Returning the results"
],
"metadata": {
"id": "eWhTtQvoESe2"
}
},
{
"cell_type": "markdown",
"metadata": {
"id": "fjlOA1D4I7vW"
},
"source": [
"## Understanding BackTrader Strategy"
]
},
{
"cell_type": "markdown",
"source": [
"### Base Strategy"
],
"metadata": {
"id": "ggTEJjunGXE_"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "xdL9oBTOxQJG"
},
"outputs": [],
"source": [
"class BaseStrategy(bt.Strategy):\n",
" def __init__(self):\n",
" super().__init__()\n",
" self.order = None\n",
"\n",
" def log(self, txt, dt=None):\n",
" \"\"\"Logging function for strategy\"\"\"\n",
" dt = dt or self.data.datetime.datetime(0)\n",
" print(f'{dt.isoformat()}: {txt}')\n",
"\n",
"\n",
" def notify_order(self, order):\n",
" if order.status in [order.Submitted, order.Accepted]:\n",
" # Buy/Sell order submitted/accepted to/by broker - Nothing to do\n",
" return\n",
"\n",
" # Check if an order has been completed\n",
" if order.status in [order.Completed]:\n",
" if order.isbuy():\n",
" self.log(f'BUY Executed, {order.executed.price:.2f}')\n",
" pass\n",
" elif order.issell():\n",
" self.log(f'SELL Executed, {order.executed.price:.2f}')\n",
" pass\n",
" self.bar_executed = len(self)\n",
"\n",
" elif order.status in [order.Canceled, order.Margin, order.Rejected]:\n",
" self.log(f'Order failed({order.getstatusname()})')\n",
"\n",
" # Write down: no pending order\n",
" self.order = None\n",
"\n",
" def notify_trade(self, trade):\n",
" if not trade.isclosed:\n",
" return\n",
" self.log(\"Trade Summary, gross: %.2f, net: %.2f, duration: %d\\n\" % (trade.pnl, trade.pnlcomm, trade.barlen))\n",
" # self.log(\"Trade Summary, gross: %.2f, duration: %d\\n\" % (trade.pnl, trade.barlen))"
]
},
{
"cell_type": "markdown",
"source": [
"### Log Close Price Strategy"
],
"metadata": {
"id": "1rJm0o-wGcfW"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "EnUExjm_zYIJ"
},
"outputs": [],
"source": [
"class LogCloseStrategy(BaseStrategy):\n",
" def next(self):\n",
" # Simply log the closing price\n",
" self.log('Close, %.2f' % self.data.close[0])"
]
},
{
"cell_type": "markdown",
"source": [
"### Simple Buy Strategy\n",
"\n",
"The Simple Buy Strategy buys a stock when its price falls for three consecutive sessions."
],
"metadata": {
"id": "S4JN6rs1Gf9Y"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "GH0jIvI34KnJ"
},
"outputs": [],
"source": [
"class SimpleBuyStrategy(BaseStrategy):\n",
" def next(self):\n",
" self.log('Close, %.2f' % self.data.close[0])\n",
"\n",
" if self.data.close[0] < self.data.close[-1]:\n",
" if self.data.close[-1] < self.data.close[-2]:\n",
" # price has been falling 3 sessions in a row\n",
" self.log('BUY Triggered, %.2f' % self.data.close[0])\n",
" self.buy()"
]
},
{
"cell_type": "markdown",
"source": [
"### Simple Trade Strategy\n",
"\n",
"The Simple Trade Strategy buys a stock when its price falls for three consecutive sessions and sells it after holding for n sessions."
],
"metadata": {
"id": "7-fsPYznGjNS"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "s5Mp9gmgAvMo"
},
"outputs": [],
"source": [
"class SimpleTradeStrategy(BaseStrategy):\n",
" params = (('exitbars', 5),)\n",
"\n",
" def next(self):\n",
" # self.log('Close, %.2f' % self.data.close[0])\n",
"\n",
" if not self.position:\n",
" if self.data.close[0] < self.data.close[-1]:\n",
" if self.data.close[-1] < self.data.close[-2]:\n",
" # price has been falling 3 sessions in a row\n",
" self.buy()\n",
" else:\n",
" if len(self) >= (self.bar_executed + self.params.exitbars):\n",
" self.sell()"
]
},
{
"cell_type": "code",
"source": [
"# Initialize Engine\n",
"cerebro = bt.Cerebro()\n",
"\n",
"# Initialize Broker Configs\n",
"cerebro.broker.setcash(100000.0) # Starting with 1 Lakh cash\n",
"cerebro.broker.setcommission(0.0003) # 0.03% Brokerage\n",
"\n",
"\n",
"# Feed Data\n",
"data0 = bt.feeds.PandasData(dataname=data, tz=pytz.timezone('Asia/Kolkata'))\n",
"cerebro.adddata(data0)\n",
"\n",
"# Add Strategy\n",
"cerebro.addstrategy(SimpleTradeStrategy, exitbars=3)\n",
"\n",
"# Add Trade Analyzer\n",
"cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name = \"ta\")\n",
"\n",
"# Backtest\n",
"print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())\n",
"result = cerebro.run()\n",
"print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "7rZQCcjulQl-",
"outputId": "5f9e5c18-ca7d-4e6c-f652-9a925d78579e"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Starting Portfolio Value: 100000.00\n",
"2024-04-04T00:00:00: BUY Executed, 22592.10\n",
"2024-04-10T00:00:00: SELL Executed, 22720.25\n",
"2024-04-10T00:00:00: Trade Summary, gross: 128.15, net: 114.56, duration: 4\n",
"\n",
"2024-04-16T00:00:00: BUY Executed, 22125.30\n",
"2024-04-23T00:00:00: SELL Executed, 22447.05\n",
"2024-04-23T00:00:00: Trade Summary, gross: 321.75, net: 308.38, duration: 4\n",
"\n",
"2024-05-07T00:00:00: BUY Executed, 22489.75\n",
"2024-05-13T00:00:00: SELL Executed, 22027.95\n",
"2024-05-13T00:00:00: Trade Summary, gross: -461.80, net: -475.16, duration: 4\n",
"\n",
"2024-05-28T00:00:00: BUY Executed, 22977.15\n",
"2024-06-03T00:00:00: SELL Executed, 23337.90\n",
"2024-06-03T00:00:00: Trade Summary, gross: 360.75, net: 346.86, duration: 4\n",
"\n",
"2024-07-12T00:00:00: BUY Executed, 24387.95\n",
"2024-07-19T00:00:00: SELL Executed, 24853.80\n",
"2024-07-19T00:00:00: Trade Summary, gross: 465.85, net: 451.08, duration: 4\n",
"\n",
"2024-07-23T00:00:00: BUY Executed, 24568.90\n",
"2024-07-29T00:00:00: SELL Executed, 24943.30\n",
"2024-07-29T00:00:00: Trade Summary, gross: 374.40, net: 359.55, duration: 4\n",
"\n",
"2024-08-06T00:00:00: BUY Executed, 24189.85\n",
"2024-08-12T00:00:00: SELL Executed, 24320.05\n",
"2024-08-12T00:00:00: Trade Summary, gross: 130.20, net: 115.65, duration: 4\n",
"\n",
"2024-08-14T00:00:00: BUY Executed, 24184.40\n",
"2024-08-21T00:00:00: SELL Executed, 24680.55\n",
"2024-08-21T00:00:00: Trade Summary, gross: 496.15, net: 481.49, duration: 4\n",
"\n",
"2024-09-06T00:00:00: BUY Executed, 25093.70\n",
"2024-09-12T00:00:00: SELL Executed, 25059.65\n",
"2024-09-12T00:00:00: Trade Summary, gross: -34.05, net: -49.10, duration: 4\n",
"\n",
"Final Portfolio Value: 101653.30\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "TR58tvBBM9YM"
},
"source": [
"## Analyze Trading Strategy"
]
},
{
"cell_type": "code",
"source": [
"def display_trade_analysis(stats):\n",
" \"\"\"\n",
" Displays a summary of trade analysis based on Backtrader's Analyzer results.\n",
"\n",
" This function prints out key statistics such as the number of open trades, closed trades, win/loss ratio,\n",
" longest winning/losing streak, and overall net profit/loss. It also calculates and displays the strike rate\n",
" (percentage of won trades out of total closed trades).\n",
"\n",
" :param stats: Backtrader's TradeAnalyzer object that holds trade performance metrics.\n",
" \"\"\"\n",
" try:\n",
" # Retrieve key metrics from the trade statistics\n",
" total_open = stats.total.open # Number of trades still open\n",
" total_closed = stats.total.closed # Number of trades that have been closed\n",
" total_won = stats.won.total # Total number of winning trades\n",
" total_lost = stats.lost.total # Total number of losing trades\n",
" win_streak = stats.streak.won.longest # Longest winning streak\n",
" lost_streak = stats.streak.lost.longest # Longest losing streak\n",
" pnl_net = round(stats.pnl.net.total, 2) # Net profit/loss, rounded to 2 decimal places\n",
"\n",
" # Calculate strike rate (percentage of closed trades that were winners)\n",
" strike_rate = round((total_won / total_closed) * 100, 2) if total_closed > 0 else 0\n",
"\n",
" # Define headers and values for display\n",
" headers_1 = [\"Total Open\", \"Total Closed\", \"Total Won\", \"Total Lost\"]\n",
" headers_2 = [\"Strike Rate(%)\", \"Win Streak\", \"Loss Streak\", \"Net PnL\"]\n",
" row_1 = [total_open, total_closed, total_won, total_lost]\n",
" row_2 = [strike_rate, win_streak, lost_streak, pnl_net]\n",
"\n",
" # Determine the longest header length for formatting consistency\n",
" max_header_length = max(len(headers_1), len(headers_2))\n",
"\n",
" # Prepare rows for display\n",
" rows_to_display = [headers_1, row_1, headers_2, row_2]\n",
"\n",
" # Define the formatting for the display (20 characters width for each column)\n",
" row_format = \"{:<20}\" * (max_header_length + 1)\n",
"\n",
" # Display the results\n",
" print(\"\\nTrade Analysis Results:\")\n",
" print(\"-\" * 100)\n",
" for row in rows_to_display:\n",
" print(row_format.format(\"\", *row))\n",
" print(\"-\" * 100)\n",
"\n",
" except (KeyError, AttributeError) as e:\n",
" # Handle cases where no trade data is available or stats fields are missing\n",
" print(\"No trade analysis available. It seems no trades have been taken or the stats are incomplete.\")\n",
" print(f\"Error details: {e}\")\n"
],
"metadata": {
"id": "OFIfnARgbZRu"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Display Analysis Result"
],
"metadata": {
"id": "bqytuoYJ_YvN"
}
},
{
"cell_type": "code",
"source": [
"# To print the full analysis (raw data)\n",
"# result[0].analyzers.ta.print()\n",
"\n",
"# To display a clean, summarized format of the analysis\n",
"display_trade_analysis(result[0].analyzers.ta.get_analysis())"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "m9bKgUVXgLIC",
"outputId": "fe5034c9-280f-4365-e0ce-53066eb820ae"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Trade Analysis Results:\n",
"----------------------------------------------------------------------------------------------------\n",
" Total Open Total Closed Total Won Total Lost \n",
" 0 5 1 4 \n",
" Strike Rate(%) Win Streak Loss Streak Net PnL \n",
" 20.0 1 3 176.1 \n",
"----------------------------------------------------------------------------------------------------\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4ubRbO0vSl3P"
},
"source": [
"## Implementing Practical Trade Strategies"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "zdoXWfPgJ4NI"
},
"source": [
"### Simple Moving Average Crossover Strategy\n",
"\n",
"Traders often use moving averages to identify trends and potential entry/exit points. A basic moving average strategy might involve buying when the price moves above a moving average and selling when it falls below\n",
"\n",
"\n",
"A moving average crossover system is an improvisation over the plain vanilla moving average system. Instead of the usual single moving average in a MA crossover system, the trader combines two moving averages.\n",
"\n",
"The entry and exit rules for the crossover system is as stated below:\n",
"\n",
"1. Buy (fresh long) when the short term moving averages turns greater than the long term moving average. Stay in the trade as long as this condition is satisfied\n",
"\n",
"2. Exit the long position (square off) when the short term moving average turns lesser than the longer-term moving average"
]
},
{
"cell_type": "code",
"source": [
"class SmaCross(BaseStrategy):\n",
" # list of parameters which are configurable for the strategy\n",
" params = dict(\n",
" pfast=10, # period for the fast moving average\n",
" pslow=30 # period for the slow moving average\n",
" )\n",
"\n",
" def __init__(self):\n",
" sma1 = bt.ind.SMA(period=self.params.pfast) # fast moving average\n",
" sma2 = bt.ind.SMA(period=self.params.pslow) # slow moving average\n",
" self.crossover = bt.ind.CrossOver(sma1, sma2) # crossover signal\n",
"\n",
" def next(self):\n",
" if not self.position: # not in the market\n",
" if self.crossover > 0: # if fast crosses slow to the upside\n",
" self.log(f'Buy : %.2f' % (self.data[0]))\n",
" self.buy() # enter long\n",
"\n",
" elif self.crossover < 0: # in the market & cross to the downside\n",
" self.log(f'Sell: %.2f' % (self.data[0]))\n",
" self.close() # close long position"
],
"metadata": {
"id": "uHy4VIiFiwGu"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Exponential Moving Average Crossover Strategy\n",
"\n",
"An exponential moving average (EMA) scales the data according to its newness. Recent data gets the maximum weightage, and the oldest gets the least weightage.\n",
"\n",
"In a crossover system, the price chart is overlaid with two EMAs. The shorter EMA is faster to react, while the longer EMA is slower to react.\n",
"\n",
"The outlook turns bullish when the faster EMA crosses and is above the slower EMA. Hence one should look at buying the stock. The trade lasts upto a point where the faster EMA starts going below, the slower EMA"
],
"metadata": {
"id": "vzBkSfLYqXSB"
}
},
{
"cell_type": "code",
"source": [
"class EmaCross(BaseStrategy):\n",
" # list of parameters which are configurable for the strategy\n",
" params = dict(\n",
" pfast=7, # period for the fast moving average\n",
" pslow=21 # period for the slow moving average\n",
" )\n",
"\n",
" def __init__(self):\n",
" ema1 = bt.ind.EMA(period=self.params.pfast) # fast moving average\n",
" ema2 = bt.ind.EMA(period=self.params.pslow) # slow moving average\n",
" self.crossover = bt.ind.CrossOver(ema1, ema2) # crossover signal\n",
"\n",
" def next(self):\n",
" if not self.position: # not in the market\n",
" if self.crossover > 0: # if fast crosses slow to the upside\n",
" self.log(f'Buy : %.2f' % (self.data[0]))\n",
" self.buy() # enter long\n",
"\n",
" elif self.crossover < 0: # in the market & cross to the downside\n",
" self.log(f'Sell: %.2f' % (self.data[0]))\n",
" self.close() # close long position"
],
"metadata": {
"id": "HokFASc5l9Lp"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Relative strength Index Strategy\n",
"Relative strength Index or just RSI is the most popular leading indicator, which gives out the strongest signals during the periods of sideways and non-trending ranges.\n",
"\n",
"RSI is a momentum oscillator which oscillates between 0 and 100 level.\n",
"\n",
"A value between 0 and 30 is considered oversold. Hence the trader should look at buying opportunities.\n",
"\n",
"A value between 70 and 100 is considered overbought. Hence the trader should look at selling opportunities."
],
"metadata": {
"id": "0ABbtK06tV_x"
}
},
{
"cell_type": "code",
"source": [
"class RSI(BaseStrategy):\n",
" # List of parameters which are configurable for the strategy\n",
" params = dict(\n",
" period=14, # Period for the RSI calculation\n",
" rsi_overbought=60, # Upper threshold for RSI (overbought)\n",
" rsi_oversold=40 # Lower threshold for RSI (oversold)\n",
" )\n",
"\n",
" def __init__(self):\n",
" # Initialize the RSI indicator\n",
" self.rsi = bt.ind.RSI(period=self.params.period)\n",
"\n",
" def next(self):\n",
" # self.log(self.rsi[0])\n",
" if not self.position:\n",
" if self.rsi[0] < self.params.rsi_oversold:\n",
" self.log(f'Buy : %.2f' % (self.data[0]))\n",
" self.buy() # enter long\n",
" elif self.rsi[0] > self.params.rsi_overbought:\n",
" self.log(f'Sell: %.2f' % (self.data[0]))\n",
" self.close() # close long position"
],
"metadata": {
"id": "yTUmFxYhtXnQ"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Strategy with Target and StopLoss"
],
"metadata": {
"id": "rmfflN80_nJk"
}
},
{
"cell_type": "markdown",
"source": [
"### MaruBozu Strategy\n",
"\n",
"Marubozu is a candlestick with no upper and lower shadow.\n",
"\n",
"- A bullish marubozu indicates bullishness.\n",
" - Buy around the closing price of a bullish marubozu\n",
" - Keep the low of the marubozu as the stoploss\n",
"- A bearish marubozu indicates bearishness.\n",
" - Sell around the closing price of a bearish marubozu\n",
" - Keep the high of the marubozu as the stoploss"
],
"metadata": {
"id": "qzIam04dKtT_"
}
},
{
"cell_type": "code",
"source": [
"class MarubozuStrategy(BaseStrategy):\n",
" params = dict(\n",
" rrr=2, # risk reward ratio\n",
" )\n",
"\n",
" def __init__(self):\n",
" super().__init__()\n",
" self.orefs = []\n",
"\n",
" # Marubozu indicator\n",
" self.marubozu = bt.talib.CDLMARUBOZU(self.data.open, self.data.high, self.data.low, self.data.close)\n",
"\n",
" def notify_order(self, order):\n",
" if not order.alive() and order.ref in self.orefs:\n",
" self.orefs.remove(order.ref)\n",
"\n",
" def next(self):\n",
" if self.orefs:\n",
" return # pending orders do nothing\n",
"\n",
" # Check if we are in the market\n",
" if not self.position:\n",
" # We are not in the market, look for a signal to OPEN trades\n",
" if self.marubozu[0] == 100:\n",
" # strong buy momentum, open long position\n",
" stoploss = self.data.low[0] # Low of marubozu will act as stoploss\n",
" target = (self.params.rrr * (self.data.close[0] - stoploss)) + self.data[0]\n",
" self.log(f'Long : %.2f, Target: %.2f, Stoploss: %.2f' % (self.data[0], target, stoploss))\n",
" os = self.buy_bracket(price=self.data[0], limitprice=target, stopprice=stoploss)\n",
" self.orefs = [o.ref for o in os]\n",
" elif self.marubozu[0] == -100:\n",
" # strong sell momentum, open short position\n",
" stoploss = self.data_high[0] # High of marubozu will act as stoploss\n",
" target = self.data[0] - (self.params.rrr * (stoploss - self.data[0]))\n",
" self.log(f'Short: %.2f, Target: %.2f, Stoploss: %.2f' % (self.data[0], target, stoploss))\n",
" os = self.sell_bracket(price=self.data[0], limitprice=target, stopprice=stoploss)\n",
" self.orefs = [o.ref for o in os]\n"
],
"metadata": {
"id": "mLIoBvHiLSOa"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Initialize Engine\n",
"cerebro = bt.Cerebro()\n",
"\n",
"# Initialize Broker Configs\n",
"cerebro.broker.setcash(1000000.0) # Starting with 1 Lakh cash\n",
"# cerebro.broker.setcommission(0.0003) # 0.03% Brokerage\n",
"\n",
"\n",
"# Feed Data\n",
"data0 = bt.feeds.PandasData(dataname=data, tz=pytz.timezone('Asia/Kolkata'))\n",
"cerebro.adddata(data0)\n",
"\n",
"# Add Strategy\n",
"cerebro.addstrategy(MarubozuStrategy)\n",
"\n",
"# Add Trade Analyzer\n",
"cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name = \"ta\")\n",
"\n",
"# Backtest\n",
"print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())\n",
"result = cerebro.run()\n",
"print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Mhl0aq0HJlct",
"outputId": "b5b64c56-9209-4118-cbdd-74e9b601891f"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Starting Portfolio Value: 1000000.00\n",
"2023-12-27T00:00:00: Long : 21654.75, Target: 21972.65, Stoploss: 21495.80\n",
"2024-01-08T00:00:00: Trade Summary, gross: -158.95, net: -158.95, duration: 4\n",
"\n",
"2024-02-27T00:00:00: Long : 22198.35, Target: 22423.75, Stoploss: 22085.65\n",
"2024-02-29T00:00:00: Trade Summary, gross: -263.15, net: -263.15, duration: 1\n",
"\n",
"2024-03-01T00:00:00: Long : 22338.75, Target: 22920.75, Stoploss: 22047.75\n",
"2024-03-13T00:00:00: Trade Summary, gross: -291.00, net: -291.00, duration: 5\n",
"\n",
"2024-06-07T00:00:00: Long : 23290.15, Target: 24292.35, Stoploss: 22789.05\n",
"2024-07-03T00:00:00: Trade Summary, gross: 1002.20, net: 1002.20, duration: 16\n",
"\n",
"2024-07-25T00:00:00: Long : 24406.10, Target: 24796.70, Stoploss: 24210.80\n",
"2024-08-06T00:00:00: Trade Summary, gross: -113.00, net: -113.00, duration: 1\n",
"\n",
"Final Portfolio Value: 1000176.10\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "YnuChnJ0EWPw"
},
"source": [
"## Appendix"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "bCcsDNgvEZSH"
},
"source": [
"### Logging DateTime with Timezone Support in Backtrader\n",
"When analyzing intraday trades, having accurate timestamps with timezone support can be crucial for understanding when trades occur in relation to market hours. Backtrader supports timezone handling, allowing you to log the exact datetime of each event during strategy execution.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Rv66efiZEXbV"
},
"outputs": [],
"source": [
"import pytz\n",
"\n",
"class BaseStrategy(bt.Strategy):\n",
"\n",
" def log(self, txt, dt=None):\n",
" \"\"\"Logging function for strategy\"\"\"\n",
" dt = dt or self.data.datetime.datetime(0)\n",
" print(f'{dt.isoformat()}: {txt}')\n",
"\n",
"# When feeding your data into Backtrader,\n",
"# you can specify the timezone using the tz parameter.\n",
"data0 = bt.feeds.PandasData(dataname=data, tz=pytz.timezone('Asia/Kolkata'))"
]
},
{
"cell_type": "markdown",
"source": [
"### Simulating Broker Commision\n",
"\n",
"Within the regular cerebro creation/set-up process, just add a call to setcommission over the broker member attribute."
],
"metadata": {
"id": "MYRxmetf1CFW"
}
},
{
"cell_type": "code",
"source": [
"# cerebro.broker.setcommission(0.0003) # 0.03% Brokerage"
],
"metadata": {
"id": "WIGsZTLB4bMo"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"To Log the commision related info use following:"
],
"metadata": {
"id": "yx4wKG5Z470a"
}
},
{
"cell_type": "code",
"source": [
"# self.log(\"Trade Summary, gross: %.2f, net: %.2f, duration: %d\\n\" % (trade.pnl, trade.pnlcomm, trade.barlen))\n",
"# self.log(\"BUY EXECUTED, price: %.2f, commision: %.2f\" % (order.executed.price, order.executed.comm))"
],
"metadata": {
"id": "n66ZAWjl47Lh"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Customize Order Size"
],
"metadata": {
"id": "snJIdpSC1Pb0"
}
},
{
"cell_type": "code",
"source": [
"# cerebro.addsizer(bt.sizers.AllInSizerInt)\n",
"# cerebro.addsizer(bt.sizers.FixedSize, stake=10)"
],
"metadata": {
"id": "HsuYdiJX1Gb1"
},
"execution_count": null,
"outputs": []
}
],
"metadata": {
"colab": {
"toc_visible": true,
"provenance": [],
"authorship_tag": "ABX9TyMIaGiK8z4u/C4NSafnXKaz",
"include_colab_link": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment