QuantumLearner commited on
Commit
6c8956d
·
verified ·
1 Parent(s): 1d28a66

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +284 -0
app.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import yfinance as yf
3
+ import numpy as np
4
+ import pandas as pd
5
+ import plotly.graph_objects as go
6
+ from itertools import product
7
+ from datetime import datetime, timedelta
8
+
9
+ # Function to calculate Hull Moving Average (HMA)
10
+ def hull_moving_average(data, window):
11
+ half_length = int(window / 2)
12
+ sqrt_length = int(np.sqrt(window))
13
+ wma_half = data['Close'].rolling(half_length).apply(lambda x: np.dot(x, range(1, half_length+1)) / sum(range(1, half_length+1)), raw=True)
14
+ wma_full = data['Close'].rolling(window).apply(lambda x: np.dot(x, range(1, window+1)) / sum(range(1, window+1)), raw=True)
15
+ hma = 2 * wma_half - wma_full
16
+ hma = hma.rolling(sqrt_length).apply(lambda x: np.dot(x, range(1, sqrt_length+1)) / sum(range(1, sqrt_length+1)), raw=True)
17
+ return hma
18
+
19
+ # Function to calculate signals based on HMA crossover
20
+ def calculate_signals_hma(data, short_window, long_window):
21
+ data['short_hma'] = hull_moving_average(data, short_window)
22
+ data['long_hma'] = hull_moving_average(data, long_window)
23
+
24
+ data['signal'] = np.where(data['short_hma'] > data['long_hma'], 1, -1) # 1 for Buy, -1 for Sell
25
+ data['positions'] = data['signal'].diff() # Difference to detect signal changes
26
+ return data
27
+
28
+ # Function to calculate accuracy of the strategy with adjustable day threshold
29
+ def calculate_accuracy(data, buy_threshold, sell_threshold):
30
+ buy_signals = data[data['positions'] == 2] # 2 indicates a Buy signal after a Sell
31
+ sell_signals = data[data['positions'] == -2] # -2 indicates a Sell signal after a Buy
32
+
33
+ buy_accuracy = (data['Close'].shift(-buy_threshold)[buy_signals.index] > buy_signals['Close']).mean()
34
+ sell_accuracy = (data['Close'].shift(-sell_threshold)[sell_signals.index] < sell_signals['Close']).mean()
35
+
36
+ overall_accuracy = (buy_accuracy + sell_accuracy) / 2
37
+ return overall_accuracy, buy_accuracy, sell_accuracy
38
+
39
+ # Function to optimize HMA parameters based on accuracy
40
+ def optimize_hma(data, short_windows, long_windows, buy_threshold, sell_threshold):
41
+ results = []
42
+ best_accuracy = 0
43
+ best_params = None
44
+
45
+ for short_window, long_window in product(short_windows, long_windows):
46
+ if short_window >= long_window:
47
+ continue
48
+ temp_data = calculate_signals_hma(data.copy(), short_window, long_window)
49
+ accuracy, buy_accuracy, sell_accuracy = calculate_accuracy(temp_data, buy_threshold, sell_threshold)
50
+
51
+ results.append((short_window, long_window, accuracy))
52
+
53
+ if accuracy > best_accuracy:
54
+ best_accuracy = accuracy
55
+ best_params = (short_window, long_window, buy_accuracy, sell_accuracy)
56
+
57
+ results_df = pd.DataFrame(results, columns=['Short_HMA', 'Long_HMA', 'Accuracy'])
58
+ return best_params, best_accuracy, results_df
59
+
60
+ # Plotting function with win rates in the legend next to buy/sell signals
61
+ def plot_results(data, best_short_window, best_long_window, horizon_name, best_accuracy, buy_accuracy, sell_accuracy):
62
+ data = calculate_signals_hma(data.copy(), best_short_window, best_long_window)
63
+
64
+ fig = go.Figure()
65
+
66
+ # Add price and HMA lines
67
+ fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name='Price', hovertemplate='%{x|%Y-%m-%d}'))
68
+ fig.add_trace(go.Scatter(x=data.index, y=data['short_hma'], mode='lines', name=f'Short HMA ({best_short_window})', hovertemplate='%{x|%Y-%m-%d}'))
69
+ fig.add_trace(go.Scatter(x=data.index, y=data['long_hma'], mode='lines', name=f'Long HMA ({best_long_window})', hovertemplate='%{x|%Y-%m-%d}'))
70
+
71
+ # Add Buy/Sell signals with increased marker size
72
+ buy_signals = data[data['positions'] == 2] # 2 indicates a Buy signal after a Sell
73
+ sell_signals = data[data['positions'] == -2] # -2 indicates a Sell signal after a Buy
74
+ fig.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['Close'], mode='markers',
75
+ marker=dict(color='green', size=15, symbol='triangle-up'), # Increased size
76
+ name=f'Buy Signal (Win Rate: {buy_accuracy:.2f})', hovertemplate='%{x|%Y-%m-%d}'))
77
+ fig.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['Close'], mode='markers',
78
+ marker=dict(color='red', size=15, symbol='triangle-down'), # Increased size
79
+ name=f'Sell Signal (Win Rate: {sell_accuracy:.2f})', hovertemplate='%{x|%Y-%m-%d}'))
80
+
81
+ # Set title and layout, including more detailed date formatting for x-axis
82
+ fig.update_layout(
83
+ title=f'{horizon_name} Horizon: Price and HMA with Buy/Sell Signals (Best Accuracy: {best_accuracy:.2f})',
84
+ xaxis_title='Date',
85
+ yaxis_title='Price',
86
+ xaxis=dict(
87
+ tickformat="%b %Y", # Month and year format
88
+ dtick="M1", # Set tick interval to every month
89
+ tickangle=45, # Rotate the tick labels for better readability
90
+ ),
91
+ width=1000,
92
+ height=600
93
+ )
94
+ return fig
95
+
96
+
97
+ # Plotting function for strategy performance over time
98
+ def plot_strategy_over_time(data, best_short_window, best_long_window):
99
+ data = calculate_signals_hma(data.copy(), best_short_window, best_long_window)
100
+
101
+ # Rolling accuracy calculation
102
+ window_size = 252 # Using a 1-year window for rolling accuracy
103
+ data['rolling_accuracy'] = data['signal'].rolling(window=window_size).apply(lambda x: (x.shift(-1) * x > 0).mean(), raw=False)
104
+
105
+ fig = go.Figure()
106
+ fig.add_trace(go.Scatter(x=data.index, y=data['rolling_accuracy'], mode='lines', name='Rolling Accuracy', hovertemplate='%{x|%Y-%m-%d}'))
107
+
108
+ fig.update_layout(
109
+ title='Strategy Accuracy Over Time',
110
+ xaxis_title='Date',
111
+ yaxis_title='Rolling Accuracy',
112
+ width=1000,
113
+ height=400
114
+ )
115
+ return fig
116
+
117
+ # Streamlit app layout
118
+ st.set_page_config(layout="wide")
119
+
120
+ # Sidebar configuration
121
+ with st.sidebar:
122
+ st.header("Input Parameters")
123
+ st.subheader("How to Use")
124
+ st.write("""
125
+ - Select the stock ticker.
126
+ - Set the start and end dates.
127
+ - Click 'Run' to execute the strategy.
128
+ """)
129
+
130
+ with st.expander("Ticker Parameters", expanded=True):
131
+ ticker = st.text_input("Stock Ticker", value="AAPL", help="Enter the stock ticker symbol (e.g., AAPL, TSLA)")
132
+ start_date = st.date_input("Start Date", value=datetime(2019, 1, 1), help="Select the start date for the data")
133
+ end_date = st.date_input("End Date", value=datetime.now() + timedelta(days=1), help="Select the end date for the data")
134
+
135
+ with st.expander("Select Horizon", expanded=True):
136
+ st.radio("Horizon", ["Short-Term", "Medium-Term", "Long-Term"], key='horizon_page')
137
+
138
+ # Load appropriate horizon settings based on the selected page
139
+ horizons = {
140
+ 'Short-Term': {'short_windows': range(5, 20, 2), 'long_windows': range(20, 50, 3), 'buy_threshold': 1, 'sell_threshold': 1},
141
+ 'Medium-Term': {'short_windows': range(20, 50, 2), 'long_windows': range(50, 100, 5), 'buy_threshold': 5, 'sell_threshold': 5},
142
+ 'Long-Term': {'short_windows': range(50, 100, 5), 'long_windows': range(100, 200, 10), 'buy_threshold': 10, 'sell_threshold': 10},
143
+ }
144
+ selected_horizon = horizons[st.session_state.horizon_page]
145
+
146
+ # Sticky Run button at the bottom of the sidebar
147
+ st.markdown("---") # Separator line
148
+ st.markdown("<div style='position: fixed; bottom: 0; width: 100%; background-color: #ffffff; padding: 10px;'>", unsafe_allow_html=True)
149
+ run_button = st.button("Run")
150
+ st.markdown("</div>", unsafe_allow_html=True)
151
+
152
+ # Title based on the selected page
153
+ st.title(f"Hull Moving Average Cross-Over Strategy Optimizer - {st.session_state.horizon_page}")
154
+
155
+ # Explanation with LaTeX formulas
156
+ st.write("""
157
+ This application optimizes a trading strategy based on the Hull Moving Average. The strategy uses a cross-over method to generate buy and sell signals by finding the best MA parameters in a given horizon. To read more about moving averages methodologies, visit [this link](https://entreprenerdly.com/top-36-moving-averages-methods-for-stock-prices-in-python/).
158
+ """)
159
+ st.latex(r"""
160
+ \text{HMA} = \text{WMA}(2 \times \text{WMA}(n/2) - \text{WMA}(n), \sqrt{n})
161
+ """)
162
+
163
+ st.write("""
164
+ The cross-over signals are generated based on the following rule:
165
+ """)
166
+ st.latex(r"""
167
+ \text{Signal} =
168
+ \begin{cases}
169
+ \text{Buy} & \text{if } \text{Short HMA} > \text{Long HMA} \\
170
+ \text{Sell} & \text{if } \text{Short HMA} < \text{Long HMA}
171
+ \end{cases}
172
+ """)
173
+
174
+ # Main application logic
175
+ if run_button:
176
+ if 'data' not in st.session_state or st.session_state.get('ticker') != ticker or st.session_state.get('start_date') != start_date or st.session_state.get('end_date') != end_date:
177
+ st.session_state['data'] = yf.download(ticker, start=start_date, end=end_date)
178
+ st.session_state['ticker'] = ticker
179
+ st.session_state['start_date'] = start_date
180
+ st.session_state['end_date'] = end_date
181
+
182
+ data = st.session_state['data']
183
+
184
+ # Cache optimization results for each horizon
185
+ if f'{st.session_state.horizon_page}_results' not in st.session_state:
186
+ st.session_state[f'{st.session_state.horizon_page}_results'] = optimize_hma(data, selected_horizon['short_windows'], selected_horizon['long_windows'], selected_horizon['buy_threshold'], selected_horizon['sell_threshold'])
187
+
188
+ # Unpack the results from the session state
189
+ best_params, best_accuracy, results_df = st.session_state[f'{st.session_state.horizon_page}_results']
190
+ best_short_window, best_long_window, buy_accuracy, sell_accuracy = best_params
191
+
192
+ # Display results
193
+ st.write(f"**{st.session_state.horizon_page} Horizon - Best Short HMA**: {best_short_window}, **Best Long HMA**: {best_long_window}, **Best Accuracy**: {best_accuracy:.2f}")
194
+ st.write(f"**Buy Win Rate**: {buy_accuracy:.2f}, **Sell Win Rate**: {sell_accuracy:.2f}")
195
+
196
+ # Plot results
197
+ fig = plot_results(data, best_short_window, best_long_window, st.session_state.horizon_page, best_accuracy, buy_accuracy, sell_accuracy)
198
+ st.plotly_chart(fig, use_container_width=True)
199
+
200
+ # Plot strategy performance over time
201
+ st.write("Strategy Performance Over Time")
202
+ strategy_fig = plot_strategy_over_time(data, best_short_window, best_long_window)
203
+ st.plotly_chart(strategy_fig, use_container_width=True)
204
+
205
+ # Display heatmap of accuracy with annotations
206
+ st.write(f"{st.session_state.horizon_page} Horizon: Accuracy Heatmap of HMA Combinations")
207
+ heatmap_df = results_df.pivot(index='Short_HMA', columns='Long_HMA', values='Accuracy')
208
+
209
+ # Create the heatmap with annotations
210
+ heatmap_fig = go.Figure(data=go.Heatmap(
211
+ z=heatmap_df.values,
212
+ x=heatmap_df.columns,
213
+ y=heatmap_df.index,
214
+ colorscale='YlGnBu',
215
+ text=heatmap_df.values, # Use the values for the text inside the heatmap
216
+ texttemplate="%{text:.2f}", # Format text with two decimal places
217
+ hovertemplate="Short HMA: %{y}<br>Long HMA: %{x}<br>Accuracy: %{text:.2f}<extra></extra>",
218
+ showscale=True
219
+ ))
220
+
221
+ heatmap_fig.update_layout(
222
+ title=f'{st.session_state.horizon_page} Horizon: Accuracy Heatmap of HMA Combinations',
223
+ xaxis_title='Long HMA',
224
+ yaxis_title='Short HMA',
225
+ width=1000,
226
+ height=600
227
+ )
228
+
229
+ st.plotly_chart(heatmap_fig, use_container_width=True)
230
+
231
+ # Re-display the results if they exist and user switches pages without re-running
232
+ else:
233
+ if f'{st.session_state.horizon_page}_results' in st.session_state:
234
+ # Unpack the results from the session state
235
+ best_params, best_accuracy, results_df = st.session_state[f'{st.session_state.horizon_page}_results']
236
+ best_short_window, best_long_window, buy_accuracy, sell_accuracy = best_params
237
+
238
+ # Display results
239
+ st.write(f"**{st.session_state.horizon_page} Horizon - Best Short HMA**: {best_short_window}, **Best Long HMA**: {best_long_window}, **Best Accuracy**: {best_accuracy:.2f}")
240
+ st.write(f"**Buy Win Rate**: {buy_accuracy:.2f}, **Sell Win Rate**: {sell_accuracy:.2f}")
241
+
242
+ # Plot results
243
+ fig = plot_results(st.session_state['data'], best_short_window, best_long_window, st.session_state.horizon_page, best_accuracy, buy_accuracy, sell_accuracy)
244
+ st.plotly_chart(fig, use_container_width=True)
245
+
246
+ # Plot strategy performance over time
247
+ st.write("Strategy Performance Over Time")
248
+ strategy_fig = plot_strategy_over_time(st.session_state['data'], best_short_window, best_long_window)
249
+ st.plotly_chart(strategy_fig, use_container_width=True)
250
+
251
+ # Display heatmap of accuracy with annotations
252
+ st.write(f"{st.session_state.horizon_page} Horizon: Accuracy Heatmap of HMA Combinations")
253
+ heatmap_df = results_df.pivot(index='Short_HMA', columns='Long_HMA', values='Accuracy')
254
+
255
+ # Create the heatmap with annotations
256
+ heatmap_fig = go.Figure(data=go.Heatmap(
257
+ z=heatmap_df.values,
258
+ x=heatmap_df.columns,
259
+ y=heatmap_df.index,
260
+ colorscale='YlGnBu',
261
+ text=heatmap_df.values, # Use the values for the text inside the heatmap
262
+ texttemplate="%{text:.2f}", # Format text with two decimal places
263
+ hovertemplate="Short HMA: %{y}<br>Long HMA: %{x}<br>Accuracy: %{text:.2f}<extra></extra>",
264
+ showscale=True
265
+ ))
266
+
267
+ heatmap_fig.update_layout(
268
+ title=f'{st.session_state.horizon_page} Horizon: Accuracy Heatmap of HMA Combinations',
269
+ xaxis_title='Long HMA',
270
+ yaxis_title='Short HMA',
271
+ width=1000,
272
+ height=600
273
+ )
274
+
275
+ st.plotly_chart(heatmap_fig, use_container_width=True)
276
+
277
+
278
+ hide_streamlit_style = """
279
+ <style>
280
+ #MainMenu {visibility: hidden;}
281
+ footer {visibility: hidden;}
282
+ </style>
283
+ """
284
+ st.markdown(hide_streamlit_style, unsafe_allow_html=True)