Market / app.py
mgbam's picture
Update app.py
2713d3a verified
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.*
""")