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