{ "cells": [ { "cell_type": "markdown", "id": "a7f13baa-bf3a-41e2-b4f4-bef957746b6a", "metadata": {}, "source": [ "# How to backtest chart patterns with VectorBT PRO" ] }, { "cell_type": "markdown", "id": "1e5237a6-cb1e-42b2-8b74-841af2e8859a", "metadata": {}, "source": [ "VectorBT PRO (https://vectorbt.pro/) is a proprietary Python package designed for backtesting and analyzing quantitative trading strategies. It provides a comprehensive suite of tools for every stage of an algorithmic trading workflow, including data acquisition, signal generation and analysis, portfolio optimization, strategy simulation, hyperparameter tuning, and cross-validation. These modular components empower users to flexibly customize their analysis, setting it apart from monolithic backtesting frameworks." ] }, { "cell_type": "markdown", "id": "51ad2b2b-3ffa-4600-9f03-547f83d8babb", "metadata": {}, "source": [ "One of these components is a data pattern detector that efficiently scans data using variable-length windows, assessing their similarity to a specified pattern. This process, optimized with Numba (https://numba.pydata.org/), operates on any hardware without the need for machine learning. To showcase the detector's capabilities, we will conduct backtesting on a range of patterns and their combinations on a single dataset." ] }, { "cell_type": "markdown", "id": "36f9e6a9-eedf-4595-b214-2d00f02d9c90", "metadata": {}, "source": [ "## Imports and set up" ] }, { "cell_type": "markdown", "id": "33459b0c-c21f-4251-b13b-6492c9171f6c", "metadata": {}, "source": [ "Due to VectorBT PRO's self-contained design, only minimal imports are necessary." ] }, { "cell_type": "code", "execution_count": null, "id": "bc8d53d7-0290-4e6c-b760-6c9ba8a6873e", "metadata": {}, "outputs": [], "source": [ "from vectorbtpro import *\n", "# whats_imported()\n", "\n", "vbt.settings.set_theme(\"dark\")" ] }, { "cell_type": "markdown", "id": "0aec0980-6ee2-41b1-a713-4a062a823fe5", "metadata": {}, "source": [ "VectorBT PRO features built-in data downloading from sources such as Yahoo Finance, Alpaca, Polygon, TradingView, and many more. We will perform pattern detection on hourly price data pulled from TradingView." ] }, { "cell_type": "code", "execution_count": null, "id": "d12bcb35-95ba-424e-8dfc-0e9edff8df99", "metadata": {}, "outputs": [], "source": [ "symbols = [\n", " \"NASDAQ:META\",\n", " \"NASDAQ:AMZN\",\n", " \"NASDAQ:AAPL\",\n", " \"NASDAQ:NFLX\",\n", " \"NASDAQ:GOOG\",\n", "]\n", "\n", "data = vbt.TVData.pull(symbols, timeframe=\"hourly\")" ] }, { "cell_type": "markdown", "id": "77e48d78-436d-4a52-95d4-8ff8c1e8ff4c", "metadata": {}, "source": [ "TradingView does not offer the option to specify a date range in advance, so we will need to select it afterward." ] }, { "cell_type": "code", "execution_count": null, "id": "b96e37aa-3c00-4373-8030-ca3d97f872b1", "metadata": {}, "outputs": [], "source": [ "start_date = \"2020\"\n", "end_date = None\n", "\n", "data = data.xloc[start_date:end_date]" ] }, { "cell_type": "markdown", "id": "3c9c8009-3a78-4799-bc98-2bd191e22851", "metadata": {}, "source": [ "Ensure that our data spans the correct date period and is free of NaN values." ] }, { "cell_type": "code", "execution_count": null, "id": "76b530eb-f42e-4bdf-b270-20298a66eb6b", "metadata": {}, "outputs": [], "source": [ "print(data.stats())" ] }, { "cell_type": "markdown", "id": "4cf31468-ce25-4284-b0c6-dec873e62268", "metadata": {}, "source": [ "As pattern detection requires only a single time series, we must choose the suitable feature. We'll utilize HLC/3, which effectively captures price fluctuations." ] }, { "cell_type": "code", "execution_count": null, "id": "650c3662-684c-4e57-b7fa-45ba8b2f7f1d", "metadata": {}, "outputs": [], "source": [ "price = data.hlc3" ] }, { "cell_type": "markdown", "id": "74dcad43-dd64-435a-9a9d-591681514209", "metadata": {}, "source": [ "## Define patterns" ] }, { "cell_type": "markdown", "id": "f387c42a-1224-46d9-9397-b6479e6e21e7", "metadata": {}, "source": [ "Numerous chart patterns can be translated into numerical sequences, like the \"Double Top\" pattern (https://www.investopedia.com/terms/d/doubletop.asp) represented as [1, 3, 2, 3, 1]. It's important to note that while the numbers themselves can be arbitrary, their relative spacing should mirror the relative distance between the pattern's chart points. For instance, in this sequence, 2 aligns with the midpoint between valley point 1 and peak point 3. The same principle applies to temporal distribution: points should be equidistant from one another." ] }, { "cell_type": "code", "execution_count": null, "id": "40827a2a-ee12-4feb-9f6a-4505ed24060d", "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": "af76a114-d588-443a-8c62-19274c97c416", "metadata": {}, "source": [ "Confirm the visual representation of a pattern by plotting its corresponding line graph." ] }, { "cell_type": "code", "execution_count": null, "id": "0114e669-fff0-48b1-922b-412ad6941914", "metadata": {}, "outputs": [], "source": [ "pd.Series(bullish_patterns[\"double_bottom\"]).vbt.plot().show_svg()" ] }, { "cell_type": "markdown", "id": "11172c01-2675-4c12-ab51-ae21137c097a", "metadata": {}, "source": [ "Each generated sequence serves as a rough approximation of the desired chart pattern, and there's no need for precise adjustments: VectorBT PRO's similarity-based algorithm is flexible and can identify patterns, even if they are not perfectly consistent in their design." ] }, { "cell_type": "markdown", "id": "4292665d-4168-436a-a59d-94b42bfd9482", "metadata": {}, "source": [ "## Detect patterns in data" ] }, { "cell_type": "markdown", "id": "0a355587-347a-4f4f-9f7a-fa041127f36a", "metadata": {}, "source": [ "Iterate through each pattern, dataset, and timestamp within the dataset. Search for matches within windows spanning from 1 to 30 days, and create a record for each match that exceeds a pre-defined minimum similarity score, which is set by default to 85%." ] }, { "cell_type": "code", "execution_count": null, "id": "d1a9af95-b7d1-4f29-9a6b-b40d57e5f597", "metadata": {}, "outputs": [], "source": [ "min_window = 24\n", "max_window = 24 * 30\n", "\n", "def detect_patterns(patterns):\n", " return vbt.PatternRanges.from_pattern_search(\n", " price,\n", " open=data.open, # OHLC for plotting\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( # multithreading\n", " engine=\"threadpool\", \n", " chunk_len=\"auto\", \n", " )\n", " )\n", "\n", "bullish_matches = detect_patterns(vbt.Param(bullish_patterns, name=\"bullish_pattern\"))\n", "bearish_matches = detect_patterns(vbt.Param(bearish_patterns, name=\"bearish_pattern\"))" ] }, { "cell_type": "markdown", "id": "12733006-548c-4c28-a4ac-902aa066f0b3", "metadata": {}, "source": [ "In just several minutes, VectorBT PRO seamlessly detected matches among all patterns. This process, involving around 230 million unique pattern and window combinations, was executed in parallel." ] }, { "cell_type": "markdown", "id": "714ddd1f-f5a5-420e-9d4d-707e4b5e4685", "metadata": {}, "source": [ "Get the number of matches for each pattern and dataset." ] }, { "cell_type": "code", "execution_count": null, "id": "77c5957e-a906-4c0c-998a-5b2e92fd652d", "metadata": {}, "outputs": [], "source": [ "print(bullish_matches.count())" ] }, { "cell_type": "markdown", "id": "88b7627b-f48c-4d51-986e-cc269abf9604", "metadata": {}, "source": [ "Plot the pattern and dataset with the most matches." ] }, { "cell_type": "code", "execution_count": null, "id": "708b0d74-8c5e-4921-87e2-4704050ce7ed", "metadata": {}, "outputs": [], "source": [ "vbt.settings.plotting.auto_rangebreaks = True # for stocks\n", "\n", "display_column = bullish_matches.count().idxmax()\n", "\n", "bullish_matches.plot(column=display_column, fit_ranges=True).show_svg()" ] }, { "cell_type": "markdown", "id": "af6a60cf-0d98-49e5-ad48-cc872f6d2ce9", "metadata": {}, "source": [ "Zoom in on a match." ] }, { "cell_type": "code", "execution_count": null, "id": "f22c6c82-cc7c-4d5d-94b9-e14753e82072", "metadata": {}, "outputs": [], "source": [ "display_match = 3\n", "\n", "bullish_matches.plot(column=display_column, fit_ranges=display_match).show_svg()" ] }, { "cell_type": "markdown", "id": "45f49c51-bd6f-4952-8ff4-76f6ebc00f7f", "metadata": {}, "source": [ "The window data closely aligns with the pattern. This functionality is highly comprehensive, offering the flexibility to adjust fitness levels, modify rescaling and interpolation algorithms, and more to suit specific requirements." ] }, { "cell_type": "markdown", "id": "16779944-3cae-44e8-a63d-36194479217c", "metadata": {}, "source": [ "## Transform matches to signals" ] }, { "cell_type": "markdown", "id": "26c9e03d-95ff-44a3-bd56-2a581673aa27", "metadata": {}, "source": [ "To conduct backtesting on the identified patterns, we will convert them into signals, triggering a signal once a pattern has fully developed." ] }, { "cell_type": "code", "execution_count": null, "id": "ce91720c-dd56-496e-aadb-faad71e1a529", "metadata": {}, "outputs": [], "source": [ "entries = bullish_matches.last_pd_mask\n", "exits = bearish_matches.last_pd_mask" ] }, { "cell_type": "markdown", "id": "d049c224-03b7-42fa-8927-51a502812e54", "metadata": {}, "source": [ "Generate a Cartesian product of bullish and bearish patterns to systematically test each bullish pattern against each bearish pattern." ] }, { "cell_type": "code", "execution_count": null, "id": "a5f0a9db-632d-4705-af3f-1c33dfb6f884", "metadata": {}, "outputs": [], "source": [ "entries, exits = entries.vbt.x(exits)" ] }, { "cell_type": "markdown", "id": "90a043fe-c990-4358-94be-b8f4b92dec4f", "metadata": {}, "source": [ "Both arrays have been converted into equally-shaped DataFrames, each comprising 125 columns. Each column represents an individual backtest, encompassing three parameters: bullish pattern, bearish pattern, and symbol." ] }, { "cell_type": "code", "execution_count": null, "id": "8b3a1466-246b-42b5-a61d-a0ae1137c54d", "metadata": {}, "outputs": [], "source": [ "print(entries.columns)" ] }, { "cell_type": "markdown", "id": "05669332-15a4-4ac5-b376-bdc08006d952", "metadata": {}, "source": [ "## Backtest signals" ] }, { "cell_type": "markdown", "id": "a44e90d0-f172-445a-9f4b-865444ae0cb3", "metadata": {}, "source": [ "Establish a portfolio by simulating signals." ] }, { "cell_type": "code", "execution_count": null, "id": "5d3ec70d-73e4-407d-8ea7-2a6b0f4436ba", "metadata": {}, "outputs": [], "source": [ "pf = vbt.Portfolio.from_signals(data, entries, exits)" ] }, { "cell_type": "markdown", "id": "ff7d821d-f20e-45c3-83d5-ad1aa2ba109b", "metadata": {}, "source": [ "Get the mean total return for every combination of bullish and bearish patterns." ] }, { "cell_type": "code", "execution_count": null, "id": "ad8b789d-c543-4e42-8df7-1a351cceda5f", "metadata": {}, "outputs": [], "source": [ "mean_total_return = pf.total_return.groupby([\"bullish_pattern\", \"bearish_pattern\"]).mean()\n", "\n", "print(mean_total_return)" ] }, { "cell_type": "markdown", "id": "946aa00a-b183-496e-8a63-7f11485ad3dc", "metadata": {}, "source": [ "As visual beings, let's represent these values as a heatmap." ] }, { "cell_type": "code", "execution_count": null, "id": "d74ac965-0461-4563-813e-56b9cce979c8", "metadata": {}, "outputs": [], "source": [ "mean_total_return.vbt.heatmap(x_level=\"bearish_pattern\", y_level=\"bullish_pattern\").show_svg()" ] }, { "cell_type": "markdown", "id": "5d9f9706-d3da-480e-8b65-5eaa47196049", "metadata": {}, "source": [ "Although the displayed performance of each pattern combination does not guarantee future results, it provides insight into how the market responded to pattern events in the past. For instance, it's noteworthy that the \"Bearish Symmetrical Triangle\" exhibited a notably bullish trend. Cross-validation and robustness testing are next essential steps for a comprehensive assessment." ] }, { "cell_type": "markdown", "id": "2b5b8516-8620-41aa-a11c-96b48798c343", "metadata": {}, "source": [ "Read more at https://vectorbt.pro/tutorials/patterns-and-projections/" ] }, { "cell_type": "code", "execution_count": null, "id": "9efd6597-880f-4769-a486-65e17b1c5475", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.8" } }, "nbformat": 4, "nbformat_minor": 5 }