Skip to content

Commit 171d2e8

Browse files
authored
Improve and refactor tests (#48)
1 parent fb46ce2 commit 171d2e8

File tree

9 files changed

+243
-156
lines changed

9 files changed

+243
-156
lines changed

optopsy/core.py

+14-31
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def _calculate_profit_loss(data):
4646
def _calculate_profit_loss_pct(data):
4747
return data.assign(
4848
long_profit_pct=lambda r: round((r["exit"] - r["entry"]) / r["entry"], 2)
49-
).assign(short_profit_pct=lambda r: round((r["entry"] - r["exit"]) / r["exit"], 2))
49+
).assign(short_profit_pct=lambda r: round((r["entry"] - r["exit"]) / r["entry"], 2))
5050

5151

5252
def _select_final_output_column(data, cols, side):
@@ -81,7 +81,9 @@ def _cut_options_by_otm(data, otm_pct_interval, max_otm_pct_interval):
8181

8282

8383
def _group_by_intervals(data, cols, drop_na):
84-
grouped_dataset = data.groupby(cols)[["long_profit_pct", "short_profit_pct"]].describe()
84+
grouped_dataset = data.groupby(cols)[
85+
["long_profit_pct", "short_profit_pct"]
86+
].describe()
8587
grouped_dataset.columns = [
8688
"_".join(col).rstrip("_") for col in grouped_dataset.columns.values
8789
]
@@ -94,48 +96,29 @@ def _group_by_intervals(data, cols, drop_na):
9496
return grouped_dataset
9597

9698

97-
def _evaluate(entries, exits):
98-
return (
99-
entries.merge(
100-
right=exits,
101-
on=["underlying_symbol", "option_type", "expiration", "strike"],
102-
suffixes=("_entry", "_exit"),
103-
)
104-
# by default we use the midpoint spread price to calculate entry and exit costs
105-
.assign(entry=lambda r: (r["bid_entry"] + r["ask_entry"]) / 2).assign(
106-
exit=lambda r: (r["bid_exit"] + r["ask_exit"]) / 2
107-
)
108-
)
109-
110-
11199
def _evaluate_options(data, min_bid_ask, exit_dte):
112-
"""
113-
Take all option entries and exits and calculate the profit/loss
114-
per option chain.
115-
116-
Args:
117-
data: Dataframe containing option chains
118-
119-
Returns:
120-
DataFrame with each option chain evaluated in all entry and exit combinations
121-
122-
"""
123-
124100
# remove option chains that are worthless, it's unrealistic to enter
125101
# trades with worthless options
126102
entries = _remove_min_bid_ask(data, min_bid_ask)
127103

128104
# to reduce unnecessary computation, filter for options with the desired exit DTE
129105
exits = _get(data, "dte", exit_dte)
130-
evaluated_options = _evaluate(entries, exits)
131106

132107
return (
133-
evaluated_options.pipe(_remove_invalid_evaluated_options)
108+
entries.merge(
109+
right=exits,
110+
on=["underlying_symbol", "option_type", "expiration", "strike"],
111+
suffixes=("_entry", "_exit"),
112+
)
113+
# by default we use the midpoint spread price to calculate entry and exit costs
114+
.assign(entry=lambda r: (r["bid_entry"] + r["ask_entry"]) / 2)
115+
.assign(exit=lambda r: (r["bid_exit"] + r["ask_exit"]) / 2)
116+
.pipe(_remove_invalid_evaluated_options)
134117
.pipe(_calculate_profit_loss)
135118
)[evaluated_cols]
136119

137120

138-
def _process_entries_and_exits(data, **kwargs):
121+
def _evaluate_all_options(data, **kwargs):
139122
return (
140123
data.pipe(_assign_dte)
141124
.pipe(_trim_data, "dte", kwargs["exit_dte"], kwargs["max_entry_dte"])

optopsy/definitions.py

+46-27
Original file line numberDiff line numberDiff line change
@@ -16,74 +16,93 @@
1616
# columns of dataframe after generating strategy
1717
single_strike_internal_cols = [
1818
"underlying_symbol",
19+
"underlying_price_entry",
20+
"option_type",
1921
"expiration",
2022
"dte_entry",
21-
"dte_range",
22-
"otm_pct_range",
2323
"strike",
24+
"entry",
25+
"exit",
26+
"long_profit",
27+
"short_profit",
28+
"long_profit_pct",
29+
"short_profit_pct",
30+
]
31+
32+
33+
straddle_internal_cols = [
34+
"underlying_symbol",
2435
"underlying_price_entry",
25-
"underlying_price_exit",
36+
"expiration",
37+
"dte_entry",
38+
"option_type_leg1",
39+
"option_type_leg2",
40+
"strike",
2641
"entry",
2742
"exit",
2843
"long_profit",
2944
"short_profit",
45+
"long_profit_pct",
46+
"short_profit_pct",
3047
]
3148

49+
3250
double_strike_internal_cols = [
3351
"underlying_symbol",
52+
"underlying_price_entry",
3453
"expiration",
3554
"dte_entry",
36-
"dte_range",
37-
"otm_pct_range_leg1",
55+
"option_type_leg1",
3856
"strike_leg1",
39-
"otm_pct_range_leg2",
57+
"option_type_leg2",
4058
"strike_leg2",
41-
"underlying_price_entry",
42-
"underlying_price_exit",
4359
"entry",
4460
"exit",
4561
"long_profit",
4662
"short_profit",
63+
"long_profit_pct",
64+
"short_profit_pct",
4765
]
4866

4967
triple_strike_internal_cols = [
5068
"underlying_symbol",
69+
"underlying_price_entry",
5170
"expiration",
5271
"dte_entry",
53-
"dte_range",
54-
"leg1_otm_pct_range",
55-
"leg1_strike",
56-
"leg2_otm_pct_range",
57-
"leg2_strike",
58-
"leg3_otm_pct_range",
59-
"leg3_strike",
60-
"underlying_price_entry",
61-
"underlying_price_exit",
72+
"option_type_leg1",
73+
"strike_leg1",
74+
"option_type_leg2",
75+
"strike_leg2",
76+
"option_type_leg3",
77+
"strike_leg3",
6278
"entry",
6379
"exit",
6480
"long_profit",
6581
"short_profit",
82+
"long_profit_pct",
83+
"short_profit_pct",
6684
]
6785

6886
quadruple_strike_internal_cols = [
6987
"underlying_symbol",
88+
"underlying_price_entry",
7089
"expiration",
7190
"dte_entry",
7291
"dte_range",
73-
"leg1_otm_pct_range",
74-
"leg1_strike",
75-
"leg2_otm_pct_range",
76-
"leg2_strike",
77-
"leg3_otm_pct_range",
78-
"leg3_strike",
79-
"leg4_otm_pct_range",
80-
"leg4_strike",
81-
"underlying_price_entry",
82-
"underlying_price_exit",
92+
"option_type_leg1",
93+
"strike_leg1",
94+
"option_type_leg2",
95+
"strike_leg2",
96+
"option_type_leg3",
97+
"strike_leg3",
98+
"option_type_leg4",
99+
"strike_leg4",
83100
"entry",
84101
"exit",
85102
"long_profit",
86103
"short_profit",
104+
"long_profit_pct",
105+
"short_profit_pct",
87106
]
88107

89108
# base columns of dataframe after aggregation(minus the calculated columns)

optopsy/strategies.py

+25-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .core import (
2-
_process_entries_and_exits,
2+
_evaluate_all_options,
33
_calls,
44
_puts,
55
_group_by_intervals,
@@ -16,16 +16,16 @@
1616
"otm_pct_interval": 0.05,
1717
"max_otm_pct": 1,
1818
"min_bid_ask": 0.05,
19-
"side": None,
19+
"side": "long",
2020
"drop_nan": True,
2121
"raw": False,
2222
"on": None,
2323
}
2424

2525

26-
def _process_strategy(data, internal_cols, external_cols, strategy_func, params):
26+
def _process_strategy(data, internal_cols, external_cols, _strategy_func, params):
2727
return (
28-
_process_entries_and_exits(
28+
_evaluate_all_options(
2929
data,
3030
dte_interval=params["dte_interval"],
3131
max_entry_dte=params["max_entry_dte"],
@@ -34,15 +34,18 @@ def _process_strategy(data, internal_cols, external_cols, strategy_func, params)
3434
max_otm_pct=params["max_otm_pct"],
3535
min_bid_ask=params["min_bid_ask"],
3636
)
37-
.pipe(strategy_func, params["on"], internal_cols)
38-
.pipe(_format_output, params, external_cols)
37+
.pipe(_strategy_func, params["on"])
38+
.pipe(_calculate_profit_loss_pct)
39+
.pipe(_format_output, params, internal_cols, external_cols)
3940
)
4041

4142

42-
def _format_output(data, params, external_cols):
43+
def _format_output(data, params, internal_cols, external_cols):
44+
if params["raw"]:
45+
return data[internal_cols]
46+
4347
return (
44-
data.pipe(_calculate_profit_loss_pct)
45-
.pipe(_group_by_intervals, external_cols, params["drop_nan"])
48+
data.pipe(_group_by_intervals, external_cols, params["drop_nan"])
4649
.reset_index()
4750
.pipe(
4851
_select_final_output_column,
@@ -52,7 +55,7 @@ def _format_output(data, params, external_cols):
5255
)
5356

5457

55-
def _straddles(data, on, internal_cols):
58+
def _straddles(data, on):
5659
calls = _calls(data)
5760
puts = _puts(data)
5861

@@ -63,13 +66,15 @@ def _straddles(data, on, internal_cols):
6366
suffixes=("_leg1", "_leg2"),
6467
)
6568
.assign(long_profit=lambda r: (r["long_profit_leg1"] + r["long_profit_leg2"]))
66-
.assign(short_profit=lambda r: (r["short_profit_leg1"] + r["short_profit_leg2"]))
69+
.assign(
70+
short_profit=lambda r: (r["short_profit_leg1"] + r["short_profit_leg2"])
71+
)
6772
.assign(entry=lambda r: (r["entry_leg1"] + r["entry_leg2"]))
6873
.assign(exit=lambda r: (r["exit_leg1"] + r["exit_leg2"]))
69-
)[internal_cols]
74+
)
7075

7176

72-
def _strangles(data, on, internal_cols):
77+
def _strangles(data, on):
7378
calls = _calls(data)
7479
puts = _puts(data)
7580

@@ -85,10 +90,12 @@ def _apply_strangle_rules(d):
8590
)
8691
.pipe(_apply_strangle_rules)
8792
.assign(long_profit=lambda r: (r["long_profit_leg1"] + r["long_profit_leg2"]))
88-
.assign(short_profit=lambda r: (r["short_profit_leg1"] + r["short_profit_leg2"]))
93+
.assign(
94+
short_profit=lambda r: (r["short_profit_leg1"] + r["short_profit_leg2"])
95+
)
8996
.assign(entry=lambda r: (r["entry_leg1"] + r["entry_leg2"]))
9097
.assign(exit=lambda r: (r["exit_leg1"] + r["exit_leg2"]))
91-
)[internal_cols]
98+
)
9299

93100

94101
def singles_calls(data, **kwargs):
@@ -97,7 +104,7 @@ def singles_calls(data, **kwargs):
97104
_process_strategy,
98105
single_strike_internal_cols,
99106
single_strike_external_cols,
100-
lambda d, o, c: d,
107+
lambda d, o: d,
101108
params,
102109
)
103110

@@ -108,7 +115,7 @@ def singles_puts(data, **kwargs):
108115
_process_strategy,
109116
single_strike_internal_cols,
110117
single_strike_external_cols,
111-
lambda d, o, c: d,
118+
lambda d, o: d,
112119
params,
113120
)
114121

@@ -128,7 +135,7 @@ def straddles(data, **kwargs):
128135
params = {**default_kwargs, **kwargs}
129136
return _process_strategy(
130137
data,
131-
single_strike_internal_cols,
138+
straddle_internal_cols,
132139
single_strike_external_cols,
133140
_straddles,
134141
params,

samples/spx_strangles_example.py

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def run_strategy():
4949

5050
if __name__ == "__main__":
5151
import timeit
52+
5253
start = timeit.default_timer()
5354

5455
# All the program statements

tests/integration/__init__.py

Whitespace-only changes.

tests/test_data/data.csv

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
col1,col2,col3,col4,col5,col6,col7,col8,col9
2-
SPX,359.69,call,1/20/1990,1/2/1990,225,135.5,135.5,0
3-
SPX,359.69,call,1/20/2000,1/2/2000,320,40.9,40.9,0
4-
SPX,359.69,call,1/20/2010,1/2/2010,325,35.9,35.9,0
5-
SPX,359.69,call,1/20/2020,1/2/2020,330,30.9,30.9,0
1+
col1,col2,col3,col4,col5,col6,col7,col8,col9
2+
SPX,359.69,call,1/20/1990,1/2/1990,225,135.5,135.5,0
3+
SPX,359.69,call,1/20/2000,1/2/2000,320,40.9,40.9,0
4+
SPX,359.69,call,1/20/2010,1/2/2010,325,35.9,35.9,0
5+
SPX,359.69,call,1/20/2020,1/2/2020,330,30.9,30.9,0

tests/test_data/data_cols.csv

-5
This file was deleted.

tests/test_data/data_full.csv

-17
This file was deleted.

0 commit comments

Comments
 (0)