Spaces:
Sleeping
Sleeping
File size: 13,397 Bytes
0c4eb1e 6d1402b 0c4eb1e ecea37b 0c4eb1e 6d1402b 0c4eb1e ba5d39a 0c4eb1e ba5d39a 0c4eb1e 38f2bbe 0c4eb1e 43415d2 ba5d39a 43415d2 0c4eb1e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | 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) |