QuantumLearner commited on
Commit
83c71ac
·
verified ·
1 Parent(s): 5a62998

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +40 -77
app.py CHANGED
@@ -21,51 +21,44 @@ This tool backtests and optimizes a mean-reversion trading strategy. The idea be
21
 
22
  with st.expander("How the Strategy Works:", expanded=False):
23
  st.markdown('''
24
- 1. **Dynamic Window Sizes**: The strategy changes the window size it uses for calculating moving averages based on how volatile the market is. When volatility is high, it uses shorter windows, making it more responsive to rapid price movements. When volatility is low, it uses longer windows, which smooths out the data and reduces noise.
25
- 2. **Exponential Moving Average (EMA)**: The strategy uses an EMA to track the average price. The EMA gives more weight to recent prices, so it reacts more quickly to changes compared to a simple moving average. This helps capture shifts in the market earlier.
26
- 3. **Trend Filter**: A trend filter is added to make sure the strategy only takes trades in the direction of the overall market trend. This helps avoid taking trades that go against the bigger picture, which can lead to bad signals.
27
- 4. **Buy/Sell Signals**:
28
- - **Buy Signal**: A buy signal is generated when the price drops below the EMA and the trend shows an uptrend. This suggests that the price is likely to bounce back.
29
- - **Sell Signal**: A sell signal occurs when the price goes above the EMA and the trend is showing a downtrend, indicating the price might fall soon.
30
- 5. **Grid Search Optimization**: The app runs a grid search to test different combinations of parameters (like window sizes and thresholds) to find the ones that work best for the selected data. This helps maximize the strategy's performance.
31
  ##### **What You Can Do:**
32
- - **Adjust Parameters**: After running the initial optimization, you can tweak the base window size, alpha, beta, and signal threshold to see how the strategy’s performance changes.
33
- - **Signal Threshold**: This controls how strict the buy/sell signals are. A lower threshold will give you more signals, while a higher threshold will be more selective.
34
- - **Visual Feedback**: The app shows you the strategy’s performance visually, plotting buy/sell signals on a price chart and showing an equity curve so you can see how well the strategy performs over time.
35
  ''')
36
 
37
- # Sidebar: "How to Use" expander (closed by default)
38
  with st.sidebar.expander("How to Use", expanded=False):
39
  st.write("""
40
- 1. **Select Ticker**: Choose the asset ticker symbol (e.g., AAPL, TSLA) and date range for historical data.
41
- 2. **Run Strategy**: Click "Run Strategy" to optimize the parameters and run the backtest.
42
- 3. **Adjust Parameters**: Use the sliders to fine-tune the moving average windows, beta, and signal threshold to see how the strategy performs.
43
- 4. **Visualize**: The app displays buy/sell signals, the trend line, and the equity curve.
44
  """)
45
 
46
  st.sidebar.title("Input Parameters")
47
 
48
- # Sidebar: Select Ticker and Date Range
49
  with st.sidebar.expander("Asset Settings", expanded=True):
50
  ticker = st.text_input("Asset Symbol", value="AAPL", help="Ticker symbol or Cryptocurrency Pair (e.g., AAPL, BTC-USD)")
51
  start_date = st.date_input("Start Date", value=pd.to_datetime("2020-01-01"), help="Select the start date for historical data.")
52
  end_date = st.date_input("End Date", value=datetime.today() + pd.DateOffset(1), help="Select the end date for historical data.")
53
 
54
- # Function to download data
55
  @st.cache_data
56
  def get_data(ticker, start, end):
57
  data = yf.download(ticker, start=start, end=end)
58
  return data['Close']
59
 
60
- # Exponential Moving Average based OU parameters with adjustment
61
  def OU_parameters_ema(data, window):
62
- # Ensure window is a scalar integer
63
- window = int(window)
64
  mu = data.ewm(span=window).mean()
65
  sigma = data.ewm(span=window).std()
66
  return mu, sigma
67
 
68
- # Dynamic window size based on volatility
69
  def dynamic_window(data, base_window=60, volatility_window=20):
70
  volatility = data.rolling(window=volatility_window).std()
71
  adjusted_window = base_window / (volatility / volatility.mean())
@@ -74,7 +67,6 @@ def dynamic_window(data, base_window=60, volatility_window=20):
74
  adjusted_window = adjusted_window.round().astype(int).clip(lower=20, upper=120)
75
  return adjusted_window
76
 
77
- # Trading strategy with trend filter and adjustable parameters
78
  def trading_strategy(data, base_window=60, base_alpha=1.0, beta=0.1, trend_window=200, signal_threshold=0):
79
  windows = dynamic_window(data, base_window=base_window)
80
  buy_signals = []
@@ -82,16 +74,23 @@ def trading_strategy(data, base_window=60, base_alpha=1.0, beta=0.1, trend_windo
82
  positions = []
83
  trend = data.rolling(window=trend_window).mean()
84
 
 
85
  for i in range(len(data)):
86
- # Cast window to integer to avoid ambiguous Series error
 
 
 
 
 
87
  window = int(windows.iloc[i])
88
  mu, sigma = OU_parameters_ema(data[:i+1], window=window)
89
- alpha = base_alpha + beta * sigma.iloc[-1]
90
- price = data.iloc[i]
91
- mu_value = mu.iloc[-1]
92
- sigma_value = sigma.iloc[-1]
93
- trend_value = trend.iloc[i]
94
-
 
95
  if price < mu_value - (alpha + signal_threshold) * sigma_value and price < trend_value:
96
  buy_signals.append(price)
97
  sell_signals.append(np.nan)
@@ -107,14 +106,12 @@ def trading_strategy(data, base_window=60, base_alpha=1.0, beta=0.1, trend_windo
107
 
108
  return buy_signals, sell_signals, positions, trend
109
 
110
- # Function to calculate performance metric and equity curve
111
  def calculate_performance(data, positions):
112
  returns = np.diff(data) / data[:-1]
113
  strategy_returns = np.array(positions[:-1]) * returns
114
- equity_curve = np.cumprod(1 + strategy_returns) * 100 # Starting equity of 100
115
  return equity_curve
116
 
117
- # Grid search for best parameters with progress
118
  def grid_search(data, param_grid):
119
  best_params = None
120
  best_performance = -np.inf
@@ -141,34 +138,25 @@ def grid_search(data, param_grid):
141
  best_positions = positions
142
  best_trend = trend
143
 
144
- progress_bar.empty() # Remove the progress bar when done
145
  return best_params, best_performance, best_positions, best_trend
146
 
147
- # Run Button in the Sidebar
148
  run_button = st.sidebar.button("Run Strategy")
149
 
150
  if run_button:
151
- # Get historical data
152
  data = get_data(ticker, start_date, end_date)
153
-
154
- # Define the parameter grid
155
  param_grid = {
156
  'base_window': [30, 50, 70, 90],
157
  'base_alpha': [0.5, 1.0, 1.5],
158
  'beta': [0.05, 0.1, 0.15],
159
  'trend_window': [100, 200, 300]
160
  }
161
-
162
- # Perform grid search to find the best parameters
163
  best_params, best_performance, best_positions, best_trend = grid_search(data, param_grid)
164
-
165
- # Cache the results for faster updates when adjusting parameters
166
  st.session_state['data'] = data
167
  st.session_state['best_params'] = best_params
168
  st.session_state['best_positions'] = best_positions
169
  st.session_state['best_trend'] = best_trend
170
 
171
- # Display best parameters in JSON format
172
  st.json({
173
  "Best Parameters": {
174
  "Base Window": best_params[0],
@@ -178,41 +166,19 @@ if run_button:
178
  }
179
  })
180
 
181
- # If the session state has the optimized data, allow updating the signal threshold and other parameters without re-running the optimization
182
  if 'best_params' in st.session_state:
183
  st.sidebar.subheader("Adjust Parameters")
 
 
 
 
 
 
 
 
 
 
184
 
185
- base_window = st.sidebar.slider(
186
- "Base Window",
187
- 20, 120, st.session_state['best_params'][0],
188
- help="Adjust the base window size."
189
- )
190
-
191
- base_alpha = st.sidebar.slider(
192
- "Base Alpha",
193
- 0.1, 2.0, st.session_state['best_params'][1], 0.1,
194
- help="Adjust the base alpha value."
195
- )
196
-
197
- beta = st.sidebar.slider(
198
- "Beta",
199
- 0.01, 0.3, st.session_state['best_params'][2], 0.01,
200
- help="Adjust the beta value."
201
- )
202
-
203
- trend_window = st.sidebar.slider(
204
- "Trend Window",
205
- 50, 400, st.session_state['best_params'][3],
206
- help="Adjust the trend window size."
207
- )
208
-
209
- signal_threshold = st.sidebar.slider(
210
- "Signal Threshold",
211
- -0.2, 0.2, 0.0, 0.01,
212
- help="Adjust the signal threshold."
213
- )
214
-
215
- # Apply the trading strategy with adjusted parameters
216
  buy_signals, sell_signals, positions, trend = trading_strategy(
217
  st.session_state['data'],
218
  base_window=base_window,
@@ -221,11 +187,8 @@ if 'best_params' in st.session_state:
221
  trend_window=trend_window,
222
  signal_threshold=signal_threshold
223
  )
224
-
225
- # Calculate the updated equity curve
226
  equity_curve = calculate_performance(st.session_state['data'], positions)
227
 
228
- # Plotting
229
  fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
230
  subplot_titles=("Price and Signals", "Equity Curve"),
231
  vertical_spacing=0.1)
 
21
 
22
  with st.expander("How the Strategy Works:", expanded=False):
23
  st.markdown('''
24
+ 1. **Dynamic Window Sizes**: The strategy changes the window size it uses for calculating moving averages based on market volatility.
25
+ 2. **Exponential Moving Average (EMA)**: Uses an EMA that gives more weight to recent prices.
26
+ 3. **Trend Filter**: Ensures trades align with the overall trend.
27
+ 4. **Buy/Sell Signals**:
28
+ - **Buy Signal**: Generated when the price is below the EMA (with an adaptive threshold) and below the trend.
29
+ - **Sell Signal**: Generated when the price is above the EMA and above the trend.
30
+ 5. **Grid Search Optimization**: Tests different parameter combinations to find the best performance.
31
  ##### **What You Can Do:**
32
+ - **Adjust Parameters**: Tweak the base window, alpha, beta, and signal threshold.
33
+ - **Visual Feedback**: View buy/sell signals and the equity curve on a chart.
 
34
  ''')
35
 
 
36
  with st.sidebar.expander("How to Use", expanded=False):
37
  st.write("""
38
+ 1. **Select Ticker**: Choose an asset symbol (e.g., AAPL, TSLA) and a date range.
39
+ 2. **Run Strategy**: Click "Run Strategy" to optimize parameters and run the backtest.
40
+ 3. **Adjust Parameters**: Use sliders to fine-tune the moving average window, beta, and signal threshold.
41
+ 4. **Visualize**: The app displays buy/sell signals and the equity curve.
42
  """)
43
 
44
  st.sidebar.title("Input Parameters")
45
 
 
46
  with st.sidebar.expander("Asset Settings", expanded=True):
47
  ticker = st.text_input("Asset Symbol", value="AAPL", help="Ticker symbol or Cryptocurrency Pair (e.g., AAPL, BTC-USD)")
48
  start_date = st.date_input("Start Date", value=pd.to_datetime("2020-01-01"), help="Select the start date for historical data.")
49
  end_date = st.date_input("End Date", value=datetime.today() + pd.DateOffset(1), help="Select the end date for historical data.")
50
 
 
51
  @st.cache_data
52
  def get_data(ticker, start, end):
53
  data = yf.download(ticker, start=start, end=end)
54
  return data['Close']
55
 
 
56
  def OU_parameters_ema(data, window):
57
+ window = int(window) # ensure window is scalar
 
58
  mu = data.ewm(span=window).mean()
59
  sigma = data.ewm(span=window).std()
60
  return mu, sigma
61
 
 
62
  def dynamic_window(data, base_window=60, volatility_window=20):
63
  volatility = data.rolling(window=volatility_window).std()
64
  adjusted_window = base_window / (volatility / volatility.mean())
 
67
  adjusted_window = adjusted_window.round().astype(int).clip(lower=20, upper=120)
68
  return adjusted_window
69
 
 
70
  def trading_strategy(data, base_window=60, base_alpha=1.0, beta=0.1, trend_window=200, signal_threshold=0):
71
  windows = dynamic_window(data, base_window=base_window)
72
  buy_signals = []
 
74
  positions = []
75
  trend = data.rolling(window=trend_window).mean()
76
 
77
+ # For indices where the trend is not fully defined, append NaNs and no position.
78
  for i in range(len(data)):
79
+ if i < trend_window - 1:
80
+ buy_signals.append(np.nan)
81
+ sell_signals.append(np.nan)
82
+ positions.append(0)
83
+ continue
84
+
85
  window = int(windows.iloc[i])
86
  mu, sigma = OU_parameters_ema(data[:i+1], window=window)
87
+ alpha = base_alpha + beta * float(sigma.iloc[-1])
88
+ # Convert each to float to ensure scalar comparison
89
+ price = float(data.iloc[i])
90
+ mu_value = float(mu.iloc[-1])
91
+ sigma_value = float(sigma.iloc[-1])
92
+ trend_value = float(trend.iloc[i])
93
+
94
  if price < mu_value - (alpha + signal_threshold) * sigma_value and price < trend_value:
95
  buy_signals.append(price)
96
  sell_signals.append(np.nan)
 
106
 
107
  return buy_signals, sell_signals, positions, trend
108
 
 
109
  def calculate_performance(data, positions):
110
  returns = np.diff(data) / data[:-1]
111
  strategy_returns = np.array(positions[:-1]) * returns
112
+ equity_curve = np.cumprod(1 + strategy_returns) * 100 # starting equity of 100
113
  return equity_curve
114
 
 
115
  def grid_search(data, param_grid):
116
  best_params = None
117
  best_performance = -np.inf
 
138
  best_positions = positions
139
  best_trend = trend
140
 
141
+ progress_bar.empty()
142
  return best_params, best_performance, best_positions, best_trend
143
 
 
144
  run_button = st.sidebar.button("Run Strategy")
145
 
146
  if run_button:
 
147
  data = get_data(ticker, start_date, end_date)
 
 
148
  param_grid = {
149
  'base_window': [30, 50, 70, 90],
150
  'base_alpha': [0.5, 1.0, 1.5],
151
  'beta': [0.05, 0.1, 0.15],
152
  'trend_window': [100, 200, 300]
153
  }
 
 
154
  best_params, best_performance, best_positions, best_trend = grid_search(data, param_grid)
 
 
155
  st.session_state['data'] = data
156
  st.session_state['best_params'] = best_params
157
  st.session_state['best_positions'] = best_positions
158
  st.session_state['best_trend'] = best_trend
159
 
 
160
  st.json({
161
  "Best Parameters": {
162
  "Base Window": best_params[0],
 
166
  }
167
  })
168
 
 
169
  if 'best_params' in st.session_state:
170
  st.sidebar.subheader("Adjust Parameters")
171
+ base_window = st.sidebar.slider("Base Window", 20, 120, st.session_state['best_params'][0],
172
+ help="Adjust the base window size.")
173
+ base_alpha = st.sidebar.slider("Base Alpha", 0.1, 2.0, st.session_state['best_params'][1], 0.1,
174
+ help="Adjust the base alpha value.")
175
+ beta = st.sidebar.slider("Beta", 0.01, 0.3, st.session_state['best_params'][2], 0.01,
176
+ help="Adjust the beta value.")
177
+ trend_window = st.sidebar.slider("Trend Window", 50, 400, st.session_state['best_params'][3],
178
+ help="Adjust the trend window size.")
179
+ signal_threshold = st.sidebar.slider("Signal Threshold", -0.2, 0.2, 0.0, 0.01,
180
+ help="Adjust the signal threshold.")
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  buy_signals, sell_signals, positions, trend = trading_strategy(
183
  st.session_state['data'],
184
  base_window=base_window,
 
187
  trend_window=trend_window,
188
  signal_threshold=signal_threshold
189
  )
 
 
190
  equity_curve = calculate_performance(st.session_state['data'], positions)
191
 
 
192
  fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
193
  subplot_titles=("Price and Signals", "Equity Curve"),
194
  vertical_spacing=0.1)