Skip to content

Commit c3ff033

Browse files
authored
Fixed Short put spread ratio issue (#15)
* Minor bug fixes, and code improvements * added trade numbers to results dataframe * refactored data.py, filter.py, fixed short put spreads
1 parent fc83398 commit c3ff033

21 files changed

+614
-629
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ MANIFEST
3333

3434
/data/
3535
/tests/.pytest_cache/
36+
/strategies/data/
37+
/strategies/results/
3638

.travis.yml

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1+
env:
2+
global:
3+
- CC_TEST_REPORTER_ID=9a990fea3fb57063b45010735b989c837fbf4b5da5d4bdfeafb89539e2b61d19
14
language: python
25
python:
36
- "3.6"
7+
before_script:
8+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
9+
- chmod +x ./cc-test-reporter
10+
- ./cc-test-reporter before-build
411
# command to install dependencies
512
install:
613
- pip install -r requirements.txt
7-
- pip install codecov
14+
- pip install coverage
815
- pip install pytest pytest-cov
916
# command to run tests
1017
script:
1118
- pytest --cov=./
1219

13-
after_success:
14-
- codecov
20+
after_script:
21+
- ./cc-test-reporter after-build -t coverage.py --debug --exit-code $TRAVIS_TEST_RESULT

README.md

+15-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[![Downloads](https://pepy.tech/badge/optopsy)](https://pepy.tech/project/optopsy)
2-
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/2de8f5b3fa2742de93fb60b3a1ae5683)](https://app.codacy.com/app/michaelchu/optopsy?utm_source=github.com&utm_medium=referral&utm_content=michaelchu/optopsy&utm_campaign=badger)
2+
[![Maintainability](https://api.codeclimate.com/v1/badges/37b11e992a6900d30310/maintainability)](https://codeclimate.com/github/michaelchu/optopsy/maintainability)
33
[![Build Status](https://travis-ci.org/michaelchu/optopsy.svg?branch=master)](https://travis-ci.org/michaelchu/optopsy)
4-
[![codecov](https://codecov.io/gh/michaelchu/optopsy/branch/master/graph/badge.svg)](https://codecov.io/gh/michaelchu/optopsy)
4+
[![Test Coverage](https://api.codeclimate.com/v1/badges/37b11e992a6900d30310/test_coverage)](https://codeclimate.com/github/michaelchu/optopsy/test_coverage)
55

66
# Optopsy
77

@@ -35,23 +35,20 @@ the rapid development of complex options trading strategies.
3535
* Spread delta
3636
* Spread price
3737

38-
### Planned Features
39-
* Indicator Support - Create entry and exit rules based on indicators
40-
* Optimizer - Allows users to run multiple backtests with different combinations of parameters
41-
* Option strategy support:
42-
* Single Calls/Puts
43-
* Vertical Spreads
44-
* Iron Condors (Iron Butterflies)
45-
* Covered Stock
46-
* Combos (Synthetics/Collars)
47-
* Diagonal Spreads
48-
* Calendar Spreads
49-
* Custom Spreads
50-
* Strangles
51-
* Straddles
38+
### Option strategy support
39+
* Single Calls/Puts
40+
* Vertical Spreads
41+
* (Coming Soon) Iron Condors (Iron Butterflies)
42+
* (Coming Soon) Covered Stock
43+
* (Coming Soon) Combos (Synthetics/Collars)
44+
* (Coming Soon) Diagonal Spreads
45+
* (Coming Soon) Calendar Spreads
46+
* (Coming Soon) Custom Spreads
47+
* (Coming Soon) Strangles
48+
* (Coming Soon) Straddles
5249

5350
### Dependencies
54-
You will need Python 3.6.x. It is recommended to install [Miniconda3](https://conda.io/miniconda.html). See [requirements.txt](https://github.com/michaelchu/optopsy/blob/master/requirements.txt) for full details.
51+
You will need Python 3.6.x and Pandas 0.23.1 or newer. It is recommended to install [Miniconda3](https://conda.io/miniconda.html). See [requirements.txt](https://github.com/michaelchu/optopsy/blob/master/requirements.txt) for full details.
5552

5653
### Installation
5754
```
@@ -69,7 +66,6 @@ In order to use it, you will need to define the struct variable to map the colum
6966
First we import the library and other nessesary libaries:
7067
```python
7168
import optopsy as op
72-
import os
7369
from datetime import datetime
7470
```
7571

@@ -112,13 +108,9 @@ def run_strategy():
112108
start = datetime(2016, 1, 1)
113109
end = datetime(2016, 12, 31)
114110

115-
# create the option spread that matches the entry filters
111+
# create the option spreads that matches the entry filters
116112
trades = op.strategies.short_call_spread(data, start, end, filters)
117113

118-
# we get a dataframe of our orders based on the entry filter rules, let's export
119-
# this to csv file for reference
120-
trades.to_csv('./strategies/results/trades.csv')
121-
122114
# call the run method with our data, option spreads and filters to run the backtest
123115
backtest = op.run(data, trades, filters)
124116

optopsy/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from .data import get as get
2-
from .data import gets as gets
32
from .enums import *
43
from .backtest import run
54
import optopsy.option_strategies as strategies

optopsy/backtest.py

+44-62
Original file line numberDiff line numberDiff line change
@@ -22,73 +22,60 @@
2222
from .option_queries import opt_type
2323
from .statistics import *
2424

25-
pd.set_option('display.expand_frame_repr', False)
26-
27-
sort_by = [
28-
'underlying_symbol',
29-
'quote_date',
30-
'option_type',
31-
'expiration',
32-
'strike'
33-
]
25+
pd.set_option("display.expand_frame_repr", False)
3426

35-
on = [
36-
'underlying_symbol',
37-
'option_type',
38-
'expiration',
39-
'strike'
40-
]
27+
28+
on = ["underlying_symbol", "option_type", "expiration", "strike"]
4129

4230
default_entry_filters = {
4331
"std_expr": False,
4432
"contract_size": 10,
4533
"entry_dte": (27, 30, 31),
46-
"exit_dte": None
34+
"exit_dte": None,
4735
}
4836

4937
output_cols = {
50-
'quote_date_entry': 'entry_date',
51-
'quote_date_exit': 'exit_date',
52-
'delta_entry': 'entry_delta',
53-
'underlying_price_entry': 'entry_stk_price',
54-
'underlying_price_exit': 'exit_stk_price',
55-
'dte_entry': 'DTE'
38+
"quote_date_entry": "entry_date",
39+
"quote_date_exit": "exit_date",
40+
"delta_entry": "entry_delta",
41+
"underlying_price_entry": "entry_stk_price",
42+
"underlying_price_exit": "exit_stk_price",
43+
"dte_entry": "dte",
5644
}
5745

5846
output_format = [
59-
'entry_date',
60-
'exit_date',
61-
'expiration',
62-
'DTE',
63-
'ratio',
64-
'contracts',
65-
'option_type',
66-
'strike',
67-
'entry_delta',
68-
'entry_stk_price',
69-
'exit_stk_price',
70-
'entry_opt_price',
71-
'exit_opt_price',
72-
'entry_price',
73-
'exit_price',
74-
'profit'
47+
"entry_date",
48+
"exit_date",
49+
"expiration",
50+
"underlying_symbol",
51+
"dte",
52+
"ratio",
53+
"contracts",
54+
"option_type",
55+
"strike",
56+
"entry_delta",
57+
"entry_stk_price",
58+
"exit_stk_price",
59+
"entry_opt_price",
60+
"exit_opt_price",
61+
"entry_price",
62+
"exit_price",
63+
"profit",
7564
]
7665

7766

7867
def _create_legs(data, leg):
79-
return (
80-
data
81-
.pipe(opt_type, option_type=leg[0])
82-
.assign(ratio=leg[1])
83-
)
68+
return data.pipe(opt_type, option_type=leg[0]).assign(ratio=leg[1])
8469

8570

8671
def _apply_filters(legs, filters):
8772
if not filters:
8873
return legs
8974
else:
90-
return [reduce(lambda l, f: getattr(fil, f)(l, filters[f], idx), filters, leg)
91-
for idx, leg in enumerate(legs)]
75+
return [
76+
reduce(lambda l, f: getattr(fil, f)(l, filters[f], idx), filters, leg)
77+
for idx, leg in enumerate(legs)
78+
]
9279

9380

9481
def _filter_data(data, filters):
@@ -101,37 +88,32 @@ def create_spread(data, leg_structs, filters):
10188

10289
# merge and apply leg filters to create spread
10390
filters = {**default_entry_filters, **filters}
104-
entry_filters = {f: filters[f]
105-
for f in filters if (not f.startswith('entry_spread') and
106-
not f.startswith('exit_'))}
91+
entry_filters = {
92+
f: filters[f]
93+
for f in filters
94+
if (not f.startswith("entry_spread") and not f.startswith("exit_"))
95+
}
10796
spread = _filter_data(legs, entry_filters)
10897

10998
# apply spread level filters to spread
110-
spread_filters = {f: filters[f]
111-
for f in filters if f.startswith('entry_spread')}
112-
return _filter_data(spread, spread_filters).sort_values(sort_by)
99+
spread_filters = {f: filters[f] for f in filters if f.startswith("entry_spread")}
100+
return _filter_data(spread, spread_filters)
113101

114102

115103
# this is the main function that runs the backtest engine
116-
def run(data, trades, filters, init_balance=10000, mode='midpoint'):
117-
trades = trades if isinstance(trades, list) else [trades]
118-
119-
# merge trades from multiple underlying symbols if applicable
120-
all_trades = pd.concat(trades).sort_values(sort_by)
121-
104+
def run(data, trades, filters, init_balance=10000, mode="midpoint"):
122105
# for each option to be traded, determine the historical price action
123106
filters = {**default_entry_filters, **filters}
124-
exit_filters = {f: filters[f] for f in filters if f.startswith('exit_')}
107+
exit_filters = {f: filters[f] for f in filters if f.startswith("exit_")}
125108
res = (
126-
pd
127-
.merge(all_trades, data, on=on, suffixes=('_entry', '_exit'))
109+
pd.merge(trades, data, on=on, suffixes=("_entry", "_exit"))
128110
.pipe(_filter_data, exit_filters)
129111
.pipe(calc_entry_px, mode)
130112
.pipe(calc_exit_px, mode)
131113
.pipe(calc_pnl)
132-
# .pipe(calc_running_balance, init_balance)
133114
.rename(columns=output_cols)
134-
.sort_values(['entry_date', 'expiration', 'underlying_symbol', 'strike'])
115+
.sort_values(["entry_date", "expiration", "underlying_symbol", "strike"])
116+
.pipe(assign_trade_num)
135117
)
136118

137119
return calc_total_profit(res), res[output_format]

0 commit comments

Comments
 (0)