import streamlit as st import yfinance as yf import pandas as pd import numpy as np import matplotlib.pyplot as plt from datetime import timedelta # Streamlit App Title and Description st.title("Extended MACD-RSI Combo Strategy for SPY") st.markdown(""" This app demonstrates an extended MACD-RSI based trading strategy on SPY with the following features: - **Multiple Simultaneous Positions:** Each buy signal creates a new position. - **Dynamic Trailing Stop:** Each open position is updated with a trailing stop. - **Configurable Parameters:** Adjust strategy parameters via the sidebar. - **Buy Rule:** Buy a fraction of available cash when: - The MACD line crosses above its signal line. - RSI is below 50. - No buy has been executed in the last few days. - **Sell Rule:** For each position: - **Partial Sell:** Sell a fraction of the position when the price reaches a target multiple of the entry price and RSI is above 50. - **Trailing Stop Exit:** If the price falls below the position’s dynamic trailing stop, sell the entire position. """) # Sidebar for Strategy Parameters st.sidebar.header("Strategy Parameters") initial_capital = st.sidebar.number_input("Initial Capital ($)", min_value=1000, max_value=1000000, value=100000, step=1000) buy_fraction = st.sidebar.slider("Buy Fraction (of available cash)", 0.05, 0.50, 0.15, 0.05) sell_fraction = st.sidebar.slider("Partial Sell Fraction", 0.10, 0.90, 0.40, 0.05) target_multiplier = st.sidebar.slider("Target Multiplier", 1.01, 1.20, 1.08, 0.01) trailing_stop_pct = st.sidebar.slider("Trailing Stop (%)", 0.01, 0.20, 0.08, 0.01) min_days_between_buys = st.sidebar.number_input("Minimum Days Between Buys", min_value=1, max_value=10, value=2) # Load SPY Data @st.cache_data def load_data(ticker, period="1y"): data = yf.download(ticker, period=period) data.dropna(inplace=True) return data data_load_state = st.text("Loading SPY data...") data = load_data("SPY", period="1y") data_load_state.text("Loading SPY data...done!") # --- Manual Calculation of Technical Indicators --- # Calculate MACD 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['MACD_signal'] = data['MACD'].ewm(span=9, adjust=False).mean() # Calculate RSI delta = data['Close'].diff() gain = delta.where(delta > 0, 0) loss = -delta.where(delta < 0, 0) avg_gain = gain.rolling(window=14).mean() avg_loss = loss.rolling(window=14).mean() rs = avg_gain / avg_loss data['RSI'] = 100 - (100 / (1 + rs)) # Initialize signal flags for plotting data['Buy'] = False data['Sell'] = False # Backtesting parameters cash = initial_capital equity_curve = [] last_buy_date = None open_positions = [] completed_trades = [] # Backtesting simulation loop for i in range(1, len(data)): today = data.index[i] price = float(data['Close'].iloc[i]) # Ensure price is a scalar value rsi_today = float(data['RSI'].iloc[i]) # Ensure RSI is a scalar value # Check for buy signal macd_today = float(data['MACD'].iloc[i]) signal_today = float(data['MACD_signal'].iloc[i]) macd_yesterday = float(data['MACD'].iloc[i-1]) signal_yesterday = float(data['MACD_signal'].iloc[i-1]) buy_condition = (macd_yesterday < signal_yesterday) and (macd_today > signal_today) and (rsi_today < 50) if buy_condition and (last_buy_date is None or (today - last_buy_date).days >= min_days_between_buys): allocation = cash * buy_fraction if allocation > 0: shares_bought = allocation / price cash -= allocation last_buy_date = today open_positions.append({ "entry_date": today, "entry_price": price, "allocated": allocation, "shares": shares_bought, "highest": price, "trailing_stop": price * (1 - trailing_stop_pct) }) data.at[today, 'Buy'] = True # Update positions and check sell conditions positions_to_remove = [] for idx, pos in enumerate(open_positions): current_highest = pos["highest"] if price > current_highest: pos["highest"] = price pos["trailing_stop"] = pos["highest"] * (1 - trailing_stop_pct) # Partial sell condition if price >= (pos["entry_price"] * target_multiplier) and rsi_today > 50: shares_to_sell = pos["shares"] * sell_fraction cash += shares_to_sell * price pos["shares"] -= shares_to_sell pos["allocated"] -= shares_to_sell * pos["entry_price"] data.at[today, 'Sell'] = True if pos["shares"] < 0.001: completed_trades.append({ "entry_date": pos["entry_date"], "exit_date": today, "entry_price": pos["entry_price"], "exit_price": price, "allocated": pos["allocated"] }) positions_to_remove.append(idx) continue # Trailing stop exit current_trailing_stop = pos["trailing_stop"] if price < current_trailing_stop: cash += pos["shares"] * price completed_trades.append({ "entry_date": pos["entry_date"], "exit_date": today, "entry_price": pos["entry_price"], "exit_price": price, "allocated": pos["allocated"] }) positions_to_remove.append(idx) for idx in reversed(positions_to_remove): del open_positions[idx] # Update equity curve position_value = sum(pos["shares"] * price for pos in open_positions) equity_curve.append(cash + position_value) # Build performance DataFrame performance = pd.DataFrame({ 'Date': data.index[1:len(equity_curve)+1], 'Equity': equity_curve }).set_index('Date') # Plot results st.subheader("Equity Curve") fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(performance.index, performance['Equity'], label="Total Equity") ax.set_xlabel("Date") ax.set_ylabel("Equity ($)") ax.legend() st.pyplot(fig) st.subheader("SPY Price with Buy/Sell Signals") fig2, ax2 = plt.subplots(figsize=(10, 4)) ax2.plot(data.index, data['Close'], label="SPY Close Price", color='black') ax2.scatter(data.index[data['Buy']], data['Close'][data['Buy']], marker="^", color="green", label="Buy Signal", s=100) ax2.scatter(data.index[data['Sell']], data['Close'][data['Sell']], marker="v", color="red", label="Sell Signal", s=100) ax2.set_xlabel("Date") ax2.set_ylabel("Price ($)") ax2.legend() st.pyplot(fig2) # Display performance metrics final_equity = equity_curve[-1] return_pct = ((final_equity - initial_capital) / initial_capital) * 100 st.subheader("Strategy Performance Metrics") st.write(f"**Initial Capital:** ${initial_capital:,.2f}") st.write(f"**Final Equity:** ${final_equity:,.2f}") st.write(f"**Return:** {return_pct:.2f}%") st.markdown(""" *This extended demo is for educational purposes only and does not constitute financial advice. Always test your strategies extensively before trading with real money.* """)