159 |
160 |
161 |
--------------------------------------------------------------------------------
/docs/source/_themes/klink/docs/source/examples.rst:
--------------------------------------------------------------------------------
1 | Examples
2 | ==========
3 |
4 | Here are some code examples to test out the theme.
5 |
6 | Sub-Heading
7 | -----------
8 | This is a second level heading (``h2``).
9 |
10 | Sub-Sub-Heading
11 | ~~~~~~~~~~~~~~~
12 | This is a third level heading (``h3``).
13 |
14 |
15 | Code
16 | ----
17 |
18 | Here is some ``inline code text`` and::
19 |
20 | multiline
21 | code text
22 |
23 | It also works with existing Sphinx highlighting:
24 |
25 | .. code-block:: html
26 |
27 |
28 | Hello World
29 |
30 |
31 | .. code-block:: python
32 |
33 | def hello():
34 | """Greet."""
35 | return "Hello World"
36 |
37 | .. code-block:: javascript
38 |
39 | /**
40 | * Greet.
41 | */
42 | function hello(): {
43 | return "Hello World";
44 | }
45 |
46 |
47 | Admonitions
48 | -----------
49 |
50 | See Also
51 | ~~~~~~~~
52 |
53 | .. seealso:: This is a **seealso**.
54 |
55 | .. seealso::
56 |
57 | This is a longer seealso. It might also contain links to our code such as a
58 | link to :func:`convert_notebooks ` and it may also
59 | simply contain a normal hyperlink to http://www.google.com.
60 |
61 | Note
62 | ~~~~
63 | .. note:: This is a **note**.
64 |
65 | .. note::
66 |
67 | This is a longer note. It might also contain links to our code such as a
68 | link to :func:`convert_notebooks ` and it may also
69 | simply contain a normal hyperlink to http://www.google.com.
70 |
71 | Warning
72 | ~~~~~~~
73 | .. warning:: This is a **warning**.
74 |
75 | .. warning::
76 |
77 | This is a longer warning. It might also contain links to our code such as a
78 | link to :func:`convert_notebooks ` and it may also
79 | simply contain a normal hyperlink to http://www.google.com.
80 |
81 | Danger
82 | ~~~~~~
83 | .. danger:: This is **danger**-ous.
84 |
85 | .. danger::
86 |
87 | This is a longer danger. It might also contain links to our code such as a
88 | link to :func:`convert_notebooks ` and it may also
89 | simply contain a normal hyperlink to http://www.google.com.
90 |
91 | Footnotes
92 | ---------
93 | I have footnoted a first item [#f1]_ and second item [#f2]_.
94 |
95 | .. rubric:: Footnotes
96 | .. [#f1] My first footnote.
97 | .. [#f2] My second footnote.
98 |
99 | Tables
100 | ------
101 | Here are some examples of Sphinx
102 | `tables `_.
103 |
104 | Grid
105 | ~~~~
106 |
107 | +------------------------+------------+----------+----------+
108 | | Header1 | Header2 | Header3 | Header4 |
109 | +========================+============+==========+==========+
110 | | row1, cell1 | cell2 | cell3 | cell4 |
111 | +------------------------+------------+----------+----------+
112 | | row2 ... | ... | ... | |
113 | +------------------------+------------+----------+----------+
114 | | ... | ... | ... | |
115 | +------------------------+------------+----------+----------+
116 |
117 | Simple
118 | ~~~~~~
119 |
120 | ===== ===== =======
121 | H1 H2 H3
122 | ===== ===== =======
123 | cell1 cell2 cell3
124 | ... ... ...
125 | ... ... ...
126 | ===== ===== =======
127 |
128 | Code Documentation
129 | ~~~~~~~~~~~~~~~~~~
130 |
131 | An example Python function.
132 |
133 | .. py:function:: format_exception(etype, value, tb[, limit=None])
134 |
135 | Format the exception with a traceback.
136 |
137 | :param etype: exception type
138 | :param value: exception value
139 | :param tb: traceback object
140 | :param limit: maximum number of stack frames to show
141 | :type limit: integer or None
142 | :rtype: list of strings
143 |
144 | An example JavaScript function.
145 |
146 | .. js:class:: MyAnimal(name[, age])
147 |
148 | :param string name: The name of the animal
149 | :param number age: an optional age for the animal
150 |
151 | IPython Notebook
152 | ----------------
153 |
154 | This is what Notebook integration looks like:
155 |
156 | .. include:: nb-examples.rst
157 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | bt - Flexible Backtesting for Python
2 | ====================================
3 |
4 | What is bt?
5 | -----------
6 |
7 | **bt** is a flexible backtesting framework for Python used to test quantitative
8 | trading strategies. **Backtesting** is the process of testing a strategy over a given
9 | data set. This framework allows you to easily create strategies that mix and match
10 | different :class:`Algos `. It aims to foster the creation of easily testable, re-usable and
11 | flexible blocks of strategy logic to facilitate the rapid development of complex
12 | trading strategies.
13 |
14 | The goal: to save **quants** from re-inventing the wheel and let them focus on the
15 | important part of the job - strategy development.
16 |
17 | **bt** is coded in **Python** and joins a vibrant and rich ecosystem for data analysis.
18 | Numerous libraries exist for machine learning, signal processing and statistics and can be leveraged to avoid
19 | re-inventing the wheel - something that happens all too often when using other
20 | languages that don't have the same wealth of high-quality, open-source projects.
21 |
22 | bt is built atop `ffn `_ - a financial function library for Python. Check it out!
23 |
24 | A Quick Example
25 | ---------------
26 |
27 | Here is a quick taste of bt:
28 |
29 | .. include:: intro.rst
30 |
31 | As you can see, the strategy logic is easy to understand and more importantly,
32 | easy to modify. The idea of using simple, composable Algos to create strategies is one of the
33 | core building blocks of bt.
34 |
35 | Features
36 | ---------
37 |
38 | * **Tree Structure**
39 | :doc:`The tree structure ` facilitates the construction and composition of complex algorithmic trading
40 | strategies that are modular and re-usable. Furthermore, each tree :class:`Node `
41 | has its own :func:`price index ` that can be
42 | used by Algos to determine a Node's allocation.
43 |
44 | * **Algorithm Stacks**
45 | :class:`Algos ` and :class:`AlgoStacks ` are
46 | another core feature that facilitate the creation of modular and re-usable strategy
47 | logic. Due to their modularity, these logic blocks are also easier to test -
48 | an important step in building robust financial solutions.
49 |
50 | * **Transaction Cost Modeling**
51 | Through the use of a commission function and instrument-specific, time-varying
52 | bid/offer spreads passed to the
53 | :class:`Backtest `.
54 |
55 | * **Fixed Income**
56 | Strategies can include coupon-paying instruments such as bonds,
57 | unfunded instruments such as swaps, holding costs, and the option for notional weighting.
58 | These are extensions of :doc:`the tree structure `.
59 |
60 | * **Charting and Reporting**
61 | bt also provides many useful charting functions that help visualize backtest
62 | results. We also plan to add more charts, tables and report formats in the future,
63 | such as automatically generated PDF reports.
64 |
65 | * **Detailed Statistics**
66 | Furthermore, bt calculates a bunch of stats relating to a backtest and offers a quick way to compare
67 | these various statistics across many different backtests via :class:`Results'
68 | ` display methods.
69 |
70 |
71 | Roadmap
72 | --------
73 |
74 | Future development efforts will focus on:
75 |
76 | * **Speed**
77 | Due to the flexible nature of bt, a trade-off had to be made between
78 | usability and performance. Usability will always be the priority, but we do
79 | wish to enhance the performance as much as possible.
80 |
81 | * **Algos**
82 | We will also be developing more algorithms as time goes on. We also
83 | encourage anyone to contribute their own algos as well.
84 |
85 | * **Charting and Reporting**
86 | This is another area we wish to constantly improve on
87 | as reporting is an important aspect of the job. Charting and reporting also
88 | facilitate finding bugs in strategy logic.
89 |
90 |
91 | .. toctree::
92 | :maxdepth: 2
93 |
94 | Overview
95 | Installation Guide
96 | All About Algos
97 | The Tree Structure
98 | Examples
99 | API
100 |
--------------------------------------------------------------------------------
/docs/source/_themes/klink/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: http://pmorissette.github.io/klink/_static/logo.png
2 |
3 |
4 | klink - A Simple & Clean Sphinx Theme
5 | =====================================
6 |
7 | Klink is a **simple** and **clean** theme for creating `Sphinx docs
8 | `__. It is heavily inspired by the beautiful `jrnl theme
9 | `__. It also supports embedding `IPython
10 | Notebooks `__ which can be mighty useful.
11 |
12 | For a live demo, please visit `our docs `__.
13 |
14 | Options
15 | -------
16 |
17 | Here are the theme options. They should be added to the html_theme_options in
18 | your **conf.py** file.
19 |
20 | * **github**
21 | The github address of the project. The format is name/project
22 | (pmorissette/klink).
23 | * **logo**
24 | The logo file. Assumed to be in the _static dir. Default is logo.png. The logo
25 | should be 150x150.
26 | * **analytics_id**
27 | Your Google Analytics id (usually starts with UA-...)
28 |
29 | IPython Notebook Integration
30 | ----------------------------
31 |
32 | With the klink helper function **convert_notebooks()**, all notebooks will be
33 | converted to .rst so that they can be included in your docs. This includes all
34 | output including images. It’s a very convenient way to create Python docs!
35 |
36 | All you have to do is create notebooks within your source directory (same directory
37 | as your conf.py file). Then, you add a call to klink.convert_notebooks() in your
38 | conf.py. You can also mix in **Mardown** cells or **Raw NBConvert** cells in
39 | your workbook. These will be converted to rst as well.
40 |
41 | If you use the Raw NBConvert type cells, add a blank line at the start. There
42 | seems to be a bug in the rst conversion and if the cell does not begin with a
43 | blank line, you may run into some issues.
44 |
45 | Using a Raw NBConvert cell with rst text inside is convenient, especially if you
46 | want to have links to other parts of your Sphinx docs.
47 |
48 | Installation
49 | ------------
50 |
51 | Assuming you have pip installed:
52 |
53 | .. code:: sh
54 |
55 | $ pip install klink
56 |
57 | That's it.
58 |
59 | Usage
60 | -----
61 |
62 | In your docs' **conf.py** file, add the following:
63 |
64 | .. code:: python
65 |
66 | import klink
67 |
68 | html_theme = 'klink'
69 | html_theme_path [klink.get_html_theme_path()]
70 | html_theme_options = {
71 | 'github': 'yourname/yourrepo',
72 | 'analytics_id': 'UA-your-number-here',
73 | 'logo': 'logo.png'
74 | }
75 |
76 | Klink also comes with a useful helper function that allows you to integrate an
77 | IPython Notebook into a .rst file. It basically converts the Notebook to .rst
78 | and copies the static data (images, etc) to your _static dir.
79 |
80 | If you have IPython Notebooks that you would like to integrate, use the
81 | following code to your **conf.py**:
82 |
83 | .. code:: python
84 |
85 | klink.convert_notebooks()
86 |
87 | Once the conversion is done, you will have a .rst file with the same name as
88 | each one of your notebooks.
89 |
90 |
91 | *NOTE: Place your notebooks in your docs' source dir.*
92 |
93 | Now all you have to do is use the **include** command to insert them into your
94 | docs.
95 |
96 |
97 | Customization
98 | -------------
99 |
100 | Obviously, some of you will want to customize the theme. The easiest way to
101 | achieve this is to clone the repo into your _themes folder (create it if it does
102 | not exist in your docs' source dir). To change the style, I recommend editing
103 | the LESS files themselves. You will also need lessc to convert from less to css.
104 | See the css command in the Makefile for an example.
105 |
106 | You may also want to explore the option of using **git subtree**. Here is a good
107 | `intro tutorial `__.
108 |
109 | You will also need to change your conf.py file. The following settings should
110 | work::
111 |
112 | html_theme = 'klink'
113 | html_theme_path ['_themes']
114 | html_theme_options = {
115 | 'github': 'yourname/yourrepo',
116 | 'analytics_id': 'UA-your-number-here',
117 | 'logo': 'logo.png'
118 | }
119 |
120 |
121 | License
122 | -------
123 |
124 | MIT
125 |
--------------------------------------------------------------------------------
/docs/source/ERC.rst:
--------------------------------------------------------------------------------
1 | Equally Weighted Risk Contributions Portfolio
2 | ---------------------------------------------
3 |
4 | .. code:: ipython3
5 |
6 | import numpy as np
7 | import pandas as pd
8 | import matplotlib.pyplot as plt
9 |
10 | import ffn
11 | import bt
12 |
13 | %matplotlib inline
14 |
15 | Create Fake Index Data
16 | ~~~~~~~~~~~~~~~~~~~~~~
17 |
18 | .. code:: ipython3
19 |
20 | mean = np.array([0.05/252 + 0.02/252, 0.03/252 + 0.02/252])
21 | volatility = np.array([0.2/np.sqrt(252), 0.05/np.sqrt(252)])
22 | variance = np.power(volatility,2)
23 | correlation = np.array(
24 | [
25 | [1, 0.25],
26 | [0.25,1]
27 | ]
28 | )
29 | covariance = np.zeros((2,2))
30 | for i in range(len(variance)):
31 | for j in range(len(variance)):
32 | covariance[i,j] = correlation[i,j]*volatility[i]*volatility[j]
33 |
34 | covariance
35 |
36 |
37 |
38 |
39 | .. parsed-literal::
40 | :class: pynb-result
41 |
42 | array([[1.58730159e-04, 9.92063492e-06],
43 | [9.92063492e-06, 9.92063492e-06]])
44 |
45 |
46 |
47 | .. code:: ipython3
48 |
49 | names = ['foo','bar','rf']
50 | dates = pd.date_range(start='2015-01-01',end='2018-12-31', freq=pd.tseries.offsets.BDay())
51 | n = len(dates)
52 | rdf = pd.DataFrame(
53 | np.zeros((n, len(names))),
54 | index = dates,
55 | columns = names
56 | )
57 |
58 | np.random.seed(1)
59 | rdf.loc[:,['foo','bar']] = np.random.multivariate_normal(mean,covariance,size=n)
60 | rdf['rf'] = 0.02/252
61 |
62 | pdf = 100*np.cumprod(1+rdf)
63 | pdf.plot();
64 |
65 |
66 |
67 | .. image:: _static/ERC_4_0.png
68 | :class: pynb
69 | :width: 377px
70 | :height: 262px
71 |
72 |
73 | Build and run ERC Strategy
74 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
75 |
76 | You can read more about ERC here.
77 | http://thierry-roncalli.com/download/erc.pdf
78 |
79 | .. code:: ipython3
80 |
81 | runAfterDaysAlgo = bt.algos.RunAfterDays(
82 | 20*6 + 1
83 | )
84 |
85 | selectTheseAlgo = bt.algos.SelectThese(['foo','bar'])
86 |
87 | # algo to set the weights so each asset contributes the same amount of risk
88 | # with data over the last 6 months excluding yesterday
89 | weighERCAlgo = bt.algos.WeighERC(
90 | lookback=pd.DateOffset(days=20*6),
91 | covar_method='standard',
92 | risk_parity_method='slsqp',
93 | maximum_iterations=1000,
94 | tolerance=1e-9,
95 | lag=pd.DateOffset(days=1)
96 | )
97 |
98 | rebalAlgo = bt.algos.Rebalance()
99 |
100 | strat = bt.Strategy(
101 | 'ERC',
102 | [
103 | runAfterDaysAlgo,
104 | selectTheseAlgo,
105 | weighERCAlgo,
106 | rebalAlgo
107 | ]
108 | )
109 |
110 | backtest = bt.Backtest(
111 | strat,
112 | pdf,
113 | integer_positions=False
114 | )
115 |
116 | res_target = bt.run(backtest)
117 |
118 | .. code:: ipython3
119 |
120 | res_target.get_security_weights().plot();
121 |
122 |
123 |
124 | .. image:: _static/ERC_7_0.png
125 | :class: pynb
126 | :width: 373px
127 | :height: 262px
128 |
129 |
130 | .. code:: ipython3
131 |
132 | res_target.prices.plot();
133 |
134 |
135 |
136 | .. image:: _static/ERC_8_0.png
137 | :class: pynb
138 | :width: 376px
139 | :height: 264px
140 |
141 |
142 | .. code:: ipython3
143 |
144 | weights_target = res_target.get_security_weights().copy()
145 | rolling_cov_target = pdf.loc[:,weights_target.columns].pct_change().rolling(window=252).cov()*252
146 |
147 |
148 | trc_target = pd.DataFrame(
149 | np.nan,
150 | index = weights_target.index,
151 | columns = weights_target.columns
152 | )
153 |
154 | for dt in pdf.index:
155 | trc_target.loc[dt,:] = weights_target.loc[dt,:].values*(rolling_cov_target.loc[dt,:].values@weights_target.loc[dt,:].values)/np.sqrt(weights_target.loc[dt,:].values@rolling_cov_target.loc[dt,:].values@weights_target.loc[dt,:].values)
156 |
157 |
158 | fig, ax = plt.subplots(nrows=1,ncols=1)
159 | trc_target.plot(ax=ax)
160 | ax.set_title('Total Risk Contribution')
161 | ax.plot();
162 |
163 |
164 |
165 |
166 | .. image:: _static/ERC_9_0.png
167 | :class: pynb
168 | :width: 386px
169 | :height: 277px
170 |
171 |
172 | You can see the Total Risk Contribution is roughly equal from both
173 | assets.
174 |
175 |
--------------------------------------------------------------------------------
/docs/source/Target_Volatility.rst:
--------------------------------------------------------------------------------
1 | Target Volatility
2 | -----------------
3 |
4 | .. code:: ipython3
5 |
6 | import numpy as np
7 | import pandas as pd
8 | import matplotlib.pyplot as plt
9 |
10 | import ffn
11 | import bt
12 |
13 | %matplotlib inline
14 |
15 | Create Fake Index Data
16 | ~~~~~~~~~~~~~~~~~~~~~~
17 |
18 | .. code:: ipython3
19 |
20 | names = ['foo','bar','rf']
21 | dates = pd.date_range(start='2015-01-01',end='2018-12-31', freq=pd.tseries.offsets.BDay())
22 | n = len(dates)
23 | rdf = pd.DataFrame(
24 | np.zeros((n, len(names))),
25 | index = dates,
26 | columns = names
27 | )
28 |
29 | np.random.seed(1)
30 | rdf['foo'] = np.random.normal(loc = 0.1/252,scale=0.2/np.sqrt(252),size=n)
31 | rdf['bar'] = np.random.normal(loc = 0.04/252,scale=0.05/np.sqrt(252),size=n)
32 | rdf['rf'] = 0.
33 |
34 | pdf = 100*np.cumprod(1+rdf)
35 | pdf.plot();
36 |
37 |
38 |
39 | .. image:: _static/Target_Volatility_3_0.png
40 | :class: pynb
41 | :width: 377px
42 | :height: 262px
43 |
44 |
45 | Build Strategy
46 | ~~~~~~~~~~~~~~
47 |
48 | .. code:: ipython3
49 |
50 | # algo to fire on the beginning of every week and to run on the first date
51 | runWeeklyAlgo = bt.algos.RunWeekly(
52 | run_on_first_date=True
53 | )
54 |
55 | selectTheseAlgo = bt.algos.SelectThese(['foo','bar'])
56 |
57 | # algo to set the weights to 1/vol contributions from each asset
58 | # with data over the last 12 months excluding yesterday
59 | weighInvVolAlgo = bt.algos.WeighInvVol(
60 | lookback=pd.DateOffset(months=12),
61 | lag=pd.DateOffset(days=1)
62 | )
63 |
64 | # algo to set overall volatility of the portfolio to an annualized 10%
65 | targetVolAlgo = bt.algos.TargetVol(
66 | 0.1,
67 | lookback=pd.DateOffset(months=12),
68 | lag=pd.DateOffset(days=1),
69 | covar_method='standard',
70 | annualization_factor=252
71 | )
72 |
73 |
74 | # algo to rebalance the current weights to weights set in target.temp
75 | rebalAlgo = bt.algos.Rebalance()
76 |
77 | # a strategy that rebalances monthly to specified weights
78 | strat = bt.Strategy('static',
79 | [
80 | runWeeklyAlgo,
81 | selectTheseAlgo,
82 | weighInvVolAlgo,
83 | targetVolAlgo,
84 | rebalAlgo
85 | ]
86 | )
87 |
88 | Run Backtest
89 | ~~~~~~~~~~~~
90 |
91 | Note: The logic of the strategy is seperate from the data used in the
92 | backtest.
93 |
94 | .. code:: ipython3
95 |
96 | # set integer_positions=False when positions are not required to be integers(round numbers)
97 | backtest = bt.Backtest(
98 | strat,
99 | pdf,
100 | integer_positions=False
101 | )
102 |
103 | res = bt.run(backtest)
104 |
105 | You can see the realized volatility below is close to the targeted 10%
106 | volatility.
107 |
108 | .. code:: ipython3
109 |
110 | fig, ax = plt.subplots(nrows=1,ncols=1)
111 | (res.prices.pct_change().rolling(window=12*20).std()*np.sqrt(252)).plot(ax = ax)
112 | ax.set_title('Rolling Volatility')
113 | ax.plot();
114 |
115 |
116 |
117 | .. image:: _static/Target_Volatility_9_0.png
118 | :class: pynb
119 | :width: 386px
120 | :height: 277px
121 |
122 |
123 | Because we are using a 1/vol allocation bar, the less risky security,
124 | has a much smaller weight.
125 |
126 | .. code:: ipython3
127 |
128 | fig, ax = plt.subplots(nrows=1,ncols=1)
129 | res.get_security_weights().plot(ax = ax)
130 | ax.set_title('Weights')
131 | ax.plot();
132 |
133 |
134 |
135 | .. image:: _static/Target_Volatility_11_0.png
136 | :class: pynb
137 | :width: 374px
138 | :height: 277px
139 |
140 |
141 | If we plot the total risk contribution of each asset class and divide by
142 | the total volatility, then we can see that both asset’s contribute
143 | roughly similar amounts of volatility.
144 |
145 | .. code:: ipython3
146 |
147 | weights = res.get_security_weights()
148 | rolling_cov = pdf.loc[:,weights.columns].pct_change().rolling(window=12*20).cov()*252
149 |
150 |
151 | trc = pd.DataFrame(
152 | np.nan,
153 | index = weights.index,
154 | columns = weights.columns
155 | )
156 | for dt in pdf.index:
157 | trc.loc[dt,:] = weights.loc[dt,:].values*rolling_cov.loc[dt,:].values@weights.loc[dt,:].values/np.sqrt(weights.loc[dt,:].values@rolling_cov.loc[dt,:].values@weights.loc[dt,:].values)
158 |
159 |
160 | fig, ax = plt.subplots(nrows=1,ncols=1)
161 | trc.plot(ax=ax)
162 | ax.set_title('% Total Risk Contribution')
163 | ax.plot();
164 |
165 |
166 |
167 | .. image:: _static/Target_Volatility_13_0.png
168 | :class: pynb
169 | :width: 380px
170 | :height: 277px
171 |
172 |
173 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 | workflow_dispatch:
8 |
9 | jobs:
10 | build:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | os:
15 | - ubuntu-latest
16 | - macos-latest
17 | - windows-latest
18 | python-version:
19 | - "3.9"
20 | - "3.10"
21 | - "3.11"
22 | - "3.12"
23 | - "3.13"
24 | cibuildwheel:
25 | - "cp39"
26 | - "cp310"
27 | - "cp311"
28 | - "cp312"
29 | - "cp313"
30 | exclude:
31 | - python-version: "3.9"
32 | cibuildwheel: "cp310"
33 | - python-version: "3.9"
34 | cibuildwheel: "cp311"
35 | - python-version: "3.9"
36 | cibuildwheel: "cp312"
37 | - python-version: "3.9"
38 | cibuildwheel: "cp313"
39 | - python-version: "3.10"
40 | cibuildwheel: "cp39"
41 | - python-version: "3.10"
42 | cibuildwheel: "cp311"
43 | - python-version: "3.10"
44 | cibuildwheel: "cp312"
45 | - python-version: "3.10"
46 | cibuildwheel: "cp313"
47 | - python-version: "3.11"
48 | cibuildwheel: "cp39"
49 | - python-version: "3.11"
50 | cibuildwheel: "cp310"
51 | - python-version: "3.11"
52 | cibuildwheel: "cp312"
53 | - python-version: "3.11"
54 | cibuildwheel: "cp313"
55 | - python-version: "3.12"
56 | cibuildwheel: "cp39"
57 | - python-version: "3.12"
58 | cibuildwheel: "cp310"
59 | - python-version: "3.12"
60 | cibuildwheel: "cp311"
61 | - python-version: "3.12"
62 | cibuildwheel: "cp313"
63 | - python-version: "3.13"
64 | cibuildwheel: "cp39"
65 | - python-version: "3.13"
66 | cibuildwheel: "cp310"
67 | - python-version: "3.13"
68 | cibuildwheel: "cp311"
69 | - python-version: "3.13"
70 | cibuildwheel: "cp312"
71 |
72 | steps:
73 | - name: Checkout
74 | uses: actions/checkout@v6
75 |
76 | - name: Set up Python ${{ matrix.python-version }}
77 | uses: actions/setup-python@v6
78 | with:
79 | python-version: ${{ matrix.python-version }}
80 |
81 | - name: Install dependencies
82 | run: |
83 | make develop
84 | python -m pip install -U wheel twine setuptools
85 |
86 | - name: Install cibuildwheel
87 | run: python -m pip install cibuildwheel==2.23.2
88 |
89 | - name: Python Wheel Steps (Linux - cibuildwheel)
90 | run: python -m cibuildwheel --output-dir dist
91 | env:
92 | CIBW_BUILD: "${{ matrix.cibuildwheel }}-manylinux*"
93 | CIBW_BUILD_VERBOSITY: 3
94 | if: ${{ runner.os == 'Linux' }}
95 |
96 | - name: Python Build Steps (Macos)
97 | run: python -m cibuildwheel --output-dir dist
98 | env:
99 | CIBW_BUILD: "${{ matrix.cibuildwheel }}-macos*"
100 | CIBW_ARCHS_MACOS: x86_64 arm64
101 | CIBW_BUILD_VERBOSITY: 3
102 | if: ${{ matrix.os == 'macos-latest' }}
103 |
104 | - name: Python Build Steps (Windows)
105 | run: python -m cibuildwheel --output-dir dist
106 | env:
107 | CIBW_BUILD: "${{ matrix.cibuildwheel }}-win_amd64"
108 | if: ${{ matrix.os == 'windows-latest' }}
109 |
110 | - name: Check Wheels
111 | run: twine check dist/*
112 |
113 | - name: Upload Wheel
114 | uses: actions/upload-artifact@v5
115 | with:
116 | name: wheel-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }}
117 | path: dist/*.whl
118 |
119 | # - name: Publish distribution 📦 to PyPI
120 | # if: ${{ startsWith(github.ref, 'refs/tags') && matrix.os != 'ubuntu-latest' }}
121 | # env:
122 | # TWINE_USERNAME: ${{ secrets.PYPI_UN }}
123 | # TWINE_PASSWORD: ${{ secrets.PYPI_PW }}
124 | # run: make upload
125 | build_sdist:
126 | strategy:
127 | matrix:
128 | os:
129 | - ubuntu-22.04
130 | python-version:
131 | - 3.9
132 |
133 | runs-on: ${{ matrix.os }}
134 |
135 | steps:
136 | - name: Checkout
137 | uses: actions/checkout@v6
138 | with:
139 | submodules: recursive
140 |
141 | - name: Set up Python ${{ matrix.python-version }}
142 | uses: actions/setup-python@v6
143 | with:
144 | python-version: ${{ matrix.python-version }}
145 |
146 | - name: Install dependencies
147 | run: make develop
148 |
149 | - name: Python SDist Steps
150 | run: python setup.py sdist
151 |
152 | - name: Check sdist
153 | run: twine check dist/*.tar.gz
154 |
155 | - name: Upload SDist
156 | uses: actions/upload-artifact@v5
157 | with:
158 | name: sdist
159 | path: dist/*.tar.gz
160 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
10 | set I18NSPHINXOPTS=%SPHINXOPTS% source
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\bt.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\bt.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/docs/source/_themes/klink/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
10 | set I18NSPHINXOPTS=%SPHINXOPTS% source
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\klink-demo.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\klink-demo.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/bt.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/bt.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/bt"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/bt"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/docs/source/_themes/klink/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/klink-demo.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/klink-demo.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/klink-demo"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/klink-demo"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://github.com/pmorissette/bt/actions/)
4 | [](https://pypi.org/project/bt/)
5 | [](https://pypi.org/project/bt/)
6 |
7 | # bt - Flexible Backtesting for Python
8 |
9 | bt is currently in alpha stage - if you find a bug, please submit an issue.
10 |
11 | Read the docs here: http://pmorissette.github.io/bt.
12 |
13 | ## What is bt?
14 |
15 | **bt** is a flexible backtesting framework for Python used to test quantitative
16 | trading strategies. **Backtesting** is the process of testing a strategy over a given
17 | data set. This framework allows you to easily create strategies that mix and match
18 | different [Algos](http://pmorissette.github.io/bt/bt.html#bt.core.Algo). It aims to foster the creation of easily testable, re-usable and
19 | flexible blocks of strategy logic to facilitate the rapid development of complex
20 | trading strategies.
21 |
22 | The goal: to save **quants** from re-inventing the wheel and let them focus on the
23 | important part of the job - strategy development.
24 |
25 | **bt** is coded in **Python** and joins a vibrant and rich ecosystem for data analysis.
26 | Numerous libraries exist for machine learning, signal processing and statistics and can be leveraged to avoid
27 | re-inventing the wheel - something that happens all too often when using other
28 | languages that don't have the same wealth of high-quality, open-source projects.
29 |
30 | bt is built atop [ffn](https://github.com/pmorissette/ffn) - a financial function library for Python. Check it out!
31 |
32 | ## Features
33 |
34 | * **Tree Structure**
35 | [The tree structure](http://pmorissette.github.io/bt/tree.html) facilitates the construction and composition of complex algorithmic trading
36 | strategies that are modular and re-usable. Furthermore, each tree [Node](http://pmorissette.github.io/bt/bt.html#bt.core.Node) has its own
37 | price index that can be used by Algos to determine a Node's allocation.
38 |
39 | * **Algorithm Stacks**
40 | [Algos](http://pmorissette.github.io/bt/bt.html#bt.core.Algo) and [AlgoStacks](http://pmorissette.github.io/bt/bt.html#bt.core.AlgoStack) are
41 | another core feature that facilitate the creation of modular and re-usable strategy
42 | logic. Due to their modularity, these logic blocks are also easier to test -
43 | an important step in building robust financial solutions.
44 |
45 | * **Charting and Reporting**
46 | bt also provides many useful charting functions that help visualize backtest
47 | results. We also plan to add more charts, tables and report formats in the future,
48 | such as automatically generated PDF reports.
49 |
50 | * **Detailed Statistics**
51 | Furthermore, bt calculates a bunch of stats relating to a backtest and offers a quick way to compare
52 | these various statistics across many different backtests via [Results](http://pmorissette.github.io/bt/bt.html#bt.backtest.Result) display methods.
53 |
54 |
55 | ## Roadmap
56 |
57 | Future development efforts will focus on:
58 |
59 | * **Speed**
60 | Due to the flexible nature of bt, a trade-off had to be made between
61 | usability and performance. Usability will always be the priority, but we do
62 | wish to enhance the performance as much as possible.
63 |
64 | * **Algos**
65 | We will also be developing more algorithms as time goes on. We also
66 | encourage anyone to contribute their own algos as well.
67 |
68 | * **Charting and Reporting**
69 | This is another area we wish to constantly improve on
70 | as reporting is an important aspect of the job. Charting and reporting also
71 | facilitate finding bugs in strategy logic.
72 |
73 | ## Installing bt
74 |
75 | The easiest way to install `bt` is from the [Python Package Index](https://pypi.python.org/pypi/bt/)
76 | using `pip`:
77 |
78 | ```bash
79 | pip install bt
80 | ```
81 |
82 |
83 | Since bt has many dependencies, we strongly recommend installing the [Anaconda Scientific Python
84 | Distribution](https://store.continuum.io/cshop/anaconda/), especially on Windows. This distribution
85 | comes with many of the required packages pre-installed, including pip. Once Anaconda is installed, the above
86 | command should complete the installation.
87 |
88 | ## Recommended Setup
89 |
90 | We believe the best environment to develop with bt is the [IPython Notebook](http://ipython.org/notebook.html).
91 | From their homepage, the IPython Notebook is:
92 |
93 | "[...] a web-based interactive computational environment
94 | where you can combine code execution, text, mathematics, plots and rich
95 | media into a single document [...]"
96 |
97 | This environment allows you to plot your charts in-line and also allows you to
98 | easily add surrounding text with Markdown. You can easily create Notebooks that
99 | you can share with colleagues and you can also save them as PDFs. If you are not
100 | yet convinced, head over to their website.
101 |
102 | ## Contributing to bt
103 |
104 | A Makefile is available to simplify local development.
105 | [GNU Make](https://www.gnu.org/software/make/) is required to run the `make` targets directly, and it is not often preinstalled [on Windows systems](https://gnuwin32.sourceforge.net/packages/make.htm).
106 |
107 | When developing in Python, it's advisable to [create and activate a virtual environment](https://docs.python.org/3/library/venv.html) to keep the project's dependencies isolated from the system.
108 |
109 | After the usual preparation steps for [contributing to a GitHub project](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) (forking, cloning, creating a feature branch), run `make develop` to install dependencies in the environment.
110 |
111 | While making changes and adding tests, run `make lint` and `make test` often to check for mistakes.
112 |
113 | After [commiting and pushing changes](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project?tool=webui#making-and-pushing-changes), [create a Pull Request](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project?tool=webui#making-a-pull-request) to discuss and get feedback on the proposed feature or fix.
114 |
--------------------------------------------------------------------------------
/docs/source/Strategy_Combination.rst:
--------------------------------------------------------------------------------
1 | Strategy Combination
2 | --------------------
3 |
4 | This notebook creates a parent strategy(combined) with 2 child
5 | strategies(Equal Weight, Inv Vol).
6 |
7 | Alternatively, it creates the 2 child strategies, runs the backtest,
8 | combines the results, and creates a parent strategy using both of the
9 | backtests.
10 |
11 | .. code:: ipython3
12 |
13 | import numpy as np
14 | import pandas as pd
15 | import matplotlib.pyplot as plt
16 |
17 | import ffn
18 | import bt
19 |
20 | %matplotlib inline
21 |
22 | Create fake data
23 | ~~~~~~~~~~~~~~~~
24 |
25 | .. code:: ipython3
26 |
27 | rf = 0.04
28 | np.random.seed(1)
29 | mus = np.random.normal(loc=0.05,scale=0.02,size=5) + rf
30 | sigmas = (mus - rf)/0.3 + np.random.normal(loc=0.,scale=0.01,size=5)
31 |
32 | num_years = 10
33 | num_months_per_year = 12
34 | num_days_per_month = 21
35 | num_days_per_year = num_months_per_year*num_days_per_month
36 |
37 | rdf = pd.DataFrame(
38 | index = pd.date_range(
39 | start="2008-01-02",
40 | periods=num_years*num_months_per_year*num_days_per_month,
41 | freq="B"
42 | ),
43 | columns=['foo','bar','baz','fake1','fake2']
44 | )
45 |
46 | for i,mu in enumerate(mus):
47 | sigma = sigmas[i]
48 | rdf.iloc[:,i] = np.random.normal(
49 | loc=mu/num_days_per_year,
50 | scale=sigma/np.sqrt(num_days_per_year),
51 | size=rdf.shape[0]
52 | )
53 | pdf = np.cumprod(1+rdf)*100
54 | pdf.iloc[0,:] = 100
55 |
56 | pdf.plot();
57 |
58 |
59 |
60 | .. image:: _static/Strategy_Combination_3_0.png
61 | :class: pynb
62 | :width: 376px
63 | :height: 251px
64 |
65 |
66 | .. code:: ipython3
67 |
68 | strategy_names = np.array(
69 | [
70 | 'Equal Weight',
71 | 'Inv Vol'
72 | ]
73 | )
74 |
75 | runMonthlyAlgo = bt.algos.RunMonthly(
76 | run_on_first_date=True,
77 | run_on_end_of_period=True
78 | )
79 | selectAllAlgo = bt.algos.SelectAll()
80 | rebalanceAlgo = bt.algos.Rebalance()
81 |
82 | strats = []
83 | tests = []
84 |
85 | for i,s in enumerate(strategy_names):
86 | if s == "Equal Weight":
87 | wAlgo = bt.algos.WeighEqually()
88 | elif s == "Inv Vol":
89 | wAlgo = bt.algos.WeighInvVol()
90 |
91 | strat = bt.Strategy(
92 | str(s),
93 | [
94 | runMonthlyAlgo,
95 | selectAllAlgo,
96 | wAlgo,
97 | rebalanceAlgo
98 | ]
99 | )
100 | strats.append(strat)
101 |
102 | t = bt.Backtest(
103 | strat,
104 | pdf,
105 | integer_positions = False,
106 | progress_bar=False
107 | )
108 | tests.append(t)
109 |
110 | .. code:: ipython3
111 |
112 | combined_strategy = bt.Strategy(
113 | 'Combined',
114 | algos = [
115 | runMonthlyAlgo,
116 | selectAllAlgo,
117 | bt.algos.WeighEqually(),
118 | rebalanceAlgo
119 | ],
120 | children = [x.strategy for x in tests]
121 | )
122 |
123 | combined_test = bt.Backtest(
124 | combined_strategy,
125 | pdf,
126 | integer_positions = False,
127 | progress_bar = False
128 | )
129 |
130 | res = bt.run(combined_test)
131 |
132 | .. code:: ipython3
133 |
134 | res.prices.plot();
135 |
136 |
137 |
138 | .. image:: _static/Strategy_Combination_6_0.png
139 | :class: pynb
140 | :width: 376px
141 | :height: 251px
142 |
143 |
144 | .. code:: ipython3
145 |
146 | res.get_security_weights().plot();
147 |
148 |
149 |
150 | .. image:: _static/Strategy_Combination_7_0.png
151 | :class: pynb
152 | :width: 380px
153 | :height: 253px
154 |
155 |
156 | In order to get the weights of each strategy, you can run each strategy,
157 | get the prices for each strategy, combine them into one price dataframe,
158 | run the combined strategy on the new data set.
159 |
160 | .. code:: ipython3
161 |
162 | strategy_names = np.array(
163 | [
164 | 'Equal Weight',
165 | 'Inv Vol'
166 | ]
167 | )
168 |
169 | runMonthlyAlgo = bt.algos.RunMonthly(
170 | run_on_first_date=True,
171 | run_on_end_of_period=True
172 | )
173 | selectAllAlgo = bt.algos.SelectAll()
174 | rebalanceAlgo = bt.algos.Rebalance()
175 |
176 | strats = []
177 | tests = []
178 | results = []
179 |
180 | for i,s in enumerate(strategy_names):
181 | if s == "Equal Weight":
182 | wAlgo = bt.algos.WeighEqually()
183 | elif s == "Inv Vol":
184 | wAlgo = bt.algos.WeighInvVol()
185 |
186 | strat = bt.Strategy(
187 | s,
188 | [
189 | runMonthlyAlgo,
190 | selectAllAlgo,
191 | wAlgo,
192 | rebalanceAlgo
193 | ]
194 | )
195 | strats.append(strat)
196 |
197 | t = bt.Backtest(
198 | strat,
199 | pdf,
200 | integer_positions = False,
201 | progress_bar=False
202 | )
203 | tests.append(t)
204 |
205 | res = bt.run(t)
206 | results.append(res)
207 |
208 |
209 | .. code:: ipython3
210 |
211 | fig, ax = plt.subplots(nrows=1,ncols=1)
212 | for i,r in enumerate(results):
213 | r.plot(ax=ax)
214 |
215 |
216 |
217 | .. image:: _static/Strategy_Combination_10_0.png
218 | :class: pynb
219 | :width: 879px
220 | :height: 320px
221 |
222 |
223 | .. code:: ipython3
224 |
225 | merged_prices_df = bt.merge(results[0].prices,results[1].prices)
226 |
227 | combined_strategy = bt.Strategy(
228 | 'Combined',
229 | algos = [
230 | runMonthlyAlgo,
231 | selectAllAlgo,
232 | bt.algos.WeighEqually(),
233 | rebalanceAlgo
234 | ]
235 | )
236 |
237 | combined_test = bt.Backtest(
238 | combined_strategy,
239 | merged_prices_df,
240 | integer_positions = False,
241 | progress_bar = False
242 | )
243 |
244 | res = bt.run(combined_test)
245 |
246 | .. code:: ipython3
247 |
248 | res.plot();
249 |
250 |
251 |
252 | .. image:: _static/Strategy_Combination_12_0.png
253 | :class: pynb
254 | :width: 879px
255 | :height: 320px
256 |
257 |
258 | .. code:: ipython3
259 |
260 | res.get_security_weights().plot();
261 |
262 |
263 |
264 | .. image:: _static/Strategy_Combination_13_0.png
265 | :class: pynb
266 | :width: 373px
267 | :height: 251px
268 |
269 |
270 |
--------------------------------------------------------------------------------
/docs/source/tree.rst:
--------------------------------------------------------------------------------
1 | The Tree Structure
2 | ==================
3 |
4 | Overview
5 | --------
6 |
7 | In addition to the concept of :class:`Algos ` and :class:`AlgoStacks `, a tree structure lies
8 | at the heart of the framework. It allows you to mix and match securities and strategies in order to express
9 | your sophisticated trading ideas. Here is a very simple diagram to help explain this concept:
10 |
11 | .. image:: _static/tree1.png
12 | :align: center
13 | :alt: simple tree structure
14 |
15 | This diagram represents the strategy we tested in the :doc:`overview example `. A simple :class:`strategy `
16 | with two children that happen to be :class:`securities `. However, children nodes don't have to be
17 | securities. They can also be strategies. This concept is very powerful as it
18 | allows you to combine strategies together and allocate capital dynamically
19 | between different strategies as time progresses using sophisticated allocation
20 | logic. This is similar to what hedge funds do - they have a portfolio of strategies and dynamically allocate capital
21 | according to a set of rules.
22 |
23 | For example, say we didn't mind having a passive bond allocation (AGG in the
24 | above graph), but we wanted to swap out the equity portion (SPY) for something a
25 | little more sophisticated. In this case, we will swap out the SPY node for another strategy.
26 | This strategy could be a momentum strategy that attempts to pick the best
27 | performing ETF every month (to keep it simple, let's say it picks either the SPY
28 | or the EEM based on total return over the past 3 months).
29 |
30 | Here is the updated graph:
31 |
32 | .. image:: _static/tree2.png
33 | :align: center
34 | :alt: advanced tree structure
35 |
36 | This approach allows you to build complex systems even though all of the building
37 | blocks may be relatively simple. Hopefully you can see how powerful this can be
38 | when designing and testing quantitative strategies.
39 |
40 | Oh and here's the code for the second example - not much more complex:
41 |
42 | .. code:: python
43 |
44 | import bt
45 |
46 | # create the momentum strategy - we will specify the children (3rd argument)
47 | # to limit the universe the strategy can choose from
48 | mom_s = bt.Strategy('mom_s', [bt.algos.RunMonthly(),
49 | bt.algos.SelectAll(),
50 | bt.algos.SelectMomentum(1),
51 | bt.algos.WeighEqually(),
52 | bt.algos.Rebalance()],
53 | ['spy', 'eem'])
54 |
55 | # create the parent strategy - this is the top-most node in the tree
56 | # Once again, we are also specifying the children. In this case, one of the
57 | # children is a Security and the other is a Strategy.
58 | parent = bt.Strategy('parent', [bt.algos.RunMonthly(),
59 | bt.algos.SelectAll(),
60 | bt.algos.WeighEqually(),
61 | bt.algos.Rebalance()],
62 | [mom_s, 'agg'])
63 |
64 | # create the backtest and run it
65 | t = bt.Backtest(parent, data)
66 | r = bt.run(t)
67 |
68 | For even more sophisticated strategies, sub-strategies can be dynamically
69 | created by constructing them with the appropriate parent argument. The code below
70 | is equivalent to the example above, but knowledge of the sub-strategy is not
71 | needed at construction time of the parent:
72 |
73 | .. code:: python
74 |
75 | # create the parent strategy first - this is the top-most node in the tree
76 | # To start, there is only one child, which is a Security
77 | parent = bt.Strategy('parent', [bt.algos.RunMonthly(),
78 | bt.algos.SelectAll(),
79 | bt.algos.WeighEqually(),
80 | bt.algos.Rebalance()],
81 | ['agg'])
82 |
83 | # Create the momentum strategy dynamically - we will specify the children
84 | # (3rd argument) to limit the universe the strategy can choose from
85 | mom_s = bt.Strategy('mom_s', [bt.algos.RunMonthly(),
86 | bt.algos.SelectAll(),
87 | bt.algos.SelectMomentum(1),
88 | bt.algos.WeighEqually(),
89 | bt.algos.Rebalance()],
90 | ['spy', 'eem'], parent = parent)
91 |
92 | # create the backtest and run it
93 | t = bt.Backtest(parent, data)
94 | r = bt.run(t)
95 |
96 | While this seems like a trivial example, it enables algos to create sub-strategies
97 | on-the-fly (based on market conditions/triggers) and register them to the target.
98 | Each of these sub-strategies will have its own algos and performance measurement.
99 |
100 |
101 | Types
102 | -----
103 | The base class for nodes in the tree is :class:`Node `, and these
104 | can be either of type :class:`StrategyBase ` or
105 | :class:`SecurityBase `.
106 |
107 | Each node offers an interface to the **current values** of many quantities of
108 | interest (price, value, weight, etc), which is useful for building Algos.
109 | Furthermore, they also offer an interface to the **history** of these quantities,
110 | which is useful for building **path-dependent** algos as well as for drilling
111 | into the strategy behavior *after* the backtest has run.
112 |
113 | For more information, see the APIs for :class:`Node `,
114 | :class:`SecurityBase ` and :class:`StrategyBase `.
115 |
116 | There are two main sub-types of :class:`StrategyBase `:
117 | * :class:`Strategy `: Market-value weighted strategy based on
118 | :class:`Algos `.
119 | * :class:`FixedIncomeStrategy `: Notional weighted
120 | strategy based on :class:`Algos `.
121 |
122 | There are also two main sub-types of :class:`SecurityBase `:
123 | * :class:`Security `: Standard security. If used within a
124 | :class:`FixedIncomeStrategy `, its notional weight
125 | is equal to market value. i.e. common stock.
126 | * :class:`CouponPayingSecurity `: A security that
127 | pays regular or irregular cashflows, and can have (asymmetric) holding costs.
128 | i.e. a corporate bond with funding and repo costs, or an unfunded swap.
129 |
130 | When using :class:`FixedIncomeStrategy `,
131 | there are additional security types that are helpful due to the different
132 | treatment of their notional weight in the portfolio. These have no effect in a standard
133 | :class:`Strategy `.
134 | * :class:`FixedIncomeSecurity `: A
135 | :class:`Security ` for which position (rather than market value)
136 | will be used as the notional weight, i.e. a zero-coupon bond.
137 | * :class:`HedgeSecurity `: A :class:`Security `
138 | for which the notional weight is zero, i.e. an ETF hedge in a bond strategy.
139 | * :class:`CouponPayingHedgeSecurity `:
140 | A :class:`CouponPayingSecurity `
141 | for which the notional weight is zero, i.e. a rates swap hedge in a CDS strategy.
142 |
143 |
144 | As in the examples, a list of strings can be passed to the strategy constructors,
145 | which will be automatically converted to instances of :class:`Security `
146 | when needed. For more fine-grained control over which security types are used
147 | (or over other arguments like the ```multiplier```), explicitly construct the
148 | security nodes yourself before passing them to the strategy.
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/docs/source/_themes/klink/klink/less/klink.less:
--------------------------------------------------------------------------------
1 | @import "vendor/3L.less";
2 | @import "vendor/font-awesome/less/font-awesome.less";
3 |
4 | @white: #f7f8f9;
5 | @green: #29b96e;
6 | @green2: #39ff98;
7 | @green3: #0e4026;
8 | @green4: #1c7f4c;
9 | @gray: #333;
10 | @gray2: #777;
11 | @gray3: #999;
12 | @border: #e3e3e3;
13 | @weight1: 400;
14 | @orange: #ff9f80;
15 | @purple: #ad19ff;
16 | @gold: #cc8c14;
17 | @pink: #ef40ff;
18 | @font1: "Open Sans", "Helvetica Neue", sans-serif;
19 | @font2: "Droid Sans Mono", monospace, serif;
20 |
21 | .normalize();
22 |
23 | body {
24 | font-family: @font1;
25 | font-weight: @weight1;
26 | color: @gray;
27 | background: @white;
28 | }
29 |
30 | h1 {
31 | padding-bottom: 40px;
32 | }
33 |
34 | a:link, a:visited {
35 | color: @green;
36 | text-decoration: none;
37 | }
38 |
39 | a:hover, a:active {
40 | text-decoration: underline;
41 | }
42 |
43 | .border-round {
44 | border: 1px solid @border;
45 | .border-radius(6px);
46 | }
47 |
48 | * > a.headerlink {
49 | display: none;
50 | }
51 |
52 | dd {
53 | color: @gray3;
54 | font-weight: @weight1;
55 | max-width: 85%;
56 | }
57 |
58 | dl.function > dt, dl.class > dt, dl.method > dt {
59 | color: @gray;
60 | font-weight: bold;
61 | }
62 |
63 | dl tt {
64 | font-family: @font1;
65 | }
66 |
67 | dl.function, dl.class {
68 | background: #f3f4f5;
69 | padding: 10px;
70 | .border-round;
71 | }
72 |
73 | em.property {
74 | font-style: normal;
75 | color: @gray3;
76 | }
77 |
78 | dt > em {
79 | font-style: normal;
80 | font-weight: 400;
81 | }
82 |
83 | input {
84 | background: transparent;
85 | border: 1px solid @gray3;
86 | .border-radius(3px);
87 | padding: 2px 5px;
88 | color: @gray2;
89 | outline: none;
90 | &:focus {
91 | background: white;
92 | }
93 | }
94 |
95 | aside {
96 | position: fixed;
97 | top: 25px;
98 | width: 240px;
99 | color: @gray3;
100 | padding-left: 20px;
101 |
102 | &>ul {
103 | list-style: none;
104 | margin: 0 4px;
105 | padding: 0;
106 | &>li {
107 | margin-bottom: 10px;
108 | font-size: 18px;
109 | color: @gray2;
110 | a:link, a:visited {
111 | color: @gray2;
112 | }
113 | ul {
114 | margin: 10px 0 0 0;
115 | padding-left: 30px;
116 | font-size: 16px;
117 | }
118 | }
119 | }
120 |
121 | input[type=submit] {
122 | display: none;
123 | }
124 |
125 | input[type=text] {
126 | width: 85%;
127 | }
128 | }
129 |
130 | .literal {
131 | color: @green;
132 | font-size: 1em;
133 | padding: 1px 2px;
134 | }
135 |
136 | .admonition {
137 | padding: 10px 20px;
138 | border: 1px solid @border;
139 | .border-radius(6px);
140 | }
141 |
142 | .admonition-title {
143 | font-weight: 600;
144 | }
145 |
146 | .ad-core {
147 | border: 1px solid @border;
148 | .border-radius(6px);
149 | padding: 10px 20px 10px 70px;
150 | position: relative;
151 | color: white;
152 | margin: 10px 0px;
153 | }
154 |
155 | .ad-core-before {
156 | font-size: 2em;
157 | display: block;
158 | position: absolute;
159 | margin: auto;
160 | top: 0;
161 | bottom: 0;
162 | left: 20px;
163 | width: 32px;
164 | height: 32px;
165 | }
166 |
167 | .seealso
168 | {
169 | .ad-core;
170 | background: #e3e3e3;
171 | color: @gray;
172 | .admonition-title {display: none;}
173 | a { color: @green; }
174 | &:before
175 | {
176 | content: @fa-var-external-link;
177 | .fa;
178 | .ad-core-before;
179 | }
180 | .literal, .highlight-note
181 | {
182 | color: white;
183 | background: @green;
184 | padding: 1px 3px;
185 | .border-radius(2px);
186 | }
187 | }
188 |
189 | .note
190 | {
191 | .ad-core;
192 | background: @green;
193 | .admonition-title {display: none;}
194 | a { color: #40ff85; }
195 | &:before
196 | {
197 | content: @fa-var-info-circle;
198 | .fa;
199 | .ad-core-before;
200 | }
201 | .literal, .highlight-note
202 | {
203 | color: white;
204 | background: #298354;
205 | padding: 1px 3px;
206 | .border-radius(2px);
207 | }
208 | }
209 |
210 | .warning
211 | {
212 | .ad-core;
213 | background: #e0ab66;
214 | .admonition-title {display: none;}
215 | a { color: #633030; }
216 | &:before
217 | {
218 | content: @fa-var-warning;
219 | .fa;
220 | .ad-core-before;
221 | }
222 | .literal, .highlight-note
223 | {
224 | color: white;
225 | background: #9b6515;
226 | padding: 1px 3px;
227 | .border-radius(2px);
228 | }
229 | }
230 |
231 | .danger
232 | {
233 | .ad-core;
234 | background: #d36871;
235 | .admonition-title {display: none;}
236 | a { color: #832929; }
237 | &:before
238 | {
239 | content: @fa-var-times-circle;
240 | .fa;
241 | .ad-core-before;
242 | }
243 | .literal, .highlight-note
244 | {
245 | color: white;
246 | background: #832929;
247 | padding: 1px 3px;
248 | .border-radius(2px);
249 | }
250 | }
251 |
252 | .footnote {
253 | font-size: 0.9em;
254 | border: none;
255 | }
256 |
257 | table {
258 | border: 1px solid @border;
259 | border-color: @border;
260 | }
261 |
262 | table td, table th {
263 | padding: 5px;
264 | }
265 |
266 | table.field-list {
267 | border: none;
268 | }
269 |
270 | li.toctree-l1.current {
271 | font-weight: 700;
272 | }
273 |
274 | li.toctree-l2 {
275 | font-weight: @weight1;
276 | }
277 |
278 | span.viewcode-link {
279 | display: block;
280 | position: absolute;
281 | right: 0px;
282 | font-size: .8em;
283 | color: @green;
284 | padding-right: 10px;
285 | }
286 |
287 | div.document {
288 | max-width: 1000px;
289 | margin: 20px auto;
290 | position: relative;
291 | min-height: 550px;
292 | }
293 |
294 | div.documentwrapper {
295 | margin-left: 280px;
296 | padding: 0 0 0 20px;
297 | }
298 |
299 | div.body {
300 | padding-top: 10px;
301 | }
302 |
303 | div.footer {
304 | font-size: .8em;
305 | text-align: center;
306 | margin: 40px 0;
307 | color: @gray3;
308 | }
309 |
310 | .code-view {
311 | background: white;
312 | padding: 10px 20px;
313 | border: 1px solid @border;
314 | .border-radius(6px);
315 | font-family: @font2;
316 | font-size: 0.9em;
317 | }
318 |
319 | .highlight {
320 | background: transparent !important;
321 |
322 | pre {
323 | .code-view;
324 | }
325 | }
326 |
327 | .code {
328 | background: transparent !important;
329 |
330 | pre {
331 | .code-view;
332 | }
333 | }
334 |
335 | .highlight-python > pre {
336 | .code-view;
337 | }
338 |
339 | .pynb-result > pre {
340 | background: inherit;
341 | border: none;
342 | }
343 |
344 | img.align-center {
345 | display: block;
346 | margin: 0 auto;
347 | padding: 15px 0px;
348 | }
349 |
350 | img.pynb {
351 | background: #fff;
352 | padding: 20px;
353 | display: block;
354 | margin: 20px auto;
355 | max-width: 95%;
356 | }
357 |
358 | img.logo {
359 | display: block;
360 | margin: 0 auto;
361 | padding-bottom: 30px;
362 | }
363 |
364 | div.pynb-result {
365 | font-family: @font2;
366 | font-size: 0.9em;
367 | max-width: 98%;
368 | margin: 0 auto;
369 |
370 | table, p {
371 | margin-left: 20px;
372 | }
373 | }
374 |
375 | div#searchbox {
376 | padding-top: 10px;
377 | }
378 |
379 | @media (max-width: 800px) {
380 | div.documentwrapper {
381 | padding-top: 200px;
382 | margin: 0px 10px;
383 | }
384 | h1, .section {
385 | margin: 0px !important;
386 | }
387 | aside {
388 | position: static;
389 | width: 100%;
390 | margin: 5px 20px;
391 | padding: 5px;
392 | }
393 | #logo {
394 | position: absolute;
395 | top: 0px;
396 | left: 50%;
397 | margin-left: -75px;
398 | }
399 | }
400 |
--------------------------------------------------------------------------------
/docs/source/intro.rst:
--------------------------------------------------------------------------------
1 | .. code:: ipython3
2 |
3 | import bt
4 |
5 | .. code:: ipython3
6 |
7 | %matplotlib inline
8 |
9 |
10 | A Simple Strategy Backtest
11 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
12 |
13 | Let's create a simple strategy. We will create a monthly rebalanced, long-only strategy where we place equal weights on each asset in our universe of assets.
14 |
15 | First, we will download some data. By default, :func:`bt.get (alias for ffn.get) ` downloads the Adjusted Close from Yahoo! Finance. We will download some data starting on January 1, 2010 for the purposes of this demo.
16 |
17 | .. code:: ipython3
18 |
19 | # fetch some data
20 | data = bt.get('spy,agg', start='2010-01-01')
21 | print(data.head())
22 |
23 |
24 | .. parsed-literal::
25 | :class: pynb-result
26 |
27 | spy agg
28 | Date
29 | 2010-01-04 89.225410 74.942825
30 | 2010-01-05 89.461586 75.283791
31 | 2010-01-06 89.524574 75.240227
32 | 2010-01-07 89.902473 75.153221
33 | 2010-01-08 90.201691 75.196724
34 |
35 |
36 |
37 | Once we have our data, we will create our strategy. The :class:`Strategy ` object contains the strategy logic by combining various :class:`Algos `.
38 |
39 | .. code:: ipython3
40 |
41 | # create the strategy
42 | s = bt.Strategy('s1', [bt.algos.RunMonthly(),
43 | bt.algos.SelectAll(),
44 | bt.algos.WeighEqually(),
45 | bt.algos.Rebalance()])
46 |
47 |
48 | Finally, we will create a :class:`Backtest `, which is the logical combination of a strategy with a data set.
49 |
50 | Once this is done, we can run the backtest and analyze the results.
51 |
52 | .. code:: ipython3
53 |
54 | # create a backtest and run it
55 | test = bt.Backtest(s, data)
56 | res = bt.run(test)
57 |
58 |
59 | Now we can analyze the results of our backtest. The :class:`Result ` object is a thin wrapper around `ffn.GroupStats `__ that adds some helper methods.
60 |
61 | .. code:: ipython3
62 |
63 | # first let's see an equity curve
64 | res.plot();
65 |
66 |
67 |
68 | .. image:: _static/intro_9_0.png
69 | :class: pynb
70 | :width: 879px
71 | :height: 304px
72 |
73 |
74 | .. code:: ipython3
75 |
76 | # ok and what about some stats?
77 | res.display()
78 |
79 |
80 | .. parsed-literal::
81 | :class: pynb-result
82 |
83 | Stat s1
84 | ------------------- ----------
85 | Start 2010-01-03
86 | End 2022-07-01
87 | Risk-free rate 0.00%
88 |
89 | Total Return 150.73%
90 | Daily Sharpe 0.90
91 | Daily Sortino 1.35
92 | CAGR 7.64%
93 | Max Drawdown -18.42%
94 | Calmar Ratio 0.41
95 |
96 | MTD 0.18%
97 | 3m -10.33%
98 | 6m -14.84%
99 | YTD -14.84%
100 | 1Y -10.15%
101 | 3Y (ann.) 5.12%
102 | 5Y (ann.) 6.44%
103 | 10Y (ann.) 7.36%
104 | Since Incep. (ann.) 7.64%
105 |
106 | Daily Sharpe 0.90
107 | Daily Sortino 1.35
108 | Daily Mean (ann.) 7.74%
109 | Daily Vol (ann.) 8.62%
110 | Daily Skew -0.98
111 | Daily Kurt 16.56
112 | Best Day 4.77%
113 | Worst Day -6.63%
114 |
115 | Monthly Sharpe 1.06
116 | Monthly Sortino 1.91
117 | Monthly Mean (ann.) 7.81%
118 | Monthly Vol (ann.) 7.36%
119 | Monthly Skew -0.39
120 | Monthly Kurt 1.59
121 | Best Month 7.57%
122 | Worst Month -6.44%
123 |
124 | Yearly Sharpe 0.81
125 | Yearly Sortino 1.75
126 | Yearly Mean 7.48%
127 | Yearly Vol 9.17%
128 | Yearly Skew -1.34
129 | Yearly Kurt 2.28
130 | Best Year 19.64%
131 | Worst Year -14.84%
132 |
133 | Avg. Drawdown -0.84%
134 | Avg. Drawdown Days 13.23
135 | Avg. Up Month 1.70%
136 | Avg. Down Month -1.80%
137 | Win Year % 83.33%
138 | Win 12m % 93.57%
139 |
140 |
141 | .. code:: ipython3
142 |
143 | # ok and how does the return distribution look like?
144 | res.plot_histogram()
145 |
146 |
147 |
148 | .. image:: _static/intro_11_0.png
149 | :class: pynb
150 | :width: 894px
151 | :height: 320px
152 |
153 |
154 | .. code:: ipython3
155 |
156 | # and just to make sure everything went along as planned, let's plot the security weights over time
157 | res.plot_security_weights()
158 |
159 |
160 |
161 | .. image:: _static/intro_12_0.png
162 | :class: pynb
163 | :width: 876px
164 | :height: 293px
165 |
166 |
167 |
168 | Modifying a Strategy
169 | ~~~~~~~~~~~~~~~~~~~~
170 |
171 | Now what if we ran this strategy weekly and also used some risk parity style approach by using weights that are proportional to the inverse of each asset's volatility? Well, all we have to do is plug in some different algos. See below:
172 |
173 | .. code:: ipython3
174 |
175 | # create our new strategy
176 | s2 = bt.Strategy('s2', [bt.algos.RunWeekly(),
177 | bt.algos.SelectAll(),
178 | bt.algos.WeighInvVol(),
179 | bt.algos.Rebalance()])
180 |
181 | # now let's test it with the same data set. We will also compare it with our first backtest.
182 | test2 = bt.Backtest(s2, data)
183 | # we include test here to see the results side-by-side
184 | res2 = bt.run(test, test2)
185 |
186 | res2.plot();
187 |
188 |
189 |
190 | .. image:: _static/intro_14_0.png
191 | :class: pynb
192 | :width: 879px
193 | :height: 304px
194 |
195 |
196 | .. code:: ipython3
197 |
198 | res2.display()
199 |
200 |
201 | .. parsed-literal::
202 | :class: pynb-result
203 |
204 | Stat s1 s2
205 | ------------------- ---------- ----------
206 | Start 2010-01-03 2010-01-03
207 | End 2022-07-01 2022-07-01
208 | Risk-free rate 0.00% 0.00%
209 |
210 | Total Return 150.73% 69.58%
211 | Daily Sharpe 0.90 0.96
212 | Daily Sortino 1.35 1.41
213 | CAGR 7.64% 4.32%
214 | Max Drawdown -18.42% -14.62%
215 | Calmar Ratio 0.41 0.30
216 |
217 | MTD 0.18% 0.38%
218 | 3m -10.33% -6.88%
219 | 6m -14.84% -12.00%
220 | YTD -14.84% -12.00%
221 | 1Y -10.15% -10.03%
222 | 3Y (ann.) 5.12% 1.84%
223 | 5Y (ann.) 6.44% 3.35%
224 | 10Y (ann.) 7.36% 3.76%
225 | Since Incep. (ann.) 7.64% 4.32%
226 |
227 | Daily Sharpe 0.90 0.96
228 | Daily Sortino 1.35 1.41
229 | Daily Mean (ann.) 7.74% 4.33%
230 | Daily Vol (ann.) 8.62% 4.50%
231 | Daily Skew -0.98 -2.21
232 | Daily Kurt 16.56 46.12
233 | Best Day 4.77% 2.84%
234 | Worst Day -6.63% -4.66%
235 |
236 | Monthly Sharpe 1.06 1.13
237 | Monthly Sortino 1.91 1.87
238 | Monthly Mean (ann.) 7.81% 4.40%
239 | Monthly Vol (ann.) 7.36% 3.89%
240 | Monthly Skew -0.39 -1.06
241 | Monthly Kurt 1.59 3.92
242 | Best Month 7.57% 4.05%
243 | Worst Month -6.44% -5.04%
244 |
245 | Yearly Sharpe 0.81 0.65
246 | Yearly Sortino 1.75 1.19
247 | Yearly Mean 7.48% 4.13%
248 | Yearly Vol 9.17% 6.31%
249 | Yearly Skew -1.34 -1.48
250 | Yearly Kurt 2.28 3.37
251 | Best Year 19.64% 11.71%
252 | Worst Year -14.84% -12.00%
253 |
254 | Avg. Drawdown -0.84% -0.48%
255 | Avg. Drawdown Days 13.23 13.68
256 | Avg. Up Month 1.70% 0.90%
257 | Avg. Down Month -1.80% -0.93%
258 | Win Year % 83.33% 83.33%
259 | Win 12m % 93.57% 91.43%
260 |
261 |
--------------------------------------------------------------------------------
/docs/source/_themes/klink/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # klink-demo documentation build configuration file, created by
4 | # sphinx-quickstart on Tue Jul 1 13:40:27 2014.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | sys.path.insert(0, os.path.abspath('../..'))
20 |
21 | import klink
22 | # convert notebooks to .rst
23 | klink.convert_notebooks()
24 |
25 | # -- General configuration -----------------------------------------------------
26 |
27 | # If your documentation needs a minimal Sphinx version, state it here.
28 | #needs_sphinx = '1.0'
29 |
30 | # Add any Sphinx extension module names here, as strings. They can be extensions
31 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
32 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
33 |
34 | # Add any paths that contain templates here, relative to this directory.
35 | templates_path = ['_templates']
36 |
37 | # The suffix of source filenames.
38 | source_suffix = '.rst'
39 |
40 | # The encoding of source files.
41 | #source_encoding = 'utf-8-sig'
42 |
43 | # The master toctree document.
44 | master_doc = 'index'
45 |
46 | # General information about the project.
47 | project = 'klink'
48 | copyright = 'klink was created by Philippe Morissette. If you find a bug, please submit an issue on Github.'
49 |
50 | # The version info for the project you're documenting, acts as replacement for
51 | # |version| and |release|, also used in various other places throughout the
52 | # built documents.
53 | #
54 | # The short X.Y version.
55 | version = '0.1'
56 | # The full version, including alpha/beta/rc tags.
57 | release = '0.1'
58 |
59 | # The language for content autogenerated by Sphinx. Refer to documentation
60 | # for a list of supported languages.
61 | #language = None
62 |
63 | # There are two options for replacing |today|: either, you set today to some
64 | # non-false value, then it is used:
65 | #today = ''
66 | # Else, today_fmt is used as the format for a strftime call.
67 | #today_fmt = '%B %d, %Y'
68 |
69 | # List of patterns, relative to source directory, that match files and
70 | # directories to ignore when looking for source files.
71 | exclude_patterns = []
72 |
73 | # The reST default role (used for this markup: `text`) to use for all documents.
74 | #default_role = None
75 |
76 | # If true, '()' will be appended to :func: etc. cross-reference text.
77 | #add_function_parentheses = True
78 |
79 | # If true, the current module name will be prepended to all description
80 | # unit titles (such as .. function::).
81 | #add_module_names = True
82 |
83 | # If true, sectionauthor and moduleauthor directives will be shown in the
84 | # output. They are ignored by default.
85 | #show_authors = False
86 |
87 | # The name of the Pygments (syntax highlighting) style to use.
88 | pygments_style = 'sphinx'
89 |
90 | # A list of ignored prefixes for module index sorting.
91 | #modindex_common_prefix = []
92 |
93 |
94 | # -- Options for HTML output ---------------------------------------------------
95 |
96 | # The theme to use for HTML and HTML Help pages. See the documentation for
97 | # a list of builtin themes.
98 | html_theme = 'klink'
99 |
100 | # Theme options are theme-specific and customize the look and feel of a theme
101 | # further. For a list of options available for each theme, see the
102 | # documentation.
103 | html_theme_options = {
104 | 'github': 'pmorissette/klink',
105 | 'analytics_id': 'UA-52308448-2'
106 | }
107 |
108 | # Add any paths that contain custom themes here, relative to this directory.
109 | html_theme_path = ['../..']
110 |
111 | # The name for this set of Sphinx documents. If None, it defaults to
112 | # " v documentation".
113 | #html_title = None
114 |
115 | # A shorter title for the navigation bar. Default is the same as html_title.
116 | #html_short_title = None
117 |
118 | # The name of an image file (relative to this directory) to place at the top
119 | # of the sidebar.
120 | #html_logo = None
121 |
122 | # The name of an image file (within the static path) to use as favicon of the
123 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
124 | # pixels large.
125 | html_favicon = 'favicon.ico'
126 |
127 | # Add any paths that contain custom static files (such as style sheets) here,
128 | # relative to this directory. They are copied after the builtin static files,
129 | # so a file named "default.css" will overwrite the builtin "default.css".
130 | html_static_path = ['_static']
131 |
132 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
133 | # using the given strftime format.
134 | #html_last_updated_fmt = '%b %d, %Y'
135 |
136 | # If true, SmartyPants will be used to convert quotes and dashes to
137 | # typographically correct entities.
138 | #html_use_smartypants = True
139 |
140 | # Custom sidebar templates, maps document names to template names.
141 | #html_sidebars = {}
142 |
143 | # Additional templates that should be rendered to pages, maps page names to
144 | # template names.
145 | #html_additional_pages = {}
146 |
147 | # If false, no module index is generated.
148 | #html_domain_indices = True
149 |
150 | # If false, no index is generated.
151 | #html_use_index = True
152 |
153 | # If true, the index is split into individual pages for each letter.
154 | #html_split_index = False
155 |
156 | # If true, links to the reST sources are added to the pages.
157 | #html_show_sourcelink = True
158 |
159 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
160 | #html_show_sphinx = True
161 |
162 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
163 | #html_show_copyright = True
164 |
165 | # If true, an OpenSearch description file will be output, and all pages will
166 | # contain a tag referring to it. The value of this option must be the
167 | # base URL from which the finished HTML is served.
168 | #html_use_opensearch = ''
169 |
170 | # This is the file name suffix for HTML files (e.g. ".xhtml").
171 | #html_file_suffix = None
172 |
173 | # Output file base name for HTML help builder.
174 | htmlhelp_basename = 'klinkdoc'
175 |
176 |
177 | # -- Options for LaTeX output --------------------------------------------------
178 |
179 | latex_elements = {
180 | # The paper size ('letterpaper' or 'a4paper').
181 | #'papersize': 'letterpaper',
182 |
183 | # The font size ('10pt', '11pt' or '12pt').
184 | #'pointsize': '10pt',
185 |
186 | # Additional stuff for the LaTeX preamble.
187 | #'preamble': '',
188 | }
189 |
190 | # Grouping the document tree into LaTeX files. List of tuples
191 | # (source start file, target name, title, author, documentclass [howto/manual]).
192 | latex_documents = [
193 | ('index', 'klink.tex', 'klink Documentation',
194 | 'Philippe Morissette', 'manual'),
195 | ]
196 |
197 | # The name of an image file (relative to this directory) to place at the top of
198 | # the title page.
199 | #latex_logo = None
200 |
201 | # For "manual" documents, if this is true, then toplevel headings are parts,
202 | # not chapters.
203 | #latex_use_parts = False
204 |
205 | # If true, show page references after internal links.
206 | #latex_show_pagerefs = False
207 |
208 | # If true, show URL addresses after external links.
209 | #latex_show_urls = False
210 |
211 | # Documents to append as an appendix to all manuals.
212 | #latex_appendices = []
213 |
214 | # If false, no module index is generated.
215 | #latex_domain_indices = True
216 |
217 |
218 | # -- Options for manual page output --------------------------------------------
219 |
220 | # One entry per manual page. List of tuples
221 | # (source start file, name, description, authors, manual section).
222 | man_pages = [
223 | ('index', 'klink', 'klink Documentation',
224 | ['Philippe Morissette'], 1)
225 | ]
226 |
227 | # If true, show URL addresses after external links.
228 | #man_show_urls = False
229 |
230 |
231 | # -- Options for Texinfo output ------------------------------------------------
232 |
233 | # Grouping the document tree into Texinfo files. List of tuples
234 | # (source start file, target name, title, author,
235 | # dir menu entry, description, category)
236 | texinfo_documents = [
237 | ('index', 'klink', 'klink Documentation',
238 | 'Philippe Morissette', 'klink', 'One line description of project.',
239 | 'Miscellaneous'),
240 | ]
241 |
242 | # Documents to append as an appendix to all manuals.
243 | #texinfo_appendices = []
244 |
245 | # If false, no module index is generated.
246 | #texinfo_domain_indices = True
247 |
248 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
249 | #texinfo_show_urls = 'footnote'
250 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # bt documentation build configuration file, created by
4 | # sphinx-quickstart on Fri Jun 27 11:34:24 2014.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys
15 | import os
16 |
17 | # If extensions (or modules to document with autodoc) are in another directory,
18 | # add these directories to sys.path here. If the directory is relative to the
19 | # documentation root, use os.path.abspath to make it absolute, like shown here.
20 | sys.path.insert(0, os.path.abspath("../../"))
21 | sys.path.insert(0, os.path.abspath("../../bt"))
22 | sys.path.insert(0, os.path.abspath("_themes/klink"))
23 |
24 | import bt # noqa: E402
25 | import klink # noqa: E402
26 |
27 | klink.convert_notebooks()
28 |
29 | html_theme_path = ["_themes/klink"]
30 | html_theme = "klink"
31 | html_theme_options = {"github": "pmorissette/bt", "analytics_id": "UA-52308448-3"}
32 |
33 | # -- General configuration -----------------------------------------------------
34 |
35 | # If your documentation needs a minimal Sphinx version, state it here.
36 | # needs_sphinx = '1.0'
37 |
38 | # Add any Sphinx extension module names here, as strings. They can be extensions
39 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
40 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.intersphinx"]
41 |
42 | intersphinx_mapping = {"ffn": ("http://pmorissette.github.io/ffn", None)}
43 |
44 | # Add any paths that contain templates here, relative to this directory.
45 | templates_path = ["_templates"]
46 |
47 | # The suffix of source filenames.
48 | source_suffix = ".rst"
49 |
50 | # The encoding of source files.
51 | # source_encoding = 'utf-8-sig'
52 |
53 | # The master toctree document.
54 | master_doc = "index"
55 |
56 | # General information about the project.
57 | project = "bt"
58 | copyright = """bt was created by Philippe Morissette.
59 | If you find a bug, please submit an issue on Github.
60 | """
61 |
62 | # The version info for the project you're documenting, acts as replacement for
63 | # |version| and |release|, also used in various other places throughout the
64 | # built documents.
65 | #
66 | # The short X.Y version.
67 | version = bt.__version__
68 | # The full version, including alpha/beta/rc tags.
69 | release = version
70 |
71 | # The language for content autogenerated by Sphinx. Refer to documentation
72 | # for a list of supported languages.
73 | # language = None
74 |
75 | # There are two options for replacing |today|: either, you set today to some
76 | # non-false value, then it is used:
77 | # today = ''
78 | # Else, today_fmt is used as the format for a strftime call.
79 | # today_fmt = '%B %d, %Y'
80 |
81 | # List of patterns, relative to source directory, that match files and
82 | # directories to ignore when looking for source files.
83 | exclude_patterns = []
84 |
85 | # The reST default role (used for this markup: `text`) to use for all documents.
86 | # default_role = None
87 |
88 | # If true, '()' will be appended to :func: etc. cross-reference text.
89 | # add_function_parentheses = True
90 |
91 | # If true, the current module name will be prepended to all description
92 | # unit titles (such as .. function::).
93 | # add_module_names = True
94 |
95 | # If true, sectionauthor and moduleauthor directives will be shown in the
96 | # output. They are ignored by default.
97 | # show_authors = False
98 |
99 | # The name of the Pygments (syntax highlighting) style to use.
100 | pygments_style = "sphinx"
101 |
102 | # A list of ignored prefixes for module index sorting.
103 | # modindex_common_prefix = []
104 |
105 |
106 | # -- Options for HTML output ---------------------------------------------------
107 |
108 | # The theme to use for HTML and HTML Help pages. See the documentation for
109 | # a list of builtin themes.
110 | # html_theme = 'default'
111 |
112 | # Theme options are theme-specific and customize the look and feel of a theme
113 | # further. For a list of options available for each theme, see the
114 | # documentation.
115 | # html_theme_options = {}
116 |
117 | # Add any paths that contain custom themes here, relative to this directory.
118 | # html_theme_path = []
119 |
120 | # The name for this set of Sphinx documents. If None, it defaults to
121 | # " v documentation".
122 | # html_title = None
123 |
124 | # A shorter title for the navigation bar. Default is the same as html_title.
125 | # html_short_title = None
126 |
127 | # The name of an image file (relative to this directory) to place at the top
128 | # of the sidebar.
129 | # html_logo = None
130 |
131 | # The name of an image file (within the static path) to use as favicon of the
132 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
133 | # pixels large.
134 | html_favicon = "favicon.ico"
135 |
136 | # Add any paths that contain custom static files (such as style sheets) here,
137 | # relative to this directory. They are copied after the builtin static files,
138 | # so a file named "default.css" will overwrite the builtin "default.css".
139 | html_static_path = ["_static"]
140 |
141 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
142 | # using the given strftime format.
143 | # html_last_updated_fmt = '%b %d, %Y'
144 |
145 | # If true, SmartyPants will be used to convert quotes and dashes to
146 | # typographically correct entities.
147 | # html_use_smartypants = True
148 |
149 | # Custom sidebar templates, maps document names to template names.
150 | # html_sidebars = {}
151 |
152 | # Additional templates that should be rendered to pages, maps page names to
153 | # template names.
154 | # html_additional_pages = {}
155 |
156 | # If false, no module index is generated.
157 | # html_domain_indices = True
158 |
159 | # If false, no index is generated.
160 | # html_use_index = True
161 |
162 | # If true, the index is split into individual pages for each letter.
163 | # html_split_index = False
164 |
165 | # If true, links to the reST sources are added to the pages.
166 | # html_show_sourcelink = True
167 |
168 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
169 | # html_show_sphinx = True
170 |
171 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
172 | # html_show_copyright = True
173 |
174 | # If true, an OpenSearch description file will be output, and all pages will
175 | # contain a tag referring to it. The value of this option must be the
176 | # base URL from which the finished HTML is served.
177 | # html_use_opensearch = ''
178 |
179 | # This is the file name suffix for HTML files (e.g. ".xhtml").
180 | # html_file_suffix = None
181 |
182 | # Output file base name for HTML help builder.
183 | htmlhelp_basename = "btdoc"
184 |
185 |
186 | # -- Options for LaTeX output --------------------------------------------------
187 |
188 | latex_elements = {
189 | # The paper size ('letterpaper' or 'a4paper').
190 | # 'papersize': 'letterpaper',
191 | # The font size ('10pt', '11pt' or '12pt').
192 | # 'pointsize': '10pt',
193 | # Additional stuff for the LaTeX preamble.
194 | # 'preamble': '',
195 | }
196 |
197 | # Grouping the document tree into LaTeX files. List of tuples
198 | # (source start file, target name, title, author, documentclass [howto/manual]).
199 | latex_documents = [
200 | ("index", "bt.tex", "bt Documentation", "Philippe Morissette", "manual"),
201 | ]
202 |
203 | # The name of an image file (relative to this directory) to place at the top of
204 | # the title page.
205 | # latex_logo = None
206 |
207 | # For "manual" documents, if this is true, then toplevel headings are parts,
208 | # not chapters.
209 | # latex_use_parts = False
210 |
211 | # If true, show page references after internal links.
212 | # latex_show_pagerefs = False
213 |
214 | # If true, show URL addresses after external links.
215 | # latex_show_urls = False
216 |
217 | # Documents to append as an appendix to all manuals.
218 | # latex_appendices = []
219 |
220 | # If false, no module index is generated.
221 | # latex_domain_indices = True
222 |
223 |
224 | # -- Options for manual page output --------------------------------------------
225 |
226 | # One entry per manual page. List of tuples
227 | # (source start file, name, description, authors, manual section).
228 | man_pages = [("index", "bt", "bt Documentation", ["Philippe Morissette"], 1)]
229 |
230 | # If true, show URL addresses after external links.
231 | # man_show_urls = False
232 |
233 |
234 | # -- Options for Texinfo output ------------------------------------------------
235 |
236 | # Grouping the document tree into Texinfo files. List of tuples
237 | # (source start file, target name, title, author,
238 | # dir menu entry, description, category)
239 | texinfo_documents = [
240 | (
241 | "index",
242 | "bt",
243 | "bt Documentation",
244 | "Philippe Morissette",
245 | "bt",
246 | "One line description of project.",
247 | "Miscellaneous",
248 | ),
249 | ]
250 |
251 | # Documents to append as an appendix to all manuals.
252 | # texinfo_appendices = []
253 |
254 | # If false, no module index is generated.
255 | # texinfo_domain_indices = True
256 |
257 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
258 | # texinfo_show_urls = 'footnote'
259 |
--------------------------------------------------------------------------------
/examples/pairs_trading.py:
--------------------------------------------------------------------------------
1 | from copy import deepcopy
2 | from future.utils import iteritems
3 | from datetime import date
4 | import pandas as pd
5 | import numpy as np
6 | import bt
7 |
8 |
9 | class PairsSignal( bt.Algo ):
10 | """
11 | Identify pairs whose indicator exceeds some threshold and save them on temp.
12 |
13 | Args:
14 | * threshold (float): The threshold to use for the indicator between pairs
15 | * indicator_name (str): The name of the indicator data set
16 | Sets:
17 | * pairs
18 | """
19 |
20 | def __init__( self, threshold, indicator_name):
21 | super(PairsSignal, self).__init__()
22 | self.threshold = threshold
23 | self.indicator_name = indicator_name
24 |
25 | def __call__(self, target):
26 | t = target.now
27 | indicators = target.get_data(self.indicator_name)
28 | columns = indicators.columns
29 | signal = indicators.loc[t,:].values.reshape(-1,1) - indicators.loc[t,:].values
30 | pairs = pd.DataFrame( signal, columns = columns, index = columns).stack()
31 | pairs.name='weight'
32 | pairs = pairs[ pairs > self.threshold ]
33 | pairs.index.names = ['sell','buy']
34 | pairs=pairs.sort_values(ascending=False)
35 | target.temp['pairs'] = pairs
36 | return True
37 |
38 |
39 | class SetupPairsTrades( bt.Algo ):
40 | """
41 | Dynamically create a new sub-strategy (with common logic) for every pairs trade.
42 |
43 | Args:
44 | * trade_algos ([Algo]): List of algos that defines the sub-strategy behavior
45 | """
46 |
47 | def __init__(self, trade_algos ):
48 | super(SetupPairsTrades,self).__init__()
49 | self.trade_algos = trade_algos
50 |
51 | def __call__(self, target):
52 | pairs = target.temp.get('pairs', None)
53 | if pairs is None or pairs.empty:
54 | return True
55 |
56 | target.temp['weights'] = {}
57 | for (sell,buy), signal in iteritems( target.temp['pairs'] ):
58 | trade_name = '%s_%s' % (buy,sell)
59 | if trade_name not in target.children:
60 | trade = bt.Strategy( trade_name, deepcopy(self.trade_algos), children = [buy, sell], parent = target )
61 | trade.setup_from_parent( buy=buy, sell=sell )
62 | target.temp['weights'][ trade_name ] = 0
63 |
64 | return True
65 |
66 |
67 | class SizePairsTrades( bt.Algo ):
68 | """
69 | Size the pairs trades by allocating capital to them.
70 |
71 | Args:
72 | * pct_of_capital (float): The percentage of current capital to allocate to new trades this timestep
73 | """
74 |
75 | def __init__(self, pct_of_capital ):
76 | super(SizePairsTrades,self).__init__()
77 | self.pct_of_capital = pct_of_capital
78 |
79 | def __call__(self, target):
80 | weights = target.temp.get('weights')
81 | if weights:
82 | trade_capital = target.capital * self.pct_of_capital / float(len(weights))
83 | for trade_name in weights:
84 | target.allocate( trade_capital, child=trade_name, update=False )
85 | target.update( target.now )
86 |
87 | return True
88 |
89 |
90 | class WeighPair( bt.Algo ):
91 | """
92 | Determine the relative weighting and leverage of the pairs trade
93 |
94 | Args:
95 | * weight( float ): The weight to put on the buy trade
96 | Sets:
97 | * weights
98 | """
99 | def __init__(self, weight):
100 | self.weight = weight
101 |
102 | def __call__(self, target):
103 | target.temp['weights'] = { target.get_data('buy') : self.weight,
104 | target.get_data('sell') : -self.weight }
105 | return True
106 |
107 |
108 | class PriceCompare( bt.Algo ):
109 | """
110 | Control flow algo that only returns True if the price of the target crosses the threshold
111 |
112 | Args
113 | * threshold (float): The price threshold
114 | * is_greater (bool): Whether to do return True when price exceeds the threshold
115 | """
116 |
117 | def __init__(self, threshold, is_greater):
118 | self.threshold = threshold
119 | self.is_greater = is_greater
120 |
121 | def __call__( self, target ):
122 | if self.is_greater:
123 | return target.price >= self.threshold
124 | else:
125 | return target.price < self.threshold
126 |
127 |
128 | class ClosePositions( bt.Algo ):
129 | """
130 | Closes all positions on a strategy, pulls the capital into the parent
131 | """
132 | def __call__( self, target ):
133 | if target.children and not target.bankrupt:
134 | target.flatten()
135 | target.update( target.now ) # Shouldn't be necessary. Need to fix in bt.
136 |
137 | if target.parent != target:
138 | capital = target.capital
139 | target.adjust(-capital, update=False, flow=True)
140 | target.parent.adjust(capital, update=True, flow=False)
141 |
142 | return False
143 |
144 |
145 | class DebugPortfolioLevel( bt.Algo ):
146 | """
147 | Print portfolio level information relevant to this strategy
148 | """
149 | def __call__( self, target ):
150 | flows = target.flows.loc[ target.now ]
151 | if flows:
152 | fmt_str = '{now} {name}: Price = {price:>6.2f}, Value = {value:>10,.0f}, Flows = {flows:>8,.0f}'
153 | else:
154 | fmt_str = '{now} {name}: Price = {price:>6.2f}, Value = {value:>10,.0f}'
155 | print( fmt_str.format(
156 | now = target.now,
157 | name = target.name,
158 | price = target.price,
159 | value = target.value,
160 | flows = flows
161 | ) )
162 |
163 |
164 | class DebugTradeLevel( bt.Algo ):
165 | """
166 | Print trade level information
167 | """
168 | def __call__( self, target ):
169 | flows = target.flows.loc[ target.now ]
170 | # Check that sub-strategy is active (and not paper trading, which is always active)
171 | if (target.capital > 0 or flows != 0) and target.parent != target:
172 | if flows:
173 | fmt_str = '{name:>33}: Price = {price:>6.2f}, Value = {value:>10,.0f}, Flows = {flows:>8,.0f}'
174 | else:
175 | fmt_str = '{name:>33}: Price = {price:>6.2f}, Value = {value:>10,.0f}'
176 | print( fmt_str.format(
177 | now = target.now,
178 | name = target.name,
179 | price = target.price,
180 | value = target.value,
181 | flows = flows
182 | ) )
183 | return True
184 |
185 |
186 | def make_data( n_assets=100, n_periods=100, start_date=date(2021,1,1), phi=0.5, corr=1.0, seed=1234 ):
187 | ''' Randomly generate a data set consisting of non-stationary prices,
188 | but where the difference between the prices of any two securities is. '''
189 | np.random.seed(seed)
190 | dts = pd.date_range( start_date, periods=n_periods)
191 | T = dts.values.astype('datetime64[D]').astype(float).reshape(-1,1)
192 | N = n_assets
193 | columns = ['s%i' %i for i in range(N)]
194 | cov = corr * np.ones( (N,N) ) + (1-corr) * np.eye(N)
195 | noise = pd.DataFrame( np.random.multivariate_normal( np.zeros(N), cov, len(dts)), index = dts, columns = columns )
196 | # Generate an AR(1) process with parameter phi
197 | eps = pd.DataFrame( np.random.multivariate_normal( np.zeros(N), np.eye(N), len(dts)), index = dts, columns=columns)
198 | alpha = 1 - phi
199 | eps.values[1:] = eps.values[1:] / alpha # To cancel out the weighting that ewm puts on the noise term after x0
200 | ar1 = eps.ewm(alpha=alpha, adjust=False).mean()
201 | ar1 *= np.sqrt(1.-phi**2) # Re-scale to unit variance, since the standard AR(1) process has variance sigma_eps/(1-phi^2)
202 | data = 100. + noise.cumsum()*np.sqrt(0.5) + ar1*np.sqrt(0.5)
203 | # With the current setup, the difference between any two series should follow a mean reverting process with std=1
204 | return data
205 |
206 |
207 | def run():
208 | """ Run the code that illustrates the pairs trading strategy """
209 | data = make_data()
210 |
211 | # Define the "entry" strategy of the trade. In this case, we give each asset unit weight and trade it
212 | trade_entry = bt.AlgoStack( bt.algos.RunOnce(), WeighPair(1.), bt.algos.Rebalance() )
213 |
214 | # Define the "exit" strategy of the trade. Here we exit when we cross either an upper/lower
215 | # threshold on the price of the strategy, or hold it for a fixed length of time.
216 | trade_exit = bt.AlgoStack(
217 | bt.algos.Or( [PriceCompare( 96., is_greater=False ),
218 | PriceCompare( 104., is_greater=True),
219 | bt.algos.RunAfterDays( 5 ) ] ),
220 | ClosePositions()
221 | )
222 | # Combine the entry, exit and debug algos for each trade
223 | trade_algos = [ bt.algos.Or( [ trade_entry, trade_exit, DebugTradeLevel() ] )]
224 |
225 | # Define the strategy for the master portfolio.
226 | strategy_algos = [
227 | PairsSignal( threshold = 4., indicator_name = 'my_indicator' ),
228 | SetupPairsTrades( trade_algos ),
229 | SizePairsTrades( pct_of_capital = 0.2 ),
230 | DebugPortfolioLevel()
231 | ]
232 |
233 | # Build and run the strategy
234 | strategy = bt.Strategy( 'PairsStrategy', strategy_algos )
235 | test = bt.Backtest( strategy, data, additional_data={'my_indicator':data} )
236 | out = bt.run( test )
237 | print(out.stats)
238 | return out
239 |
240 | if __name__ == "__main__":
241 | run()
--------------------------------------------------------------------------------
/docs/source/algos.rst:
--------------------------------------------------------------------------------
1 | Algorithms
2 | ==========
3 |
4 | Overview
5 | --------
6 |
7 | One of the core building blocks of bt is the :class:`Algo ` and the
8 | closely related :class:`AlgoStack `.
9 |
10 | .. image:: _static/stack.png
11 | :align: center
12 | :alt: algo stack
13 |
14 | Algos
15 | ~~~~~
16 | An Algo is essentially a function that returns True or False. It takes a single
17 | argument that is the :class:`Strategy ` being tested. An Algo should ideally
18 | only serve one specific purpose. This purpose can control execution flow, it can
19 | control security selection, security allocation, etc. For example,
20 | you can have an Algo that checks if the month has changed (such as
21 | :class:`bt.algos.RunMonthly`). If it has, this Algo return True, if not, False.
22 |
23 | Algo Stacks
24 | ~~~~~~~~~~~
25 | An :class:`AlgoStack ` is a class that groups together many
26 | Algos and runs them one after another as long as each Algo returns True. As soon
27 | as an Algo returns False, the AlgoStack stops its execution and returns False
28 | (an AlgoStack is an Algo after all). This allows us to combine different Algos
29 | together and control the flow of execution with the Algo return value. Many
30 | AlgoStacks can they themselves be included into another AlgoStack should the
31 | need arise.
32 |
33 | By breaking down strategy logic into these small blocks of code, we achieve
34 | testability and reusability - two appealing features when working on software
35 | development.
36 |
37 | Data Passing
38 | ------------
39 |
40 | In order to pass data between different Algos, the Strategy has two properties:
41 | **temp** and **perm**. They are both dictionaries and are used for storing data
42 | generated by Algos. Temporary data is refreshed on each data change whereas
43 | permanent data is not altered.
44 |
45 | Algos usually **set** and/or **require** values in the temp or perm objects. For example,
46 | the :class:`bt.algos.WeighEqually` Algo sets the 'weights' key in temp, and
47 | it requires the 'selected' key in temp.
48 |
49 | For example, let's take a simple select -> weight -> allocate logic chain. We
50 | would break this strategy up into 3 Algos:
51 |
52 | * **selection**
53 | Which securities do I want to allocate capital to out of the entire universe of
54 | investable assets? See, i.e.
55 | :class:`SelectAll `,
56 | :class:`SelectThese `,
57 | :class:`SelectWhere `,
58 | :class:`SelectN `,
59 | etc
60 |
61 | * **weighting**
62 | How much weight should each of the selected securities have in the target
63 | portfolio? See, i.e.
64 | :class:`WeighEqually `,
65 | :class:`WeighRandomly `
66 | :class:`WeighSpecified `,
67 | :class:`WeighTarget `,
68 | :class:`WeighInvVol `,
69 | :class:`WeighMeanVar `,
70 | etc
71 |
72 | * **allocate**
73 | Close out positions that are no longer needed and allocate capital to those
74 | that were selected and given target weights. See, i.e.
75 | :class:`Rebalance `
76 |
77 | In this case, the selection Algo could set the 'selected' key in the strategy's
78 | temp dict, and the weighting Algo could read those values and in turn set the
79 | 'weights' key in the temp dict. The allocation Algo would then read the
80 | 'weights' and act accordingly.
81 |
82 | To extend the simple select -> weight -> allocate logic chain to include an additional
83 | risk/exposure calculation step, one would do this by implementing specific Algos
84 | for this purpose. These could be used either before weighting
85 | (for risk-based portfolio construction) or after
86 | (for reporting). See, e.g. :class:`UpdateRisk `.
87 |
88 | .. note::
89 |
90 | To preserve maximal flexibility, there
91 | are currently no checks to make sure the AlgoStack is valid. Therefore, it is up
92 | to the user and creator of Algos to make sure the requirements and side effects
93 | are well documented and properly used (by the way, this may not be a great way
94 | to go about this problem. If you have a better idea, please let me know!).
95 |
96 | Developers should add a section in the docstring that outlines
97 | the "sets" and the "requires". See the doctrings of
98 | :class:`bt.algos.WeighEqually` for an example.
99 |
100 |
101 | Implementation
102 | --------------
103 |
104 | In most cases, Algos must preserve some kind of state. In this case, it is
105 | easier to implement them as classes and define the __call__ method, like
106 | below::
107 |
108 | class MyAlgo(bt.Algo):
109 |
110 | def __init__(self, arg1, arg2):
111 | self.arg1 = arg1
112 | self.arg2 = arg2
113 |
114 | def __call__(self, target):
115 | # my logic goes here
116 |
117 | # accessing/storing variables through target.temp['key']
118 |
119 | # remember to return a bool - True in most cases
120 | return True
121 |
122 | Note that the attributes on the class should **not** be specific to any particular
123 | target.
124 |
125 | However, for Algos that do not need to preserve any state, you may simply
126 | implement them as a basic function that takes one argument - the Strategy::
127 |
128 | def MyAlgo2(target):
129 | # all the logic
130 |
131 | return True
132 |
133 |
134 | Best Practices
135 | --------------
136 |
137 | Re-usability
138 | ~~~~~~~~~~~~
139 | Recall that Algos should be re-usable across different backtests (including
140 | backtests on different underlying security universes or different time ranges),
141 | and that a Backtest is the logical combination of the strategy and the data.
142 | However, there are cases when the Algo needs to use some extra data that
143 | **does** depend on the security universe or time range (i.e. a data frame of
144 | signals that has been pre-computed).
145 |
146 | The best way to handle this is to construct the Algo with the **name** of the
147 | data, and to instantiate the backtest with this named data::
148 |
149 | class MyAlgo(bt.Algo):
150 |
151 | def __init__(self, signal_name ):
152 | self.signal_name = signal_name
153 |
154 | def __call__(self, target):
155 | # my logic goes here
156 |
157 | # accessing data via target.get_data( self.signal_name )
158 |
159 | # remember to return a bool - True in most cases
160 | return True
161 |
162 | # create the strategy
163 | s = bt.Strategy('s1', [bt.algos.MyAlgo( 'my_signal' )])
164 |
165 | # create a backtest and run it
166 | test = bt.Backtest(s, data, additional_data={'my_signal':signal_df})
167 | res = bt.run(test)
168 |
169 | # Run the same strategy on different data without changing MyAlgo
170 | test = bt.Backtest(s, data2, additional_data={'my_signal':signal_df2})
171 | res = bt.run(test)
172 |
173 | Note that some additional data keys are used by the framework itself to support
174 | additional functionality (i.e. ``bidoffer``, ``coupon``, ``cost_long`` and
175 | ``cost_short``). These are documented in the ``setup`` functions of
176 | :class:`Security ` and
177 | :class:`CouponPayingSecurity `.
178 |
179 |
180 | Debugging
181 | ~~~~~~~~~
182 | The easiest way to debug algos is by adding leveraging one of the existing debug
183 | algos or by writing your own! Just insert them in the appropriate places in your
184 | algo stack, and add breakpoints to examine the state of the passed strategy.
185 |
186 | - :class:`Debug `
187 | - :class:`PrintTempData `
188 | - :class:`PrintInfo `
189 | - :class:`PrintRisk `
190 |
191 |
192 | Branching and Control Flow
193 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
194 | While the Algo setup may seem overly simple (a list of functions which returns
195 | either True or False), this is a powerful construct that allows for complex
196 | branching and conditional structures. In particular, branching is achieved via
197 | the :class:`Or Algo`.
198 |
199 | For example, the code below illustrates how printing of strategy performance can
200 | occur on a different timeline from rebalancing the portfolio. Additional conditions
201 | can be added by placing those algos at the head of the relevant stack.
202 |
203 | .. code:: python
204 |
205 | import bt
206 |
207 | data = bt.get('spy,agg', start='2010-01-01')
208 |
209 | # create two separate algo stacks and combine the branches
210 | logging_stack = bt.AlgoStack(
211 | bt.algos.RunWeekly(),
212 | bt.algos.PrintInfo('{name}:{now}. Value:{_value:0.0f}, Price:{_price:0.4f}')
213 | )
214 | trading_stack = bt.AlgoStack(
215 | bt.algos.RunMonthly(),
216 | bt.algos.SelectAll(),
217 | bt.algos.WeighEqually(),
218 | bt.algos.Rebalance()
219 | )
220 | branch_stack = bt.AlgoStack(
221 | # Upstream algos could go here...
222 | bt.algos.Or( [ logging_stack, trading_stack ] )
223 | # Downstream algos could go here...
224 | )
225 |
226 | s = bt.Strategy('strategy', branch_stack, ['spy', 'agg'])
227 | t = bt.Backtest(s, data)
228 | r = bt.run(t)
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
--------------------------------------------------------------------------------
/docs/source/PTE.rst:
--------------------------------------------------------------------------------
1 | Predicted Tracking Error Rebalance Portfolio
2 | --------------------------------------------
3 |
4 | .. code:: ipython3
5 |
6 | import numpy as np
7 | import pandas as pd
8 | import matplotlib.pyplot as plt
9 |
10 | import ffn
11 | import bt
12 |
13 | %matplotlib inline
14 |
15 | Create Fake Index Data
16 | ~~~~~~~~~~~~~~~~~~~~~~
17 |
18 | .. code:: ipython3
19 |
20 | names = ['foo','bar','rf']
21 | dates = pd.date_range(start='2015-01-01',end='2018-12-31', freq=pd.tseries.offsets.BDay())
22 | n = len(dates)
23 | rdf = pd.DataFrame(
24 | np.zeros((n, len(names))),
25 | index = dates,
26 | columns = names
27 | )
28 |
29 | np.random.seed(1)
30 | rdf['foo'] = np.random.normal(loc = 0.1/252,scale=0.2/np.sqrt(252),size=n)
31 | rdf['bar'] = np.random.normal(loc = 0.04/252,scale=0.05/np.sqrt(252),size=n)
32 | rdf['rf'] = 0.
33 |
34 | pdf = 100*np.cumprod(1+rdf)
35 | pdf.plot();
36 |
37 |
38 |
39 | .. image:: _static/PTE_3_0.png
40 | :class: pynb
41 | :width: 377px
42 | :height: 262px
43 |
44 |
45 | Build and run Target Strategy
46 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
47 |
48 | I will first run a strategy that rebalances everyday.
49 |
50 | Then I will use those weights as target to rebalance to whenever the PTE
51 | is too high.
52 |
53 | .. code:: ipython3
54 |
55 | selectTheseAlgo = bt.algos.SelectThese(['foo','bar'])
56 |
57 | # algo to set the weights to 1/vol contributions from each asset
58 | # with data over the last 3 months excluding yesterday
59 | weighInvVolAlgo = bt.algos.WeighInvVol(
60 | lookback=pd.DateOffset(months=3),
61 | lag=pd.DateOffset(days=1)
62 | )
63 |
64 | # algo to rebalance the current weights to weights set in target.temp
65 | rebalAlgo = bt.algos.Rebalance()
66 |
67 | # a strategy that rebalances daily to 1/vol weights
68 | strat = bt.Strategy(
69 | 'Target',
70 | [
71 | selectTheseAlgo,
72 | weighInvVolAlgo,
73 | rebalAlgo
74 | ]
75 | )
76 |
77 | # set integer_positions=False when positions are not required to be integers(round numbers)
78 | backtest = bt.Backtest(
79 | strat,
80 | pdf,
81 | integer_positions=False
82 | )
83 |
84 | res_target = bt.run(backtest)
85 |
86 | .. code:: ipython3
87 |
88 | res_target.get_security_weights().plot();
89 |
90 |
91 |
92 | .. image:: _static/PTE_6_0.png
93 | :class: pynb
94 | :width: 373px
95 | :height: 262px
96 |
97 |
98 | Now use the PTE rebalance algo to trigger a rebalance whenever predicted
99 | tracking error is greater than 1%.
100 |
101 | .. code:: ipython3
102 |
103 | # algo to fire whenever predicted tracking error is greater than 1%
104 | wdf = res_target.get_security_weights()
105 |
106 | PTE_rebalance_Algo = bt.algos.PTE_Rebalance(
107 | 0.01,
108 | wdf,
109 | lookback=pd.DateOffset(months=3),
110 | lag=pd.DateOffset(days=1),
111 | covar_method='standard',
112 | annualization_factor=252
113 | )
114 |
115 | selectTheseAlgo = bt.algos.SelectThese(['foo','bar'])
116 |
117 | # algo to set the weights to 1/vol contributions from each asset
118 | # with data over the last 12 months excluding yesterday
119 | weighTargetAlgo = bt.algos.WeighTarget(
120 | wdf
121 | )
122 |
123 | rebalAlgo = bt.algos.Rebalance()
124 |
125 | # a strategy that rebalances monthly to specified weights
126 | strat = bt.Strategy(
127 | 'PTE',
128 | [
129 | PTE_rebalance_Algo,
130 | selectTheseAlgo,
131 | weighTargetAlgo,
132 | rebalAlgo
133 | ]
134 | )
135 |
136 | # set integer_positions=False when positions are not required to be integers(round numbers)
137 | backtest = bt.Backtest(
138 | strat,
139 | pdf,
140 | integer_positions=False
141 | )
142 |
143 | res_PTE = bt.run(backtest)
144 |
145 | .. code:: ipython3
146 |
147 | fig, ax = plt.subplots(nrows=1,ncols=1)
148 | res_target.get_security_weights().plot(ax=ax)
149 |
150 | realized_weights_df = res_PTE.get_security_weights()
151 | realized_weights_df['PTE foo'] = realized_weights_df['foo']
152 | realized_weights_df['PTE bar'] = realized_weights_df['bar']
153 | realized_weights_df = realized_weights_df.loc[:,['PTE foo', 'PTE bar']]
154 | realized_weights_df.plot(ax=ax)
155 |
156 | ax.set_title('Target Weights vs PTE Weights')
157 | ax.plot();
158 |
159 |
160 |
161 | .. image:: _static/PTE_9_0.png
162 | :class: pynb
163 | :width: 373px
164 | :height: 277px
165 |
166 |
167 | .. code:: ipython3
168 |
169 | trans_df = pd.DataFrame(
170 | index=res_target.prices.index,
171 | columns=['Target','PTE']
172 | )
173 |
174 | transactions = res_target.get_transactions()
175 | transactions = (transactions['quantity'] * transactions['price']).reset_index()
176 |
177 | bar_mask = transactions.loc[:,'Security'] == 'bar'
178 | foo_mask = transactions.loc[:,'Security'] == 'foo'
179 |
180 | trans_df.loc[trans_df.index[4:],'Target'] = np.abs(transactions[bar_mask].iloc[:,2].values) + np.abs(transactions[foo_mask].iloc[:,2].values)
181 |
182 |
183 | .. code:: ipython3
184 |
185 | transactions = res_PTE.get_transactions()
186 | transactions = (transactions['quantity'] * transactions['price']).reset_index()
187 |
188 | bar_mask = transactions.loc[:,'Security'] == 'bar'
189 | foo_mask = transactions.loc[:,'Security'] == 'foo'
190 |
191 | trans_df.loc[transactions[bar_mask].iloc[:,0],'PTE'] = np.abs(transactions[bar_mask].iloc[:,2].values)
192 | trans_df.loc[transactions[foo_mask].iloc[:,0],'PTE'] += np.abs(transactions[foo_mask].iloc[:,2].values)
193 |
194 |
195 | .. code:: ipython3
196 |
197 | trans_df = trans_df.fillna(0)
198 |
199 | .. code:: ipython3
200 |
201 | fig, ax = plt.subplots(nrows=1,ncols=1)
202 | trans_df.cumsum().plot(ax=ax)
203 | ax.set_title('Cumulative sum of notional traded')
204 | ax.plot();
205 |
206 |
207 |
208 | .. image:: _static/PTE_13_0.png
209 | :class: pynb
210 | :width: 373px
211 | :height: 277px
212 |
213 |
214 | If we plot the total risk contribution of each asset class and divide by
215 | the total volatility, then we can see that both strategy’s contribute
216 | roughly similar amounts of volatility from both of the securities.
217 |
218 | .. code:: ipython3
219 |
220 | weights_target = res_target.get_security_weights()
221 | rolling_cov_target = pdf.loc[:,weights_target.columns].pct_change().rolling(window=3*20).cov()*252
222 |
223 | weights_PTE = res_PTE.get_security_weights().loc[:,weights_target.columns]
224 | rolling_cov_PTE = pdf.loc[:,weights_target.columns].pct_change().rolling(window=3*20).cov()*252
225 |
226 |
227 | trc_target = pd.DataFrame(
228 | np.nan,
229 | index = weights_target.index,
230 | columns = weights_target.columns
231 | )
232 |
233 | trc_PTE = pd.DataFrame(
234 | np.nan,
235 | index = weights_PTE.index,
236 | columns = [x + " PTE" for x in weights_PTE.columns]
237 | )
238 |
239 | for dt in pdf.index:
240 | trc_target.loc[dt,:] = weights_target.loc[dt,:].values*(rolling_cov_target.loc[dt,:].values@weights_target.loc[dt,:].values)/np.sqrt(weights_target.loc[dt,:].values@rolling_cov_target.loc[dt,:].values@weights_target.loc[dt,:].values)
241 | trc_PTE.loc[dt,:] = weights_PTE.loc[dt,:].values*(rolling_cov_PTE.loc[dt,:].values@weights_PTE.loc[dt,:].values)/np.sqrt(weights_PTE.loc[dt,:].values@rolling_cov_PTE.loc[dt,:].values@weights_PTE.loc[dt,:].values)
242 |
243 |
244 | fig, ax = plt.subplots(nrows=1,ncols=1)
245 | trc_target.plot(ax=ax)
246 | trc_PTE.plot(ax=ax)
247 | ax.set_title('Total Risk Contribution')
248 | ax.plot();
249 |
250 |
251 |
252 | .. image:: _static/PTE_15_0.png
253 | :class: pynb
254 | :width: 386px
255 | :height: 277px
256 |
257 |
258 | Looking at the Target strategy’s and PTE strategy’s Total Risk they are
259 | very similar.
260 |
261 | .. code:: ipython3
262 |
263 | fig, ax = plt.subplots(nrows=1,ncols=1)
264 | trc_target.sum(axis=1).plot(ax=ax,label='Target')
265 | trc_PTE.sum(axis=1).plot(ax=ax,label='PTE')
266 | ax.legend()
267 | ax.set_title('Total Risk')
268 | ax.plot();
269 |
270 |
271 |
272 | .. image:: _static/PTE_17_0.png
273 | :class: pynb
274 | :width: 380px
275 | :height: 277px
276 |
277 |
278 | .. code:: ipython3
279 |
280 | transactions = res_PTE.get_transactions()
281 | transactions = (transactions['quantity'] * transactions['price']).reset_index()
282 |
283 | bar_mask = transactions.loc[:,'Security'] == 'bar'
284 | dates_of_PTE_transactions = transactions[bar_mask].iloc[:,0]
285 | dates_of_PTE_transactions
286 |
287 |
288 |
289 |
290 | .. parsed-literal::
291 | :class: pynb-result
292 |
293 | 0 2015-01-06
294 | 2 2015-01-07
295 | 4 2015-01-08
296 | 6 2015-01-09
297 | 8 2015-01-12
298 | 10 2015-02-20
299 | 12 2015-04-07
300 | 14 2015-09-01
301 | 16 2017-03-23
302 | 18 2017-06-23
303 | 20 2017-10-24
304 | Name: Date, dtype: datetime64[ns]
305 |
306 |
307 |
308 | .. code:: ipython3
309 |
310 | fig, ax = plt.subplots(nrows=1,ncols=1)
311 | np.sum(np.abs(trc_target.values - trc_PTE.values))
312 | #.abs().sum(axis=1).plot()
313 |
314 | ax.set_title('Total Risk')
315 | ax.plot(
316 | trc_target.index,
317 | np.sum(np.abs(trc_target.values - trc_PTE.values),axis=1),
318 | label='PTE'
319 | )
320 |
321 | for i,dt in enumerate(dates_of_PTE_transactions):
322 | if i == 0:
323 | ax.axvline(x=dt,color='red',label='PTE Transaction')
324 | else:
325 | ax.axvline(x=dt,color='red')
326 |
327 | ax.legend();
328 |
329 |
330 |
331 |
332 | .. image:: _static/PTE_19_0.png
333 | :class: pynb
334 | :width: 397px
335 | :height: 266px
336 |
337 |
338 | We can see the Predicted Tracking Error of the PTE Strategy with each
339 | transaction marked.
340 |
341 |
--------------------------------------------------------------------------------