Update app.py
Browse files
app.py
CHANGED
@@ -1,10 +1,81 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
# Backtesting simulation loop
|
4 |
for i in range(1, len(data)):
|
5 |
today = data.index[i]
|
6 |
-
price = float(data['Close'].iloc[i]) #
|
7 |
-
rsi_today = float(data['RSI'].iloc[i]) #
|
8 |
|
9 |
# Check for buy signal
|
10 |
macd_today = float(data['MACD'].iloc[i])
|
@@ -25,7 +96,7 @@ for i in range(1, len(data)):
|
|
25 |
"entry_price": price,
|
26 |
"allocated": allocation,
|
27 |
"shares": shares_bought,
|
28 |
-
"highest": price,
|
29 |
"trailing_stop": price * (1 - trailing_stop_pct)
|
30 |
})
|
31 |
data.at[today, 'Buy'] = True
|
@@ -76,4 +147,39 @@ for i in range(1, len(data)):
|
|
76 |
position_value = sum(pos["shares"] * price for pos in open_positions)
|
77 |
equity_curve.append(cash + position_value)
|
78 |
|
79 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import yfinance as yf
|
3 |
+
import pandas as pd
|
4 |
+
import numpy as np
|
5 |
+
import matplotlib.pyplot as plt
|
6 |
+
from datetime import timedelta
|
7 |
+
|
8 |
+
# Streamlit App Title and Description
|
9 |
+
st.title("Extended MACD-RSI Combo Strategy for SPY")
|
10 |
+
st.markdown("""
|
11 |
+
This app demonstrates an extended MACD-RSI based trading strategy on SPY with the following features:
|
12 |
+
- **Multiple Simultaneous Positions:** Each buy signal creates a new position.
|
13 |
+
- **Dynamic Trailing Stop:** Each open position is updated with a trailing stop.
|
14 |
+
- **Configurable Parameters:** Adjust strategy parameters via the sidebar.
|
15 |
+
- **Buy Rule:**
|
16 |
+
Buy a fraction of available cash when:
|
17 |
+
- The MACD line crosses above its signal line.
|
18 |
+
- RSI is below 50.
|
19 |
+
- No buy has been executed in the last few days.
|
20 |
+
- **Sell Rule:**
|
21 |
+
For each position:
|
22 |
+
- **Partial Sell:** Sell a fraction of the position when the price reaches a target multiple of the entry price and RSI is above 50.
|
23 |
+
- **Trailing Stop Exit:** If the price falls below the position’s dynamic trailing stop, sell the entire position.
|
24 |
+
""")
|
25 |
+
|
26 |
+
# Sidebar for Strategy Parameters
|
27 |
+
st.sidebar.header("Strategy Parameters")
|
28 |
+
buy_fraction = st.sidebar.slider("Buy Fraction (of available cash)", 0.05, 0.50, 0.15, 0.05)
|
29 |
+
sell_fraction = st.sidebar.slider("Partial Sell Fraction", 0.10, 0.90, 0.40, 0.05)
|
30 |
+
target_multiplier = st.sidebar.slider("Target Multiplier", 1.01, 1.20, 1.08, 0.01)
|
31 |
+
trailing_stop_pct = st.sidebar.slider("Trailing Stop (%)", 0.01, 0.20, 0.08, 0.01)
|
32 |
+
min_days_between_buys = st.sidebar.number_input("Minimum Days Between Buys", min_value=1, max_value=10, value=2)
|
33 |
+
|
34 |
+
# Load SPY Data
|
35 |
+
@st.cache_data
|
36 |
+
def load_data(ticker, period="1y"):
|
37 |
+
data = yf.download(ticker, period=period)
|
38 |
+
data.dropna(inplace=True)
|
39 |
+
return data
|
40 |
+
|
41 |
+
data_load_state = st.text("Loading SPY data...")
|
42 |
+
data = load_data("SPY", period="1y")
|
43 |
+
data_load_state.text("Loading SPY data...done!")
|
44 |
+
|
45 |
+
# --- Manual Calculation of Technical Indicators ---
|
46 |
+
|
47 |
+
# Calculate MACD
|
48 |
+
data['EMA12'] = data['Close'].ewm(span=12, adjust=False).mean()
|
49 |
+
data['EMA26'] = data['Close'].ewm(span=26, adjust=False).mean()
|
50 |
+
data['MACD'] = data['EMA12'] - data['EMA26']
|
51 |
+
data['MACD_signal'] = data['MACD'].ewm(span=9, adjust=False).mean()
|
52 |
+
|
53 |
+
# Calculate RSI
|
54 |
+
delta = data['Close'].diff()
|
55 |
+
gain = delta.where(delta > 0, 0)
|
56 |
+
loss = -delta.where(delta < 0, 0)
|
57 |
+
avg_gain = gain.rolling(window=14).mean()
|
58 |
+
avg_loss = loss.rolling(window=14).mean()
|
59 |
+
rs = avg_gain / avg_loss
|
60 |
+
data['RSI'] = 100 - (100 / (1 + rs))
|
61 |
+
|
62 |
+
# Initialize signal flags for plotting
|
63 |
+
data['Buy'] = False
|
64 |
+
data['Sell'] = False
|
65 |
+
|
66 |
+
# Backtesting parameters
|
67 |
+
initial_capital = 100000
|
68 |
+
cash = initial_capital
|
69 |
+
equity_curve = []
|
70 |
+
last_buy_date = None
|
71 |
+
open_positions = []
|
72 |
+
completed_trades = []
|
73 |
|
74 |
# Backtesting simulation loop
|
75 |
for i in range(1, len(data)):
|
76 |
today = data.index[i]
|
77 |
+
price = float(data['Close'].iloc[i]) # Ensure price is a scalar value
|
78 |
+
rsi_today = float(data['RSI'].iloc[i]) # Ensure RSI is a scalar value
|
79 |
|
80 |
# Check for buy signal
|
81 |
macd_today = float(data['MACD'].iloc[i])
|
|
|
96 |
"entry_price": price,
|
97 |
"allocated": allocation,
|
98 |
"shares": shares_bought,
|
99 |
+
"highest": price,
|
100 |
"trailing_stop": price * (1 - trailing_stop_pct)
|
101 |
})
|
102 |
data.at[today, 'Buy'] = True
|
|
|
147 |
position_value = sum(pos["shares"] * price for pos in open_positions)
|
148 |
equity_curve.append(cash + position_value)
|
149 |
|
150 |
+
# Build performance DataFrame
|
151 |
+
performance = pd.DataFrame({
|
152 |
+
'Date': data.index[1:len(equity_curve)+1],
|
153 |
+
'Equity': equity_curve
|
154 |
+
}).set_index('Date')
|
155 |
+
|
156 |
+
# Plot results
|
157 |
+
st.subheader("Equity Curve")
|
158 |
+
fig, ax = plt.subplots(figsize=(10, 4))
|
159 |
+
ax.plot(performance.index, performance['Equity'], label="Total Equity")
|
160 |
+
ax.set_xlabel("Date")
|
161 |
+
ax.set_ylabel("Equity ($)")
|
162 |
+
ax.legend()
|
163 |
+
st.pyplot(fig)
|
164 |
+
|
165 |
+
st.subheader("SPY Price with Buy/Sell Signals")
|
166 |
+
fig2, ax2 = plt.subplots(figsize=(10, 4))
|
167 |
+
ax2.plot(data.index, data['Close'], label="SPY Close Price", color='black')
|
168 |
+
ax2.scatter(data.index[data['Buy']], data['Close'][data['Buy']], marker="^", color="green", label="Buy Signal", s=100)
|
169 |
+
ax2.scatter(data.index[data['Sell']], data['Close'][data['Sell']], marker="v", color="red", label="Sell Signal", s=100)
|
170 |
+
ax2.set_xlabel("Date")
|
171 |
+
ax2.set_ylabel("Price ($)")
|
172 |
+
ax2.legend()
|
173 |
+
st.pyplot(fig2)
|
174 |
+
|
175 |
+
# Display performance metrics
|
176 |
+
final_equity = equity_curve[-1]
|
177 |
+
return_pct = ((final_equity - initial_capital) / initial_capital) * 100
|
178 |
+
st.subheader("Strategy Performance Metrics")
|
179 |
+
st.write(f"**Initial Capital:** ${initial_capital:,.2f}")
|
180 |
+
st.write(f"**Final Equity:** ${final_equity:,.2f}")
|
181 |
+
st.write(f"**Return:** {return_pct:.2f}%")
|
182 |
+
|
183 |
+
st.markdown("""
|
184 |
+
*This extended demo is for educational purposes only and does not constitute financial advice. Always test your strategies extensively before trading with real money.*
|
185 |
+
""")
|