Market / app.py
mgbam's picture
Update app.py
f0c68c9 verified
raw
history blame
6.89 kB
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")
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!")
# --- Manual Calculation of Technical Indicators ---
# Calculate MACD manually
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 manually
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
initial_capital = 100000
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 = data['Close'].iloc[i]
rsi_today = data['RSI'].iloc[i]
# Check for buy signal
macd_today = data['MACD'].iloc[i]
signal_today = data['MACD_signal'].iloc[i]
macd_yesterday = data['MACD'].iloc[i-1]
signal_yesterday = 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):
if price > pos["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
if price < pos["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.*
""")