diff --git a/.gitignore b/.gitignore index b716404..cda9190 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ MANIFEST /tests/.pytest_cache/ /strategies/data/ /strategies/results/ +/strategies/private/ diff --git a/optopsy/backtest.py b/optopsy/backtest.py index 5c58ebd..6627ef1 100644 --- a/optopsy/backtest.py +++ b/optopsy/backtest.py @@ -60,7 +60,7 @@ "exit_opt_price", "entry_price", "exit_price", - "profit", + "cost", ] @@ -83,8 +83,36 @@ def _filter_data(data, filters): return pd.concat(_apply_filters(data, filters)) -def create_spread(data, leg_structs, filters): +def _do_dedupe(spread, groupby, col, mode): + # dedupe delta dist ties + if groupby is None: + groupby = [ + "quote_date", + "expiration", + "underlying_symbol", + "ratio", + "option_type", + ] + + on = groupby + [col] + + if mode == "min": + return spread.groupby(groupby)[col].min().to_frame().merge(spread, on=on) + else: + return spread.groupby(groupby)[col].max().to_frame().merge(spread, on=on) + + +def _dedup_rows_by_cols(spreads, cols, groupby=None, mode="max"): + return reduce(lambda i, c: _do_dedupe(spreads, groupby, c, mode), cols, spreads) + + +def create_spread(data, leg_structs, filters, sort_by, ascending): legs = [_create_legs(data, leg) for leg in leg_structs] + sort_by = ( + ["quote_date", "expiration", "underlying_symbol", "strike"] + if sort_by is None + else sort_by + ) # merge and apply leg filters to create spread filters = {**default_entry_filters, **filters} @@ -97,7 +125,12 @@ def create_spread(data, leg_structs, filters): # apply spread level filters to spread spread_filters = {f: filters[f] for f in filters if f.startswith("entry_spread")} - return _filter_data(spread, spread_filters) + return ( + _filter_data(spread, spread_filters) + .pipe(_dedup_rows_by_cols, ["delta", "strike"]) + .sort_values(sort_by, ascending=ascending) + .pipe(assign_trade_num, ["quote_date", "expiration", "underlying_symbol"]) + ) # this is the main function that runs the backtest engine @@ -113,7 +146,7 @@ def run(data, trades, filters, init_balance=10000, mode="midpoint"): .pipe(calc_pnl) .rename(columns=output_cols) .sort_values(["entry_date", "expiration", "underlying_symbol", "strike"]) - .pipe(assign_trade_num) + .pipe(assign_trade_num, ["entry_date", "expiration", "underlying_symbol"]) ) return calc_total_profit(res), res[output_format] diff --git a/optopsy/checks.py b/optopsy/checks.py new file mode 100644 index 0000000..554ffa2 --- /dev/null +++ b/optopsy/checks.py @@ -0,0 +1,80 @@ +import datetime + +filters = { + "start_date": datetime.date, + "end_date": datetime.date, + "std_expr": bool, + "contract_size": int, + "entry_dte": (int, tuple), + "entry_days": int, + "leg1_delta": (int, float, tuple), + "leg2_delta": (int, float, tuple), + "leg3_delta": (int, float, tuple), + "leg4_delta": (int, float, tuple), + "leg1_strike_pct": (int, float, tuple), + "leg2_strike_pct": (int, float, tuple), + "leg3_strike_pct": (int, float, tuple), + "leg4_strike_pct": (int, float, tuple), + "entry_spread_price": (int, float, tuple), + "entry_spread_delta": (int, float, tuple), + "entry_spread_yield": (int, float, tuple), + "exit_dte": int, + "exit_hold_days": int, + "exit_leg_1_delta": (int, float, tuple), + "exit_leg_1_otm_pct": (int, float, tuple), + "exit_profit_loss_pct": (int, float, tuple), + "exit_spread_delta": (int, float, tuple), + "exit_spread_price": (int, float, tuple), + "exit_strike_diff_pct": (int, float, tuple), +} + + +def _type_check(filter): + return all([isinstance(filter[f], filters[f]) for f in filter]) + + +def singles_checks(filter): + return "leg1_delta" in filter and _type_check(filter) + + +def _sanitize(filters, f): + return filters[f][1] if isinstance(filters[f], tuple) else filters[f] + + +def call_spread_checks(f): + return ( + "leg1_delta" in f + and "leg2_delta" in f + and _type_check(f) + and (_sanitize(f, "leg1_delta") > _sanitize(f, "leg2_delta")) + ) + + +def put_spread_checks(f): + return ( + "leg1_delta" in f + and "leg2_delta" in f + and _type_check(f) + and (_sanitize(f, "leg1_delta") < _sanitize(f, "leg2_delta")) + ) + + +def iron_condor_checks(f): + return ( + "leg1_delta" in f + and "leg2_delta" in f + and "leg3_delta" in f + and "leg4_delta" in f + and _type_check(f) + and (_sanitize(f, "leg1_delta") < _sanitize(f, "leg2_delta")) + and (_sanitize(f, "leg3_delta") > _sanitize(f, "leg4_delta")) + ) + + +def iron_condor_spread_check(ic): + return ( + ic.assign(d_strike=lambda r: ic.duplicated(subset="strike", keep=False)) + .groupby(ic.index) + .filter(lambda r: (r.d_strike == False).all()) + .drop(columns="d_strike") + ) diff --git a/optopsy/option_strategies.py b/optopsy/option_strategies.py index a457ed9..bbf0c13 100644 --- a/optopsy/option_strategies.py +++ b/optopsy/option_strategies.py @@ -16,76 +16,92 @@ from .enums import OptionType, OrderAction from .backtest import create_spread +from .checks import ( + singles_checks, + call_spread_checks, + put_spread_checks, + iron_condor_checks, + iron_condor_spread_check, +) from datetime import datetime -def _add_date_range(s, e, f): - f["start_date"] = s - f["end_date"] = e - return f - - -def _dedup_legs(spreads): - sort_by = ["quote_date", "expiration", "underlying_symbol", "strike"] - groupby = ["quote_date", "expiration", "underlying_symbol", "ratio", "option_type"] - on = groupby + ["delta"] - - return ( - spreads.groupby(groupby)["delta"] - .max() - .to_frame() - .merge(spreads, on=on) - .sort_values(sort_by) - ) +def _process_legs(data, legs, filters, check_func, sort_by=None, asc=True): + if _filter_checks(filters, check_func): + return create_spread(data, legs, filters, sort_by=sort_by, ascending=asc) + else: + raise ValueError( + "Invalid filter values provided, please check the filters and try again." + ) -def _filter_check(filters): - return True +def _filter_checks(filter, func=None): + return True if func is None else func(filter) -def _date_checks(start, end): - return isinstance(start, datetime) and isinstance(end, datetime) +def _merge(filters, start, end): + return {**filters, **{"start_date": start, "end_date": end}} -def _process_legs(data, start, end, legs, filters): - filters = _add_date_range(start, end, filters) - if _filter_check(filters) and _date_checks(start, end): - return _dedup_legs(create_spread(data, legs, filters)) - else: - raise ValueError("Invalid filters, or date types provided!") +def long_call(data, start, end, filters): + legs = [(OptionType.CALL, 1)] + return _process_legs(data, legs, _merge(filters, start, end), singles_checks) -def long_call(data, start_date, end_date, filters): - return _process_legs(data, start_date, end_date, [(OptionType.CALL, 1)], filters) +def short_call(data, start, end, filters): + legs = [(OptionType.CALL, -1)] + return _process_legs(data, legs, _merge(filters, start, end), singles_checks) -def short_call(data, start_date, end_date, filters): - return _process_legs(data, start_date, end_date, [(OptionType.CALL, -1)], filters) +def long_put(data, start, end, filters): + legs = [(OptionType.PUT, 1)] + return _process_legs(data, legs, _merge(filters, start, end), singles_checks) -def long_put(data, start_date, end_date, filters): - return _process_legs(data, start_date, end_date, [(OptionType.PUT, 1)], filters) +def short_put(data, start, end, filters): + legs = [(OptionType.PUT, -1)] + return _process_legs(data, legs, _merge(filters, start, end), singles_checks) -def short_put(data, start_date, end_date, filters): - return _process_legs(data, start_date, end_date, [(OptionType.PUT, -1)], filters) +def long_call_spread(data, start, end, filters): + legs = [(OptionType.CALL, 1), (OptionType.CALL, -1)] + return _process_legs(data, legs, _merge(filters, start, end), call_spread_checks) -def long_call_spread(data, start_date, end_date, filters): +def short_call_spread(data, start, end, filters): legs = [(OptionType.CALL, -1), (OptionType.CALL, 1)] - return _process_legs(data, start_date, end_date, legs, filters) - - -def short_call_spread(data, start_date, end_date, filters): - legs = [(OptionType.CALL, 1), (OptionType.CALL, -1)] - return _process_legs(data, start_date, end_date, legs, filters) + return _process_legs(data, legs, _merge(filters, start, end), call_spread_checks) -def long_put_spread(data, start_date, end_date, filters): +def long_put_spread(data, start, end, filters): legs = [(OptionType.PUT, -1), (OptionType.PUT, 1)] - return _process_legs(data, start_date, end_date, legs, filters) + return _process_legs(data, legs, _merge(filters, start, end), put_spread_checks) -def short_put_spread(data, start_date, end_date, filters): +def short_put_spread(data, start, end, filters): legs = [(OptionType.PUT, 1), (OptionType.PUT, -1)] - return _process_legs(data, start_date, end_date, legs, filters) + return _process_legs(data, legs, _merge(filters, start, end), put_spread_checks) + + +def long_iron_condor(data, start, end, filters): + legs = [ + (OptionType.PUT, 1), + (OptionType.PUT, -1), + (OptionType.CALL, -1), + (OptionType.CALL, 1), + ] + return _process_legs( + data, legs, _merge(filters, start, end), iron_condor_checks + ).pipe(iron_condor_spread_check) + + +def short_iron_condor(data, start, end, filters): + legs = [ + (OptionType.PUT, -1), + (OptionType.PUT, 1), + (OptionType.CALL, 1), + (OptionType.CALL, -1), + ] + return _process_legs( + data, legs, _merge(filters, start, end), iron_condor_checks + ).pipe(iron_condor_spread_check) diff --git a/optopsy/statistics.py b/optopsy/statistics.py index 312e377..481c519 100644 --- a/optopsy/statistics.py +++ b/optopsy/statistics.py @@ -14,37 +14,54 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import numpy as np + + +def _calc_opt_px(data, action): + ask = f"ask_{action}" + bid = f"bid_{action}" + + if action == "entry": + return np.where(data["ratio"] > 0, data[ask], data[bid]) + elif action == "exit": + return np.where(data["ratio"] > 0, data[bid], data[ask]) + return data + def _assign_opt_px(data, mode, action): if mode == "midpoint": - data[f"{action}_opt_price"] = data[[f"bid_{action}", f"ask_{action}"]].mean(axis=1) + bid_ask = [f"bid_{action}", f"ask_{action}"] + data[f"{action}_opt_price"] = data[bid_ask].mean(axis=1) elif mode == "market": - data[f"{action}_opt_price"] = data[f"ask_{action}"] + data[f"{action}_opt_price"] = _calc_opt_px(data, action) return data -def assign_trade_num(data): - groupby = ["entry_date", "expiration", "underlying_symbol"] +def assign_trade_num(data, groupby): data["trade_num"] = data.groupby(groupby).ngroup() data.set_index("trade_num", inplace=True) return data - + def calc_entry_px(data, mode="midpoint"): - return _assign_opt_px(data, mode,'entry') + return _assign_opt_px(data, mode, "entry") def calc_exit_px(data, mode="midpoint"): - return _assign_opt_px(data, mode, 'exit') + return _assign_opt_px(data, mode, "exit") def calc_pnl(data): # calculate the p/l for the trades - data["entry_price"] = data["entry_opt_price"] * data["ratio"] * data["contracts"] - data["exit_price"] = data["exit_opt_price"] * data["ratio"] * data["contracts"] - data["profit"] = data["exit_price"] - data["entry_price"] - return data + data["entry_price"] = ( + data["entry_opt_price"] * data["ratio"] * data["contracts"] * 100 + ) + data["exit_price"] = ( + data["exit_opt_price"] * data["ratio"] * -1 * data["contracts"] * 100 + ) + data["cost"] = data["exit_price"] + data["entry_price"] + return data.round(2) def calc_total_profit(data): - return data["profit"].sum().round(2) + return data["cost"].sum().round(2) diff --git a/requirements.txt b/requirements.txt index 4475484..5d02d7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ pandas>=0.23.1 enum34; python_version <= '2.7' pathlib2; python_version <= '2.7' pytest>=3.10.0 +numpy>=1.14.3 pyprind>=2.11.2 \ No newline at end of file diff --git a/setup.py b/setup.py index 0a5f3c5..6fb3a5d 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,19 @@ from setuptools import setup -setup(name='optopsy', - description='Python Backtesting library for options trading strategies', - long_description=open("README.md").read(), - long_description_content_type='text/markdown', - version='1.0.1', - url='https://github.com/michaelchu/optopsy', - author='Michael Chu', - author_email='mchchu88@gmail.com', - license='GPL-3.0-or-later', - classifiers=[ - "Operating System :: OS Independent", - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - 'Programming Language :: Python :: 3.6' - ], - packages=['optopsy'] - ) +setup( + name="optopsy", + description="Python Backtesting library for options trading strategies", + long_description=open("README.md").read(), + long_description_content_type="text/markdown", + version="1.0.3", + url="https://github.com/michaelchu/optopsy", + author="Michael Chu", + author_email="mchchu88@gmail.com", + license="GPL-3.0-or-later", + classifiers=[ + "Operating System :: OS Independent", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Programming Language :: Python :: 3.6", + ], + packages=["optopsy"], +) diff --git a/strategies/sample_spx_strategy.py b/strategies/sample_spx_strategy.py new file mode 100755 index 0000000..402a8c0 --- /dev/null +++ b/strategies/sample_spx_strategy.py @@ -0,0 +1,80 @@ +import optopsy as op +import os +from datetime import datetime +import pandas as pd + + +def run_strategy(input_data): + # define the entry and exit filters to use for this strategy, full list of + # filters will be listed in the documentation (WIP). + filters = { + "entry_dte": (27, 30, 31), + "leg1_delta": 0.50, + "leg2_delta": 0.30, + "contract_size": 10, + } + + # set the start and end dates for the backtest, the dates are inclusive + # start and end dates are python datetime objects + start = datetime(2016, 1, 1) + end = datetime(2016, 12, 31) + + # create the option spread that matches the entry filters + trades = op.strategies.short_call_spread(input_data, start, end, filters) + + # call the run method with our data, option spreads and filters to run the backtest + # backtest will return a tuple with the profit amount and a dataframe + # containing the backtest results(the return format may be subject to change) + return op.run(input_data, trades, filters) + + +def store_and_get_data(file_name): + # absolute file path to our input file + curr_file = os.path.abspath(os.path.dirname(__file__)) + file = os.path.join(curr_file, "data", f"{file_name}.pkl") + + # check if we have a pickle store + if os.path.isfile(file): + print("pickle file found, retrieving...") + return pd.read_pickle(file) + else: + print("no picked file found, retrieving csv data...") + + csv_file = os.path.join(curr_file, "data", f"{file_name}.csv") + data = op.get(csv_file, SPX_FILE_STRUCT, prompt=False) + + print("storing to pickle file...") + pd.to_pickle(data, file) + return data + + +if __name__ == "__main__": + # Here we define the struct to match the format of our csv file + # the struct indices are 0-indexed where first column of the csv file + # is mapped to 0 + SPX_FILE_STRUCT = ( + ("underlying_symbol", 0), + ("underlying_price", 1), + ("option_symbol", 3), + ("option_type", 5), + ("expiration", 6), + ("quote_date", 7), + ("strike", 8), + ("bid", 10), + ("ask", 11), + ("delta", 15), + ("gamma", 16), + ("theta", 17), + ("vega", 18), + ) + + # retrieve the data from pickle file if available, + # otherwise read in the csv file + filename = "SPX_2016" + results = store_and_get_data(filename).pipe(run_strategy) + print("Trades: \n %s" % results[1]) + print("Total Profit: %s" % results[0]) + + + + diff --git a/strategies/sample_strategy.py b/strategies/sample_strategy.py deleted file mode 100755 index 4c6e7b3..0000000 --- a/strategies/sample_strategy.py +++ /dev/null @@ -1,61 +0,0 @@ -import optopsy as op -import os -from datetime import datetime - -# absolute file path to our input file -CURRENT_FILE = os.path.abspath(os.path.dirname(__file__)) -FILE = os.path.join(CURRENT_FILE, "data", "SPX_2016.csv") - -# Here we define the struct to match the format of our csv file -# the struct indices are 0-indexed where first column of the csv file -# is mapped to 0 -SPX_FILE_STRUCT = ( - ("underlying_symbol", 0), - ("underlying_price", 1), - ("option_symbol", 3), - ("option_type", 5), - ("expiration", 6), - ("quote_date", 7), - ("strike", 8), - ("bid", 10), - ("ask", 11), - ("delta", 15), - ("gamma", 16), - ("theta", 17), - ("vega", 18), -) - - -def run_strategy(): - - # provide the absolute file path and data struct to be used. - data = op.get(FILE, SPX_FILE_STRUCT, prompt=False) - - # define the entry and exit filters to use for this strategy, full list of - # filters will be listed in the documentation (WIP). - filters = { - "entry_dte": (27, 30, 31), - "leg1_delta": 0.30, - "leg2_delta": 0.50, - "contract_size": 10, - } - - # set the start and end dates for the backtest, the dates are inclusive - # start and end dates are python datetime objects - start = datetime(2016, 1, 1) - end = datetime(2016, 12, 31) - - # create the option spread that matches the entry filters - trades = op.strategies.short_call_spread(data, start, end, filters) - - # call the run method with our data, option spreads and filters to run the backtest - backtest = op.run(data, trades, filters) - - # backtest will return a tuple with the profit amount and a dataframe - # containing the backtest results(the return format may be subject to change) - backtest[1].to_csv("./strategies/results/results.csv") - print("Total Profit: %s" % backtest[0]) - - -if __name__ == "__main__": - run_strategy() diff --git a/tests/integration/test_integration_iron_condors.py b/tests/integration/test_integration_iron_condors.py new file mode 100644 index 0000000..05e412d --- /dev/null +++ b/tests/integration/test_integration_iron_condors.py @@ -0,0 +1,239 @@ +from optopsy.backtest import run +from optopsy.option_strategies import long_iron_condor, short_iron_condor +from optopsy.data import get +from datetime import datetime +import os +import pytest + +CURRENT_FILE = os.path.abspath(os.path.dirname(__file__)) +TEST_FILE_PATH_FULL = os.path.join( + CURRENT_FILE, "../test_data/test_options_data_full.csv" +) + + +def test_long_iron_condor_integration(hod_struct): + data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) + + filters = { + "leg1_delta": (0.05, 0.10, 0.15), + "leg2_delta": (0.25, 0.30, 0.45), + "leg3_delta": (0.25, 0.30, 0.45), + "leg4_delta": (0.05, 0.10, 0.15), + "entry_dte": 31, + "exit_dte": 7, + } + + start = datetime(2018, 1, 1) + end = datetime(2018, 2, 28) + + trades = long_iron_condor(data, start, end, filters) + backtest = run(data, trades, filters) + print(backtest[1]) + assert backtest[0] == 53225 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 9] == -0.10 + and backtest[1].iat[0, 8] == 2580 + and backtest[1].iat[0, 16] == 4550.0 + ) + assert ( + backtest[1].iat[1, 5] == -1 + and backtest[1].iat[1, 9] == -0.30 + and backtest[1].iat[1, 8] == 2665 + and backtest[1].iat[1, 16] == -12550.0 + ) + assert ( + backtest[1].iat[2, 5] == -1 + and backtest[1].iat[2, 9] == 0.31 + and backtest[1].iat[2, 8] == 2720 + and backtest[1].iat[2, 16] == 110550.0 + ) + assert ( + backtest[1].iat[3, 5] == 1 + and backtest[1].iat[3, 9] == 0.10 + and backtest[1].iat[3, 8] == 2750 + and backtest[1].iat[3, 16] == -87925.0 + ) + assert ( + backtest[1].iat[4, 5] == 1 + and backtest[1].iat[4, 9] == -0.10 + and backtest[1].iat[4, 8] == 2675 + and backtest[1].iat[4, 16] == -10500.0 + ) + assert ( + backtest[1].iat[5, 5] == -1 + and backtest[1].iat[5, 9] == -0.30 + and backtest[1].iat[5, 8] == 2775 + and backtest[1].iat[5, 16] == 60200.0 + ) + assert ( + backtest[1].iat[6, 5] == -1 + and backtest[1].iat[6, 9] == 0.30 + and backtest[1].iat[6, 8] == 2865 + and backtest[1].iat[6, 16] == -14250.0 + ) + assert ( + backtest[1].iat[7, 5] == 1 + and backtest[1].iat[7, 9] == 0.10 + and backtest[1].iat[7, 8] == 2920 + and backtest[1].iat[7, 16] == 3150.0 + ) + + +def test_long_iron_condor_market_integration(hod_struct): + data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) + + filters = { + "leg1_delta": (0.05, 0.10, 0.15), + "leg2_delta": (0.25, 0.30, 0.45), + "leg3_delta": (0.25, 0.30, 0.45), + "leg4_delta": (0.05, 0.10, 0.15), + "entry_dte": 31, + "exit_dte": 7, + } + + start = datetime(2018, 1, 1) + end = datetime(2018, 2, 28) + + trades = long_iron_condor(data, start, end, filters) + backtest = run(data, trades, filters, mode="market") + print(backtest[1]) + assert backtest[0] == 61350.0 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 9] == -0.10 + and backtest[1].iat[0, 8] == 2580 + and backtest[1].iat[0, 16] == 4700.0 + ) + assert ( + backtest[1].iat[1, 5] == -1 + and backtest[1].iat[1, 9] == -0.30 + and backtest[1].iat[1, 8] == 2665 + and backtest[1].iat[1, 16] == -12300.0 + ) + assert ( + backtest[1].iat[2, 5] == -1 + and backtest[1].iat[2, 9] == 0.31 + and backtest[1].iat[2, 8] == 2720 + and backtest[1].iat[2, 16] == 113300.0 + ) + assert ( + backtest[1].iat[3, 5] == 1 + and backtest[1].iat[3, 9] == 0.10 + and backtest[1].iat[3, 8] == 2750 + and backtest[1].iat[3, 16] == -86650.0 + ) + assert ( + backtest[1].iat[4, 5] == 1 + and backtest[1].iat[4, 9] == -0.10 + and backtest[1].iat[4, 8] == 2675 + and backtest[1].iat[4, 16] == -9900.0 + ) + assert ( + backtest[1].iat[5, 5] == -1 + and backtest[1].iat[5, 9] == -0.30 + and backtest[1].iat[5, 8] == 2775 + and backtest[1].iat[5, 16] == 62900.0 + ) + assert ( + backtest[1].iat[6, 5] == -1 + and backtest[1].iat[6, 9] == 0.30 + and backtest[1].iat[6, 8] == 2865 + and backtest[1].iat[6, 16] == -14000.0 + ) + assert ( + backtest[1].iat[7, 5] == 1 + and backtest[1].iat[7, 9] == 0.10 + and backtest[1].iat[7, 8] == 2920 + and backtest[1].iat[7, 16] == 3300.0 + ) + + +def test_long_iron_condor_butterfly_integration(hod_struct): + data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) + + filters = { + "leg1_delta": (0.25, 0.30, 0.45), + "leg2_delta": (0.45, 0.50, 0.55), + "leg3_delta": (0.45, 0.50, 0.55), + "leg4_delta": (0.25, 0.30, 0.45), + "entry_dte": (18, 18, 18), + "exit_dte": 7, + } + + start = datetime(2018, 1, 1) + end = datetime(2018, 2, 28) + + trades = long_iron_condor(data, start, end, filters) + backtest = run(data, trades, filters) + print(backtest[1]) + assert backtest[1].empty + + +def test_short_iron_condor_integration(hod_struct): + data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) + + filters = { + "leg1_delta": (0.05, 0.10, 0.15), + "leg2_delta": (0.25, 0.30, 0.45), + "leg3_delta": (0.25, 0.30, 0.45), + "leg4_delta": (0.05, 0.10, 0.15), + "entry_dte": 31, + "exit_dte": 7, + } + + start = datetime(2018, 1, 1) + end = datetime(2018, 2, 28) + + trades = short_iron_condor(data, start, end, filters) + backtest = run(data, trades, filters) + print(backtest[1]) + assert backtest[0] == -53225 + assert ( + backtest[1].iat[0, 5] == -1 + and backtest[1].iat[0, 9] == -0.10 + and backtest[1].iat[0, 8] == 2580 + and backtest[1].iat[0, 16] == -4550.0 + ) + assert ( + backtest[1].iat[1, 5] == 1 + and backtest[1].iat[1, 9] == -0.30 + and backtest[1].iat[1, 8] == 2665 + and backtest[1].iat[1, 16] == 12550.0 + ) + assert ( + backtest[1].iat[2, 5] == 1 + and backtest[1].iat[2, 9] == 0.31 + and backtest[1].iat[2, 8] == 2720 + and backtest[1].iat[2, 16] == -110550.0 + ) + assert ( + backtest[1].iat[3, 5] == -1 + and backtest[1].iat[3, 9] == 0.10 + and backtest[1].iat[3, 8] == 2750 + and backtest[1].iat[3, 16] == 87925.0 + ) + assert ( + backtest[1].iat[4, 5] == -1 + and backtest[1].iat[4, 9] == -0.10 + and backtest[1].iat[4, 8] == 2675 + and backtest[1].iat[4, 16] == 10500.0 + ) + assert ( + backtest[1].iat[5, 5] == 1 + and backtest[1].iat[5, 9] == -0.30 + and backtest[1].iat[5, 8] == 2775 + and backtest[1].iat[5, 16] == -60200.0 + ) + assert ( + backtest[1].iat[6, 5] == 1 + and backtest[1].iat[6, 9] == 0.30 + and backtest[1].iat[6, 8] == 2865 + and backtest[1].iat[6, 16] == 14250.0 + ) + assert ( + backtest[1].iat[7, 5] == -1 + and backtest[1].iat[7, 9] == 0.10 + and backtest[1].iat[7, 8] == 2920 + and backtest[1].iat[7, 16] == -3150.0 + ) diff --git a/tests/integration/test_integration_singles.py b/tests/integration/test_integration_singles.py index 7efa5ef..0e42096 100644 --- a/tests/integration/test_integration_singles.py +++ b/tests/integration/test_integration_singles.py @@ -21,7 +21,46 @@ def test_long_call_integration(hod_struct): trades = long_call(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == 963.0 + print(backtest[1]) + assert backtest[0] == -96300.0 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 9] == 0.31 + and backtest[1].iat[0, 8] == 2720 + and backtest[1].iat[0, 16] == -110550.0 + ) + assert ( + backtest[1].iat[1, 5] == 1 + and backtest[1].iat[1, 9] == 0.30 + and backtest[1].iat[1, 8] == 2865 + and backtest[1].iat[1, 16] == 14250.0 + ) + + +def test_long_call_market_integration(hod_struct): + data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) + + filters = {"entry_dte": 31, "leg1_delta": 0.30, "exit_dte": 7} + + start = datetime(2018, 1, 1) + end = datetime(2018, 2, 28) + + trades = long_call(data, start, end, filters) + backtest = run(data, trades, filters, mode="market") + print(backtest[1]) + assert backtest[0] == -93300.0 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 9] == 0.31 + and backtest[1].iat[0, 8] == 2720 + and backtest[1].iat[0, 16] == -107800.0 + ) + assert ( + backtest[1].iat[1, 5] == 1 + and backtest[1].iat[1, 9] == 0.30 + and backtest[1].iat[1, 8] == 2865 + and backtest[1].iat[1, 16] == 14500.0 + ) def test_long_call_no_exit_dte_integration(hod_struct): @@ -34,7 +73,18 @@ def test_long_call_no_exit_dte_integration(hod_struct): trades = long_call(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == 818.75 + print(backtest[1]) + assert backtest[0] == -81875 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 9] == 0.31 + and backtest[1].iat[0, 16] == -96200.0 + ) + assert ( + backtest[1].iat[1, 5] == 1 + and backtest[1].iat[1, 9] == 0.30 + and backtest[1].iat[1, 16] == 14325.0 + ) def test_short_call_integration(hod_struct): @@ -47,10 +97,20 @@ def test_short_call_integration(hod_struct): trades = short_call(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == -963.0 - - -def test_long_put_spread_integration(hod_struct): + assert backtest[0] == 96300.0 + assert ( + backtest[1].iat[0, 5] == -1 + and backtest[1].iat[0, 9] == 0.31 + and backtest[1].iat[0, 16] == 110550.0 + ) + assert ( + backtest[1].iat[1, 5] == -1 + and backtest[1].iat[1, 9] == 0.30 + and backtest[1].iat[1, 16] == -14250.0 + ) + + +def test_long_put_integration(hod_struct): data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) filters = {"entry_dte": 31, "leg1_delta": 0.30, "exit_dte": 7} @@ -60,10 +120,21 @@ def test_long_put_spread_integration(hod_struct): trades = long_put(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == 476.5 - - -def test_short_put_spread_integration(hod_struct): + print(backtest[1]) + assert backtest[0] == -47650 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 9] == -0.3 + and backtest[1].iat[0, 16] == 12550.0 + ) + assert ( + backtest[1].iat[1, 5] == 1 + and backtest[1].iat[1, 9] == -0.3 + and backtest[1].iat[1, 16] == -60200.0 + ) + + +def test_short_put_integration(hod_struct): data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) filters = {"entry_dte": 31, "leg1_delta": 0.30, "exit_dte": 7} @@ -73,4 +144,14 @@ def test_short_put_spread_integration(hod_struct): trades = short_put(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == -476.5 + assert backtest[0] == 47650 + assert ( + backtest[1].iat[0, 5] == -1 + and backtest[1].iat[0, 9] == -0.3 + and backtest[1].iat[0, 16] == -12550.0 + ) + assert ( + backtest[1].iat[1, 5] == -1 + and backtest[1].iat[1, 9] == -0.3 + and backtest[1].iat[1, 16] == 60200.0 + ) diff --git a/tests/integration/test_integration_verticals.py b/tests/integration/test_integration_verticals.py index 83e36c8..1b33e79 100644 --- a/tests/integration/test_integration_verticals.py +++ b/tests/integration/test_integration_verticals.py @@ -19,52 +19,153 @@ def test_long_call_spread_integration(hod_struct): data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) - filters = {"entry_dte": 31, "leg1_delta": 0.30, "leg2_delta": 0.50, "exit_dte": 7} + filters = {"entry_dte": 31, "leg1_delta": 0.50, "leg2_delta": 0.30, "exit_dte": 7} start = datetime(2018, 1, 1) end = datetime(2018, 2, 28) trades = long_call_spread(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == -80.25 - assert backtest[1].iat[0, 5] == 1 - assert backtest[1].iat[1, 5] == -1 - assert backtest[1].iat[2, 5] == 1 - assert backtest[1].iat[3, 5] == -1 + print(backtest[1]) + assert backtest[0] == 8025 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 8] == 2700 + and backtest[1].iat[0, 9] == 0.49 + and backtest[1].iat[0, 16] == -121000.0 + ) + assert ( + backtest[1].iat[1, 5] == -1 + and backtest[1].iat[1, 8] == 2720 + and backtest[1].iat[1, 9] == 0.31 + and backtest[1].iat[1, 16] == 110550.0 + ) + assert ( + backtest[1].iat[2, 5] == 1 + and backtest[1].iat[2, 8] == 2825 + and backtest[1].iat[2, 9] == 0.51 + and backtest[1].iat[2, 16] == 32725.0 + ) + assert ( + backtest[1].iat[3, 5] == -1 + and backtest[1].iat[3, 8] == 2865 + and backtest[1].iat[3, 9] == 0.30 + and backtest[1].iat[3, 16] == -14250.0 + ) + + +def test_long_call_spread_market_integration(hod_struct): + data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) + + filters = {"entry_dte": 31, "leg1_delta": 0.50, "leg2_delta": 0.30, "exit_dte": 7} + + start = datetime(2018, 1, 1) + end = datetime(2018, 2, 28) + + trades = long_call_spread(data, start, end, filters) + backtest = run(data, trades, filters, mode="market") + print(backtest[1]) + assert backtest[0] == 14250.0 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 8] == 2700 + and backtest[1].iat[0, 9] == 0.49 + and backtest[1].iat[0, 16] == -118100.0 + ) + assert ( + backtest[1].iat[1, 5] == -1 + and backtest[1].iat[1, 8] == 2720 + and backtest[1].iat[1, 9] == 0.31 + and backtest[1].iat[1, 16] == 113300.0 + ) + assert ( + backtest[1].iat[2, 5] == 1 + and backtest[1].iat[2, 8] == 2825 + and backtest[1].iat[2, 9] == 0.51 + and backtest[1].iat[2, 16] == 33050.0 + ) + assert ( + backtest[1].iat[3, 5] == -1 + and backtest[1].iat[3, 8] == 2865 + and backtest[1].iat[3, 9] == 0.30 + and backtest[1].iat[3, 16] == -14000.0 + ) def test_long_call_spread_no_exit_dte_integration(hod_struct): data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) - filters = {"entry_dte": 31, "leg1_delta": 0.30, "leg2_delta": 0.50} + filters = {"entry_dte": 31, "leg1_delta": 0.50, "leg2_delta": 0.30} start = datetime(2018, 1, 1) end = datetime(2018, 2, 28) trades = long_call_spread(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == -72.00 - assert backtest[1].iat[0, 5] == 1 - assert backtest[1].iat[1, 5] == -1 - assert backtest[1].iat[2, 5] == 1 - assert backtest[1].iat[3, 5] == -1 + print(backtest[1]) + assert backtest[0] == 7200.00 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 8] == 2700 + and backtest[1].iat[0, 9] == 0.49 + and backtest[1].iat[0, 16] == -107600.0 + ) + assert ( + backtest[1].iat[1, 5] == -1 + and backtest[1].iat[1, 8] == 2720 + and backtest[1].iat[1, 9] == 0.31 + and backtest[1].iat[1, 16] == 96200.0 + ) + assert ( + backtest[1].iat[2, 5] == 1 + and backtest[1].iat[2, 8] == 2825 + and backtest[1].iat[2, 9] == 0.51 + and backtest[1].iat[2, 16] == 32925.0 + ) + assert ( + backtest[1].iat[3, 5] == -1 + and backtest[1].iat[3, 8] == 2865 + and backtest[1].iat[3, 9] == 0.30 + and backtest[1].iat[3, 16] == -14325.0 + ) def test_short_call_spread_integration(hod_struct): data = get(TEST_FILE_PATH_FULL, hod_struct, prompt=False) - filters = {"entry_dte": 31, "leg1_delta": 0.30, "leg2_delta": 0.50, "exit_dte": 7} + filters = {"entry_dte": 31, "leg1_delta": 0.50, "leg2_delta": 0.30, "exit_dte": 7} start = datetime(2018, 1, 1) end = datetime(2018, 2, 28) trades = short_call_spread(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == 80.25 - assert backtest[1].iat[0, 5] == -1 - assert backtest[1].iat[1, 5] == 1 - assert backtest[1].iat[2, 5] == -1 - assert backtest[1].iat[3, 5] == 1 + print(backtest[1]) + assert backtest[0] == -8025 + assert ( + backtest[1].iat[0, 5] == -1 + and backtest[1].iat[0, 8] == 2700 + and backtest[1].iat[0, 9] == 0.49 + and backtest[1].iat[0, 16] == 121000.0 + ) + assert ( + backtest[1].iat[1, 5] == 1 + and backtest[1].iat[1, 8] == 2720 + and backtest[1].iat[1, 9] == 0.31 + and backtest[1].iat[1, 16] == -110550.0 + ) + assert ( + backtest[1].iat[2, 5] == -1 + and backtest[1].iat[2, 8] == 2825 + and backtest[1].iat[2, 9] == 0.51 + and backtest[1].iat[2, 16] == -32725.0 + ) + assert ( + backtest[1].iat[3, 5] == 1 + and backtest[1].iat[3, 8] == 2865 + and backtest[1].iat[3, 9] == 0.30 + and backtest[1].iat[3, 16] == 14250.0 + ) def test_long_put_spread_integration(hod_struct): @@ -77,11 +178,32 @@ def test_long_put_spread_integration(hod_struct): trades = long_put_spread(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == 227.50 - assert backtest[1].iat[0, 5] == -1 - assert backtest[1].iat[1, 5] == 1 - assert backtest[1].iat[2, 5] == -1 - assert backtest[1].iat[3, 5] == 1 + print(backtest[1]) + assert backtest[0] == -25650 + assert ( + backtest[1].iat[0, 5] == -1 + and backtest[1].iat[0, 8] == 2665 + and backtest[1].iat[0, 9] == -0.30 + and backtest[1].iat[0, 16] == -12550.0 + ) + assert ( + backtest[1].iat[1, 5] == 1 + and backtest[1].iat[1, 8] == 2700 + and backtest[1].iat[1, 9] == -0.51 + and backtest[1].iat[1, 16] == 22400.0 + ) + assert ( + backtest[1].iat[2, 5] == -1 + and backtest[1].iat[2, 8] == 2775 + and backtest[1].iat[2, 9] == -0.30 + and backtest[1].iat[2, 16] == 60200.0 + ) + assert ( + backtest[1].iat[3, 5] == 1 + and backtest[1].iat[3, 8] == 2830 + and backtest[1].iat[3, 9] == -0.51 + and backtest[1].iat[3, 16] == -95700.0 + ) def test_short_put_spread_integration(hod_struct): @@ -94,8 +216,29 @@ def test_short_put_spread_integration(hod_struct): trades = short_put_spread(data, start, end, filters) backtest = run(data, trades, filters) - assert backtest[0] == -227.50 - assert backtest[1].iat[0, 5] == 1 - assert backtest[1].iat[1, 5] == -1 - assert backtest[1].iat[2, 5] == 1 - assert backtest[1].iat[3, 5] == -1 + print(backtest[1]) + assert backtest[0] == 25650 + assert ( + backtest[1].iat[0, 5] == 1 + and backtest[1].iat[0, 8] == 2665 + and backtest[1].iat[0, 9] == -0.30 + and backtest[1].iat[0, 16] == 12550.0 + ) + assert ( + backtest[1].iat[1, 5] == -1 + and backtest[1].iat[1, 8] == 2700 + and backtest[1].iat[1, 9] == -0.51 + and backtest[1].iat[1, 16] == -22400.0 + ) + assert ( + backtest[1].iat[2, 5] == 1 + and backtest[1].iat[2, 8] == 2775 + and backtest[1].iat[2, 9] == -0.30 + and backtest[1].iat[2, 16] == -60200.0 + ) + assert ( + backtest[1].iat[3, 5] == -1 + and backtest[1].iat[3, 8] == 2830 + and backtest[1].iat[3, 9] == -0.51 + and backtest[1].iat[3, 16] == 95700.0 + ) diff --git a/tests/test_iron_condor_strategy.py b/tests/test_iron_condor_strategy.py new file mode 100644 index 0000000..ca50167 --- /dev/null +++ b/tests/test_iron_condor_strategy.py @@ -0,0 +1,182 @@ +from optopsy.enums import OrderAction +from optopsy.option_strategies import long_iron_condor, short_iron_condor +from datetime import datetime +import pytest + + +start = datetime(1990, 1, 20) +end = datetime(1990, 1, 20) + + +params_butterfly = { + "leg1_delta": (0.25, 0.30, 0.45), + "leg2_delta": (0.45, 0.50, 0.55), + "leg3_delta": (0.45, 0.50, 0.55), + "leg4_delta": (0.25, 0.30, 0.45), + "entry_dte": (18, 18, 18), +} + +params = { + "leg1_delta": (0.05, 0.10, 0.15), + "leg2_delta": (0.25, 0.30, 0.45), + "leg3_delta": (0.25, 0.30, 0.45), + "leg4_delta": (0.05, 0.10, 0.15), + "entry_dte": (18, 18, 18), +} + +params_float = { + "leg1_delta": 0.10, + "leg2_delta": 0.30, + "leg3_delta": 0.30, + "leg4_delta": 0.10, + "entry_dte": 18, +} + +invalid_params = { + "leg1_delta": (0.05, 0.30, 0.15), + "leg2_delta": (0.25, 0.10, 0.45), + "leg3_delta": (0.25, 0.10, 0.45), + "leg4_delta": (0.05, 0.30, 0.15), + "entry_dte": (18, 18, 18), +} + +invalid_params_2 = { + "leg1_delta": (0.05, 0.10, 0.15), + "leg2_delta": (0.25, 0.30, 0.45), + "leg3_delta": (0.25, 0.10, 0.45), + "leg4_delta": (0.05, 0.30, 0.15), + "entry_dte": (18, 18, 18), +} + +invalid_params_3 = { + "leg1_delta": (0.05, 0.30, 0.15), + "leg2_delta": (0.25, 0.10, 0.45), + "leg3_delta": (0.25, 0.30, 0.45), + "leg4_delta": (0.05, 0.10, 0.15), + "entry_dte": (18, 18, 18), +} + +invalid_params_4 = { + "leg1_delta": datetime(2016, 1, 31), + "leg2_delta": (0.25, 0.10, 0.45), + "leg3_delta": (0.25, 0.30, 0.45), + "leg4_delta": (0.05, 0.10, 0.15), + "entry_dte": "(18, 18, 18)", +} + + +def test_long_iron_condor_spread(options_data): + actual_spread = long_iron_condor(options_data, start, end, params) + print(actual_spread) + assert ( + actual_spread.iat[0, 3] == 1 + and actual_spread.iat[0, 5] == 340 + and actual_spread.iat[0, 9] == -0.09 + and actual_spread.iat[0, 4] == "p" + ) + assert ( + actual_spread.iat[1, 3] == -1 + and actual_spread.iat[1, 5] == 355 + and actual_spread.iat[1, 9] == -0.31 + and actual_spread.iat[1, 4] == "p" + ) + assert ( + actual_spread.iat[2, 3] == -1 + and actual_spread.iat[2, 5] == 365 + and actual_spread.iat[2, 9] == 0.35 + and actual_spread.iat[2, 4] == "c" + ) + assert ( + actual_spread.iat[3, 3] == 1 + and actual_spread.iat[3, 5] == 375 + and actual_spread.iat[3, 9] == 0.10 + and actual_spread.iat[3, 4] == "c" + ) + + +def test_short_iron_condor_spread(options_data): + actual_spread = short_iron_condor(options_data, start, end, params) + print(actual_spread) + assert ( + actual_spread.iat[0, 3] == -1 + and actual_spread.iat[0, 5] == 340 + and actual_spread.iat[0, 9] == -0.09 + and actual_spread.iat[0, 4] == "p" + ) + assert ( + actual_spread.iat[1, 3] == 1 + and actual_spread.iat[1, 5] == 355 + and actual_spread.iat[1, 9] == -0.31 + and actual_spread.iat[1, 4] == "p" + ) + assert ( + actual_spread.iat[2, 3] == 1 + and actual_spread.iat[2, 5] == 365 + and actual_spread.iat[2, 9] == 0.35 + and actual_spread.iat[2, 4] == "c" + ) + assert ( + actual_spread.iat[3, 3] == -1 + and actual_spread.iat[3, 5] == 375 + and actual_spread.iat[3, 9] == 0.10 + and actual_spread.iat[3, 4] == "c" + ) + + +def test_same_middle_strikes(options_data): + actual_spread = long_iron_condor(options_data, start, end, params_butterfly) + print(actual_spread) + assert actual_spread.empty == True + + +def test_float_params(options_data): + actual_spread = long_iron_condor(options_data, start, end, params_float) + print(actual_spread) + assert ( + actual_spread.iat[0, 3] == 1 + and actual_spread.iat[0, 5] == 340 + and actual_spread.iat[0, 9] == -0.09 + and actual_spread.iat[0, 4] == "p" + ) + assert ( + actual_spread.iat[1, 3] == -1 + and actual_spread.iat[1, 5] == 355 + and actual_spread.iat[1, 9] == -0.31 + and actual_spread.iat[1, 4] == "p" + ) + assert ( + actual_spread.iat[2, 3] == -1 + and actual_spread.iat[2, 5] == 365 + and actual_spread.iat[2, 9] == 0.35 + and actual_spread.iat[2, 4] == "c" + ) + assert ( + actual_spread.iat[3, 3] == 1 + and actual_spread.iat[3, 5] == 375 + and actual_spread.iat[3, 9] == 0.10 + and actual_spread.iat[3, 4] == "c" + ) + + +def test_invalid_deltas(options_data): + with pytest.raises(ValueError): + actual_spread = long_iron_condor(options_data, start, end, invalid_params) + print(actual_spread) + + +def test_invalid_deltas_2(options_data): + with pytest.raises(ValueError): + actual_spread = long_iron_condor(options_data, start, end, invalid_params_2) + print(actual_spread) + + +def test_invalid_deltas_3(options_data): + with pytest.raises(ValueError): + actual_spread = long_iron_condor(options_data, start, end, invalid_params_3) + print(actual_spread) + + +def test_invalid_deltas_4(options_data): + with pytest.raises(ValueError): + actual_spread = long_iron_condor(options_data, start, end, invalid_params_4) + print(actual_spread) diff --git a/tests/test_single_strategy.py b/tests/test_single_strategy.py index 5ecddb1..b7206ea 100644 --- a/tests/test_single_strategy.py +++ b/tests/test_single_strategy.py @@ -17,6 +17,7 @@ def test_long_call(options_data): {"leg1_delta": (0.45, 0.50, 0.55), "entry_dte": (18, 18, 18)}, ) results = actual_spread + print(results) assert all(results["option_type"] == "c") assert all(v in [0.55, 0.51] for v in results["delta"].unique().tolist()) assert results.shape == (1, 15) diff --git a/tests/test_vertical_strategy.py b/tests/test_vertical_strategy.py index 9d79f17..a920d35 100644 --- a/tests/test_vertical_strategy.py +++ b/tests/test_vertical_strategy.py @@ -6,45 +6,105 @@ short_put_spread, ) from datetime import datetime +import pytest start = datetime(1990, 1, 20) end = datetime(1990, 1, 20) -params = { + +call_params = { + "leg1_delta": (0.45, 0.50, 0.55), + "leg2_delta": (0.25, 0.30, 0.45), + "entry_dte": (18, 18, 18), +} + +put_params = { "leg1_delta": (0.25, 0.30, 0.45), "leg2_delta": (0.45, 0.50, 0.55), "entry_dte": (18, 18, 18), } -def _test_call_results(result): - assert all(result["option_type"] == "c") - assert all(v in [0.35, 0.55] for v in result["delta"].unique().tolist()) - assert result.shape == (2, 15) +def test_long_call_spread(options_data): + actual_spread = long_call_spread(options_data, start, end, call_params) + print(actual_spread) + assert all(actual_spread["option_type"] == "c") + assert ( + actual_spread.iat[0, 3] == 1 + and actual_spread.iat[0, 5] == 360 + and actual_spread.iat[0, 9] == 0.55 + ) + assert ( + actual_spread.iat[1, 3] == -1 + and actual_spread.iat[0, 5] == 360 + and actual_spread.iat[1, 9] == 0.35 + ) -def _test_put_results(result): - assert all(result["option_type"] == "p") - assert all(v in [-0.31, -0.46] for v in result["delta"].unique().tolist()) - assert result.shape == (2, 15) +def test_invalid_long_call_spread(options_data): + with pytest.raises(ValueError): + long_call_spread(options_data, start, end, put_params) -def test_long_call_spread(options_data): - actual_spread = long_call_spread(options_data, start, end, params) - _test_call_results(actual_spread) +def test_short_call_spread(options_data): + actual_spread = short_call_spread(options_data, start, end, call_params) + print(actual_spread) + assert all(actual_spread["option_type"] == "c") + assert ( + actual_spread.iat[0, 3] == -1 + and actual_spread.iat[0, 5] == 360 + and actual_spread.iat[0, 9] == 0.55 + ) + assert ( + actual_spread.iat[1, 3] == 1 + and actual_spread.iat[1, 5] == 365 + and actual_spread.iat[1, 9] == 0.35 + ) -def test_short_call_spread(options_data): - actual_spread = short_call_spread(options_data, start, end, params) - _test_call_results(actual_spread) +def test_invalid_short_call_spread(options_data): + with pytest.raises(ValueError): + short_call_spread(options_data, start, end, put_params) def test_long_put_spread(options_data): - actual_spread = long_put_spread(options_data, start, end, params) - _test_put_results(actual_spread) + actual_spread = long_put_spread(options_data, start, end, put_params) + print(actual_spread) + assert all(actual_spread["option_type"] == "p") + assert ( + actual_spread.iat[0, 3] == -1 + and actual_spread.iat[0, 5] == 355 + and actual_spread.iat[0, 9] == -0.31 + ) + assert ( + actual_spread.iat[1, 3] == 1 + and actual_spread.iat[1, 5] == 360 + and actual_spread.iat[1, 9] == -0.46 + ) + + +def test_invalid_long_put_spread(options_data): + with pytest.raises(ValueError): + long_put_spread(options_data, start, end, call_params) def test_short_put_spread(options_data): - actual_spread = short_put_spread(options_data, start, end, params) - _test_put_results(actual_spread) + actual_spread = short_put_spread(options_data, start, end, put_params) + print(actual_spread) + assert all(actual_spread["option_type"] == "p") + assert ( + actual_spread.iat[0, 3] == 1 + and actual_spread.iat[0, 5] == 355 + and actual_spread.iat[0, 9] == -0.31 + ) + assert ( + actual_spread.iat[1, 3] == -1 + and actual_spread.iat[1, 5] == 360 + and actual_spread.iat[1, 9] == -0.46 + ) + + +def test_invalid_short_put_spread(options_data): + with pytest.raises(ValueError): + short_put_spread(options_data, start, end, call_params)