import streamlit as st import yfinance as yf import numpy as np import pandas as pd import plotly.graph_objects as go from bayes_opt import BayesianOptimization import warnings from datetime import datetime, timedelta warnings.filterwarnings('ignore') # Set the app layout to wide st.set_page_config(layout="wide", page_title="ATR-based Dynamic Stop Loss Estimation") # Initialize session state for data persistence if 'data' not in st.session_state: st.session_state.data = None if 'optimized_params' not in st.session_state: st.session_state.optimized_params = None if 'atr_multiplier' not in st.session_state: st.session_state.atr_multiplier = None if 'atr_window' not in st.session_state: st.session_state.atr_window = None # Sidebar for user input and navigation st.sidebar.title("Input Parameters") with st.sidebar.expander("Stop-Loss Strategy", expanded=True): page = st.radio("Select Mode", ["Run Strategy", "Optimize Parameters"]) # Common user inputs in the sidebar with collapsible sections with st.sidebar: with st.expander("How to use", expanded=False): st.header("How to Use") st.markdown(""" 1. Select **Run Strategy** to calculate stop-loss thresholds using specified ATR parameters. 2. Select **Optimize Parameters** to find the optimal ATR settings using Bayesian Optimization. 3. Set the symbol, date range, and strategy parameters in the **Parameters** section. 4. Click **Run Strategy** or **Run Optimization** to execute the selected operation. """) # Symbol and Date Section with st.expander("Symbol and Date Range", expanded=True): symbol = st.text_input("Asset Symbol", value="ASML", help="Enter the stock or cryptocurrency symbol, e.g., AAPL, BTC-USD") start_date = st.date_input("Start Date", value=pd.to_datetime("2022-01-01"), help="Select the start date for the data") end_date = st.date_input("End Date", value=datetime.now() + timedelta(days=1), help="Select the end date for the data") # ATR Parameters Section with st.expander("ATR Parameters", expanded=True): atr_window = st.slider("ATR Window", min_value=5, max_value=60, value=14, step=1, help="Adjust the ATR window length") atr_multiplier = st.slider("ATR Multiplier", min_value=1.0, max_value=5.0, value=2.0, step=0.1, help="Set the ATR multiplier for stop-loss calculation") # Strategy Parameters Section with st.expander("Strategy Parameters", expanded=True): min_holding_period = st.slider("Minimum Holding Period (days)", min_value=1, max_value=10, value=5, step=1, help="Set the minimum holding period in days. This would find the best stop loss given how many days you want to hold the asset") if page == "Run Strategy": run_strategy_button = st.button("Run Strategy") elif page == "Optimize Parameters": run_optimization_button = st.button("Run Optimization") # Main page description st.header("ATR-based Dynamic Stop Loss Estimation") st.markdown(""" This application esimates dynamic stop-loss levels using the Average True Range (ATR) indicator. You can either run a strategy with specific ATR parameters or optimize those parameters to find the best stop-loss settings. The app works with both stocks and cryptocurrency pairs. """) # Function to fetch data with yfinance adjustments @st.cache_data(show_spinner=False) def fetch_data(symbol, start, end): data = yf.download(symbol, 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 fetched for {symbol} from {start} to {end}.") return data # Function to compute Relative Strength Index (RSI) def compute_RSI(series, period=14): delta = series.diff(1) gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() RS = gain / loss RSI = 100 - (100 / (1 + RS)) return RSI # Function to calculate various technical indicators def calculate_indicators(data, atr_window): data['TR'] = np.maximum((data['High'] - data['Low']), np.maximum(abs(data['High'] - data['Close'].shift(1)), abs(data['Low'] - data['Close'].shift(1)))) data['ATR'] = data['TR'].rolling(window=int(atr_window)).mean() data['EMA12'] = data['Close'].ewm(span=12, adjust=False).mean() data['EMA26'] = data['Close'].ewm(span=26, adjust=False).mean() data['MACD'] = data['EMA12'] - data['EMA26'] data['Signal'] = data['MACD'].ewm(span=9, adjust=False).mean() data['RSI'] = compute_RSI(data['Close']) return data # Function to calculate stop loss levels def calculate_stop_loss(data, atr_multiplier): data['Buy_Stop_Loss'] = data['Close'] - (data['ATR'] * atr_multiplier) data['Sell_Stop_Loss'] = data['Close'] + (data['ATR'] * atr_multiplier) return data # Function to enforce minimum holding period def enforce_min_holding_period(signals, min_holding_period): hold_signals = signals.copy() for i in range(1, len(signals)): if signals.iloc[i]: hold_signals.iloc[i + 1:i + min_holding_period] = False return hold_signals # Function to backtest the strategy def backtest_strategy(atr_multiplier, atr_window, data, min_holding_period): data = calculate_indicators(data.copy(), atr_window) data = calculate_stop_loss(data, atr_multiplier) buy_signals = ((data['Close'] > data['Buy_Stop_Loss']) & (data['MACD'] > data['Signal']) & (data['RSI'] < 70)).shift(1).fillna(False) sell_signals = ((data['Close'] < data['Sell_Stop_Loss']) & (data['MACD'] < data['Signal']) & (data['RSI'] > 30)).shift(1).fillna(False) buy_signals = enforce_min_holding_period(buy_signals, min_holding_period) sell_signals = enforce_min_holding_period(sell_signals, min_holding_period) returns = data['Close'].pct_change().fillna(0) strategy_returns = returns * buy_signals.astype(int) - returns * sell_signals.astype(int) cumulative_returns = (1 + strategy_returns).cumprod() data['Cumulative_Strategy_Returns'] = cumulative_returns return data # Function to plot results def plot_results(data, symbol, show_strategy_returns=False): fig = go.Figure() fig.add_trace(go.Scatter( x=data.index, y=data['Close'], mode='lines', name='Close Price', line=dict(color='blue'), hovertemplate="Date: %{x}
Close: %{y:.2f}" )) fig.add_trace(go.Scatter( x=data.index, y=data['Buy_Stop_Loss'], mode='lines', name='Buy Stop Loss', line=dict(color='red', dash='dash'), hovertemplate="Date: %{x}
Buy Stop Loss: %{y:.2f}" )) fig.add_trace(go.Scatter( x=data.index, y=data['Sell_Stop_Loss'], mode='lines', name='Sell Stop Loss', line=dict(color='green', dash='dash'), hovertemplate="Date: %{x}
Sell Stop Loss: %{y:.2f}" )) fig.update_layout( title=f'{symbol} Stop Loss Levels based on ATR', xaxis_title='Date', yaxis_title='Price', width=1200, height=600 ) st.plotly_chart(fig, use_container_width=True) if show_strategy_returns: fig2 = go.Figure() fig2.add_trace(go.Scatter( x=data.index, y=data['Cumulative_Strategy_Returns'], mode='lines', name='Strategy Returns', line=dict(color='purple'), hovertemplate="Date: %{x}
Cumulative Return: %{y:.2f}" )) fig2.update_layout( title=f'{symbol} Cumulative Strategy Returns', xaxis_title='Date', yaxis_title='Cumulative Returns', width=1200, height=600 ) st.plotly_chart(fig2, use_container_width=True) # Function to optimize parameters def optimize_parameters(data, min_atr_multiplier, max_atr_multiplier, min_holding_period, progress_bar): def objective(atr_multiplier, atr_window): data_with_indicators = calculate_indicators(data.copy(), int(atr_window)) data_with_returns = backtest_strategy(atr_multiplier, int(atr_window), data_with_indicators, min_holding_period) return data_with_returns['Cumulative_Strategy_Returns'].iloc[-1] optimizer = BayesianOptimization( f=objective, pbounds={"atr_multiplier": (min_atr_multiplier, max_atr_multiplier), "atr_window": (5, 60)}, random_state=42 ) for i in range(1, 101): optimizer.maximize(init_points=1, n_iter=1) progress_bar.progress(i / 100) return optimizer.max['params'] # Page 1: Run Strategy with User-Specified Parameters if page == "Run Strategy": st.subheader("Run Strategy with Specified Parameters") if 'run_strategy_button' in locals() and run_strategy_button: with st.spinner("Fetching data..."): data = fetch_data(symbol, start_date, end_date) st.session_state.data = data # Store data in session state with st.spinner("Calculating indicators..."): data = calculate_indicators(st.session_state.data, atr_window) st.session_state.data = data with st.spinner("Running Strategy..."): data = backtest_strategy(atr_multiplier, atr_window, st.session_state.data, min_holding_period) st.session_state.data = data plot_results(st.session_state.data, symbol, show_strategy_returns=False) # If the data has already been processed, display it elif st.session_state.data is not None: plot_results(st.session_state.data, symbol, show_strategy_returns=False) # Page 2: Optimize Parameters elif page == "Optimize Parameters": st.subheader("Optimize Strategy Parameters") min_atr_multiplier = st.sidebar.slider("Minimum ATR Multiplier", min_value=1.0, max_value=3.0, value=1.5, step=0.1) max_atr_multiplier = st.sidebar.slider("Maximum ATR Multiplier", min_value=3.0, max_value=5.0, value=3.5, step=0.1) if 'run_optimization_button' in locals() and run_optimization_button: with st.spinner("Fetching data..."): data = fetch_data(symbol, start_date, end_date) st.session_state.data = data with st.spinner("Calculating indicators..."): data = calculate_indicators(st.session_state.data, atr_window=14) # Use a default window to calculate indicators st.session_state.data = data with st.spinner("Optimizing Parameters..."): progress_bar = st.progress(0) optimized_params = optimize_parameters(st.session_state.data, min_atr_multiplier, max_atr_multiplier, min_holding_period, progress_bar) st.session_state.optimized_params = optimized_params st.session_state.atr_multiplier = optimized_params['atr_multiplier'] st.session_state.atr_window = optimized_params['atr_window'] st.success(f"Optimization complete! Best ATR Multiplier: {st.session_state.atr_multiplier}, Best ATR Window: {st.session_state.atr_window}") with st.spinner("Running Strategy with Optimized Parameters..."): data = backtest_strategy(st.session_state.atr_multiplier, int(st.session_state.atr_window), st.session_state.data, min_holding_period) st.session_state.data = data plot_results(st.session_state.data, symbol, show_strategy_returns=True) # If the optimization has already been run, display the results elif st.session_state.optimized_params is not None: plot_results(st.session_state.data, symbol, show_strategy_returns=True) st.success(f"Optimization complete! Best ATR Multiplier: {st.session_state.atr_multiplier}, Best ATR Window: {st.session_state.atr_window}") # Hide Streamlit style hide_streamlit_style = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)