mirror of
https://github.com/wassname/options_backtester.git
synced 2026-07-15 11:24:40 +08:00
1.2 MiB
1.2 MiB
In [1]:
import os
import sys
BACKTESTER_DIR = os.path.realpath(os.path.join(os.getcwd(), '..', '..'))
DATA_DIR = os.path.join(BACKTESTER_DIR, 'data')
OPTIONS_DATA = os.path.join(DATA_DIR, 'SPX_2017.csv')
STOCKS_DATA = os.path.join(DATA_DIR, 'portfolio_data_2017.csv')
sys.path.append(BACKTESTER_DIR) # Add backtester base dir to $PYTHONPATHIn [2]:
import pyfolio as pf
import matplotlib.pyplot as plt
from backtester import Backtest
from backtester.strategy import Strategy, StrategyLeg
from backtester.enums import Type, Direction, Stock
from backtester.datahandler import HistoricalOptionsData, TiingoData, Schema
from backtester.statistics import monthly_returns_heatmap, returns_histogram, returns_chart/Users/jrchatruc/anaconda3/lib/python3.7/site-packages/pyfolio/pos.py:27: UserWarning: Module "zipline.assets" not found; mutltipliers will not be applied to position notionals. 'Module "zipline.assets" not found; mutltipliers will not be applied' +
In [3]:
plt.style.use("seaborn")
plt.rcParams["figure.figsize"] = (14, 8)In [ ]:
%env TIINGO_API_KEY=your_tiingo_api_keyIn [ ]:
api_key = os.environ["TIINGO_API_KEY"]
start = datetime.datetime(2017, 1, 1)
end = datetime.datetime(2017, 12, 31)
tickers = ["VOO", "TUR", "RSX", "EWY", "EWS", "VTIP", "TLT", "BWX", "PDBC", "IAU", "VNQI"]
symbols = pdr.get_data_tiingo(tickers, api_key=api_key, start=start, end=end)In [ ]:
save_path = os.path.join(DATA_DIR, 'portfolio_data_2017.csv')
symbols.to_csv(save_path)In [4]:
stock_data = TiingoData(STOCKS_DATA)In [5]:
stock_data.head()Out [5]:
| symbol | date | close | high | low | open | volume | adjClose | adjHigh | adjLow | adjOpen | adjVolume | divCash | splitFactor | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | VOO | 2017-01-03 | 206.74 | 207.33 | 205.560 | 206.68 | 4750181 | 194.997818 | 195.554308 | 193.884838 | 194.941226 | 4750181 | 0.0 | 1.0 |
| 1 | VOO | 2017-01-04 | 207.96 | 208.18 | 207.120 | 207.20 | 4622614 | 196.148526 | 196.356030 | 195.356235 | 195.431691 | 4622614 | 0.0 | 1.0 |
| 2 | VOO | 2017-01-05 | 207.80 | 208.04 | 207.013 | 207.75 | 2772065 | 195.997613 | 196.223982 | 195.255312 | 195.950453 | 2772065 | 0.0 | 1.0 |
| 3 | VOO | 2017-01-06 | 208.61 | 209.09 | 207.400 | 207.99 | 2194576 | 196.761608 | 197.214345 | 195.620332 | 196.176822 | 2194576 | 0.0 | 1.0 |
| 4 | VOO | 2017-01-09 | 207.95 | 208.48 | 207.885 | 208.34 | 1705181 | 196.139094 | 196.638991 | 196.077785 | 196.506943 | 1705181 | 0.0 | 1.0 |
In [6]:
VOO = Stock('VOO', 0.1)
TUR = Stock('TUR', 0.05)
RSX = Stock('RSX', 0.05)
EWY = Stock('EWY', 0.05)
EWS = Stock('EWS', 0.05)
VTIP = Stock('VTIP', 0.10)
TLT = Stock('TLT', 0.20)
BWX = Stock('BWX', 0.10)
PDBC = Stock('PDBC', 0.05)
IAU = Stock('IAU', 0.15)
VNQI = Stock('VNQI', 0.10)In [7]:
0.10 + 0.05 + 0.05 + 0.05 + 0.05 + 0.10 + 0.20 + 0.10 + 0.05 + 0.15 + 0.10Out [7]:
1.0000000000000002
In [8]:
stocks = [
VOO,
TUR,
RSX,
EWY,
EWS,
VTIP,
TLT,
BWX,
PDBC,
IAU,
VNQI
]In [9]:
options_schema = Schema.options()
options_schema.update({
'contract': 'optionroot',
'date': 'quotedate'})Out [9]:
Schema([Field(name='underlying', mapping='underlying'), Field(name='underlying_last', mapping='underlying_last'), Field(name='date', mapping='quotedate'), Field(name='contract', mapping='optionroot'), Field(name='type', mapping='type'), Field(name='expiration', mapping='expiration'), Field(name='strike', mapping='strike'), Field(name='bid', mapping='bid'), Field(name='ask', mapping='ask'), Field(name='volume', mapping='volume'), Field(name='open_interest', mapping='open_interest')])
In [10]:
options_data = HistoricalOptionsData(OPTIONS_DATA, schema=None)
schema = options_data.schemaIn [11]:
options_data.head()Out [11]:
| underlying | underlying_last | exchange | optionroot | optionext | type | expiration | quotedate | strike | last | ... | ask | volume | openinterest | impliedvol | delta | gamma | theta | vega | optionalias | dte | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | SPX | 2257.83 | * | SPX170120C00100000 | NaN | call | 2017-01-20 | 2017-01-03 | 100 | 2161.50 | ... | 2158.7 | 0 | 5615 | 0.1214 | 1.0 | 0.0 | -1.0239 | 0.0 | SPX170120C00100000 | 17 |
| 1 | SPX | 2257.83 | * | SPX170120C00200000 | NaN | call | 2017-01-20 | 2017-01-03 | 200 | 2060.95 | ... | 2058.8 | 0 | 6355 | 0.1214 | 1.0 | 0.0 | -2.0479 | 0.0 | SPX170120C00200000 | 17 |
| 2 | SPX | 2257.83 | * | SPX170120C00300000 | NaN | call | 2017-01-20 | 2017-01-03 | 300 | 1960.45 | ... | 1958.8 | 0 | 2000 | 0.1214 | 1.0 | 0.0 | -3.0718 | 0.0 | SPX170120C00300000 | 17 |
| 3 | SPX | 2257.83 | * | SPX170120C00400000 | NaN | call | 2017-01-20 | 2017-01-03 | 400 | 1760.55 | ... | 1858.9 | 0 | 25 | 0.1214 | 1.0 | 0.0 | -4.0957 | 0.0 | SPX170120C00400000 | 17 |
| 4 | SPX | 2257.83 | * | SPX170120C00500000 | NaN | call | 2017-01-20 | 2017-01-03 | 500 | 1749.33 | ... | 1758.9 | 0 | 3813 | 0.1214 | 1.0 | 0.0 | -5.1197 | 0.0 | SPX170120C00500000 | 17 |
5 rows × 21 columns
In [12]:
long_straddle = Strategy(schema)In [13]:
leg1 = StrategyLeg("leg_1", schema, option_type=Type.CALL, direction=Direction.BUY)
leg2 = StrategyLeg("leg_2", schema, option_type=Type.PUT, direction=Direction.BUY)In [14]:
leg1.entry_filter = (schema.underlying == "SPX") & (schema.dte >= 31) & (schema.dte <= 60) & (schema.strike >= schema.underlying_last * 0.95) & (schema.strike <= schema.underlying_last * 1.05)
leg2.entry_filter = (schema.underlying == "SPX") & (schema.dte >= 31) & (schema.dte <= 60) & (schema.strike >= schema.underlying_last * 0.95) & (schema.strike <= schema.underlying_last * 1.05)
leg1.exit_filter = (schema.dte <= 60)
leg2.exit_filter = (schema.dte <= 60)
long_straddle.add_legs([leg1, leg2]);In [15]:
bt = Backtest({'stocks': 0.97, 'options': 0.03, 'cash': 0})
bt.stocks = stocks
bt.options_strategy = long_straddle
bt.options_data = options_data
bt.stocks_data = stock_dataIn [16]:
bt.run(rebalance_freq=1)Out [16]:
0% [██████████████████████████████] 100% | ETA: 00:00:00 Total time elapsed: 00:00:04
| leg_1 | leg_2 | totals | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| contract | underlying | expiration | type | strike | cost | order | contract | underlying | expiration | type | strike | cost | order | cost | qty | date | |
| 0 | SPX170217C02150000 | SPX | 2017-02-17 | call | 2150 | 11800.0 | Order.BTO | SPX170217P02150000 | SPX | 2017-02-17 | put | 2150 | 1220.0 | Order.BTO | 13020.0 | 2.0 | 2017-01-03 |
| 1 | SPX170217C02150000 | SPX | 2017-02-17 | call | 2150 | -12740.0 | Order.STC | SPX170217P02150000 | SPX | 2017-02-17 | put | 2150 | -160.0 | Order.STC | -12900.0 | 2.0 | 2017-02-01 |
| 2 | SPX170317C02170000 | SPX | 2017-03-17 | call | 2170 | 11660.0 | Order.BTO | SPX170317P02170000 | SPX | 2017-03-17 | put | 2170 | 1010.0 | Order.BTO | 12670.0 | 2.0 | 2017-02-01 |
| 3 | SPX170317C02170000 | SPX | 2017-03-17 | call | 2170 | -22360.0 | Order.STC | SPX170317P02170000 | SPX | 2017-03-17 | put | 2170 | -55.0 | Order.STC | -22415.0 | 2.0 | 2017-03-01 |
| 4 | SPX170421C02280000 | SPX | 2017-04-21 | call | 2280 | 12610.0 | Order.BTO | SPX170421P02280000 | SPX | 2017-04-21 | put | 2280 | 1110.0 | Order.BTO | 13720.0 | 2.0 | 2017-03-01 |
| 5 | SPX170421C02280000 | SPX | 2017-04-21 | call | 2280 | -8120.0 | Order.STC | SPX170421P02280000 | SPX | 2017-04-21 | put | 2280 | -285.0 | Order.STC | -8405.0 | 2.0 | 2017-04-03 |
| 6 | SPX170519C02245000 | SPX | 2017-05-19 | call | 2245 | 12420.0 | Order.BTO | SPX170519P02245000 | SPX | 2017-05-19 | put | 2245 | 1040.0 | Order.BTO | 13460.0 | 2.0 | 2017-04-03 |
| 7 | SPX170519C02245000 | SPX | 2017-05-19 | call | 2245 | -14080.0 | Order.STC | SPX170519P02245000 | SPX | 2017-05-19 | put | 2245 | -120.0 | Order.STC | -14200.0 | 2.0 | 2017-05-01 |
| 8 | SPX170616C02270000 | SPX | 2017-06-16 | call | 2270 | 12470.0 | Order.BTO | SPX170616P02270000 | SPX | 2017-06-16 | put | 2270 | 820.0 | Order.BTO | 13290.0 | 2.0 | 2017-05-01 |
| 9 | SPX170616C02270000 | SPX | 2017-06-16 | call | 2270 | -15690.0 | Order.STC | SPX170616P02270000 | SPX | 2017-06-16 | put | 2270 | -65.0 | Order.STC | -15755.0 | 2.0 | 2017-06-01 |
| 10 | SPX170721C02310000 | SPX | 2017-07-21 | call | 2310 | 12970.0 | Order.BTO | SPX170721P02310000 | SPX | 2017-07-21 | put | 2310 | 860.0 | Order.BTO | 13830.0 | 2.0 | 2017-06-01 |
| 11 | SPX170721C02310000 | SPX | 2017-07-21 | call | 2310 | -11650.0 | Order.STC | SPX170721P02310000 | SPX | 2017-07-21 | put | 2310 | -220.0 | Order.STC | -11870.0 | 2.0 | 2017-07-03 |
| 12 | SPX170818C02310000 | SPX | 2017-08-18 | call | 2310 | 12590.0 | Order.BTO | SPX170818P02310000 | SPX | 2017-08-18 | put | 2310 | 1060.0 | Order.BTO | 13650.0 | 2.0 | 2017-07-03 |
| 13 | SPX170818C02310000 | SPX | 2017-08-18 | call | 2310 | -16190.0 | Order.STC | SPX170818P02310000 | SPX | 2017-08-18 | put | 2310 | -120.0 | Order.STC | -16310.0 | 2.0 | 2017-08-01 |
| 14 | SPX170915C02355000 | SPX | 2017-09-15 | call | 2355 | 12700.0 | Order.BTO | SPX170915P02355000 | SPX | 2017-09-15 | put | 2355 | 790.0 | Order.BTO | 13490.0 | 2.0 | 2017-08-01 |
| 15 | SPX170915C02355000 | SPX | 2017-09-15 | call | 2355 | -11830.0 | Order.STC | SPX170915P02355000 | SPX | 2017-09-15 | put | 2355 | -100.0 | Order.STC | -11930.0 | 2.0 | 2017-09-01 |
| 16 | SPX171020C02355000 | SPX | 2017-10-20 | call | 2355 | 13230.0 | Order.BTO | SPX171020P02355000 | SPX | 2017-10-20 | put | 2355 | 1020.0 | Order.BTO | 14250.0 | 2.0 | 2017-09-01 |
| 17 | SPX171020C02355000 | SPX | 2017-10-20 | call | 2355 | -17230.0 | Order.STC | SPX171020P02355000 | SPX | 2017-10-20 | put | 2355 | -95.0 | Order.STC | -17325.0 | 2.0 | 2017-10-02 |
| 18 | SPX171117C02405000 | SPX | 2017-11-17 | call | 2405 | 13220.0 | Order.BTO | SPX171117P02405000 | SPX | 2017-11-17 | put | 2405 | 780.0 | Order.BTO | 14000.0 | 2.0 | 2017-10-02 |
| 19 | SPX171117C02405000 | SPX | 2017-11-17 | call | 2405 | -16950.0 | Order.STC | SPX171117P02405000 | SPX | 2017-11-17 | put | 2405 | -110.0 | Order.STC | -17060.0 | 2.0 | 2017-11-01 |
| 20 | SPX171215C02455000 | SPX | 2017-12-15 | call | 2455 | 13090.0 | Order.BTO | SPX171215P02455000 | SPX | 2017-12-15 | put | 2455 | 850.0 | Order.BTO | 13940.0 | 2.0 | 2017-11-01 |
| 21 | SPX171215C02455000 | SPX | 2017-12-15 | call | 2455 | -18550.0 | Order.STC | SPX171215P02455000 | SPX | 2017-12-15 | put | 2455 | -120.0 | Order.STC | -18670.0 | 2.0 | 2017-12-01 |
| 22 | SPX180119C02515000 | SPX | 2018-01-19 | call | 2515 | 14480.0 | Order.BTO | SPX180119P02515000 | SPX | 2018-01-19 | put | 2515 | 1070.0 | Order.BTO | 15550.0 | 2.0 | 2017-12-01 |
In [17]:
bt.balanceOut [17]:
| total capital | cash | VOO | TUR | RSX | EWY | EWS | VTIP | TLT | BWX | ... | IAU | VNQI | options qty | calls capital | puts capital | stocks qty | options capital | stocks capital | % change | accumulated return | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2017-01-02 | 1.000000e+06 | 1000000.000000 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | 0.0 | 0.000000e+00 | NaN | NaN |
| 2017-01-03 | 9.991600e+05 | 4252.203989 | 96913.915483 | 48486.088959 | 48493.996041 | 48485.711480 | 48496.962760 | 96970.393641 | 193897.445209 | 96996.896604 | ... | 145492.92 | 96981.885738 | 2.0 | 22960.0 | 2240.0 | 34536.0 | 25200.0 | 9.697078e+05 | -0.000840 | 0.999160 |
| 2017-01-04 | 1.005354e+06 | 4252.203989 | 97485.817277 | 48563.271527 | 48808.018217 | 48817.743929 | 49267.903052 | 97009.844574 | 194642.955279 | 97299.303141 | ... | 146144.77 | 98265.641145 | 2.0 | 24600.0 | 1620.0 | 34536.0 | 26220.0 | 9.748815e+05 | 0.006199 | 1.005354 |
| 2017-01-05 | 1.013846e+06 | 4252.203989 | 97410.813763 | 48362.596851 | 48606.146819 | 49302.331828 | 50207.486533 | 97128.197374 | 197689.822522 | 98395.526836 | ... | 148361.06 | 99432.691515 | 2.0 | 24380.0 | 1600.0 | 34536.0 | 25980.0 | 9.836141e+05 | 0.008447 | 1.013846 |
| 2017-01-06 | 1.010046e+06 | 4252.203989 | 97790.519052 | 47884.064932 | 48269.694487 | 48763.900829 | 49894.292040 | 96970.393641 | 195874.667569 | 97715.112129 | ... | 147318.10 | 99335.437317 | 2.0 | 26020.0 | 1240.0 | 34536.0 | 27260.0 | 9.785336e+05 | -0.003749 | 1.010046 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2017-12-21 | 1.172150e+06 | 4311.464660 | 114876.694450 | 62052.589006 | 57247.456248 | 55795.201266 | 56439.121909 | 112930.078174 | 223250.588763 | 111696.704128 | ... | 168071.82 | 114300.129718 | 2.0 | 34780.0 | 500.0 | 36059.0 | 35280.0 | 1.132559e+06 | 0.002874 | 1.172150 |
| 2017-12-22 | 1.175638e+06 | 4311.464660 | 114853.445706 | 62409.383959 | 58044.851172 | 56275.993827 | 56351.551511 | 112884.385243 | 223555.184269 | 111934.694264 | ... | 169037.75 | 115033.070138 | 2.0 | 34280.0 | 520.0 | 36059.0 | 34800.0 | 1.136527e+06 | 0.002976 | 1.175638 |
| 2017-12-26 | 1.178685e+06 | 4311.464660 | 114720.416392 | 62721.579544 | 58594.778705 | 56066.616421 | 56285.873712 | 113002.044540 | 224218.127430 | 111934.694264 | ... | 170003.68 | 115206.661291 | 2.0 | 34160.0 | 470.0 | 36059.0 | 34630.0 | 1.139744e+06 | 0.002592 | 1.178685 |
| 2017-12-27 | 1.184637e+06 | 4311.464660 | 114785.826746 | 62587.781436 | 58292.318562 | 56904.126044 | 56504.799708 | 113048.432900 | 227138.660814 | 112410.674538 | ... | 170831.62 | 116016.753334 | 2.0 | 34180.0 | 520.0 | 36059.0 | 34700.0 | 1.145626e+06 | 0.005050 | 1.184637 |
| 2017-12-28 | 1.189650e+06 | 4311.464660 | 115000.746480 | 64460.954942 | 58264.822185 | 57997.541385 | 56723.725705 | 113071.627080 | 226941.569604 | 112371.009515 | ... | 171659.56 | 116768.981660 | 2.0 | 34140.0 | 420.0 | 36059.0 | 34560.0 | 1.150779e+06 | 0.004231 | 1.189650 |
251 rows × 21 columns
In [18]:
bt.balance['total capital'].plot();In [19]:
bt.balance[[stock.symbol for stock in stocks]].plot();In [20]:
returns_chart(bt.balance)Out [20]:
In [21]:
monthly_returns_heatmap(bt.balance)Out [21]:
In [22]:
returns_histogram(bt.balance)Out [22]:
In [23]:
bt.run(rebalance_freq=1, sma_days=30);0% [██████████████████████████████] 100% | ETA: 00:00:00 Total time elapsed: 00:00:05
In [26]:
bt.balance[[stock.symbol for stock in stocks]].plot();In [25]:
pf.create_returns_tear_sheet(returns = bt.balance['% change'].dropna())| Start date | 2017-01-03 | |
|---|---|---|
| End date | 2017-12-28 | |
| Total months | 11 | |
| Backtest | ||
| Annual return | 13.3% | |
| Cumulative returns | 13.2% | |
| Annual volatility | 4.9% | |
| Sharpe ratio | 2.59 | |
| Calmar ratio | 5.99 | |
| Stability | 0.92 | |
| Max drawdown | -2.2% | |
| Omega ratio | 1.53 | |
| Sortino ratio | 4.13 | |
| Skew | -0.02 | |
| Kurtosis | 0.67 | |
| Tail ratio | 1.10 | |
| Daily value at risk | -0.6% | |
/Users/jrchatruc/anaconda3/lib/python3.7/site-packages/numpy/core/fromnumeric.py:61: FutureWarning: The current behaviour of 'Series.argmin' is deprecated, use 'idxmin' instead. The behavior of 'argmin' will be corrected to return the positional minimum in the future. For now, use 'series.values.argmin' or 'np.argmin(np.array(values))' to get the position of the minimum row. return bound(*args, **kwds)
| Worst drawdown periods | Net drawdown in % | Peak date | Valley date | Recovery date | Duration |
|---|---|---|---|---|---|
| 0 | 2.22 | 2017-06-06 | 2017-07-06 | 2017-08-28 | 60 |
| 1 | 1.82 | 2017-09-07 | 2017-09-27 | 2017-10-27 | 37 |
| 2 | 1.80 | 2017-03-01 | 2017-03-09 | 2017-04-24 | 39 |
| 3 | 1.10 | 2017-01-17 | 2017-01-19 | 2017-01-31 | 11 |
| 4 | 0.98 | 2017-05-15 | 2017-05-18 | 2017-05-22 | 6 |