Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import yfinance as yf | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.graph_objs as go | |
| from datetime import datetime, timedelta | |
| from plotly.subplots import make_subplots | |
| # Set Streamlit page configuration | |
| st.set_page_config(page_title="Pivot Point-RSI Volatility Trading Strategy", layout="wide") | |
| # App title | |
| st.title("Pivot Point-RSI Volatility Trading Strategy") | |
| # Description of the app | |
| st.markdown(''' | |
| This tool executes a trading strategy that combines Pivot Points, RSI, and ATR to identify buy and sell signals. | |
| It works by calculating support and resistance levels using Pivot Points, determining market momentum through RSI, and adjusting | |
| stop-loss and target levels based on market volatility using ATR. | |
| The strategy finds the best possible parameters through optimization. You can also manually input your own settings. | |
| ''') | |
| # Sidebar: How to use the app | |
| with st.sidebar.expander("How to Use", expanded=False): | |
| st.write(''' | |
| 1. Select the asset and date range for backtesting. | |
| 2. Click "Run Strategy" to generate results. | |
| 3. View the equity curve, buy/sell signals, and performance metrics. | |
| 4. Adjust the parameters post-run to fine-tune results. | |
| ''') | |
| # Sidebar: Select Ticker and Date Range | |
| with st.sidebar.expander("Asset Settings", expanded=True): | |
| ticker = st.text_input("Asset Symbol", value="ASML.AS", help="Ticker Symbol or Cryptocurrency Pair (e.g., AAPL, BTC-USD)") | |
| start_date = st.date_input("Start Date", value=datetime(2020, 1, 1), help="Select the start date for historical data.") | |
| end_date = st.date_input("End Date", value=datetime.today() + timedelta(days=1), help="Select the end date for historical data.") | |
| # Download data | |
| def download_data(ticker, start, end): | |
| data = yf.download(ticker, start=start, end=end, auto_adjust=False) | |
| if isinstance(data.columns, pd.MultiIndex): | |
| data.columns = data.columns.get_level_values(0) | |
| if data.empty: | |
| raise ValueError(f"No data retrieved for {ticker}") | |
| if len(data) < 24: # Minimum needed for 24-period Pivot Point calculation | |
| raise ValueError(f"Insufficient data points for {ticker}. Need at least 24 days.") | |
| return data | |
| # Function to execute and cache strategy results | |
| def execute_strategy(data, strategy_params): | |
| data_with_signals = trading_strategy(data.copy(), **strategy_params) | |
| profit, equity_curve, accuracy_rate = backtest_strategy(data_with_signals) | |
| return data_with_signals, profit, equity_curve, accuracy_rate, strategy_params | |
| # Trading strategy function | |
| def trading_strategy(data, rsi_period, rsi_oversold, rsi_overbought, atr_period, stop_loss_multiplier, target_multiplier, tolerance): | |
| data['high24'] = data['High'].rolling(window=24).max() | |
| data['low24'] = data['Low'].rolling(window=24).min() | |
| data['close24'] = data['Close'].rolling(window=24).mean() | |
| data['pivot'] = (data['high24'] + data['low24'] + data['close24']) / 3 | |
| data['support'] = (2 * data['pivot'].rolling(window=12).min()) - data['high24'] | |
| data['resistance'] = (2 * data['pivot'].rolling(window=12).max()) - data['low24'] | |
| delta = data['Close'].diff() | |
| up, down = delta.copy(), delta.copy() | |
| up[up < 0] = 0 | |
| down[down > 0] = 0 | |
| roll_up1 = up.ewm(span=rsi_period).mean() | |
| roll_down1 = down.abs().ewm(span=rsi_period).mean() | |
| RS1 = roll_up1 / roll_down1 | |
| data['rsi'] = 100.0 - (100.0 / (1.0 + RS1)) | |
| data['HL'] = data['High'] - data['Low'] | |
| data['absHC'] = abs(data['High'] - data['Close'].shift()) | |
| data['absLC'] = abs(data['Low'] - data['Close'].shift()) | |
| data['TR'] = data[['HL', 'absHC', 'absLC']].max(axis=1) | |
| data['atr'] = data['TR'].rolling(window=atr_period).mean() | |
| data['signal'] = np.where( | |
| (data['Close'] >= data['support'] * (1 - tolerance)) & (data['Close'] <= data['support'] * (1 + tolerance)) & (data['rsi'] < rsi_oversold), 'Buy', | |
| np.where((data['Close'] >= data['resistance'] * (1 - tolerance)) & (data['Close'] <= data['resistance'] * (1 + tolerance)) & (data['rsi'] > rsi_overbought), 'Sell', 'Hold') | |
| ) | |
| data['stop_loss'] = data['Close'] - stop_loss_multiplier * data['atr'] | |
| data['target'] = data['Close'] + target_multiplier * data['atr'] | |
| return data | |
| # Backtest function | |
| def backtest_strategy(data): | |
| initial_capital = 100000 | |
| position = 0 | |
| capital = initial_capital | |
| equity_curve = [] | |
| correct_signals = 0 | |
| total_signals = 0 | |
| for index, row in data.iterrows(): | |
| if row['signal'] == 'Buy' and position == 0: | |
| position = 1 | |
| entry_price = row['Close'] | |
| stop_loss = row['stop_loss'] | |
| target = row['target'] | |
| elif row['signal'] == 'Sell' and position == 0: | |
| position = -1 | |
| entry_price = row['Close'] | |
| stop_loss = row['stop_loss'] | |
| target = row['target'] | |
| if position == 1: | |
| if row['Low'] <= stop_loss: | |
| capital += (stop_loss - entry_price) * 100 | |
| position = 0 | |
| elif row['High'] >= target: | |
| capital += (target - entry_price) * 100 | |
| position = 0 | |
| elif row['signal'] == 'Sell': | |
| capital += (row['Close'] - entry_price) * 100 | |
| position = 0 | |
| elif position == -1: | |
| if row['High'] >= stop_loss: | |
| capital += (entry_price - stop_loss) * 100 | |
| position = 0 | |
| elif row['Low'] <= target: | |
| capital += (entry_price - target) * 100 | |
| position = 0 | |
| elif row['signal'] == 'Buy': | |
| capital += (entry_price - row['Close']) * 100 | |
| position = 0 | |
| equity_curve.append(capital) | |
| final_profit = equity_curve[-1] - initial_capital | |
| return final_profit, equity_curve, 0 | |
| # Optimized parameters (fixed) | |
| optimized_params = { | |
| 'rsi_period': 13, | |
| 'rsi_oversold': 30, | |
| 'rsi_overbought': 70, | |
| 'atr_period': 14, | |
| 'stop_loss_multiplier': 2.25, | |
| 'target_multiplier': 4.5, | |
| 'tolerance': 0.01 | |
| } | |
| # Run button to initiate the strategy | |
| run_button = st.sidebar.button("Run Strategy") | |
| # Running the strategy | |
| if run_button: | |
| try: | |
| # Download data | |
| data = download_data(ticker, start_date, end_date) | |
| # Execute the strategy with optimized parameters | |
| data_with_signals, profit, equity_curve, _, best_params = execute_strategy(data, optimized_params) | |
| # Cache results in session state | |
| st.session_state['data_with_signals'] = data_with_signals | |
| st.session_state['equity_curve'] = equity_curve | |
| st.session_state['best_params'] = best_params | |
| # Display optimized parameters | |
| st.json(best_params) | |
| except Exception as e: | |
| st.error(f"An error occurred while running the analysis: {e}") | |
| # If session state contains data, allow for post-run parameter adjustments | |
| if 'data_with_signals' in st.session_state: | |
| st.sidebar.markdown("### Adjust Parameters Post-Run") | |
| adjusted_rsi_period = st.sidebar.slider("RSI Period", 5, 30, st.session_state['best_params']['rsi_period'], step=1, help="RSI period defines the sensitivity of the RSI. Lower values make the RSI more sensitive to price changes, generating more signals. Higher values smooth out price movements, reducing the number of signals.") | |
| adjusted_stop_loss_multiplier = st.sidebar.slider("Stop Loss Multiplier", 1.0, 3.0, st.session_state['best_params']['stop_loss_multiplier'], step=0.1, help="Stop Loss Multiplier adjusts how far the stop-loss is set from the entry price. Lower values keep the stop-loss closer, reducing risk but increasing the chance of being stopped out. Higher values place the stop-loss further away, allowing more flexibility but with higher potential risk.") | |
| adjusted_target_multiplier = st.sidebar.slider("Target Multiplier", 1.0, 5.0, st.session_state['best_params']['target_multiplier'], step=0.1, help="Target Multiplier adjusts how far the profit target is set from the entry price. Lower values take profits earlier but reduce potential gains. Higher values aim for larger profits but increase the chance of price reversal before hitting the target.") | |
| adjusted_tolerance = st.sidebar.slider("Signal Tolerance", 0.0, 0.05, st.session_state['best_params']['tolerance'], step=0.01, help="Signal Tolerance adjusts how strictly the strategy follows support and resistance levels. Lower values make signals more precise but reduce their frequency. Higher values increase the frequency of signals by being more lenient, which can lead to false signals.") | |
| # Recalculate the strategy with adjusted parameters | |
| updated_params = { | |
| 'rsi_period': adjusted_rsi_period, | |
| 'rsi_oversold': 30, | |
| 'rsi_overbought': 70, | |
| 'atr_period': 14, | |
| 'stop_loss_multiplier': adjusted_stop_loss_multiplier, | |
| 'target_multiplier': adjusted_target_multiplier, | |
| 'tolerance': adjusted_tolerance | |
| } | |
| updated_data = trading_strategy(st.session_state['data_with_signals'].copy(), **updated_params) | |
| # Plotting with adjustments for easier comparison of x-axis | |
| fig = make_subplots(rows=3, cols=1, shared_xaxes=True, | |
| subplot_titles=("Price and Bollinger Bands", "RSI", "Equity Curve"), | |
| vertical_spacing=0.20) | |
| # Price and signal plot | |
| fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['Close'], mode='lines', name='Close Price')) | |
| fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['support'], mode='lines', name='Support Level', line=dict(dash='dash'))) | |
| fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['resistance'], mode='lines', name='Resistance Level', line=dict(dash='dash'))) | |
| # Buy/Sell Signals with increased marker size | |
| buy_signals = updated_data[updated_data['signal'] == 'Buy'] | |
| sell_signals = updated_data[updated_data['signal'] == 'Sell'] | |
| fig.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['Close'], mode='markers', name='Buy Signal', | |
| marker=dict(color='green', symbol='triangle-up', size=12))) | |
| fig.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['Close'], mode='markers', name='Sell Signal', | |
| marker=dict(color='red', symbol='triangle-down', size=12))) | |
| # RSI Plot with black line, red upper threshold, and green lower threshold | |
| fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['rsi'], mode='lines', name='RSI', line=dict(color='white')), row=2, col=1) | |
| fig.add_shape(type="line", x0=updated_data.index[0], x1=updated_data.index[-1], y0=30, y1=30, | |
| line=dict(dash='dash', color='green'), row=2, col=1) | |
| fig.add_shape(type="line", x0=updated_data.index[0], x1=updated_data.index[-1], y0=70, y1=70, | |
| line=dict(dash='dash', color='red'), row=2, col=1) | |
| # Equity Curve Plot | |
| fig.add_trace(go.Scatter(x=updated_data.index, y=st.session_state['equity_curve'], mode='lines', name='Equity Curve'), row=3, col=1) | |
| # Move the legend outside the plot and increase gap | |
| fig.update_layout( | |
| title=f'{ticker} Strategy with Adjusted Parameters', | |
| xaxis_title='Date', | |
| yaxis_title='Price', | |
| legend=dict(orientation="h", yanchor="bottom", y=1.15, xanchor="center", x=0.5, traceorder='normal', valign='top', borderwidth=0), | |
| height=1000, | |
| margin=dict(t=30, b=30), | |
| font=dict(size=12), | |
| annotations=[dict(font=dict(color="white", size=14)) for _ in range(3)] | |
| ) | |
| # Display the chart | |
| st.plotly_chart(fig, use_container_width=True) | |
| hide_streamlit_style = """ | |
| <style> | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| </style> | |
| """ | |
| st.markdown(hide_streamlit_style, unsafe_allow_html=True) |