mirror of
https://github.com/wassname/options_backtester.git
synced 2026-06-27 16:46:04 +08:00
Added README toy example that can be run
This commit is contained in:
@@ -52,51 +52,465 @@ $> make test
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Example:
|
### Sample backtest
|
||||||
|
|
||||||
|
You can run this example by putting the code into a Jupyter Notebook/Lab file in this directory.
|
||||||
|
|
||||||
We'll run a backtest of a stock portfolio holding `$AAPL` and `$GOOG`, and simultaneously buying 10% OTM calls and puts on `$SPX` ([long strangle](https://www.investopedia.com/terms/s/strangle.asp)).
|
|
||||||
We'll allocate 97% of our capital to stocks and the rest to options, and do a rebalance every month.
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from backtester import Backtest, Type, Direction, Stock
|
import os
|
||||||
from backtester.strategy import Strategy, StrategyLeg
|
import sys
|
||||||
|
|
||||||
|
BACKTESTER_DIR = os.path.realpath(os.path.join(os.getcwd(), '.', '.'))
|
||||||
|
TEST_DATA_DIR = os.path.join(BACKTESTER_DIR, 'backtester', 'test', 'test_data')
|
||||||
|
SAMPLE_STOCK_DATA = os.path.join(TEST_DATA_DIR, 'test_data_stocks.csv')
|
||||||
|
SAMPLE_OPTIONS_DATA = os.path.join(TEST_DATA_DIR, 'test_data_options.csv')
|
||||||
|
|
||||||
|
sys.path.append(BACKTESTER_DIR) # Add backtester base dir to $PYTHONPATH
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
from backtester import Backtest, Stock, Type, Direction
|
||||||
from backtester.datahandler import HistoricalOptionsData, TiingoData
|
from backtester.datahandler import HistoricalOptionsData, TiingoData
|
||||||
|
from backtester.strategy import Strategy, StrategyLeg
|
||||||
|
```
|
||||||
|
|
||||||
# Stocks data
|
First we construct an options datahandler.
|
||||||
stocks_data = TiingoData('stocks.csv')
|
|
||||||
stocks = [Stock(symbol='AAPL', percentage=0.5), Stock(symbol='GOOG', percentage=0.5)]
|
|
||||||
|
|
||||||
# Options data
|
|
||||||
options_data = HistoricalOptionsData('options.h5', key='/SPX')
|
|
||||||
schema = options_data.schema
|
|
||||||
|
|
||||||
# Long strangle
|
```python
|
||||||
leg_1 = StrategyLeg('leg_1', schema, option_type=Type.PUT, direction=Direction.BUY)
|
options_data = HistoricalOptionsData(SAMPLE_OPTIONS_DATA)
|
||||||
leg_1.entry_filter = (schema.underlying == 'SPX') & (schema.dte >= 60) & (schema.underlying_last <=
|
options_schema = options_data.schema
|
||||||
1.1 * schema.strike)
|
```
|
||||||
leg_1.exit_filter = (schema.dte <= 30)
|
|
||||||
|
|
||||||
leg_2 = StrategyLeg('leg_2', schema, option_type=Type.CALL, direction=Direction.BUY)
|
Next, we'll create a toy options strategy. It will simply buy a call and a put with `dte` between $80$ and $52$ and exit them a month later.
|
||||||
leg_2.entry_filter = (schema.underlying == 'SPX') & (schema.dte >= 60) & (schema.underlying_last >=
|
|
||||||
0.9 * schema.strike)
|
|
||||||
leg_2.exit_filter = (schema.dte <= 30)
|
|
||||||
|
|
||||||
strategy = Strategy(schema)
|
|
||||||
strategy.add_legs([leg_1, leg_2])
|
|
||||||
|
|
||||||
allocation = {'stocks': .97, 'options': .03}
|
```python
|
||||||
initial_capital = 1_000_000
|
sample_strategy = Strategy(options_schema)
|
||||||
bt = Backtest(allocation, initial_capital)
|
|
||||||
|
leg1 = StrategyLeg('leg_1', options_schema, option_type=Type.CALL, direction=Direction.BUY)
|
||||||
|
leg1.entry_filter = (options_schema.dte < 80) & (options_schema.dte > 52)
|
||||||
|
|
||||||
|
leg1.exit_filter = (options_schema.dte <= 52)
|
||||||
|
|
||||||
|
leg2 = StrategyLeg('leg_2', options_schema, option_type=Type.PUT, direction=Direction.BUY)
|
||||||
|
leg2.entry_filter = (options_schema.dte < 80) & (options_schema.dte > 52)
|
||||||
|
|
||||||
|
leg2.exit_filter = (options_schema.dte <= 52)
|
||||||
|
|
||||||
|
sample_strategy.add_legs([leg1, leg2]);
|
||||||
|
```
|
||||||
|
|
||||||
|
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. In this case, we will hold `VOO`, `TUR` and `RSX`, with $0.4$, $0.1$ and $0.5$ weights respectively.
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
stocks_data = TiingoData(SAMPLE_STOCK_DATA)
|
||||||
|
stocks = [Stock('VOO', 0.4), Stock('TUR', 0.1), Stock('RSX', 0.5)]
|
||||||
|
```
|
||||||
|
|
||||||
|
We set our portfolio allocation, i.e. how much of our capital will be invested in stocks, options and cash. We'll allocate 50% of our capital to stocks and the rest to options.
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
allocation = {'stocks': 0.5, 'options': 0.5, 'cash': 0.0}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, we create the `Backtest` object.
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
bt = Backtest(allocation, initial_capital=1_000_000)
|
||||||
|
|
||||||
bt.stocks = stocks
|
bt.stocks = stocks
|
||||||
bt.stocks_data = stocks_data
|
bt.stocks_data = stocks_data
|
||||||
bt.options_data = options_data
|
|
||||||
bt.options_strategy = strategy
|
|
||||||
|
|
||||||
|
bt.options_strategy = sample_strategy
|
||||||
|
bt.options_data = options_data
|
||||||
|
```
|
||||||
|
|
||||||
|
And run the backtest with a rebalancing period of one month.
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
bt.run(rebalance_freq=1)
|
bt.run(rebalance_freq=1)
|
||||||
|
```
|
||||||
|
|
||||||
|
0% [██████████████████████████████] 100% | ETA: 00:00:00
|
||||||
|
Total time elapsed: 00:00:00
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead tr th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<table border="1" class="dataframe">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th colspan="7" halign="left">leg_1</th>
|
||||||
|
<th colspan="7" halign="left">leg_2</th>
|
||||||
|
<th colspan="3" halign="left">totals</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>contract</th>
|
||||||
|
<th>underlying</th>
|
||||||
|
<th>expiration</th>
|
||||||
|
<th>type</th>
|
||||||
|
<th>strike</th>
|
||||||
|
<th>cost</th>
|
||||||
|
<th>order</th>
|
||||||
|
<th>contract</th>
|
||||||
|
<th>underlying</th>
|
||||||
|
<th>expiration</th>
|
||||||
|
<th>type</th>
|
||||||
|
<th>strike</th>
|
||||||
|
<th>cost</th>
|
||||||
|
<th>order</th>
|
||||||
|
<th>cost</th>
|
||||||
|
<th>qty</th>
|
||||||
|
<th>date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>0</th>
|
||||||
|
<td>SPX170317C00300000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-03-17</td>
|
||||||
|
<td>call</td>
|
||||||
|
<td>300</td>
|
||||||
|
<td>195010.0</td>
|
||||||
|
<td>Order.BTO</td>
|
||||||
|
<td>SPX170317P00300000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-03-17</td>
|
||||||
|
<td>put</td>
|
||||||
|
<td>300</td>
|
||||||
|
<td>5.0</td>
|
||||||
|
<td>Order.BTO</td>
|
||||||
|
<td>195015.0</td>
|
||||||
|
<td>2.0</td>
|
||||||
|
<td>2017-01-03</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>1</th>
|
||||||
|
<td>SPX170317C00300000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-03-17</td>
|
||||||
|
<td>call</td>
|
||||||
|
<td>300</td>
|
||||||
|
<td>-197060.0</td>
|
||||||
|
<td>Order.STC</td>
|
||||||
|
<td>SPX170317P00300000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-03-17</td>
|
||||||
|
<td>put</td>
|
||||||
|
<td>300</td>
|
||||||
|
<td>-0.0</td>
|
||||||
|
<td>Order.STC</td>
|
||||||
|
<td>-197060.0</td>
|
||||||
|
<td>2.0</td>
|
||||||
|
<td>2017-02-01</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2</th>
|
||||||
|
<td>SPX170421C00500000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-04-21</td>
|
||||||
|
<td>call</td>
|
||||||
|
<td>500</td>
|
||||||
|
<td>177260.0</td>
|
||||||
|
<td>Order.BTO</td>
|
||||||
|
<td>SPX170421P01375000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-04-21</td>
|
||||||
|
<td>put</td>
|
||||||
|
<td>1375</td>
|
||||||
|
<td>60.0</td>
|
||||||
|
<td>Order.BTO</td>
|
||||||
|
<td>177320.0</td>
|
||||||
|
<td>2.0</td>
|
||||||
|
<td>2017-02-01</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>3</th>
|
||||||
|
<td>SPX170421C00500000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-04-21</td>
|
||||||
|
<td>call</td>
|
||||||
|
<td>500</td>
|
||||||
|
<td>-188980.0</td>
|
||||||
|
<td>Order.STC</td>
|
||||||
|
<td>SPX170421P01375000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-04-21</td>
|
||||||
|
<td>put</td>
|
||||||
|
<td>1375</td>
|
||||||
|
<td>-5.0</td>
|
||||||
|
<td>Order.STC</td>
|
||||||
|
<td>-188985.0</td>
|
||||||
|
<td>2.0</td>
|
||||||
|
<td>2017-03-01</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>4</th>
|
||||||
|
<td>SPX170519C01000000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-05-19</td>
|
||||||
|
<td>call</td>
|
||||||
|
<td>1000</td>
|
||||||
|
<td>138940.0</td>
|
||||||
|
<td>Order.BTO</td>
|
||||||
|
<td>SPX170519P01650000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-05-19</td>
|
||||||
|
<td>put</td>
|
||||||
|
<td>1650</td>
|
||||||
|
<td>100.0</td>
|
||||||
|
<td>Order.BTO</td>
|
||||||
|
<td>139040.0</td>
|
||||||
|
<td>3.0</td>
|
||||||
|
<td>2017-03-01</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>5</th>
|
||||||
|
<td>SPX170519C01000000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-05-19</td>
|
||||||
|
<td>call</td>
|
||||||
|
<td>1000</td>
|
||||||
|
<td>-135290.0</td>
|
||||||
|
<td>Order.STC</td>
|
||||||
|
<td>SPX170519P01650000</td>
|
||||||
|
<td>SPX</td>
|
||||||
|
<td>2017-05-19</td>
|
||||||
|
<td>put</td>
|
||||||
|
<td>1650</td>
|
||||||
|
<td>-20.0</td>
|
||||||
|
<td>Order.STC</td>
|
||||||
|
<td>-135310.0</td>
|
||||||
|
<td>3.0</td>
|
||||||
|
<td>2017-04-03</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The trade log (`bt.trade_log`) shows we executed 6 trades: we bought one call and one put on _2017-01-03_, _2017-02-01_ and _2017-03-01_, and exited those positions on _2017-02-01_, _2017-03-01_ and _2017-04-03_ respectively.
|
||||||
|
|
||||||
|
The balance data structure shows how our positions evolved over time:
|
||||||
|
- We started with $1000000 on _2017-01-02_
|
||||||
|
- `total capital` is the sum of `cash`, `stocks capital` and `options capital`
|
||||||
|
- `% change` shows the inter day change in `total capital`
|
||||||
|
- `accumulated return` gives the compounded return in `total capital` since the start of the backtest
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
bt.balance.head()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<style scoped>
|
||||||
|
.dataframe tbody tr th:only-of-type {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe tbody tr th {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataframe thead th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<table border="1" class="dataframe">
|
||||||
|
<thead>
|
||||||
|
<tr style="text-align: right;">
|
||||||
|
<th></th>
|
||||||
|
<th>total capital</th>
|
||||||
|
<th>cash</th>
|
||||||
|
<th>VOO</th>
|
||||||
|
<th>TUR</th>
|
||||||
|
<th>RSX</th>
|
||||||
|
<th>options qty</th>
|
||||||
|
<th>calls capital</th>
|
||||||
|
<th>puts capital</th>
|
||||||
|
<th>stocks qty</th>
|
||||||
|
<th>VOO qty</th>
|
||||||
|
<th>TUR qty</th>
|
||||||
|
<th>RSX qty</th>
|
||||||
|
<th>options capital</th>
|
||||||
|
<th>stocks capital</th>
|
||||||
|
<th>% change</th>
|
||||||
|
<th>accumulated return</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>2017-01-02</th>
|
||||||
|
<td>1.000000e+06</td>
|
||||||
|
<td>1000000.00000</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>0.0</td>
|
||||||
|
<td>0.000000</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
<td>NaN</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2017-01-03</th>
|
||||||
|
<td>9.990300e+05</td>
|
||||||
|
<td>110117.40592</td>
|
||||||
|
<td>199872.763320</td>
|
||||||
|
<td>49993.281167</td>
|
||||||
|
<td>249986.549593</td>
|
||||||
|
<td>2.0</td>
|
||||||
|
<td>389060.0</td>
|
||||||
|
<td>0.0</td>
|
||||||
|
<td>16186.0</td>
|
||||||
|
<td>1025.0</td>
|
||||||
|
<td>1758.0</td>
|
||||||
|
<td>13403.0</td>
|
||||||
|
<td>389060.0</td>
|
||||||
|
<td>499852.594080</td>
|
||||||
|
<td>-0.000970</td>
|
||||||
|
<td>0.999030</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2017-01-04</th>
|
||||||
|
<td>1.004228e+06</td>
|
||||||
|
<td>110117.40592</td>
|
||||||
|
<td>201052.238851</td>
|
||||||
|
<td>50072.862958</td>
|
||||||
|
<td>251605.333911</td>
|
||||||
|
<td>2.0</td>
|
||||||
|
<td>391380.0</td>
|
||||||
|
<td>0.0</td>
|
||||||
|
<td>16186.0</td>
|
||||||
|
<td>1025.0</td>
|
||||||
|
<td>1758.0</td>
|
||||||
|
<td>13403.0</td>
|
||||||
|
<td>391380.0</td>
|
||||||
|
<td>502730.435720</td>
|
||||||
|
<td>0.005203</td>
|
||||||
|
<td>1.004228</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2017-01-05</th>
|
||||||
|
<td>1.002706e+06</td>
|
||||||
|
<td>110117.40592</td>
|
||||||
|
<td>200897.553535</td>
|
||||||
|
<td>49865.950301</td>
|
||||||
|
<td>250564.686850</td>
|
||||||
|
<td>2.0</td>
|
||||||
|
<td>391260.0</td>
|
||||||
|
<td>0.0</td>
|
||||||
|
<td>16186.0</td>
|
||||||
|
<td>1025.0</td>
|
||||||
|
<td>1758.0</td>
|
||||||
|
<td>13403.0</td>
|
||||||
|
<td>391260.0</td>
|
||||||
|
<td>501328.190686</td>
|
||||||
|
<td>-0.001516</td>
|
||||||
|
<td>1.002706</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>2017-01-06</th>
|
||||||
|
<td>1.003201e+06</td>
|
||||||
|
<td>110117.40592</td>
|
||||||
|
<td>201680.647945</td>
|
||||||
|
<td>49372.543196</td>
|
||||||
|
<td>248830.275081</td>
|
||||||
|
<td>2.0</td>
|
||||||
|
<td>393200.0</td>
|
||||||
|
<td>0.0</td>
|
||||||
|
<td>16186.0</td>
|
||||||
|
<td>1025.0</td>
|
||||||
|
<td>1758.0</td>
|
||||||
|
<td>13403.0</td>
|
||||||
|
<td>393200.0</td>
|
||||||
|
<td>499883.466222</td>
|
||||||
|
<td>0.000494</td>
|
||||||
|
<td>1.003201</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
Evolution of our total capital over time:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
bt.balance['total capital'].plot();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
Evolution of our stock positions over time:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
bt.balance[[stock.symbol for stock in stocks]].plot();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
More plots and statistics are available in the `backtester.statistics` module.
|
||||||
|
|
||||||
|
### Other strategies
|
||||||
|
|
||||||
|
The `Strategy` and `StrategyLeg` classes allow for more complex strategies; for instance, a [long strangle](https://www.investopedia.com/terms/s/strangle.asp) could be implemented like so:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Long strangle
|
||||||
|
leg_1 = StrategyLeg('leg_1', options_schema, option_type=Type.PUT, direction=Direction.BUY)
|
||||||
|
leg_1.entry_filter = (options_schema.underlying == 'SPX') & (options_schema.dte >= 60) & (options_schema.underlying_last <= 1.1 * options_schema.strike)
|
||||||
|
leg_1.exit_filter = (options_schema.dte <= 30)
|
||||||
|
|
||||||
|
leg_2 = StrategyLeg('leg_2', options_schema, option_type=Type.CALL, direction=Direction.BUY)
|
||||||
|
leg_2.entry_filter = (options_schema.underlying == 'SPX') & (options_schema.dte >= 60) & (options_schema.underlying_last >= 0.9 * options_schema.strike)
|
||||||
|
leg_2.exit_filter = (options_schema.dte <= 30)
|
||||||
|
|
||||||
|
strategy = Strategy(options_schema)
|
||||||
|
strategy.add_legs([leg_1, leg_2]);
|
||||||
```
|
```
|
||||||
|
|
||||||
You can explore more usage examples in the Jupyter [notebooks](backtester/examples/).
|
You can explore more usage examples in the Jupyter [notebooks](backtester/examples/).
|
||||||
|
|
||||||
|
|
||||||
## Recommended reading
|
## Recommended reading
|
||||||
|
|
||||||
For complete novices in finance and economics, this [post](https://notamonadtutorial.com/how-to-earn-your-macroeconomics-and-finance-white-belt-as-a-software-developer-136e7454866f) gives a comprehensive introduction.
|
For complete novices in finance and economics, this [post](https://notamonadtutorial.com/how-to-earn-your-macroeconomics-and-finance-white-belt-as-a-software-developer-136e7454866f) gives a comprehensive introduction.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Reference in New Issue
Block a user