| 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 |
|
|
| |
| 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. |
| """) |
|
|
| |
| 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) |
|
|
| |
| @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!") |
|
|
| |
|
|
| |
| 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() |
|
|
| |
| 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)) |
|
|
| |
| data['Buy'] = False |
| data['Sell'] = False |
|
|
| |
| cash = initial_capital |
| equity_curve = [] |
| last_buy_date = None |
| open_positions = [] |
| completed_trades = [] |
|
|
| |
| for i in range(1, len(data)): |
| today = data.index[i] |
| price = float(data['Close'].iloc[i]) |
| rsi_today = float(data['RSI'].iloc[i]) |
| |
| |
| 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 |
|
|
| |
| 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) |
| |
| |
| 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 |
| |
| |
| 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] |
| |
| |
| position_value = sum(pos["shares"] * price for pos in open_positions) |
| equity_curve.append(cash + position_value) |
|
|
| |
| performance = pd.DataFrame({ |
| 'Date': data.index[1:len(equity_curve)+1], |
| 'Equity': equity_curve |
| }).set_index('Date') |
|
|
| |
| 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) |
|
|
| |
| 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.* |
| """) |