{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Backtester Examples" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data files" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import os\n", "import sys\n", "\n", "BACKTESTER_DIR = os.path.realpath(os.path.join(os.getcwd(), '..', '..'))\n", "TEST_DATA_DIR = os.path.join(BACKTESTER_DIR, 'backtester', 'test', 'test_data')\n", "SAMPLE_STOCK_DATA = os.path.join(TEST_DATA_DIR, 'test_data_stocks.csv')\n", "SAMPLE_OPTIONS_DATA = os.path.join(TEST_DATA_DIR, 'test_data_options.csv')\n", "\n", "sys.path.append(BACKTESTER_DIR) # Add backtester base dir to $PYTHONPATH" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sample backtest" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from backtester import Backtest, Stock, Type, Direction\n", "from backtester.datahandler import HistoricalOptionsData, TiingoData\n", "from backtester.strategy import Strategy, StrategyLeg" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we construct an options datahandler." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "options_data = HistoricalOptionsData(SAMPLE_OPTIONS_DATA)\n", "options_schema = options_data.schema" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we'll create a toy options strategy." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "sample_strategy = Strategy(options_schema)\n", "\n", "leg1 = StrategyLeg('leg_1', options_schema, option_type=Type.CALL, direction=Direction.BUY)\n", "leg1.entry_filter = ((options_schema.contract == 'SPX170317C00300000') &\n", " (options_schema.dte == 73)) | ((options_schema.contract == 'SPX170421C00500000') &\n", " (options_schema.dte == 51))\n", "\n", "leg1.exit_filter = (options_schema.dte == 44) | (options_schema.dte == 18)\n", "\n", "leg2 = StrategyLeg('leg_2', options_schema, option_type=Type.PUT, direction=Direction.BUY)\n", "leg2.entry_filter = ((options_schema.contract == 'SPX170317P00300000') &\n", " (options_schema.dte == 73)) | ((options_schema.contract == 'SPX170421P01375000') &\n", " (options_schema.dte == 51))\n", "\n", "leg2.exit_filter = (options_schema.dte == 44) | (options_schema.dte == 18)\n", "\n", "sample_strategy.add_legs([leg1, leg2])\n", "sample_strategy.add_exit_thresholds(profit_pct=0.2, loss_pct=0.2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We do the same for stocks: create a datahandler together with a list of the stocks we want in our inventory and their corresponding weights." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "stocks_data = TiingoData(SAMPLE_STOCK_DATA)\n", "stocks = [Stock('VOO', 0.4), Stock('TUR', 0.1), Stock('RSX', 0.5)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We set our portfolio allocation, i.e. how much of our capital will be invested in stocks, options and cash." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "allocation = {'stocks': 0.5, 'options': 0.5, 'cash': 0.0}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we create the `Backtest` object." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "bt = Backtest(allocation, initial_capital=1_000_000)\n", "\n", "bt.stocks = stocks\n", "bt.stocks_data = stocks_data\n", "\n", "bt.options_strategy = sample_strategy\n", "bt.options_data = options_data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And run the backtest with a rebalancing period of one month." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "0% [██████████████████████████████] 100% | ETA: 00:00:00\n", "Total time elapsed: 00:00:00\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
leg_1leg_2totals
contractunderlyingexpirationtypestrikecostordercontractunderlyingexpirationtypestrikecostordercostqtydate
0SPX170317C00300000SPX2017-03-17call300195010.0Order.BTOSPX170317P00300000SPX2017-03-17put3005.0Order.BTO195015.02.02017-01-03
1SPX170317C00300000SPX2017-03-17call300-197060.0Order.STCSPX170317P00300000SPX2017-03-17put300-0.0Order.STC-197060.02.02017-02-01
2SPX170421C00500000SPX2017-04-21call500189250.0Order.BTOSPX170421P01375000SPX2017-04-21put137540.0Order.BTO189290.02.02017-03-01
3SPX170421C00500000SPX2017-04-21call500-185650.0Order.STCSPX170421P01375000SPX2017-04-21put1375-0.0Order.STC-185650.02.02017-04-03
\n", "
" ], "text/plain": [ " leg_1 \\\n", " contract underlying expiration type strike cost order \n", "0 SPX170317C00300000 SPX 2017-03-17 call 300 195010.0 Order.BTO \n", "1 SPX170317C00300000 SPX 2017-03-17 call 300 -197060.0 Order.STC \n", "2 SPX170421C00500000 SPX 2017-04-21 call 500 189250.0 Order.BTO \n", "3 SPX170421C00500000 SPX 2017-04-21 call 500 -185650.0 Order.STC \n", "\n", " leg_2 \\\n", " contract underlying expiration type strike cost order \n", "0 SPX170317P00300000 SPX 2017-03-17 put 300 5.0 Order.BTO \n", "1 SPX170317P00300000 SPX 2017-03-17 put 300 -0.0 Order.STC \n", "2 SPX170421P01375000 SPX 2017-04-21 put 1375 40.0 Order.BTO \n", "3 SPX170421P01375000 SPX 2017-04-21 put 1375 -0.0 Order.STC \n", "\n", " totals \n", " cost qty date \n", "0 195015.0 2.0 2017-01-03 \n", "1 -197060.0 2.0 2017-02-01 \n", "2 189290.0 2.0 2017-03-01 \n", "3 -185650.0 2.0 2017-04-03 " ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bt.run(rebalance_freq=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The trade log (`bt.trade_log`) shows we executed 4 trades: we bought 2 calls and 2 puts on _2017-01-03_ and _2017-03-01_, and exited those positions on _2017-02-01_ and _2017-04-03_ respectively." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The balance data structure shows how our positions evolved in time:\n", "- We started with $1000000 on _2017-01-02_\n", "- `total capital` is the sum of `cash`, `stocks capital` and `options capital`\n", "- `% change` shows the inter day change in `total capital`\n", "- `accumulated return` gives the compounded return in `total capital` since the start of the backtest" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
total capitalcashVOOTURRSXoptions qtycalls capitalputs capitalstocks qtyoptions capitalstocks capital% changeaccumulated return
2017-01-021.000000e+061000000.000000NaNNaNNaNNaNNaNNaNNaN0.00.000000NaNNaN
2017-01-039.990300e+05110117.405920199872.76332049993.281167249986.5495932.0389060.00.016186.0389060.0499852.594080-0.0009700.999030
2017-01-041.004228e+06110117.405920201052.23885150072.862958251605.3339112.0391380.00.016186.0391380.0502730.4357200.0052031.004228
2017-01-051.002706e+06110117.405920200897.55353549865.950301250564.6868502.0391260.00.016186.0391260.0501328.190686-0.0015161.002706
2017-01-061.003201e+06110117.405920201680.64794549372.543196248830.2750812.0393200.00.016186.0393200.0499883.4662220.0004941.003201
..........................................
2017-05-121.008599e+06507298.036182203126.83607250621.271130247552.5671570.00.00.016415.00.0501300.674359-0.0028051.008599
2017-05-151.015904e+06507298.036182204154.45918951695.869759252755.2848440.00.00.016415.00.0508605.6137920.0072431.015904
2017-05-161.014651e+06507298.036182204117.42772551811.005327251424.3570640.00.00.016415.00.0507352.790116-0.0012331.014651
2017-05-171.005524e+06507298.036182200497.60215250659.649653247068.5934190.00.00.016415.00.0498225.845224-0.0089951.005524
2017-05-181.001336e+06507298.036182201228.97356049853.700681242954.8166430.00.00.016415.00.0494037.490883-0.0041651.001336
\n", "

96 rows × 13 columns

\n", "
" ], "text/plain": [ " total capital cash VOO TUR \\\n", "2017-01-02 1.000000e+06 1000000.000000 NaN NaN \n", "2017-01-03 9.990300e+05 110117.405920 199872.763320 49993.281167 \n", "2017-01-04 1.004228e+06 110117.405920 201052.238851 50072.862958 \n", "2017-01-05 1.002706e+06 110117.405920 200897.553535 49865.950301 \n", "2017-01-06 1.003201e+06 110117.405920 201680.647945 49372.543196 \n", "... ... ... ... ... \n", "2017-05-12 1.008599e+06 507298.036182 203126.836072 50621.271130 \n", "2017-05-15 1.015904e+06 507298.036182 204154.459189 51695.869759 \n", "2017-05-16 1.014651e+06 507298.036182 204117.427725 51811.005327 \n", "2017-05-17 1.005524e+06 507298.036182 200497.602152 50659.649653 \n", "2017-05-18 1.001336e+06 507298.036182 201228.973560 49853.700681 \n", "\n", " RSX options qty calls capital puts capital \\\n", "2017-01-02 NaN NaN NaN NaN \n", "2017-01-03 249986.549593 2.0 389060.0 0.0 \n", "2017-01-04 251605.333911 2.0 391380.0 0.0 \n", "2017-01-05 250564.686850 2.0 391260.0 0.0 \n", "2017-01-06 248830.275081 2.0 393200.0 0.0 \n", "... ... ... ... ... \n", "2017-05-12 247552.567157 0.0 0.0 0.0 \n", "2017-05-15 252755.284844 0.0 0.0 0.0 \n", "2017-05-16 251424.357064 0.0 0.0 0.0 \n", "2017-05-17 247068.593419 0.0 0.0 0.0 \n", "2017-05-18 242954.816643 0.0 0.0 0.0 \n", "\n", " stocks qty options capital stocks capital % change \\\n", "2017-01-02 NaN 0.0 0.000000 NaN \n", "2017-01-03 16186.0 389060.0 499852.594080 -0.000970 \n", "2017-01-04 16186.0 391380.0 502730.435720 0.005203 \n", "2017-01-05 16186.0 391260.0 501328.190686 -0.001516 \n", "2017-01-06 16186.0 393200.0 499883.466222 0.000494 \n", "... ... ... ... ... \n", "2017-05-12 16415.0 0.0 501300.674359 -0.002805 \n", "2017-05-15 16415.0 0.0 508605.613792 0.007243 \n", "2017-05-16 16415.0 0.0 507352.790116 -0.001233 \n", "2017-05-17 16415.0 0.0 498225.845224 -0.008995 \n", "2017-05-18 16415.0 0.0 494037.490883 -0.004165 \n", "\n", " accumulated return \n", "2017-01-02 NaN \n", "2017-01-03 0.999030 \n", "2017-01-04 1.004228 \n", "2017-01-05 1.002706 \n", "2017-01-06 1.003201 \n", "... ... \n", "2017-05-12 1.008599 \n", "2017-05-15 1.015904 \n", "2017-05-16 1.014651 \n", "2017-05-17 1.005524 \n", "2017-05-18 1.001336 \n", "\n", "[96 rows x 13 columns]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bt.balance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Statistics and Plots" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from backtester.statistics import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Summary table of our options strategy." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Strategy
Total trades2
Number of wins1
Number of losses1
Win %50.00%
Largest loss$7280.00
Profit factor1.00
Average profit$-1595.00
Average P&L %0.00%
Total P&L %99.68%
" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "summary(bt.trade_log, bt.balance)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plot of the accumulated returns over time." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.VConcatChart(...)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "returns_chart(bt.balance)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Daily returns histogram." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "returns_histogram(bt.balance)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "monthly_returns_heatmap(bt.balance)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.5" } }, "nbformat": 4, "nbformat_minor": 4 }