├── .github
├── FUNDING.yml
├── issue_template.md
├── workflows
│ ├── deploy-docs.yml
│ └── ci.yml
└── deploy-gh-pages.sh
├── doc
├── logo.png
├── scripts
│ ├── strip_yaml.awk
│ ├── ipython_config.py
│ └── logo.py
├── pdoc_template
│ ├── logo.mako
│ ├── credits.mako
│ ├── config.mako
│ └── head.mako
├── README.md
├── examples
│ ├── Strategies Library.py
│ ├── Multiple Time Frames.py
│ ├── Parameter Heatmap & Optimization.py
│ ├── Trading with Machine Learning.py
│ └── Quick Start User Guide.py
├── build.sh
└── alternatives.md
├── requirements.txt
├── MANIFEST.in
├── .flake8
├── .gitignore
├── backtesting
├── test
│ ├── __main__.py
│ ├── __init__.py
│ └── _test.py
├── autoscale_cb.js
├── __init__.py
├── _util.py
├── lib.py
└── _plotting.py
├── .codecov.yml
├── setup.cfg
├── CONTRIBUTING.md
├── CHANGELOG.md
├── README.md
├── setup.py
└── LICENSE.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: kernc
2 |
--------------------------------------------------------------------------------
/doc/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndersonJo/backtesting.py/master/doc/logo.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # To run example notebooks, install required and test dependencies
2 | .[test]
3 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | exclude MANIFEST.in
2 | exclude .*
3 |
4 | recursive-exclude .* *
5 | recursive-exclude doc *
6 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 100
3 | exclude =
4 | .git,
5 | __pycache__,
6 | doc/examples
7 |
--------------------------------------------------------------------------------
/doc/scripts/strip_yaml.awk:
--------------------------------------------------------------------------------
1 | #!/usr/bin/awk -f
2 |
3 | # Remove YAML front matter from jupytext-converted .py notebooks
4 |
5 | BEGIN { drop = 0; }
6 | /^# ---$/ { if (NR <= 3) { drop = 1 } else { drop = 0; next } }
7 | drop == 0 { print }
8 |
--------------------------------------------------------------------------------
/doc/pdoc_template/logo.mako:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 | *.html
3 | *.png
4 | _version.py
5 |
6 | *.egg-info
7 | .eggs/*
8 | __pycache__/*
9 | dist/*
10 |
11 | .coverage
12 | .coverage.*
13 | htmlcov/*
14 |
15 | doc/build/*
16 |
17 | .idea/*
18 | .vscode/
19 |
20 | **/.ipynb_checkpoints
21 | *~*
22 |
23 | .venv/
24 |
--------------------------------------------------------------------------------
/backtesting/test/__main__.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import unittest
3 |
4 | suite = unittest.defaultTestLoader.discover('backtesting.test',
5 | pattern='_test*.py')
6 | if __name__ == '__main__':
7 | result = unittest.TextTestRunner(verbosity=2).run(suite)
8 | sys.exit(not result.wasSuccessful())
9 |
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | comment: off
2 | coverage:
3 | range: 75..95
4 | precision: 0
5 | status:
6 | patch:
7 | default:
8 | target: 90
9 | project:
10 | default:
11 | target: auto
12 | threshold: 5
13 | # Fix for https://github.com/codecov/codecov-python/issues/136
14 | fixes:
15 | - "__init__.py::backtesting/__init__.py"
16 |
--------------------------------------------------------------------------------
/doc/scripts/ipython_config.py:
--------------------------------------------------------------------------------
1 | # In build.sh, this file is copied into (and removed from)
2 | # ~/.ipython/profile_default/startup/
3 |
4 | import pandas as pd
5 | pd.set_option("display.max_rows", 30)
6 | # This an alternative to setting display.preceision=2,
7 | # which doesn't work well for our dtype=object Series.
8 | pd.set_option('display.float_format', '{:.2f}'.format)
9 | del pd
10 |
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 | Backtesting.py Documentation
2 | ============================
3 | After installing documentation dependencies:
4 |
5 | pip install .[doc,test]
6 |
7 | build HTML documentation by running:
8 |
9 | ./build.sh
10 |
11 | When submitting pull requests that change example notebooks,
12 | commit example _.py_ files too
13 | (`build.sh` should tell you how to make them).
14 |
--------------------------------------------------------------------------------
/doc/pdoc_template/credits.mako:
--------------------------------------------------------------------------------
1 | <%!
2 | from backtesting import __version__
3 | %>
4 |
5 | backtesting ${__version__}
6 | 卐
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 100
3 |
4 | [mypy]
5 | warn_unused_ignores = True
6 | warn_redundant_casts = True
7 | ignore_missing_imports = True
8 |
9 | [coverage:run]
10 | parallel = 1
11 | concurrency =
12 | multiprocessing
13 | source =
14 | backtesting
15 | doc/examples
16 | omit =
17 |
18 | [coverage:report]
19 | exclude_lines =
20 | ^\s*continue\b
21 | ^\s*return\b
22 | ^\s*raise\b
23 | ^\s*except\b
24 | ^\s*warnings\.warn\(
25 | ^\s*warn\(
26 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | ### Expected Behavior
2 |
3 |
4 | ### Actual Behavior
5 |
6 |
9 |
10 |
11 | ### Steps to Reproduce
12 |
13 |
15 |
16 | 1.
17 | 2.
18 | 3.
19 |
20 | ### Additional info
21 |
22 |
23 |
24 | - Backtesting version: 0.?.?
25 |
--------------------------------------------------------------------------------
/doc/pdoc_template/config.mako:
--------------------------------------------------------------------------------
1 | <%!
2 | html_lang = 'en'
3 | show_inherited_members = False
4 | extract_module_toc_into_sidebar = True
5 | list_class_variables_in_index = True
6 | sort_identifiers = True
7 | show_type_annotations = False
8 | show_source_code = False
9 | google_search_query = '''
10 | inurl:kernc.github.io/backtesting.py
11 | inurl:github.com/kernc/backtesting.py
12 | '''
13 |
14 |
15 | from pdoc.html_helpers import glimpse as _glimpse
16 |
17 | # Make visible the code block from the first paragraph of the
18 | # `backtesting.backtesting` module
19 | def glimpse(text, *args, **kwargs):
20 | return _glimpse(text, max_length=180, paragraph=False)
21 | %>
22 |
--------------------------------------------------------------------------------
/backtesting/test/__init__.py:
--------------------------------------------------------------------------------
1 | """Data and utilities for testing."""
2 | import pandas as pd
3 |
4 |
5 | def _read_file(filename):
6 | from os.path import dirname, join
7 |
8 | return pd.read_csv(join(dirname(__file__), filename),
9 | index_col=0, parse_dates=True, infer_datetime_format=True)
10 |
11 |
12 | GOOG = _read_file('GOOG.csv')
13 | """DataFrame of daily NASDAQ:GOOG (Google/Alphabet) stock price data from 2004 to 2013."""
14 |
15 | EURUSD = _read_file('EURUSD.csv')
16 | """DataFrame of hourly EUR/USD forex data from April 2017 to February 2018."""
17 |
18 |
19 | def SMA(arr: pd.Series, n: int) -> pd.Series:
20 | """
21 | Returns `n`-period simple moving average of array `arr`.
22 | """
23 | return pd.Series(arr).rolling(n).mean()
24 |
--------------------------------------------------------------------------------
/doc/scripts/logo.py:
--------------------------------------------------------------------------------
1 | from bokeh.io import show, output_file
2 | from bokeh.models import ColumnDataSource
3 | from bokeh.plotting import figure
4 |
5 | output_file("backtesting_logo.html")
6 |
7 | source = ColumnDataSource(data=dict(
8 | colors=[['#00a618', '#d0d000', 'tomato'][i]
9 | for i in [0, 0, 1, 0, 1, 0, 0, 1, 0, 2]],
10 | x=list(range(10)),
11 | bottom=[1, 3, 4, 3, 2, 3, 5, 5, 7, 6.5],
12 | top= [4, 7, 6, 5, 4, 6, 8, 7, 9, 8])) # noqa: E222,E251
13 |
14 |
15 | p = figure(plot_height=800, plot_width=1200, tools='wheel_zoom,save')
16 | p.vbar('x', .6, 'bottom', 'top', source=source,
17 | line_color='black', line_width=2,
18 | fill_color='colors')
19 |
20 | p.xgrid.grid_line_color = None
21 | p.ygrid.grid_line_color = None
22 | p.y_range.start = -2
23 | p.y_range.end = 12
24 | p.x_range.start = -2
25 | p.x_range.end = 11
26 | p.background_fill_color = None
27 | p.border_fill_color = None
28 |
29 | show(p)
30 |
--------------------------------------------------------------------------------
/doc/pdoc_template/head.mako:
--------------------------------------------------------------------------------
1 | <%!
2 | from pdoc.html_helpers import minify_css
3 | %>
4 | <%def name="homelink()" filter="minify_css">
5 | .homelink {
6 | display: block;
7 | font-size: 2em;
8 | font-weight: bold;
9 | color: #555;
10 | text-align: center;
11 | padding: .5em 0;
12 | }
13 | .homelink:hover {
14 | color: inherit;
15 | }
16 | .homelink img {
17 | display: block;
18 | max-width:40%;
19 | max-height: 5em;
20 | margin: auto;
21 | margin-bottom: .3em;
22 | }
23 | %def>
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-docs.yml:
--------------------------------------------------------------------------------
1 | name: Deploy docs
2 | on:
3 | push:
4 | tags: ['[0-9]+.[0-9]+.*']
5 |
6 | jobs:
7 | deploy:
8 | name: Deploy
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Set up Python
13 | uses: actions/setup-python@v2
14 | with:
15 | python-version: 3.8
16 |
17 | - uses: actions/cache@v2
18 | name: Set up caches
19 | with:
20 | path: ~/.cache/pip
21 | key: ${{ runner.os }}
22 |
23 | - name: Checkout repo
24 | uses: actions/checkout@v2
25 | with:
26 | fetch-depth: 3
27 | - name: Fetch tags
28 | run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
29 |
30 | - name: Install dependencies
31 | run: |
32 | pip install -U pip setuptools wheel
33 | pip install -U -e .[doc,test]
34 |
35 | - name: Build docs
36 | run: time catchsegv doc/build.sh
37 |
38 | - name: Deploy docs
39 | env:
40 | GH_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
41 | run: .github/deploy-gh-pages.sh
42 |
--------------------------------------------------------------------------------
/.github/deploy-gh-pages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eu
3 |
4 | if [ ! -d doc/build ]; then
5 | echo 'Error: invalid directory. Deploy from repo root.'
6 | exit 1
7 | fi
8 |
9 | [ "$GH_PASSWORD" ] || exit 12
10 |
11 | sitemap() {
12 | WEBSITE='https://kernc.github.io/backtesting.py'
13 | find -name '*.html' |
14 | sed "s,^\.,$WEBSITE," |
15 | sed 's/index.html$//' |
16 | grep -v '/google.*\.html$' |
17 | sort -u > 'sitemap.txt'
18 | echo "Sitemap: $WEBSITE/sitemap.txt" > 'robots.txt'
19 | }
20 |
21 | head=$(git rev-parse HEAD)
22 |
23 | git clone -b gh-pages "https://kernc:$GH_PASSWORD@github.com/$GITHUB_REPOSITORY.git" gh-pages
24 | mkdir -p gh-pages/doc
25 | cp -R doc/build/* gh-pages/doc/
26 | cd gh-pages
27 | sitemap
28 | git add *
29 | if git diff --staged --quiet; then
30 | echo "$0: No changes to commit."
31 | exit 0
32 | fi
33 |
34 | if ! git config user.name; then
35 | git config user.name 'github-actions'
36 | git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
37 | fi
38 |
39 | git commit -a -m "CI: Update docs for ${GITHUB_REF#refs/tags/} ($head)"
40 | git push
41 |
--------------------------------------------------------------------------------
/backtesting/autoscale_cb.js:
--------------------------------------------------------------------------------
1 | if (!window._bt_scale_range) {
2 | window._bt_scale_range = function (range, min, max, pad) {
3 | "use strict";
4 | if (min !== Infinity && max !== -Infinity) {
5 | pad = pad ? (max - min) * .03 : 0;
6 | range.start = min - pad;
7 | range.end = max + pad;
8 | } else console.error('backtesting: scale range error:', min, max, range);
9 | };
10 | }
11 |
12 | clearTimeout(window._bt_autoscale_timeout);
13 |
14 | window._bt_autoscale_timeout = setTimeout(function () {
15 | /**
16 | * @variable cb_obj `fig_ohlc.x_range`.
17 | * @variable source `ColumnDataSource`
18 | * @variable ohlc_range `fig_ohlc.y_range`.
19 | * @variable volume_range `fig_volume.y_range`.
20 | */
21 | "use strict";
22 |
23 | let i = Math.max(Math.floor(cb_obj.start), 0),
24 | j = Math.min(Math.ceil(cb_obj.end), source.data['ohlc_high'].length);
25 |
26 | let max = Math.max.apply(null, source.data['ohlc_high'].slice(i, j)),
27 | min = Math.min.apply(null, source.data['ohlc_low'].slice(i, j));
28 | _bt_scale_range(ohlc_range, min, max, true);
29 |
30 | if (volume_range) {
31 | max = Math.max.apply(null, source.data['Volume'].slice(i, j));
32 | _bt_scale_range(volume_range, 0, max * 1.03, false);
33 | }
34 |
35 | }, 50);
36 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push: { branches: [master] }
4 | pull_request: { branches: [master] }
5 | schedule: [ cron: '2 2 * * 6' ] # Every Saturday, 02:02
6 |
7 | jobs:
8 | build:
9 | name: Build
10 | runs-on: ubuntu-18.04
11 |
12 | strategy:
13 | matrix:
14 | python-version: [3.6, 3.7]
15 | include:
16 | - python-version: 3.8
17 | test-type: lint
18 | - python-version: 3.8
19 | test-type: docs
20 |
21 | steps:
22 | - name: Set up Python ${{ matrix.python-version }}
23 | uses: actions/setup-python@v2
24 | with:
25 | python-version: ${{ matrix.python-version }}
26 |
27 | - uses: actions/cache@v2
28 | name: Set up caches
29 | with:
30 | path: ~/.cache/pip
31 | key: ${{ runner.os }}-py${{ matrix.python-version }}
32 |
33 | - name: Checkout repo
34 | uses: actions/checkout@v2
35 | with:
36 | fetch-depth: 3
37 | - name: Fetch tags
38 | run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
39 |
40 | - name: Install dependencies
41 | run: |
42 | pip install -U pip setuptools wheel
43 | pip install -U --pre .[test]
44 |
45 | - name: Install lint dependencies
46 | if: matrix.test-type == 'lint'
47 | run: pip install -U .[dev]
48 |
49 | - name: Install docs dependencies
50 | if: matrix.test-type == 'docs'
51 | run: pip install -e .[doc,test] # -e provides _version.py for pdoc
52 |
53 | - name: Test w/ Coverage, Lint
54 | if: matrix.test-type == 'lint'
55 | env: { BOKEH_BROWSER: none }
56 | run: |
57 | flake8
58 | mypy backtesting
59 | time catchsegv coverage run -m backtesting.test
60 | bash <(curl -s https://codecov.io/bash)
61 |
62 | - name: Test
63 | if: '! matrix.test-type'
64 | env: { BOKEH_BROWSER: none }
65 | run: time catchsegv python -m backtesting.test
66 |
67 | - name: Test docs
68 | if: matrix.test-type == 'docs'
69 | run: time catchsegv doc/build.sh
70 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing guidelines
2 | =======================
3 |
4 | Issues
5 | ------
6 | Before reporting an issue, see if a similar issue is already open.
7 | Also check if a similar issue was recently closed — your bug might
8 | have been fixed already.
9 |
10 | To have your issue dealt with promptly, it's best to construct a
11 | [minimal working example] that exposes the issue in a clear and
12 | reproducible manner. Make sure to understand
13 | [how to report bugs effectively][bugs].
14 |
15 | Show verbatim code in [fenced code blocks], and use the
16 | preview function!
17 |
18 | [minimal working example]: https://en.wikipedia.org/wiki/Minimal_working_example
19 | [bugs]: https://www.chiark.greenend.org.uk/~sgtatham/bugs.html
20 | [fenced code blocks]: https://www.markdownguide.org/extended-syntax/#fenced-code-blocks
21 |
22 |
23 | Installation
24 | ------------
25 | To install a developmental version of the project,
26 | first [fork the project]. Then:
27 |
28 | git clone git@github.com:YOUR_USERNAME/backtesting.py
29 | cd backtesting.py
30 | pip3 install -e .[doc,test,dev]
31 |
32 | [fork the project]: https://help.github.com/articles/fork-a-repo/
33 |
34 |
35 | Testing
36 | -------
37 | Please write reasonable unit tests for any new / changed functionality.
38 | See _backtesting/test_ directory for existing tests.
39 | Before submitting a PR, ensure the tests pass:
40 |
41 | python -m backtesting.test
42 |
43 | Also ensure that idiomatic code style is respected by running:
44 |
45 | flake8
46 | mypy backtesting
47 |
48 |
49 | Documentation
50 | -------------
51 | See _doc/README.md_. Besides Jupyter Notebook examples, all documentation
52 | is generated from [pdoc]-compatible docstrings in code.
53 |
54 | [pdoc]: https://pdoc3.github.io/pdoc
55 |
56 |
57 | Pull requests
58 | -------------
59 | A general recommended reading:
60 | [How to make your code reviewer fall in love with you][code-review].
61 | Please use explicit commit messages. See [NumPy's development workflow]
62 | for inspiration.
63 |
64 | [code-review]: https://mtlynch.io/code-review-love/
65 | [NumPy's development workflow]: https://numpy.org/doc/stable/dev/development_workflow.html
66 |
--------------------------------------------------------------------------------
/backtesting/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | ## Manuals
3 |
4 | * [**Quick Start User Guide**](../examples/Quick Start User Guide.html)
5 |
6 | ## Tutorials
7 |
8 | * [Library of Utilities and Composable Base Strategies](../examples/Strategies Library.html)
9 | * [Multiple Time Frames](../examples/Multiple Time Frames.html)
10 | * [**Parameter Heatmap & Optimization**](../examples/Parameter Heatmap & Optimization.html)
11 | * [Trading with Machine Learning](../examples/Trading with Machine Learning.html)
12 |
13 | These tutorials are also available as live Jupyter notebooks:
14 | [][binder]
15 | [][colab]
16 |
In Colab, you might have to `!pip install backtesting`.
17 |
18 | [binder]: \
19 | https://mybinder.org/v2/gh/kernc/backtesting.py/master?\
20 | urlpath=lab%2Ftree%2Fdoc%2Fexamples%2FQuick%20Start%20User%20Guide.ipynb
21 | [colab]: https://colab.research.google.com/github/kernc/backtesting.py/
22 |
23 | ## Example Strategies
24 |
25 | * (contributions welcome)
26 |
27 |
28 | .. tip::
29 | For an overview of recent changes, see
30 | [What's New](https://github.com/kernc/backtesting.py/blob/master/CHANGELOG.md).
31 |
32 |
33 | ## FAQ
34 |
35 | Some answers to frequent and popular questions can be found on the
36 | [issue tracker](https://github.com/kernc/backtesting.py/issues?q=label%3Aquestion+-label%3Ainvalid)
37 | or on the [discussion forum](https://github.com/kernc/backtesting.py/discussions) on GitHub.
38 | Please use the search!
39 |
40 | ## License
41 |
42 | This software is licensed under the terms of [AGPL 3.0]{: rel=license},
43 | meaning you can use it for any reasonable purpose and remain in
44 | complete ownership of all the excellent trading strategies you produce,
45 | but you are also encouraged to make sure any upgrades to _Backtesting.py_
46 | itself find their way back to the community.
47 |
48 | [AGPL 3.0]: https://www.gnu.org/licenses/agpl-3.0.html
49 |
50 | # API Reference Documentation
51 | """
52 | try:
53 | from ._version import version as __version__ # noqa: F401
54 | except ImportError:
55 | __version__ = '?.?.?' # Package not installed
56 |
57 | from .backtesting import Backtest, Strategy # noqa: F401
58 | from . import lib # noqa: F401
59 | from ._plotting import set_bokeh_output # noqa: F401
60 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | What's New
2 | ==========
3 |
4 | These were the major changes contributing to each release:
5 |
6 |
7 | ### 0.x.x
8 |
9 |
10 | ### 0.3.1
11 | (2021-01-25)
12 |
13 | * Avoid some `pandas.Index` deprecations
14 | * Fix `Backtest.plot(show_legend=False)` for recent Bokeh
15 |
16 |
17 | ### 0.3.0
18 | (2020-11-24)
19 |
20 | * Faster [model-based optimization](https://kernc.github.io/backtesting.py/doc/examples/Parameter%20Heatmap%20&%20Optimization.html#Model-based-optimization) using scikit-optimize (#154)
21 | * Optionally faster [optimization](https://kernc.github.io/backtesting.py/doc/backtesting/backtesting.html#backtesting.backtesting.Backtest.optimize) by randomized grid search (#154)
22 | * _Annualized_ Return/Volatility/Sharpe/Sortino/Calmar stats (#156)
23 | * Auto close open trades on backtest finish
24 | * Add `Backtest.plot(plot_return=)`, akin to `plot_equity=`
25 | * Update Expectancy formula (#181)
26 |
27 |
28 | ### 0.2.4
29 | (2020-10-27)
30 |
31 | * Add [`lib.random_ohlc_data()`](https://kernc.github.io/backtesting.py/doc/backtesting/lib.html#backtesting.lib.random_ohlc_data) OHLC data generator
32 | * Aggregate Equity on 'last' when plot resampling
33 | * Update stats calculation for Buy & Hold to be long-only (#152)
34 |
35 |
36 | ### 0.2.3
37 | (2020-09-10)
38 |
39 | * Link hover crosshairs across plots
40 | * Clicking plot legend glyph toggles indicator visibility
41 | * Fix Bokeh tooltip showing literal '\ '
42 |
43 |
44 | ### 0.2.2
45 | (2020-08-21)
46 |
47 |
48 | ### 0.2.1
49 | (2020-08-03)
50 |
51 | * Add [`Trade.entry_time/.exit_time`](https://kernc.github.io/backtesting.py/doc/backtesting/backtesting.html#backtesting.backtesting.Trade)
52 | * Handle SL/TP hit on the same day the position was opened
53 |
54 |
55 | ### 0.2.0
56 | (2020-07-15)
57 |
58 | * New Order/Trade/Position API (#47)
59 | * Add data pandas accessors [`.df` and `.s`](https://kernc.github.io/backtesting.py/doc/backtesting/backtesting.html#backtesting.backtesting.Strategy.data)
60 | * Add `Backtest(..., exclusive_orders=)` that closes previous trades on new orders
61 | * Add `Backtest(..., hedging=)` that makes FIFO trade closing optional
62 | * Add `bt.plot(reverse_indicators=)` param
63 | * Add `bt.plot(resample=)` and auto-downsample large data
64 | * Use geometric mean return in Sharpe/Sortino stats computation
65 |
66 |
67 | ### 0.1.8
68 | (2020-07-14)
69 |
70 | * Add Profit Factor statistic (#85)
71 |
72 |
73 | ### 0.1.7
74 | (2020-03-23)
75 |
76 | * Fix support for 2-D indicators
77 | * Fix tooltip Date field formatting with Bokeh 2.0.0
78 |
79 |
80 | ### 0.1.6
81 | (2020-03-09)
82 |
83 |
84 | ### 0.1.5
85 | (2020-03-02)
86 |
87 |
88 | ### 0.1.4
89 | (2020-02-25)
90 |
91 |
92 | ### 0.1.3
93 | (2020-02-24)
94 |
95 | * Show number of trades on OHLC plot legend
96 | * Add parameter agg= to lib.resample_apply()
97 | * Reset position price (etc.) after closing position
98 | * Fix pandas insertion error on Windos
99 |
100 |
101 | ### 0.1.2
102 | (2019-09-23)
103 |
104 | * Make plot span 100% of browser width
105 |
106 |
107 | ### 0.1.1
108 | (2019-09-23)
109 |
110 | * Avoid multiprocessing trouble on Windos (#6)
111 | * Add scatter plot indicators
112 |
113 |
114 | ### 0.1.0
115 | (2019-01-15)
116 |
117 | * Initial release
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://kernc.github.io/backtesting.py/)
2 |
3 | Backtesting.py
4 | ==============
5 | [](https://github.com/kernc/backtesting.py/actions)
6 | [](https://codecov.io/gh/kernc/backtesting.py)
7 | [](https://pypi.org/project/backtesting)
8 | [](https://pypi.org/project/backtesting)
9 | [](https://github.com/sponsors/kernc)
10 |
11 | Backtest trading strategies with Python.
12 |
13 | [**Project website**](https://kernc.github.io/backtesting.py)
14 |
15 | [Documentation]
16 |
17 | [](#top) the project if you use it.
18 |
19 | [Documentation]: https://kernc.github.io/backtesting.py/doc/backtesting/
20 |
21 |
22 | Installation
23 | ------------
24 |
25 | $ pip install backtesting
26 |
27 |
28 | Usage
29 | -----
30 | ```python
31 | from backtesting import Backtest, Strategy
32 | from backtesting.lib import crossover
33 |
34 | from backtesting.test import SMA, GOOG
35 |
36 |
37 | class SmaCross(Strategy):
38 | def init(self):
39 | price = self.data.Close
40 | self.ma1 = self.I(SMA, price, 10)
41 | self.ma2 = self.I(SMA, price, 20)
42 |
43 | def next(self):
44 | if crossover(self.ma1, self.ma2):
45 | self.buy()
46 | elif crossover(self.ma2, self.ma1):
47 | self.sell()
48 |
49 |
50 | bt = Backtest(GOOG, SmaCross, commission=.002,
51 | exclusive_orders=True)
52 | stats = bt.run()
53 | bt.plot()
54 | ```
55 |
56 | Results in:
57 |
58 | ```text
59 | Start 2004-08-19 00:00:00
60 | End 2013-03-01 00:00:00
61 | Duration 3116 days 00:00:00
62 | Exposure Time [%] 94.27
63 | Equity Final [$] 68935.12
64 | Equity Peak [$] 68991.22
65 | Return [%] 589.35
66 | Buy & Hold Return [%] 703.46
67 | Return (Ann.) [%] 25.42
68 | Volatility (Ann.) [%] 38.43
69 | Sharpe Ratio 0.66
70 | Sortino Ratio 1.30
71 | Calmar Ratio 0.77
72 | Max. Drawdown [%] -33.08
73 | Avg. Drawdown [%] -5.58
74 | Max. Drawdown Duration 688 days 00:00:00
75 | Avg. Drawdown Duration 41 days 00:00:00
76 | # Trades 93
77 | Win Rate [%] 53.76
78 | Best Trade [%] 57.12
79 | Worst Trade [%] -16.63
80 | Avg. Trade [%] 1.96
81 | Max. Trade Duration 121 days 00:00:00
82 | Avg. Trade Duration 32 days 00:00:00
83 | Profit Factor 2.13
84 | Expectancy [%] 6.91
85 | SQN 1.78
86 | _strategy SmaCross(n1=10, n2=20)
87 | _equity_curve Equ...
88 | _trades Size EntryB...
89 | dtype: object
90 | ```
91 | [](https://kernc.github.io/backtesting.py/#example)
92 |
93 | Find more usage examples in the [documentation].
94 |
95 | Features
96 | --------
97 | * Simple, well-documented API
98 | * Blazing fast execution
99 | * Built-in optimizer
100 | * Library of composable base strategies and utilities
101 | * Indicator-library-agnostic
102 | * Supports _any_ financial instrument with candlestick data
103 | * Detailed results
104 | * Interactive visualizations
105 |
106 | Alternatives
107 | ------------
108 | See [alternatives.md] for a list of alternative Python
109 | backtesting frameworks and related packages.
110 |
111 | [alternatives.md]: https://github.com/kernc/backtesting.py/blob/master/doc/alternatives.md
112 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | if sys.version_info < (3, 6):
5 | sys.exit('ERROR: Backtesting.py requires Python 3.6+')
6 |
7 |
8 | if __name__ == '__main__':
9 | from setuptools import setup, find_packages
10 |
11 | setup(
12 | name='Backtesting',
13 | description="Backtest trading strategies in Python",
14 | license='AGPL-3.0',
15 | url='https://kernc.github.io/backtesting.py/',
16 | project_urls={
17 | 'Documentation': 'https://kernc.github.io/backtesting.py/doc/backtesting/',
18 | 'Source': 'https://github.com/kernc/backtesting.py/',
19 | 'Tracker': 'https://github.com/kernc/backtesting.py/issues',
20 | },
21 | long_description=open(os.path.join(os.path.dirname(__file__), 'README.md'),
22 | encoding='utf-8').read(),
23 | long_description_content_type='text/markdown',
24 | packages=find_packages(),
25 | include_package_data=True,
26 | setup_requires=[
27 | 'setuptools_git',
28 | 'setuptools_scm',
29 | ],
30 | use_scm_version={
31 | 'write_to': os.path.join('backtesting', '_version.py'),
32 | },
33 | install_requires=[
34 | 'numpy',
35 | 'pandas >= 0.25.0, != 0.25.0',
36 | 'bokeh >= 1.4.0',
37 | ],
38 | extras_require={
39 | 'doc': [
40 | 'pdoc3',
41 | 'jupytext >= 1.3',
42 | 'nbconvert',
43 | 'ipykernel', # for nbconvert
44 | 'jupyter_client', # for nbconvert
45 | ],
46 | 'test': [
47 | 'seaborn',
48 | 'matplotlib',
49 | 'scikit-learn',
50 | 'scikit-optimize',
51 | ],
52 | 'dev': [
53 | 'flake8',
54 | 'coverage',
55 | 'mypy',
56 | ],
57 | },
58 | test_suite="backtesting.test",
59 | python_requires='>=3.6',
60 | author='Zach Lûster',
61 | classifiers=[
62 | 'Intended Audience :: Financial and Insurance Industry',
63 | 'Intended Audience :: Science/Research',
64 | 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
65 | 'Operating System :: OS Independent',
66 | 'Programming Language :: Python :: 3 :: Only',
67 | 'Topic :: Office/Business :: Financial :: Investment',
68 | 'Topic :: Scientific/Engineering :: Visualization',
69 | ],
70 | keywords=[
71 | 'algo',
72 | 'algorithmic',
73 | 'ashi',
74 | 'backtest',
75 | 'backtesting',
76 | 'bitcoin',
77 | 'bokeh',
78 | 'bonds',
79 | 'candle',
80 | 'candlestick',
81 | 'cboe',
82 | 'chart',
83 | 'cme',
84 | 'commodities',
85 | 'crash',
86 | 'crypto',
87 | 'currency',
88 | 'doji',
89 | 'drawdown',
90 | 'equity',
91 | 'etf',
92 | 'ethereum',
93 | 'exchange',
94 | 'finance',
95 | 'financial',
96 | 'forecast',
97 | 'forex',
98 | 'fund',
99 | 'futures',
100 | 'fx',
101 | 'fxpro',
102 | 'gold',
103 | 'heiken',
104 | 'historical',
105 | 'indicator',
106 | 'invest',
107 | 'investing',
108 | 'investment',
109 | 'macd',
110 | 'market',
111 | 'mechanical',
112 | 'money',
113 | 'oanda',
114 | 'ohlc',
115 | 'ohlcv',
116 | 'order',
117 | 'price',
118 | 'profit',
119 | 'quant',
120 | 'quantitative',
121 | 'rsi',
122 | 'silver',
123 | 'stocks',
124 | 'strategy',
125 | 'ticker',
126 | 'trader',
127 | 'trading',
128 | 'tradingview',
129 | 'usd',
130 | ],
131 | )
132 |
--------------------------------------------------------------------------------
/doc/examples/Strategies Library.py:
--------------------------------------------------------------------------------
1 | # ---
2 | # jupyter:
3 | # jupytext:
4 | # text_representation:
5 | # extension: .py
6 | # format_name: light
7 | # format_version: '1.5'
8 | # jupytext_version: 1.5.1
9 | # kernelspec:
10 | # display_name: Python 3
11 | # language: python
12 | # name: python3
13 | # ---
14 |
15 | # Library of Composable Base Strategies
16 | # ======================
17 | #
18 | # This tutorial will show how to reuse composable base trading strategies that are part of _backtesting.py_ software distribution.
19 | # It is, henceforth, assumed you're already familiar with
20 | # [basic package usage](https://kernc.github.io/backtesting.py/doc/examples/Quick Start User Guide.html).
21 | #
22 | # We'll extend the same moving average cross-over strategy as in
23 | # [Quick Start User Guide](https://kernc.github.io/backtesting.py/doc/examples/Quick Start User Guide.html),
24 | # but we'll rewrite it as a vectorized signal strategy and add trailing stop-loss.
25 | #
26 | # Again, we'll use our helper moving average function.
27 |
28 | from backtesting.test import SMA
29 |
30 | # Part of this software distribution is
31 | # [`backtesting.lib`](https://kernc.github.io/backtesting.py/doc/backtesting/lib.html)
32 | # module that contains various reusable utilities for strategy development.
33 | # Some of those utilities are composable base strategies we can extend and build upon.
34 | #
35 | # We import and extend two of those strategies here:
36 | # * [`SignalStrategy`](https://kernc.github.io/backtesting.py/doc/backtesting/lib.html#backtesting.lib.SignalStrategy)
37 | # which decides upon a single signal vector whether to buy into a position, akin to
38 | # [vectorized backtesting](https://www.google.com/search?q=vectorized+backtesting)
39 | # engines, and
40 | # * [`TrailingStrategy`](https://kernc.github.io/backtesting.py/doc/backtesting/lib.html#backtesting.lib.TrailingStrategy)
41 | # which automatically trails the current price with a stop-loss order some multiple of
42 | # [average true range](https://en.wikipedia.org/wiki/Average_true_range)
43 | # (ATR) away.
44 |
45 | # +
46 | import pandas as pd
47 | from backtesting.lib import SignalStrategy, TrailingStrategy
48 |
49 |
50 | class SmaCross(SignalStrategy,
51 | TrailingStrategy):
52 | n1 = 10
53 | n2 = 25
54 |
55 | def init(self):
56 | # In init() and in next() it is important to call the
57 | # super method to properly initialize the parent classes
58 | super().init()
59 |
60 | # Precompute the two moving averages
61 | sma1 = self.I(SMA, self.data.close, self.n1)
62 | sma2 = self.I(SMA, self.data.close, self.n2)
63 |
64 | # Where sma1 crosses sma2 upwards. Diff gives us [-1,0, *1*]
65 | signal = (pd.Series(sma1) > sma2).astype(int).diff().fillna(0)
66 | signal = signal.replace(-1, 0) # Upwards/long only
67 |
68 | # Use 95% of available liquidity (at the time) on each order.
69 | # (Leaving a value of 1. would instead buy a single share.)
70 | entry_size = signal * .95
71 |
72 | # Set order entry sizes using the method provided by
73 | # `SignalStrategy`. See the docs.
74 | self.set_signal(entry_size=entry_size)
75 |
76 | # Set trailing stop-loss to 2x ATR using
77 | # the method provided by `TrailingStrategy`
78 | self.set_trailing_sl(2)
79 |
80 |
81 | # -
82 |
83 | # Note, since the strategies in `lib` may require their own intialization and next-tick logic, be sure to **always call `super().init()` and `super().next()` in your overridden methods**.
84 | #
85 | # Let's see how the example strategy fares on historical Google data.
86 |
87 | # +
88 | from backtesting import Backtest
89 | from backtesting.test import GOOG
90 |
91 | bt = Backtest(GOOG, SmaCross, commission=.002)
92 |
93 | bt.run()
94 | bt.plot()
95 | # -
96 |
97 | # Notice how managing risk with a trailing stop-loss secures our gains and limits our losses.
98 | #
99 | # For other strategies of the sort, and other reusable utilities in general, see
100 | # [**_backtesting.lib_ module reference**](https://kernc.github.io/backtesting.py/doc/backtesting/lib.html).
101 |
102 | # Learn more by exploring further
103 | # [examples](https://kernc.github.io/backtesting.py/doc/backtesting/index.html#tutorials)
104 | # or find more framework options in the
105 | # [full API reference](https://kernc.github.io/backtesting.py/doc/backtesting/index.html#header-submodules).
106 |
--------------------------------------------------------------------------------
/doc/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eu
3 | IS_RELEASE="$([[ "${GITHUB_REF:-}" == refs/tags/* ]] && echo 1 || true)"
4 |
5 | die () { echo "ERROR: $*" >&2; exit 2; }
6 |
7 | for cmd in pdoc3 \
8 | jupytext \
9 | jupyter-nbconvert; do
10 | command -v "$cmd" >/dev/null ||
11 | die "Missing $cmd; \`pip install backtesting[doc]\`"
12 | done
13 |
14 | DOCROOT="$(dirname "$(readlink -f "$0")")"
15 | BUILDROOT="$DOCROOT/build"
16 |
17 |
18 | echo
19 | echo 'Building API reference docs'
20 | echo
21 | mkdir -p "$BUILDROOT"
22 | rm -r "$BUILDROOT" 2>/dev/null || true
23 | pushd "$DOCROOT/.." >/dev/null
24 | pdoc3 --html \
25 | ${IS_RELEASE+--template-dir "$DOCROOT/pdoc_template"} \
26 | --output-dir "$BUILDROOT" \
27 | backtesting
28 | popd >/dev/null
29 |
30 |
31 | echo
32 | echo 'Ensuring example notebooks match their py counterparts'
33 | echo
34 | strip_yaml () { awk -f "$DOCROOT/scripts/strip_yaml.awk" "$@"; }
35 | for ipynb in "$DOCROOT"/examples/*.ipynb; do
36 | echo "Checking: '$ipynb'"
37 | diff <(strip_yaml "${ipynb%.ipynb}.py") <(jupytext --to py --output - "$ipynb" | strip_yaml) ||
38 | die "Notebook and its matching .py file differ. Maybe run: \`jupytext --to py '$ipynb'\` ?"
39 | done
40 |
41 |
42 | echo
43 | echo 'Converting example notebooks → py → HTML'
44 | echo
45 | jupytext --test --update --to ipynb "$DOCROOT/examples"/*.py
46 | { mkdir -p ~/.ipython/profile_default/startup
47 | cp -f "$DOCROOT/scripts/ipython_config.py" ~/.ipython/profile_default/startup/99-backtesting-docs.py
48 | trap 'rm -f ~/.ipython/profile_default/startup/99-backtesting-docs.py' EXIT; }
49 | PYTHONWARNINGS='ignore::UserWarning,ignore::RuntimeWarning' \
50 | jupyter-nbconvert --execute --to=html \
51 | --ExecutePreprocessor.timeout=300 \
52 | --output-dir="$BUILDROOT/examples" "$DOCROOT/examples"/*.ipynb
53 |
54 |
55 | if [ "$IS_RELEASE" ]; then
56 | echo -e '\nAdding GAnalytics code\n'
57 |
58 | ANALYTICS=""
59 | find "$BUILDROOT" -name '*.html' -print0 |
60 | xargs -0 -- sed -i "s#