diff --git a/atr_regime_backtest.py b/atr_regime_backtest.py new file mode 100644 index 0000000..0768cd4 --- /dev/null +++ b/atr_regime_backtest.py @@ -0,0 +1,43 @@ +from datetime import datetime +from dotenv import load_dotenv +from os import getenv +from pandas import concat + +from backtesting import backtest_iron_condor +from backtesting.credit_targeting import create_strategies +from backtesting.filter import ATRRegimeFilter +from plotting import BacktestChart, plot + +load_dotenv() + +if __name__ == '__main__': + start_date = datetime(2016, 1, 1) + end_date = datetime.now() + + credit_target = float(getenv('CREDIT_TARGET')) + entry_times = getenv('ENTRY_TIMES').split(',') + + backtest_results = [] + + for entry_time in entry_times: + call_spread_strategy, put_spread_strategy = create_strategies(credit_target, entry_time) + + backtest_result = backtest_iron_condor( + f'${credit_target:.2f} Iron Condor @ {call_spread_strategy.trade_entry_time}', + call_spread_strategy, + put_spread_strategy, + start_date, + end_date, + filters = [ATRRegimeFilter()] + ) + + backtest_results.append(backtest_result) + + combined_backtest_results = concat(backtest_results) + summed_results = combined_backtest_results.groupby('Date')['Cumulative Profit'].sum().reset_index() + + plot(BacktestChart( + dates = summed_results['Date'], + profit = summed_results['Cumulative Profit'], + title = f'${credit_target:.2f} Iron Condor (ATR Regime Filter)' + )) \ No newline at end of file diff --git a/backtesting/filter/__init__.py b/backtesting/filter/__init__.py index f7e8bb1..993c9f4 100644 --- a/backtesting/filter/__init__.py +++ b/backtesting/filter/__init__.py @@ -1,3 +1,4 @@ +from .atr_regime_filter import ATRRegimeFilter from .backtest_filter import BacktestFilter from .volatility_regime_filter import VolatilityRegimeFilter from .vvix_regime_filter import VVIXRegimeFilter \ No newline at end of file diff --git a/backtesting/filter/atr_regime_filter.py b/backtesting/filter/atr_regime_filter.py new file mode 100644 index 0000000..9a9307d --- /dev/null +++ b/backtesting/filter/atr_regime_filter.py @@ -0,0 +1,64 @@ +import numpy as np + +from datetime import datetime, timedelta +from dotenv import load_dotenv +from os import getenv +from pandas import concat, DataFrame, Series +from database.ohlc import ohlc + +from .backtest_filter import BacktestFilter + +load_dotenv() + +class ATRRegimeFilter(BacktestFilter): + def __init__(self): + self.backtest_filter = self.filter() + + def calculate_atr(self, high, low, close, period = 5): + tr = np.maximum(high - low, np.abs(high - close.shift(1)), np.abs(low - close.shift(1))) + atr = tr.rolling(window = period).mean() + return atr + + def filter(self) -> DataFrame: + data_start_date = datetime.strptime(getenv('OPTION_DATA_START_DATE'), '%Y-%m-%d') + now = datetime.now() + + spx_data = ohlc( + symbol = 'SPX.XO', + timeframe = '1d', + start_date = data_start_date - timedelta(weeks = 52), + end_date = now + ) + spx_data.rename(columns = {'Timestamp': 'Date'}, inplace = True) + + spx_data['ATR'] = self.calculate_atr(spx_data['High'], spx_data['Low'], spx_data['Close']) + + """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + Filtering is based on the previous day's close, so the current date can be included even though the + data may not be availble yet. + + This allows for utilizing the filter in live trading to decide whether to trade prior to market open. + """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + spx_data = concat([ + spx_data, + DataFrame({ + 'Date': [datetime.combine(now, datetime.min.time())], + 'Open': [0.0], + 'High': [0.0], + 'Low': [0.0], + 'Close': [0.0], + 'Volume': [0.0], + 'ATR': [0.0] + })], + ignore_index = True + ) + + percent_rank = lambda x: Series(x).rank(pct = True).iloc[-1] + spx_data['ATR_Percent_Rank'] = spx_data['ATR'].shift(1).rolling(window = 252).apply(percent_rank) + + filtered_data = spx_data[spx_data['Date'] >= data_start_date].copy() + # filtered_data['Trade Allowed'] = (filtered_data['ATR_Percent_Rank'] > 0.25) & (filtered_data['ATR_Percent_Rank'] < 0.75) + filtered_data['Trade Allowed'] = filtered_data['ATR_Percent_Rank'] < 0.75 + + filtered_data = filtered_data[['Date', 'Trade Allowed']].reset_index(drop = True) + return filtered_data \ No newline at end of file diff --git a/test/filter/atr_regime_filter_test.py b/test/filter/atr_regime_filter_test.py new file mode 100644 index 0000000..50fae31 --- /dev/null +++ b/test/filter/atr_regime_filter_test.py @@ -0,0 +1,4 @@ +from backtesting.filter import ATRRegimeFilter + +filter = ATRRegimeFilter() +print(filter.backtest_filter) \ No newline at end of file