File size: 7,262 Bytes
8cba972 2713d3a 8cba972 f023109 8cba972 f023109 f0c68c9 cbdd805 f023109 f0c68c9 f023109 f0c68c9 f023109 8cba972 f023109 f0c68c9 f023109 f0c68c9 f023109 cbdd805 f023109 f0c68c9 cbdd805 f023109 f0c68c9 f023109 f0c68c9 f023109 f0c68c9 cbdd805 f0c68c9 f023109 f0c68c9 f023109 f0c68c9 f023109 8cba972 |
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 |
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.*
""") |