├── tests ├── __init__.py └── bench.py ├── docs ├── source │ ├── ERC.ipynb │ ├── PTE.ipynb │ ├── Trend_1.ipynb │ ├── Trend_2.ipynb │ ├── _themes │ │ └── klink │ │ │ ├── MANIFEST.in │ │ │ ├── klink │ │ │ ├── less │ │ │ │ ├── vendor │ │ │ │ │ └── font-awesome │ │ │ │ │ │ ├── less │ │ │ │ │ │ ├── extras.less │ │ │ │ │ │ ├── fixed-width.less │ │ │ │ │ │ ├── core.less │ │ │ │ │ │ ├── bordered-pulled.less │ │ │ │ │ │ ├── rotated-flipped.less │ │ │ │ │ │ ├── larger.less │ │ │ │ │ │ ├── list.less │ │ │ │ │ │ ├── font-awesome.less │ │ │ │ │ │ ├── stacked.less │ │ │ │ │ │ ├── path.less │ │ │ │ │ │ ├── mixins.less │ │ │ │ │ │ └── spinning.less │ │ │ │ │ │ └── fonts │ │ │ │ │ │ ├── FontAwesome.otf │ │ │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ │ │ └── fontawesome-webfont.woff │ │ │ │ └── klink.less │ │ │ ├── static │ │ │ │ ├── img │ │ │ │ │ ├── logo.png │ │ │ │ │ └── favicon.ico │ │ │ │ └── fonts │ │ │ │ │ ├── FontAwesome.otf │ │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ │ └── fontawesome-webfont.woff │ │ │ ├── theme.conf │ │ │ ├── demo.py │ │ │ ├── layout.html │ │ │ └── __init__.py │ │ │ ├── docs │ │ │ ├── source │ │ │ │ ├── _static │ │ │ │ │ ├── logo.png │ │ │ │ │ ├── favicon.ico │ │ │ │ │ ├── intro_3_1.png │ │ │ │ │ ├── intro_4_1.png │ │ │ │ │ ├── nb-examples_3_1.png │ │ │ │ │ └── nb-examples_4_1.png │ │ │ │ ├── klink.rst │ │ │ │ ├── intro.rst │ │ │ │ ├── index.rst │ │ │ │ ├── install.rst │ │ │ │ ├── nb-examples.rst │ │ │ │ ├── examples.rst │ │ │ │ └── conf.py │ │ │ ├── make.bat │ │ │ └── Makefile │ │ │ ├── setup.py │ │ │ ├── Makefile │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ └── README.rst │ ├── Buy_and_hold.ipynb │ ├── Fixed_Income.ipynb │ ├── Target_Volatility.ipynb │ ├── Strategy_Combination.ipynb │ ├── modules.rst │ ├── _static │ │ ├── logo.png │ │ ├── stack.png │ │ ├── tree1.png │ │ ├── tree2.png │ │ ├── ERC_3_1.png │ │ ├── ERC_4_0.png │ │ ├── ERC_6_1.png │ │ ├── ERC_7_0.png │ │ ├── ERC_7_1.png │ │ ├── ERC_8_0.png │ │ ├── ERC_8_1.png │ │ ├── ERC_9_0.png │ │ ├── PTE_2_1.png │ │ ├── PTE_3_0.png │ │ ├── PTE_5_1.png │ │ ├── PTE_6_0.png │ │ ├── PTE_8_1.png │ │ ├── PTE_9_0.png │ │ ├── favicon.ico │ │ ├── PTE_12_1.png │ │ ├── PTE_13_0.png │ │ ├── PTE_14_1.png │ │ ├── PTE_15_0.png │ │ ├── PTE_16_1.png │ │ ├── PTE_17_0.png │ │ ├── PTE_18_1.png │ │ ├── PTE_19_0.png │ │ ├── intro_11_0.png │ │ ├── intro_12_0.png │ │ ├── intro_14_0.png │ │ ├── intro_9_0.png │ │ ├── Trend_1_11_0.png │ │ ├── Trend_1_12_0.png │ │ ├── Trend_1_2_1.png │ │ ├── Trend_1_3_0.png │ │ ├── Trend_1_4_0.png │ │ ├── Trend_1_5_0.png │ │ ├── Trend_2_1_1.png │ │ ├── Trend_2_2_0.png │ │ ├── Trend_2_4_0.png │ │ ├── Trend_2_5_0.png │ │ ├── examples-nb_5_0.png │ │ ├── examples-nb_6_0.png │ │ ├── Buy_and_hold_10_0.png │ │ ├── Buy_and_hold_11_1.png │ │ ├── Buy_and_hold_12_0.png │ │ ├── Buy_and_hold_13_1.png │ │ ├── Buy_and_hold_14_0.png │ │ ├── Buy_and_hold_2_1.png │ │ ├── Buy_and_hold_3_0.png │ │ ├── Buy_and_hold_9_0.png │ │ ├── Fixed_Income_13_0.png │ │ ├── Fixed_Income_13_1.png │ │ ├── Fixed_Income_14_0.png │ │ ├── Fixed_Income_14_1.png │ │ ├── Fixed_Income_15_0.png │ │ ├── Fixed_Income_15_1.png │ │ ├── Fixed_Income_18_0.png │ │ ├── Fixed_Income_18_1.png │ │ ├── Fixed_Income_19_0.png │ │ ├── Fixed_Income_19_1.png │ │ ├── Fixed_Income_20_0.png │ │ ├── Fixed_Income_20_1.png │ │ ├── Fixed_Income_21_0.png │ │ ├── Fixed_Income_21_1.png │ │ ├── examples-nb_11_0.png │ │ ├── examples-nb_12_0.png │ │ ├── examples-nb_17_0.png │ │ ├── examples-nb_18_0.png │ │ ├── examples-nb_25_0.png │ │ ├── examples-nb_26_0.png │ │ ├── examples-nb_28_0.png │ │ ├── examples-nb_29_0.png │ │ ├── examples-nb_31_0.png │ │ ├── examples-nb_32_0.png │ │ ├── examples-nb_33_0.png │ │ ├── examples-nb_35_0.png │ │ ├── examples-nb_36_0.png │ │ ├── examples-nb_37_0.png │ │ ├── Target_Volatility_10_1.png │ │ ├── Target_Volatility_11_0.png │ │ ├── Target_Volatility_12_1.png │ │ ├── Target_Volatility_13_0.png │ │ ├── Target_Volatility_2_1.png │ │ ├── Target_Volatility_3_0.png │ │ ├── Target_Volatility_8_1.png │ │ ├── Target_Volatility_9_0.png │ │ ├── Strategy_Combination_10_0.png │ │ ├── Strategy_Combination_12_0.png │ │ ├── Strategy_Combination_12_1.png │ │ ├── Strategy_Combination_13_0.png │ │ ├── Strategy_Combination_13_1.png │ │ ├── Strategy_Combination_3_0.png │ │ ├── Strategy_Combination_3_1.png │ │ ├── Strategy_Combination_6_0.png │ │ ├── Strategy_Combination_6_1.png │ │ ├── Strategy_Combination_7_0.png │ │ └── Strategy_Combination_7_1.png │ ├── examples.rst │ ├── bt.rst │ ├── install.rst │ ├── index.rst │ ├── ERC.rst │ ├── Target_Volatility.rst │ ├── Strategy_Combination.rst │ ├── tree.rst │ ├── intro.rst │ ├── conf.py │ ├── algos.rst │ └── PTE.rst ├── make.bat └── Makefile ├── pyproject.toml ├── .github ├── dependabot.yml └── workflows │ ├── regression.yml │ ├── build.yml │ └── deploy.yml ├── bt └── __init__.py ├── MANIFEST.in ├── .gitignore ├── LICENSE ├── Makefile ├── setup.py ├── examples ├── buy_and_hold.py └── pairs_trading.py └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/ERC.ipynb: -------------------------------------------------------------------------------- 1 | ../../examples/ERC.ipynb -------------------------------------------------------------------------------- /docs/source/PTE.ipynb: -------------------------------------------------------------------------------- 1 | ../../examples/PTE.ipynb -------------------------------------------------------------------------------- /docs/source/Trend_1.ipynb: -------------------------------------------------------------------------------- 1 | ../../examples/trend_1.ipynb -------------------------------------------------------------------------------- /docs/source/Trend_2.ipynb: -------------------------------------------------------------------------------- 1 | ../../examples/trend_2.ipynb -------------------------------------------------------------------------------- /docs/source/_themes/klink/MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft klink 2 | -------------------------------------------------------------------------------- /docs/source/Buy_and_hold.ipynb: -------------------------------------------------------------------------------- 1 | ../../examples/buy_and_hold.ipynb -------------------------------------------------------------------------------- /docs/source/Fixed_Income.ipynb: -------------------------------------------------------------------------------- 1 | ../../examples/fixed_income.ipynb -------------------------------------------------------------------------------- /docs/source/Target_Volatility.ipynb: -------------------------------------------------------------------------------- 1 | ../../examples/Target_Volatility.ipynb -------------------------------------------------------------------------------- /docs/source/Strategy_Combination.ipynb: -------------------------------------------------------------------------------- 1 | ../../examples/Strategy_Combination.ipynb -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | bt 2 | == 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | bt 8 | -------------------------------------------------------------------------------- /docs/source/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/logo.png -------------------------------------------------------------------------------- /docs/source/_static/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/stack.png -------------------------------------------------------------------------------- /docs/source/_static/tree1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/tree1.png -------------------------------------------------------------------------------- /docs/source/_static/tree2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/tree2.png -------------------------------------------------------------------------------- /docs/source/_static/ERC_3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/ERC_3_1.png -------------------------------------------------------------------------------- /docs/source/_static/ERC_4_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/ERC_4_0.png -------------------------------------------------------------------------------- /docs/source/_static/ERC_6_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/ERC_6_1.png -------------------------------------------------------------------------------- /docs/source/_static/ERC_7_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/ERC_7_0.png -------------------------------------------------------------------------------- /docs/source/_static/ERC_7_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/ERC_7_1.png -------------------------------------------------------------------------------- /docs/source/_static/ERC_8_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/ERC_8_0.png -------------------------------------------------------------------------------- /docs/source/_static/ERC_8_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/ERC_8_1.png -------------------------------------------------------------------------------- /docs/source/_static/ERC_9_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/ERC_9_0.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_2_1.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_3_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_3_0.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_5_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_5_1.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_6_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_6_0.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_8_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_8_1.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_9_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_9_0.png -------------------------------------------------------------------------------- /docs/source/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/favicon.ico -------------------------------------------------------------------------------- /docs/source/_static/PTE_12_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_12_1.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_13_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_13_0.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_14_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_14_1.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_15_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_15_0.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_16_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_16_1.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_17_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_17_0.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_18_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_18_1.png -------------------------------------------------------------------------------- /docs/source/_static/PTE_19_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/PTE_19_0.png -------------------------------------------------------------------------------- /docs/source/_static/intro_11_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/intro_11_0.png -------------------------------------------------------------------------------- /docs/source/_static/intro_12_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/intro_12_0.png -------------------------------------------------------------------------------- /docs/source/_static/intro_14_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/intro_14_0.png -------------------------------------------------------------------------------- /docs/source/_static/intro_9_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/intro_9_0.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_1_11_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_1_11_0.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_1_12_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_1_12_0.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_1_2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_1_2_1.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_1_3_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_1_3_0.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_1_4_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_1_4_0.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_1_5_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_1_5_0.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_2_1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_2_1_1.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_2_2_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_2_2_0.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_2_4_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_2_4_0.png -------------------------------------------------------------------------------- /docs/source/_static/Trend_2_5_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Trend_2_5_0.png -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/extras.less: -------------------------------------------------------------------------------- 1 | // Extras 2 | // -------------------------- 3 | -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_5_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_5_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_6_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_6_0.png -------------------------------------------------------------------------------- /docs/source/_static/Buy_and_hold_10_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Buy_and_hold_10_0.png -------------------------------------------------------------------------------- /docs/source/_static/Buy_and_hold_11_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Buy_and_hold_11_1.png -------------------------------------------------------------------------------- /docs/source/_static/Buy_and_hold_12_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Buy_and_hold_12_0.png -------------------------------------------------------------------------------- /docs/source/_static/Buy_and_hold_13_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Buy_and_hold_13_1.png -------------------------------------------------------------------------------- /docs/source/_static/Buy_and_hold_14_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Buy_and_hold_14_0.png -------------------------------------------------------------------------------- /docs/source/_static/Buy_and_hold_2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Buy_and_hold_2_1.png -------------------------------------------------------------------------------- /docs/source/_static/Buy_and_hold_3_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Buy_and_hold_3_0.png -------------------------------------------------------------------------------- /docs/source/_static/Buy_and_hold_9_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Buy_and_hold_9_0.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_13_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_13_0.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_13_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_13_1.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_14_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_14_0.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_14_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_14_1.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_15_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_15_0.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_15_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_15_1.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_18_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_18_0.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_18_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_18_1.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_19_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_19_0.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_19_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_19_1.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_20_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_20_0.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_20_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_20_1.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_21_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_21_0.png -------------------------------------------------------------------------------- /docs/source/_static/Fixed_Income_21_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Fixed_Income_21_1.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_11_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_11_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_12_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_12_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_17_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_17_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_18_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_18_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_25_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_25_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_26_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_26_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_28_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_28_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_29_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_29_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_31_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_31_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_32_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_32_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_33_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_33_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_35_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_35_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_36_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_36_0.png -------------------------------------------------------------------------------- /docs/source/_static/examples-nb_37_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/examples-nb_37_0.png -------------------------------------------------------------------------------- /docs/source/_static/Target_Volatility_10_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Target_Volatility_10_1.png -------------------------------------------------------------------------------- /docs/source/_static/Target_Volatility_11_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Target_Volatility_11_0.png -------------------------------------------------------------------------------- /docs/source/_static/Target_Volatility_12_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Target_Volatility_12_1.png -------------------------------------------------------------------------------- /docs/source/_static/Target_Volatility_13_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Target_Volatility_13_0.png -------------------------------------------------------------------------------- /docs/source/_static/Target_Volatility_2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Target_Volatility_2_1.png -------------------------------------------------------------------------------- /docs/source/_static/Target_Volatility_3_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Target_Volatility_3_0.png -------------------------------------------------------------------------------- /docs/source/_static/Target_Volatility_8_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Target_Volatility_8_1.png -------------------------------------------------------------------------------- /docs/source/_static/Target_Volatility_9_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Target_Volatility_9_0.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_10_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_10_0.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_12_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_12_0.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_12_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_12_1.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_13_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_13_0.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_13_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_13_1.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_3_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_3_0.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_3_1.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_6_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_6_0.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_6_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_6_1.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_7_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_7_0.png -------------------------------------------------------------------------------- /docs/source/_static/Strategy_Combination_7_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_static/Strategy_Combination_7_1.png -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/static/img/logo.png -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/docs/source/_static/logo.png -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/docs/source/_static/favicon.ico -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/_static/intro_3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/docs/source/_static/intro_3_1.png -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/_static/intro_4_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/docs/source/_static/intro_4_1.png -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = css/klink.css 4 | 5 | [options] 6 | analytics_id = 7 | logo = logo.png 8 | github = 9 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/_static/nb-examples_3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/docs/source/_static/nb-examples_3_1.png -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/_static/nb-examples_4_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/docs/source/_static/nb-examples_4_1.png -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/less/vendor/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "cython>=0.29.25"] 3 | 4 | [tool.ruff] 5 | line-length = 180 6 | 7 | [tool.ruff.lint.per-file-ignores] 8 | "__init__.py" = ["F401", "F403"] 9 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/less/vendor/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/less/vendor/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmorissette/bt/HEAD/docs/source/_themes/klink/klink/less/vendor/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "pip" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | 13 | -------------------------------------------------------------------------------- /bt/__init__.py: -------------------------------------------------------------------------------- 1 | import ffn 2 | from ffn import data, get, merge, utils 3 | 4 | from . import algos, backtest, core 5 | from .backtest import Backtest, run 6 | from .core import Algo, AlgoStack, CouponPayingHedgeSecurity, CouponPayingSecurity, FixedIncomeSecurity, FixedIncomeStrategy, HedgeSecurity, Security, Strategy 7 | 8 | __version__ = "1.1.2" 9 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/klink.rst: -------------------------------------------------------------------------------- 1 | klink API 2 | ========= 3 | 4 | :mod:`klink` Package 5 | -------------------- 6 | 7 | .. automodule:: klink.__init__ 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | 13 | .. automodule:: klink.demo 14 | :members: 15 | :undoc-members: 16 | :show-inheritance: 17 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft ffn 2 | include LICENSE 3 | include README.md 4 | include pyproject.toml 5 | 6 | prune docs 7 | prune tests 8 | 9 | # Patterns to exclude from any directory 10 | global-exclude *~ 11 | global-exclude *.pyc 12 | global-exclude *.pyo 13 | global-exclude .git 14 | global-exclude .ipynb_checkpoints 15 | global-exclude .mypy_cache 16 | global-exclude .DS_Store 17 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font-family: FontAwesome; 7 | font-style: normal; 8 | font-weight: normal; 9 | line-height: 1; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | -------------------------------------------------------------------------------- /docs/source/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | Here are a few examples to give you a better idea of what **bt** is all about. 5 | 6 | .. include:: examples-nb.rst 7 | .. include:: Buy_and_Hold.rst 8 | .. include:: Trend_1.rst 9 | .. include:: Trend_2.rst 10 | .. include:: Strategy_Combination.rst 11 | .. include:: Target_Volatility-nb.rst 12 | .. include:: ERC.rst 13 | .. include:: PTE.rst 14 | .. include:: Fixed_Income.rst 15 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from klink import __version__ 3 | 4 | setup( 5 | name='klink', 6 | version=__version__, 7 | url='https://github.com/pmorissette/klink', 8 | description='Klink is a simple and clean theme for creating Sphinx docs, inspired by jrnl', 9 | license='MIT', 10 | author='Philippe Morissette', 11 | author_email='morissette.philippe@gmail.com', 12 | packages=['klink'] 13 | ) 14 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .@{fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: -@fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "spinning.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /docs/source/bt.rst: -------------------------------------------------------------------------------- 1 | bt Package 2 | ========== 3 | 4 | :mod:`bt` Package 5 | ----------------- 6 | 7 | .. automodule:: bt.__init__ 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`algos` Module 13 | ------------------- 14 | 15 | .. automodule:: bt.algos 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`backtest` Module 21 | ---------------------- 22 | 23 | .. automodule:: bt.backtest 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`core` Module 29 | ------------------ 30 | 31 | .. automodule:: bt.core 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}')"; 7 | src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype')", 8 | ~"url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff')", 9 | ~"url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype')", 10 | ~"url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg')"; 11 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon-rotate(@degrees, @rotation) { 5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 6 | -webkit-transform: rotate(@degrees); 7 | -moz-transform: rotate(@degrees); 8 | -ms-transform: rotate(@degrees); 9 | -o-transform: rotate(@degrees); 10 | transform: rotate(@degrees); 11 | } 12 | 13 | .fa-icon-flip(@horiz, @vert, @rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 15 | -webkit-transform: scale(@horiz, @vert); 16 | -moz-transform: scale(@horiz, @vert); 17 | -ms-transform: scale(@horiz, @vert); 18 | -o-transform: scale(@horiz, @vert); 19 | transform: scale(@horiz, @vert); 20 | } 21 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/Makefile: -------------------------------------------------------------------------------- 1 | TMPREPO=/tmp/docs/klink 2 | 3 | .PHONY: clean css docs serve pages dist 4 | 5 | clean: 6 | - rm -rf build 7 | - rm -rf dist 8 | - rm -rf klink.egg-info 9 | 10 | css: 11 | lessc --clean-css klink/less/klink.less klink/static/css/klink.css 12 | - cp klink/static/css/klink.css docs/build/html/_static/css/klink.css 13 | 14 | docs: css 15 | $(MAKE) -C docs/ clean 16 | $(MAKE) -C docs/ html 17 | 18 | serve: 19 | cd docs/build/html; \ 20 | python -m SimpleHTTPServer 9090 21 | 22 | pages: 23 | - rm -rf $(TMPREPO) 24 | git clone -b gh-pages git@github.com:pmorissette/klink.git $(TMPREPO) 25 | rm -rf $(TMPREPO)/* 26 | cp -r docs/build/html/* $(TMPREPO) 27 | cd $(TMPREPO); \ 28 | git add -A ; \ 29 | git commit -a -m 'auto-updating docs' ; \ 30 | git push 31 | 32 | dist: 33 | python setup.py sdist upload 34 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | 4 | # C extensions & files 5 | *.so 6 | *.c 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | 26 | # Installer logs 27 | pip-log.txt 28 | pip-delete-this-directory.txt 29 | 30 | # Unit test / coverage reports 31 | htmlcov/ 32 | .tox/ 33 | .coverage 34 | .cache 35 | nosetests.xml 36 | coverage.xml 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Mr Developer 42 | .mr.developer.cfg 43 | .project 44 | .pydevproject 45 | 46 | # Rope 47 | .ropeproject 48 | 49 | # Django stuff: 50 | *.log 51 | *.pot 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # vim .swp 57 | *.swp 58 | 59 | # notebooks 60 | docs/source/.ipynb_checkpoints/ 61 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/less/vendor/font-awesome/less/spinning.less: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: spin 2s infinite linear; 6 | -moz-animation: spin 2s infinite linear; 7 | -o-animation: spin 2s infinite linear; 8 | animation: spin 2s infinite linear; 9 | } 10 | 11 | @-moz-keyframes spin { 12 | 0% { -moz-transform: rotate(0deg); } 13 | 100% { -moz-transform: rotate(359deg); } 14 | } 15 | @-webkit-keyframes spin { 16 | 0% { -webkit-transform: rotate(0deg); } 17 | 100% { -webkit-transform: rotate(359deg); } 18 | } 19 | @-o-keyframes spin { 20 | 0% { -o-transform: rotate(0deg); } 21 | 100% { -o-transform: rotate(359deg); } 22 | } 23 | @keyframes spin { 24 | 0% { 25 | -webkit-transform: rotate(0deg); 26 | transform: rotate(0deg); 27 | } 28 | 100% { 29 | -webkit-transform: rotate(359deg); 30 | transform: rotate(359deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions & files 6 | *.so 7 | *.c 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | bin/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | MANIFEST 26 | 27 | # Installer logs 28 | pip-log.txt 29 | pip-delete-this-directory.txt 30 | 31 | # Unit test / coverage reports 32 | htmlcov/ 33 | .tox/ 34 | .coverage 35 | .cache 36 | nosetests.xml 37 | coverage.xml 38 | python_junit.xml 39 | 40 | # Translations 41 | *.mo 42 | 43 | # Mr Developer 44 | .mr.developer.cfg 45 | .project 46 | .pydevproject 47 | 48 | # Rope 49 | .ropeproject 50 | 51 | # Django stuff: 52 | *.log 53 | *.pot 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # vim .swp 59 | *.swp 60 | 61 | # notebooks 62 | docs/source/.ipynb_checkpoints/ 63 | 64 | # ignore PyCharm and IntelliJ project metadata 65 | /.idea/ 66 | *.iml 67 | 68 | # ignore vs code 69 | .vscode 70 | 71 | # macOS 72 | .DS_Store 73 | 74 | # jupyter 75 | .ipynb_checkpoints -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Philippe Morissette 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Philippe Morissette 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/regression.yml: -------------------------------------------------------------------------------- 1 | name: Regression and Version Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | runs-on: ${{ matrix.os }} 17 | environment: dev 18 | 19 | strategy: 20 | matrix: 21 | python-version: [3.9] 22 | os: [ubuntu-latest] 23 | pandas_version: 24 | - '>=2.2' 25 | - '<2' 26 | numpy_version: 27 | - '<2' 28 | - '>=2' 29 | exclude: 30 | - numpy_version: '>=2' 31 | pandas_version: '<2' 32 | 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v6 36 | 37 | - name: Set up Python ${{ matrix.python-version }} 38 | uses: actions/setup-python@v6 39 | with: 40 | python-version: ${{ matrix.python-version }} 41 | cache: 'pip' 42 | cache-dependency-path: 'setup.py' 43 | 44 | - name: Install dependencies 45 | run: make develop 46 | 47 | - name: Test 48 | run: make test 49 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/demo.py: -------------------------------------------------------------------------------- 1 | class TestClass(object): 2 | 3 | """ 4 | This is a test class. Used for demo purposes. 5 | 6 | Lorem ipsum dolor sit amet, cu vero viris mollis his. Ex est 7 | iusto constituam. Id eam graeci iuvaret facilis, erant dicunt in 8 | quo, te iudico periculis interpretaris sed. Audire tibique te pro, 9 | equidem repudiare an vim. Nostrum placerat liberavisse ne eam. 10 | Zril corpora expetenda ex mea, at wisi vitae vocibus vix. 11 | 12 | .. note:: 13 | 14 | Does not actually do anything. Just for demo. 15 | 16 | Args: 17 | name (str): Name arg 18 | age (int): Age arg 19 | 20 | """ 21 | 22 | def __init__(self, name, age): 23 | self.name = name 24 | self.age = age 25 | 26 | def method1(self, arg1, arg2): 27 | """ 28 | Does stuff with args. 29 | 30 | Demo method1 - this method does stuff. Interesting right? 31 | 32 | Args: 33 | arg1 (type): The first arg needed to do stuff. 34 | arg2 (type): The second arg needed to do stuff. 35 | 36 | Returns: 37 | str - stuff string 38 | 39 | """ 40 | pass 41 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Status 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, macos-latest, windows-latest] 20 | python-version: [3.11] 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v6 25 | 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v6 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | cache: 'pip' 31 | cache-dependency-path: 'setup.py' 32 | 33 | - name: Install dependencies 34 | run: make develop 35 | 36 | - name: Lint 37 | run: make lint 38 | 39 | - name: Test 40 | run: make test 41 | 42 | - name: Package and check 43 | run: make dist 44 | 45 | - name: Install cibuildwheel 46 | run: python -m pip install cibuildwheel==2.23.2 47 | 48 | - name: Build wheels 49 | run: python -m cibuildwheel --output-dir dist 50 | env: 51 | CIBW_BUILD: "cp311-*" 52 | 53 | - name: Check Wheels 54 | run: twine check dist/* 55 | 56 | - uses: actions/upload-artifact@v5 57 | with: 58 | path: ./dist/*.whl 59 | name: wheel-test-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }} 60 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TMPREPO=/tmp/docs/bt 2 | 3 | default: build_dev 4 | 5 | .PHONY: dist upload docs pages serve klink notebooks test lint fix develop 6 | 7 | develop: 8 | python -m pip install -e .[dev] 9 | 10 | test: 11 | python -m pytest -vvv tests --cov=bt --junitxml=python_junit.xml --cov-report=xml --cov-branch --cov-report term 12 | 13 | lint: 14 | python -m ruff check bt setup.py docs/source/conf.py 15 | python -m ruff format --check bt setup.py docs/source/conf.py 16 | 17 | fix: 18 | python -m ruff check --fix bt setup.py docs/source/conf.py 19 | python -m ruff format bt setup.py docs/source/conf.py 20 | 21 | dist: 22 | python setup.py sdist 23 | python -m twine check dist/* 24 | 25 | upload: dist 26 | python -m twine upload dist/* --skip-existing 27 | 28 | docs: 29 | $(MAKE) -C docs/ clean 30 | $(MAKE) -C docs/ html 31 | 32 | pages: 33 | rm -rf $(TMPREPO) 34 | git clone -b gh-pages git@github.com:pmorissette/bt.git $(TMPREPO) 35 | rm -rf $(TMPREPO)/* 36 | cp -r docs/build/html/* $(TMPREPO) 37 | cd $(TMPREPO);\ 38 | git add -A ;\ 39 | git commit -a -m 'auto-updating docs' ;\ 40 | git push 41 | 42 | serve: 43 | cd docs/build/html; \ 44 | python -m http.server 9087 45 | 46 | build_dev: 47 | python setup.py build_ext --inplace 48 | 49 | clean: 50 | rm -rf build 51 | rm -rf dist 52 | rm -rf bt.egg-info 53 | find . -name '*.so' -delete 54 | find . -name '*.c' -delete 55 | 56 | klink: 57 | git subtree pull --prefix=docs/source/_themes/klink --squash klink master 58 | 59 | notebooks: 60 | cd docs/source; \ 61 | jupyter notebook --no-browser --ip=* 62 | -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Installing bt 5 | ------------- 6 | 7 | The easiest way to install ``bt`` is from the `Python Package Index `_ 8 | using ``pip``: 9 | 10 | .. code-block:: bash 11 | 12 | $ pip install bt 13 | 14 | Since ``bt`` has many dependencies, we strongly recommend installing the `Anaconda Scientific Python 15 | Distribution `_, especially on Windows. This distribution 16 | comes with many of the required packages pre-installed, including pip. Once Anaconda is installed, the above 17 | command should complete the installation. 18 | 19 | ``bt`` is also available on `Conda Forge `_, and installable via: 20 | 21 | .. code-block:: bash 22 | 23 | $ conda install bt --channel conda-forge 24 | 25 | 26 | ``bt`` is compatible with Python >=3.7. 27 | 28 | Recommended Setup 29 | ----------------- 30 | 31 | We believe the best environment to develop with bt is the `IPython Notebook 32 | `__. From their homepage, the IPython Notebook 33 | is: 34 | 35 | "[...] a web-based interactive computational environment 36 | where you can combine code execution, text, mathematics, plots and rich 37 | media into a single document [...]" 38 | 39 | This environment allows you to plot your charts in-line and also allows you to 40 | easily add surrounding text with Markdown. You can easily create Notebooks that 41 | you can share with colleagues and you can also save them as PDFs. If you are not 42 | yet convinced, head over to their website. 43 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/intro.rst: -------------------------------------------------------------------------------- 1 | 2 | A Quick Example 3 | ~~~~~~~~~~~~~~~ 4 | 5 | Here is a quick example of what it looks like. I am writing this in my 6 | notebook file by the way (using a Mardown cell). 7 | 8 | Here is some code + output: 9 | 10 | .. code:: python 11 | 12 | # have to comment out the magic IPython functions because of a bug 13 | #%pylab inline 14 | import numpy as np 15 | import pandas as pd 16 | .. code:: python 17 | 18 | print np.random.randn(20) 19 | 20 | .. parsed-literal:: 21 | :class: pynb-result 22 | 23 | [-0.81256785 -1.16630768 0.27555802 0.57729188 -0.64691411 0.62288591 24 | 0.27943851 0.10512695 0.23808598 -1.45293996 -0.24394825 -0.14631097 25 | 1.56377514 -0.87629984 1.64059433 -0.10259616 0.84435183 0.11718899 26 | -0.66617413 -0.81800771] 27 | 28 | 29 | .. code:: python 30 | 31 | pd.Series(np.random.randn(100)).plot() 32 | 33 | 34 | 35 | .. parsed-literal:: 36 | :class: pynb-result 37 | 38 | 39 | 40 | 41 | 42 | 43 | .. image:: _static/intro_3_1.png 44 | :class: pynb 45 | 46 | 47 | 48 | Raw NBConvert Cells 49 | ------------------- 50 | 51 | In addition to markdown cells, you may also use **Raw NBConvert** cells. This allows you to input reST directly in your notebook. It won't look as pretty in the notebook itself, but will work great for your docs. Special reST and Sphinx markup will work within a Raw NBConvert (link, code markup, etc.). 52 | 53 | By the way, :func:`convert_notebooks ` basically calls:: 54 | 55 | ipython nbconvert --to rst 56 | 57 | on all the notebooks it finds. It also handles special cases and adds special css classes for display formatting purposes. If you plan on using IPython Notebook integration, you will need an up-to-date version of `pandoc `_ for this to work properly. 58 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | 5 | {% if favicon %} 6 | 7 | {% endif %} 8 | 9 | 10 | {% endblock %} 11 | 12 | {%- block relbar1 %}{% endblock %} 13 | {%- block relbar2 %}{% endblock %} 14 | 15 | {%- block sidebar2 %} 16 | 32 | {% endblock %} 33 | 34 | {%- block footer %} 35 | 38 | 39 | {% if theme_analytics_id %} 40 | 50 | {% endif %} 51 | {%- endblock %} 52 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import os 3 | 4 | from setuptools import setup 5 | from setuptools.extension import Extension 6 | 7 | 8 | def local_file(filename): 9 | return codecs.open(os.path.join(os.path.dirname(__file__), filename), "r", "utf-8") 10 | 11 | 12 | try: 13 | from Cython.Build import cythonize 14 | except ImportError: 15 | use_cython = False 16 | else: 17 | use_cython = True 18 | 19 | ext_modules = [] 20 | 21 | if use_cython: 22 | ext_modules = cythonize("bt/core.py") 23 | else: 24 | ext_modules = [Extension("bt.core", ["bt/core.c"])] 25 | 26 | setup( 27 | name="bt", 28 | version="1.1.2", 29 | author="Philippe Morissette", 30 | author_email="morissette.philippe@gmail.com", 31 | description="A flexible backtesting framework for Python", 32 | keywords="python finance quant backtesting strategies algotrading algorithmic trading", 33 | url="https://github.com/pmorissette/bt", 34 | license="MIT", 35 | install_requires=["ffn>=1.1.2", "pyprind>=2.11", "tqdm>=4"], 36 | extras_require={ 37 | "dev": [ 38 | "cython>=0.29.25", 39 | "ffn>=1.1.2", 40 | "matplotlib>=2", 41 | "numpy>=1", 42 | "pandas>=0.19", 43 | "pyprind>=2.11", 44 | "pytest", 45 | "pytest-cov", 46 | "ruff>=0.5.0,<0.15", 47 | "setuptools", 48 | "twine", 49 | "wheel", 50 | ], 51 | }, 52 | packages=["bt"], 53 | long_description=local_file("README.md").read().replace("\r\n", "\n"), 54 | long_description_content_type="text/markdown", 55 | classifiers=[ 56 | "Development Status :: 4 - Beta", 57 | "Programming Language :: Python", 58 | "Programming Language :: Python :: 3", 59 | "Programming Language :: Python :: 3.9", 60 | "Programming Language :: Python :: 3.10", 61 | "Programming Language :: Python :: 3.11", 62 | "Programming Language :: Python :: 3.12", 63 | "Programming Language :: Python :: 3.13", 64 | "Topic :: Software Development :: Libraries", 65 | "License :: OSI Approved :: MIT License", 66 | ], 67 | ext_modules=ext_modules, 68 | python_requires=">=3.9", 69 | ) 70 | -------------------------------------------------------------------------------- /examples/buy_and_hold.py: -------------------------------------------------------------------------------- 1 | 2 | if __name__ == "__main__": 3 | 4 | import numpy as np 5 | import pandas as pd 6 | 7 | import ffn 8 | import bt 9 | 10 | 11 | names = ['foo','bar','rf'] 12 | dates = pd.date_range(start='2017-01-01',end='2017-12-31', freq=pd.tseries.offsets.BDay()) 13 | n = len(dates) 14 | rdf = pd.DataFrame( 15 | np.zeros((n, len(names))), 16 | index = dates, 17 | columns = names 18 | ) 19 | 20 | np.random.seed(1) 21 | rdf['foo'] = np.random.normal(loc = 0.1/n,scale=0.2/np.sqrt(n),size=n) 22 | rdf['bar'] = np.random.normal(loc = 0.04/n,scale=0.05/np.sqrt(n),size=n) 23 | rdf['rf'] = np.random.normal(loc=0.02/ n, scale=0.01 / np.sqrt(n), size=n) 24 | 25 | pdf = 100*np.cumprod(1+rdf) 26 | 27 | # algo to fire on the beginning of every month and to run on the first date 28 | runMonthlyAlgo = bt.algos.RunMonthly( 29 | run_on_first_date=True, 30 | run_on_end_of_period=True 31 | ) 32 | 33 | # algo to set the weights in the temp dictionary\ 34 | weights = pd.Series([0.6,0.4,0.],index = rdf.columns) 35 | weighSpecifiedAlgo = bt.algos.WeighSpecified(**weights) 36 | 37 | 38 | # algo to rebalance the current weights to weights set in temp dictionary 39 | rebalAlgo = bt.algos.Rebalance() 40 | 41 | # a strategy that rebalances monthly to specified weights 42 | s = 'monthly' 43 | strat = bt.Strategy(s, 44 | [ 45 | runMonthlyAlgo, 46 | weighSpecifiedAlgo, 47 | rebalAlgo 48 | ] 49 | ) 50 | """ 51 | runMonthlyAlgo will return True on the last day of the month. 52 | If runMonthlyAlgo returns True, then weighSpecifiedAlgo will set the weights and return True. 53 | If weighSpecifiedAlgo returns True, then rebalAlgo will rebalance the portfolio to match the 54 | target weights. 55 | """ 56 | 57 | # set integer_positions=False when positions are not required to be integers(round numbers) 58 | backtest = bt.Backtest( 59 | strat, 60 | pdf, 61 | integer_positions=False 62 | ) 63 | 64 | res = bt.run(backtest) 65 | 66 | # set riskfree as the rf index 67 | res.set_riskfree_rate(pdf['rf']) 68 | 69 | 70 | wait=1 -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | klink - A Simple & Clean Sphinx Theme 2 | ===================================== 3 | 4 | Klink is a **simple** and **clean** theme for creating `Sphinx docs 5 | `__. It is heavily inspired by the beautiful `jrnl theme 6 | `__. It also supports embedding `IPython 7 | Notebooks `__ which can be mighty useful. 8 | 9 | Options 10 | ------- 11 | 12 | Here are the theme options. They should be added to the html_theme_options in 13 | your **conf.py** file. 14 | 15 | * **github** 16 | The github address of the project. The format is name/project 17 | (pmorissette/klink). 18 | * **logo** 19 | The logo file. Assumed to be in the _static dir. Default is logo.png. The logo 20 | should be 150x150. 21 | * **analytics_id** 22 | Your Google Analytics id (usually starts with UA-...) 23 | 24 | IPython Notebook Integration 25 | ---------------------------- 26 | 27 | With the klink helper function :func:`convert_notebooks() 28 | `, all notebooks will be 29 | converted to .rst so that they can be included in your docs. This includes all 30 | output including images. It’s a very convenient way to create Python docs! 31 | 32 | All you have to do is create notebooks within your source directory (same directory 33 | as your conf.py file). Then, you add a call to klink.convert_notebooks() in your 34 | conf.py. You can also mix in **Mardown** cells or **Raw NBConvert** cells in 35 | your workbook. These will be converted to rst as well. 36 | 37 | .. note:: 38 | 39 | If you use the Raw NBConvert type cells, add a blank line at the start. There 40 | seems to be a bug in the rst conversion and if the cell does not begin with a 41 | blank line, you may run into some issues. 42 | 43 | Using a Raw NBConvert cell with rst text inside is convenient, especially if you 44 | want to have links to other parts of your Sphinx docs. 45 | 46 | .. danger:: 47 | 48 | Do not name your Notebooks with the same name as another reST file in your 49 | source directory as the file will be **overwritten** when calling convert_notebooks. 50 | 51 | .. include:: intro.rst 52 | 53 | .. toctree:: 54 | :hidden: 55 | :maxdepth: 2 56 | 57 | Overview 58 | Installation Guide 59 | Examples 60 | API 61 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/docs/source/install.rst: -------------------------------------------------------------------------------- 1 | Installation Guide 2 | ================== 3 | 4 | Installation 5 | ------------ 6 | 7 | Assuming you have pip installed: 8 | 9 | .. code:: sh 10 | 11 | $ pip install klink 12 | 13 | That's it. 14 | 15 | Usage 16 | ----- 17 | 18 | In your docs' **conf.py** file, add the following: 19 | 20 | .. code:: python 21 | 22 | import klink 23 | 24 | html_theme = 'klink' 25 | html_theme_path [klink.get_html_theme_path()] 26 | html_theme_options = { 27 | 'github': 'yourname/yourrepo', 28 | 'analytics_id': 'UA-your-number-here', 29 | 'logo': 'logo.png' 30 | } 31 | 32 | Klink also comes with a useful helper function that allows you to integrate an 33 | IPython Notebook into a .rst file. It basically converts the Notebook to .rst 34 | and copies the static data (images, etc) to your _static dir. 35 | 36 | If you have IPython Notebooks that you would like to integrate, use the 37 | following code to your **conf.py**: 38 | 39 | .. code:: python 40 | 41 | klink.convert_notebooks() 42 | 43 | Once the conversion is done, you will have a .rst file with the same name as 44 | each one of your notebooks. 45 | 46 | .. note:: 47 | 48 | Place your notebooks in your docs' source dir. **Do not** give them the same 49 | name as another reST file as this file will be **overwriiten** when you call 50 | klink.convert_notebooks. 51 | 52 | Now all you have to do is use the **include** command to insert them into your 53 | docs. 54 | 55 | 56 | Customization 57 | ------------- 58 | 59 | Obviously, some of you will want to customize the theme. The easiest way to 60 | achieve this is to clone the repo into your _themes folder (create it if it does 61 | not exist in your docs' source dir). To change the style, I recommend editing 62 | the LESS files themselves. You will also need lessc to convert from less to css. 63 | See the css command in the Makefile for an example. 64 | 65 | You may also want to explore the option of using **git subtree**. Here is a good 66 | `intro tutorial `__. 67 | 68 | You will also need to change your conf.py file. The following settings should 69 | work:: 70 | 71 | html_theme = 'klink' 72 | html_theme_path ['_themes'] 73 | html_theme_options = { 74 | 'github': 'yourname/yourrepo', 75 | 'analytics_id': 'UA-your-number-here', 76 | 'logo': 'logo.png' 77 | } 78 | 79 | -------------------------------------------------------------------------------- /tests/bench.py: -------------------------------------------------------------------------------- 1 | """ 2 | Performance benchmarks 3 | """ 4 | import numpy as np 5 | import pandas as pd 6 | import bt 7 | import cProfile 8 | 9 | 10 | def benchmark_1(): 11 | x = np.random.randn(10000, 1000) * 0.01 12 | idx = pd.date_range("1990-01-01", freq="B", periods=x.shape[0]) 13 | data = np.exp(pd.DataFrame(x, index=idx).cumsum()) 14 | 15 | s = bt.Strategy( 16 | "s", 17 | [ 18 | bt.algos.RunMonthly(), 19 | bt.algos.SelectRandomly(len(data.columns) / 2), 20 | bt.algos.WeighRandomly(), 21 | bt.algos.Rebalance(), 22 | ], 23 | ) 24 | 25 | t = bt.Backtest(s, data) 26 | return bt.run(t) 27 | 28 | 29 | def benchmark_2(): 30 | x = np.random.randn(10000, 1000) * 0.01 31 | idx = pd.date_range("1990-01-01", freq="B", periods=x.shape[0]) 32 | data = np.exp(pd.DataFrame(x, index=idx).cumsum()) 33 | bidoffer = data * 0.01 34 | coupons = data * 0.0 35 | s = bt.FixedIncomeStrategy( 36 | "s", 37 | algos=[ 38 | bt.algos.RunMonthly(), 39 | bt.algos.SelectRandomly(len(data.columns) / 2), 40 | bt.algos.WeighRandomly(), 41 | bt.algos.Rebalance(), 42 | ], 43 | children=[bt.CouponPayingSecurity(c) for c in data], 44 | ) 45 | 46 | t = bt.Backtest(s, data, additional_data={"bidoffer": bidoffer, "coupons": coupons}) 47 | return bt.run(t) 48 | 49 | 50 | def benchmark_3(): 51 | # Similar to benchmark_1, but with trading in only a small subset of assets 52 | # However, because the "multipier" is used, we can't just pass the string 53 | # names to the constructor, and so the solution is to use the lazy_add flag. 54 | # Changing lazy_add to False demonstrates the performance gain. 55 | # i.e. on Win32, it went from 4.3s with the flag to 10.9s without. 56 | 57 | x = np.random.randn(10000, 1000) * 0.01 58 | idx = pd.date_range("1990-01-01", freq="B", periods=x.shape[0]) 59 | data = np.exp(pd.DataFrame(x, index=idx).cumsum()) 60 | children = [bt.Security(name=i, multiplier=10, lazy_add=False) for i in range(1000)] 61 | s = bt.Strategy( 62 | "s", 63 | [ 64 | bt.algos.RunMonthly(), 65 | bt.algos.SelectThese([0, 1]), 66 | bt.algos.WeighRandomly(), 67 | bt.algos.Rebalance(), 68 | ], 69 | children=children, 70 | ) 71 | 72 | t = bt.Backtest(s, data) 73 | return bt.run(t) 74 | 75 | 76 | if __name__ == "__main__": 77 | print("\n\n\n================= Benchmark 1 =======================\n") 78 | cProfile.run("benchmark_1()", sort="tottime") 79 | print("\n----------------- Benchmark 1 -----------------------\n\n\n") 80 | 81 | print("\n\n\n================= Benchmark 2 =======================\n") 82 | cProfile.run("benchmark_2()", sort="tottime") 83 | print("\n----------------- Benchmark 2 -----------------------\n\n\n") 84 | 85 | print("\n\n\n================= Benchmark 3 =======================\n") 86 | cProfile.run("benchmark_3()", sort="cumtime") 87 | print("\n----------------- Benchmark 3 -----------------------\n\n\n") 88 | -------------------------------------------------------------------------------- /docs/source/_themes/klink/klink/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import call 3 | import shutil 4 | import re 5 | 6 | 7 | def convert_notebooks(): 8 | """ 9 | Converts IPython Notebooks to proper .rst files and moves static 10 | content to the _static directory. 11 | """ 12 | convert_status = call(['jupyter', 'nbconvert', '--to', 'rst', '*.ipynb']) 13 | if convert_status != 0: 14 | raise SystemError('Conversion failed! Status was %s' % convert_status) 15 | 16 | notebooks = [x for x in os.listdir('.') if '.ipynb' 17 | in x and os.path.isfile(x)] 18 | names = [os.path.splitext(x)[0] for x in notebooks] 19 | 20 | for i in range(len(notebooks)): 21 | name = names[i] 22 | notebook = notebooks[i] 23 | 24 | print('processing %s (%s)' % (name, notebook)) 25 | 26 | # move static files 27 | sdir = '%s_files' % name 28 | statics = os.listdir(sdir) 29 | statics = [os.path.join(sdir, x) for x in statics] 30 | [shutil.copy(x, '_static/') for x in statics] 31 | shutil.rmtree(sdir) 32 | 33 | # rename static dir in rst file 34 | rst_file = '%s.rst' % name 35 | print('RST file is %s' % rst_file) 36 | data = None 37 | with open(rst_file, 'r') as f: 38 | data = f.read() 39 | 40 | if data is not None: 41 | with open(rst_file, 'w') as f: 42 | # On Windows, bad escape character sequences are included (%5C) in static references 43 | # We remove these here and replace with forward slashes as appropriate 44 | # While converting the static directory name. 45 | data = re.sub('%s(%%5C|/)' % sdir, '_static/', data) 46 | f.write(data) 47 | 48 | # add special tags 49 | lines = None 50 | with open(rst_file, 'r') as f: 51 | lines = f.readlines() 52 | 53 | if lines is not None: 54 | n = len(lines) 55 | i = 0 56 | rawWatch = False 57 | 58 | while i < n: 59 | line = lines[i] 60 | # add class tags to images for css formatting 61 | if 'image::' in line: 62 | lines.insert(i + 1, ' :class: pynb\n') 63 | n += 1 64 | elif 'parsed-literal::' in line: 65 | lines.insert(i + 1, ' :class: pynb-result\n') 66 | n += 1 67 | elif 'raw:: html' in line: 68 | rawWatch = True 69 | 70 | if rawWatch: 71 | if ' 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
aaplmsftyhoo
Date
2010-01-04 29.22 27.48 17.10
2010-01-05 29.27 27.49 17.23
2010-01-06 28.81 27.32 17.17
2010-01-07 28.75 27.03 16.70
2010-01-08 28.94 27.22 16.70
94 |

5 rows × 3 columns

95 | 96 | 97 | 98 | 99 | .. code:: python 100 | 101 | data.plot() 102 | 103 | 104 | 105 | .. parsed-literal:: 106 | :class: pynb-result 107 | 108 | 109 | 110 | 111 | 112 | 113 | .. image:: _static/nb-examples_4_1.png 114 | :class: pynb 115 | 116 | 117 | .. code:: python 118 | 119 | # this is a comment 120 | data.to_returns().dropna().corr().as_format('.2f') 121 | 122 | 123 | 124 | .. raw:: html 125 | 126 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 |
aaplmsftyhoo
aapl 1.00 0.35 0.28
msft 0.35 1.00 0.37
yhoo 0.28 0.37 1.00
157 |

3 rows × 3 columns

158 |
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 | ![](http://pmorissette.github.io/bt/_static/logo.png) 2 | 3 | [![Build Status](https://github.com/pmorissette/bt/workflows/Build%20Status/badge.svg)](https://github.com/pmorissette/bt/actions/) 4 | [![PyPI Version](https://img.shields.io/pypi/v/bt)](https://pypi.org/project/bt/) 5 | [![PyPI License](https://img.shields.io/pypi/l/bt)](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 | --------------------------------------------------------------------------------