Space37 / app.py
QuantumLearner's picture
Update app.py
ba5d39a verified
import streamlit as st
import pandas as pd
import yfinance as yf
import numpy as np
import plotly.graph_objs as go
from plotly.subplots import make_subplots
import warnings
warnings.filterwarnings("ignore")
from datetime import datetime, timedelta
# Set Streamlit page configuration
st.set_page_config(page_title="Bollinger Bands and RSI Trading Strategy", layout="wide")
# Title and Description
st.title("Bollinger Bands and RSI Trading Strategy")
st.write("""
This tool backtests and optimizes a trading strategy based on Bollinger Bands and RSI.
The strategy generates buy signals when the price is below the lower Bollinger Band and RSI is oversold,
and sell signals when the price is above the upper Bollinger Band and RSI is overbought.
The process is optimized to find the best combination of parameters (RSI period, Bollinger Bands window, and Bollinger Bands standard deviation)
You can adjust the parameters post-run, and visualize the results, including buy/sell signals and the equity curve.
""")
st.sidebar.title("Input Parameters")
# Sidebar: How to use the app (Expander, closed by default)
with st.sidebar.expander("How to Use", expanded=False):
st.write("""
1. **Select Ticker and Date Range**: Choose the asset ticker (e.g., AAPL, ASML.AS, BTC-USD, EURUSD=X) and date range for historical data.
2. **Run Strategy**: Click "Run Strategy" to optimize and backtest the Bollinger Bands and RSI strategy.
3. **Adjust Parameters**: After running, adjust RSI period, Bollinger Bands window, standard deviation, and trend threshold. Results update in real time.
4. **View Results**: See strategy performance, buy/sell signals, equity curve, and indicators like Bollinger Bands and RSI.
""")
# Sidebar: Select Ticker and Date Range
with st.sidebar.expander("Asset Settings", expanded=True):
ticker = st.text_input("Asset Symbol", value="ASML.AS", help="Indicate Asset Symbol (e.g., ASML.AS, BTC-USD)")
start_date = st.date_input("Start Date", value=pd.to_datetime("2020-01-01"), help="Select the start date for historical data.")
end_date = st.date_input("End Date", value=pd.to_datetime("today") + pd.DateOffset(1), help="Select the end date for historical data.")
# Run Button in the Sidebar
run_button = st.sidebar.button("Run Strategy")
# Function to download historical data with yfinance adjustments
@st.cache_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 fetched for {ticker} from {start} to {end}.")
return data
# RSI calculation
def rsi(series, period=14):
delta = series.diff(1)
gain = delta.where(delta > 0, 0)
loss = -delta.where(delta < 0, 0)
avg_gain = gain.rolling(window=int(period)).mean()
avg_loss = loss.rolling(window=int(period)).mean()
rs = avg_gain / avg_loss
return 100 - (100 / (1 + rs))
# Bollinger Bands calculation
def bollinger_bands(series, window=20, n_std=2):
sma = series.rolling(window=int(window)).mean()
std = series.rolling(window=int(window)).std()
upper_band = sma + (n_std * std)
lower_band = sma - (n_std * std)
return upper_band, sma, lower_band
# SMA calculation
def sma(series, window):
return series.rolling(window=int(window)).mean()
# Strategy calculation function
def strategy(data, rsi_period, bb_window, bb_std_dev, sma_window=100, trend_threshold=0.01):
data['RSI'] = rsi(data['Close'], period=rsi_period)
data['BB_HIGH'], data['BB_MID'], data['BB_LOW'] = bollinger_bands(data['Close'], window=bb_window, n_std=bb_std_dev)
data['SMA_200'] = sma(data['Close'], window=sma_window)
data['SMA_Diff'] = (data['Close'] - data['SMA_200']) / data['SMA_200']
if trend_threshold == 0:
data['Trend_Condition'] = True
elif trend_threshold > 0:
data['Trend_Condition'] = data['SMA_Diff'].abs() <= trend_threshold
else:
data['Trend_Condition'] = data['SMA_Diff'].abs() >= abs(trend_threshold)
data['Buy_Signal'] = (data['RSI'] < 30) & (data['Close'] < data['BB_LOW']) & data['Trend_Condition']
data['Sell_Signal'] = (data['RSI'] > 70) & (data['Close'] > data['BB_HIGH']) & data['Trend_Condition']
data['Position'] = 0
data.loc[data['Buy_Signal'], 'Position'] = 1
data.loc[data['Sell_Signal'], 'Position'] = -1
data['Position'] = data['Position'].replace(to_replace=0, method='ffill')
data['Strategy_Returns'] = data['Position'].shift(1) * data['Close'].pct_change()
data['Equity_Curve'] = (1 + data['Strategy_Returns']).cumprod()
return data
# Optimization function
def optimize_strategy(data, rsi_period_range, bb_window_range, bb_std_dev_range, trend_threshold=0.01):
best_rsi_period = None
best_bb_window = None
best_bb_std_dev = None
best_equity = -np.inf
total_iterations = len(rsi_period_range) * len(bb_window_range) * len(bb_std_dev_range)
iteration = 0
progress_bar = st.progress(0)
for rsi_period in rsi_period_range:
for bb_window in bb_window_range:
for bb_std_dev in bb_std_dev_range:
iteration += 1
progress_bar.progress(iteration / total_iterations)
temp_data = strategy(data.copy(), rsi_period, bb_window, bb_std_dev, trend_threshold=trend_threshold)
final_equity = temp_data['Equity_Curve'].iloc[-1]
if final_equity > best_equity:
best_rsi_period = rsi_period
best_bb_window = bb_window
best_bb_std_dev = bb_std_dev
best_equity = final_equity
progress_bar.empty()
return best_rsi_period, best_bb_window, best_bb_std_dev, best_equity
# Running the strategy
if run_button or "data" in st.session_state:
if run_button:
# Download data
data = download_data(ticker, start_date, end_date)
# Parameter ranges for optimization
rsi_period_range = range(10, 31, 2)
bb_window_range = range(11, 32, 2)
bb_std_dev_range = [1.5, 1.75, 2, 2.25, 2.5]
# Optimize strategy
best_rsi_period, best_bb_window, best_bb_std_dev, best_equity = optimize_strategy(
data, rsi_period_range, bb_window_range, bb_std_dev_range, trend_threshold=0.00
)
# Cache the best parameters and data after running optimization
st.session_state['best_params'] = {
"RSI Period": best_rsi_period,
"BB Window": best_bb_window,
"BB Std Dev": float(best_bb_std_dev),
}
st.session_state['best_equity'] = best_equity
st.session_state['data'] = data
st.session_state['adjusted_params'] = None # Reset adjusted parameters
# Display best parameters in JSON format after optimization (always displayed)
st.subheader("Best Parameters from Optimization")
st.json(st.session_state['best_params'])
# Allow user to adjust parameters post-run
st.sidebar.markdown("### Adjust Parameters Post-Run")
if run_button or st.session_state['adjusted_params'] is None:
# Reset to optimized parameters when re-running
adjusted_rsi_period = st.sidebar.slider(
"RSI Period", 10, 30, st.session_state['best_params']['RSI Period'], step=2,
help="RSI Period defines the window length for calculating RSI. Increasing it smooths the RSI curve, making it less sensitive to price changes. Decreasing it increases sensitivity, generating more signals."
)
adjusted_bb_window = st.sidebar.slider(
"BB Window", 11, 32, st.session_state['best_params']['BB Window'], step=2,
help="BB Window defines the period over which Bollinger Bands are calculated. Increasing it broadens the bands, capturing long-term trends. Decreasing it narrows the bands, making them more sensitive to short-term price changes."
)
adjusted_bb_std_dev = st.sidebar.slider(
"BB Std Dev", 1.5, 2.5, float(st.session_state['best_params']['BB Std Dev']), step=0.25,
help="BB Std Dev controls how wide the Bollinger Bands are. Increasing this widens the bands, leading to fewer but more significant signals. Decreasing it tightens the bands, generating more frequent signals."
)
trend_threshold = st.sidebar.slider(
"Trend Threshold",
-0.05, 0.05, 0.00, 0.01,
help="Trend Threshold adjusts how strict the trend-following condition is. Positive values tighten the condition, filtering out minor fluctuations, while negative values allow more flexibility in trend detection."
)
else:
# Keep the adjusted parameters if they exist
adjusted_rsi_period = st.sidebar.slider(
"RSI Period", 10, 30, st.session_state['adjusted_params']['RSI Period'], step=2,
help="RSI Period defines the window length for calculating RSI. Increasing it smooths the RSI curve, making it less sensitive to price changes. Decreasing it increases sensitivity, generating more signals."
)
adjusted_bb_window = st.sidebar.slider(
"BB Window", 11, 32, st.session_state['adjusted_params']['BB Window'], step=2,
help="BB Window defines the period over which Bollinger Bands are calculated. Increasing it broadens the bands, capturing long-term trends. Decreasing it narrows the bands, making them more sensitive to short-term price changes."
)
adjusted_bb_std_dev = st.sidebar.slider(
"BB Std Dev", 1.5, 2.5, float(st.session_state['adjusted_params']['BB Std Dev']), step=0.25,
help="BB Std Dev controls how wide the Bollinger Bands are. Increasing this widens the bands, leading to fewer but more significant signals. Decreasing it tightens the bands, generating more frequent signals."
)
trend_threshold = st.sidebar.slider(
"Trend Threshold",
-0.05, 0.05, st.session_state['adjusted_params']['Trend Threshold'], 0.01,
help="Trend Threshold adjusts how strict the trend-following condition is. Positive values tighten the condition, filtering out minor fluctuations, while negative values allow more flexibility in trend detection."
)
# Recalculate the strategy with adjusted parameters
updated_data = strategy(st.session_state['data'].copy(), adjusted_rsi_period, adjusted_bb_window, adjusted_bb_std_dev, trend_threshold=trend_threshold)
# Cache the updated parameters and results
st.session_state['adjusted_params'] = {
"RSI Period": adjusted_rsi_period,
"BB Window": adjusted_bb_window,
"BB Std Dev": adjusted_bb_std_dev,
"Trend Threshold": trend_threshold
}
st.session_state['updated_data'] = updated_data
# Recalculate buy and sell signals
buy_signals = updated_data[updated_data['Buy_Signal']]
sell_signals = updated_data[updated_data['Sell_Signal']]
# Create subplots with 3 rows: Price/Bollinger, RSI, Equity Curve
fig = make_subplots(rows=3, cols=1, shared_xaxes=True,
subplot_titles=("Price and Bollinger Bands", "RSI", "Equity Curve"),
vertical_spacing=0.1)
# Price and Bollinger Bands
fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['Close'], mode='lines', name='Close Price'), row=1, col=1)
fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['BB_MID'], mode='lines', name='Bollinger Bands Mid', line=dict(color='red', dash='dash')), row=1, col=1)
fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['BB_HIGH'], mode='lines', name='Bollinger Bands High', line=dict(color='green', dash='dash')), row=1, col=1)
fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['BB_LOW'], mode='lines', name='Bollinger Bands Low', line=dict(color='cyan', dash='dash')), row=1, col=1)
# Buy/Sell Signals
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=10)), row=1, col=1)
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=10)), row=1, col=1)
# RSI
fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['RSI'], mode='lines', name='RSI', line=dict(color='purple')), row=2, col=1)
fig.add_hline(y=70, line_dash="dash", row=2, col=1, line_color="red")
fig.add_hline(y=30, line_dash="dash", row=2, col=1, line_color="green")
# Equity Curve
fig.add_trace(go.Scatter(x=updated_data.index, y=updated_data['Equity_Curve'], mode='lines', name='Equity Curve', line=dict(color='white')), row=3, col=1)
fig.update_layout(
title=f'{ticker} Bollinger Bands and RSI Strategy',
xaxis_title='Date',
height=800,
legend=dict(
orientation="h",
yanchor="bottom",
y=1.15,
xanchor="center",
x=0.5
)
)
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)