{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8ca207cc",
   "metadata": {},
   "source": [
    "<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "246d6494",
   "metadata": {},
   "source": [
    "This notebook demonstrates the use of VectorBT PRO for backtesting and analyzing quantitative trading strategies focusing on chart patterns. It provides tools for data acquisition, signal generation, portfolio optimization, and strategy simulation. The notebook includes a pattern detector optimized with Numba to scan data for specified patterns. We use hourly price data from TradingView to detect and backtest various bullish and bearish patterns. Practical applications include testing trading strategies, understanding historical market behavior, and optimizing algorithmic trading setups."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c991ec37",
   "metadata": {},
   "outputs": [],
   "source": [
    "import vectorbtpro as vbt\n",
    "import pandas as pd\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6dd037f",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.settings.set_theme(\"dark\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de1c5df4",
   "metadata": {},
   "source": [
    "Define the symbols for which we will pull data from TradingView."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "763d7811",
   "metadata": {},
   "outputs": [],
   "source": [
    "symbols = [\n",
    "    \"NASDAQ:META\",\n",
    "    \"NASDAQ:AMZN\",\n",
    "    \"NASDAQ:AAPL\",\n",
    "    \"NASDAQ:NFLX\",\n",
    "    \"NASDAQ:GOOG\",\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e383eb4",
   "metadata": {},
   "source": [
    "Pull hourly price data for the defined symbols from TradingView."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a427c9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "data = vbt.TVData.pull(symbols, timeframe=\"hourly\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47cb95de",
   "metadata": {},
   "source": [
    "Define the date range for the analysis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0cdb0559",
   "metadata": {},
   "outputs": [],
   "source": [
    "start_date = \"2020\"\n",
    "end_date = None"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "613d08bf",
   "metadata": {},
   "source": [
    "Slice the data to include only the specified date range."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d9dd962",
   "metadata": {},
   "outputs": [],
   "source": [
    "data = data.xloc[start_date:end_date]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc590aba",
   "metadata": {},
   "source": [
    "Display statistics of the data to ensure it spans the correct date period and is free of NaN values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2b713a7e",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(data.stats())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55636e91",
   "metadata": {},
   "source": [
    "Calculate the HLC3 (High-Low-Close average) as the chosen feature for pattern detection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32b66020",
   "metadata": {},
   "outputs": [],
   "source": [
    "price = data.hlc3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ff519a4",
   "metadata": {},
   "source": [
    "Define bullish and bearish chart patterns as numerical sequences."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58bd8102",
   "metadata": {},
   "outputs": [],
   "source": [
    "bullish_patterns = {\n",
    "    \"double_bottom\": [5, 1, 3, 1, 5],\n",
    "    \"exp_triangle\": [3, 4, 2, 5, 1, 6],\n",
    "    \"asc_triangle\": [1, 5, 2, 5, 3, 6],\n",
    "    \"symm_triangle\": [1, 6, 2, 5, 3, 6],\n",
    "    \"pennant\": [6, 1, 5, 2, 4, 3, 6]\n",
    "}\n",
    "bearish_patterns = {\n",
    "    \"head_and_shoulders\": [1, 4, 2, 6, 2, 4, 1],\n",
    "    \"double_top\": [1, 5, 3, 5, 1],\n",
    "    \"desc_triangle\": [6, 2, 5, 2, 4, 1],\n",
    "    \"symm_triangle\": [6, 1, 5, 2, 4, 1],\n",
    "    \"pennant\": [1, 6, 2, 5, 3, 4, 1]\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01fb8f75",
   "metadata": {},
   "source": [
    "Plot the numerical sequence of the \"double_bottom\" pattern for visual confirmation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "62a45611",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.Series(bullish_patterns[\"double_bottom\"]).vbt.plot().show_png()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1fa10c11",
   "metadata": {},
   "source": [
    "Define the minimum and maximum window lengths for pattern detection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "015b440c",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "min_window = 24\n",
    "max_window = 24 * 30"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3da2640b",
   "metadata": {},
   "source": [
    "Define a function to detect patterns in the price data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "93f4bb63",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "def detect_patterns(patterns):\n",
    "    \"\"\"Detect patterns in price data.\n",
    "\n",
    "    This function searches for specified patterns in the price data within defined windows.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    patterns : dict\n",
    "        Dictionary of patterns to detect.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    vbt.PatternRanges\n",
    "        Detected pattern ranges.\n",
    "    \"\"\"\n",
    "    \n",
    "    return vbt.PatternRanges.from_pattern_search(\n",
    "        price,\n",
    "        open=data.open,\n",
    "        high=data.high,\n",
    "        low=data.low,\n",
    "        close=data.close,\n",
    "        pattern=patterns,\n",
    "        window=min_window,\n",
    "        max_window=max_window,\n",
    "        execute_kwargs=dict(\n",
    "            engine=\"threadpool\",\n",
    "            chunk_len=\"auto\",\n",
    "            show_progress=True\n",
    "        )\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "042b70e5",
   "metadata": {},
   "source": [
    "Detect bullish and bearish patterns in the price data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "61345014",
   "metadata": {},
   "outputs": [],
   "source": [
    "bullish_matches = detect_patterns(\n",
    "    vbt.Param(\n",
    "        bullish_patterns,\n",
    "        name=\"bullish_pattern\"\n",
    "    )\n",
    ")\n",
    "bearish_matches = detect_patterns(\n",
    "    vbt.Param(\n",
    "        bearish_patterns,\n",
    "        name=\"bearish_pattern\"\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a551859b",
   "metadata": {},
   "source": [
    "Print the number of matches for each bullish pattern and dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38713797",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(bullish_matches.count())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c204331e",
   "metadata": {},
   "source": [
    "Plot the pattern and dataset with the most matches."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7dc6ae58",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.settings.plotting.auto_rangebreaks = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a31237e",
   "metadata": {},
   "outputs": [],
   "source": [
    "display_column = bullish_matches.count().idxmax()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72eaec81",
   "metadata": {},
   "outputs": [],
   "source": [
    "bullish_matches.plot(column=display_column, fit_ranges=True).show_png()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7f111fc",
   "metadata": {},
   "source": [
    "Zoom in on a specific match within the dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9d0ab460",
   "metadata": {},
   "outputs": [],
   "source": [
    "display_match = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3080ce4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "bullish_matches.plot(\n",
    "    column=display_column,\n",
    "    fit_ranges=display_match\n",
    ").show_png()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "459d9343",
   "metadata": {},
   "source": [
    "Convert detected patterns into entry and exit signals for backtesting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34771aa0",
   "metadata": {},
   "outputs": [],
   "source": [
    "entries = bullish_matches.last_pd_mask\n",
    "exits = bearish_matches.last_pd_mask"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89be1882",
   "metadata": {},
   "source": [
    "Generate a Cartesian product of bullish and bearish patterns for systematic testing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b51429ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "entries, exits = entries.vbt.x(exits)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b24cb4af",
   "metadata": {},
   "source": [
    "Print the columns representing individual backtests."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a80e5a62",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(entries.columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f39d343",
   "metadata": {},
   "source": [
    "Simulate a portfolio using the generated signals."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d278d770",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = vbt.Portfolio.from_signals(data, entries, exits)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a7c5f04",
   "metadata": {},
   "source": [
    "Calculate and print the mean total return for each combination of bullish and bearish patterns."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50369ac9",
   "metadata": {},
   "outputs": [],
   "source": [
    "mean_total_return = pf.total_return.groupby(\n",
    "    [\"bullish_pattern\", \"bearish_pattern\"]\n",
    ").mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02089a5a",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(mean_total_return)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "294cf7dc",
   "metadata": {},
   "source": [
    "Visualize the mean total return as a heatmap."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ea171583",
   "metadata": {},
   "outputs": [],
   "source": [
    "mean_total_return.vbt.heatmap(\n",
    "    x_level=\"bearish_pattern\",\n",
    "    y_level=\"bullish_pattern\"\n",
    ").show_png()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9350f03e",
   "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
}
