Update app.py
Browse files
app.py
CHANGED
@@ -3,8 +3,6 @@ import yfinance as yf
|
|
3 |
import pandas as pd
|
4 |
import numpy as np
|
5 |
import matplotlib.pyplot as plt
|
6 |
-
from ta.trend import MACD
|
7 |
-
from ta.momentum import RSIIndicator
|
8 |
from datetime import timedelta
|
9 |
|
10 |
st.title("Extended MACD-RSI Combo Strategy for SPY")
|
@@ -41,23 +39,22 @@ data_load_state = st.text("Loading SPY data...")
|
|
41 |
data = load_data("SPY", period="1y")
|
42 |
data_load_state.text("Loading SPY data...done!")
|
43 |
|
44 |
-
#
|
45 |
-
macd_indicator = MACD(close=data['Close'])
|
46 |
-
macd_val = macd_indicator.macd()
|
47 |
-
macd_signal_val = macd_indicator.macd_signal()
|
48 |
|
49 |
-
#
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
|
55 |
-
#
|
56 |
-
data['
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
|
|
|
|
61 |
|
62 |
# Initialize signal flags for plotting
|
63 |
data['Buy'] = False
|
@@ -67,14 +64,8 @@ data['Sell'] = False
|
|
67 |
initial_capital = 100000
|
68 |
cash = initial_capital
|
69 |
equity_curve = []
|
70 |
-
|
71 |
-
# To enforce the buy cooldown, track the last buy date.
|
72 |
last_buy_date = None
|
73 |
-
|
74 |
-
# List to track open positions; each position is a dictionary with details.
|
75 |
-
open_positions = [] # keys: entry_date, entry_price, shares, allocated, highest, trailing_stop
|
76 |
-
|
77 |
-
# Lists to store completed trades for analysis
|
78 |
completed_trades = []
|
79 |
|
80 |
# Backtesting simulation loop
|
@@ -83,59 +74,44 @@ for i in range(1, len(data)):
|
|
83 |
price = data['Close'].iloc[i]
|
84 |
rsi_today = data['RSI'].iloc[i]
|
85 |
|
86 |
-
#
|
87 |
-
# Signal: MACD crossover (yesterday below signal, today above signal) and RSI below 50.
|
88 |
macd_today = data['MACD'].iloc[i]
|
89 |
signal_today = data['MACD_signal'].iloc[i]
|
90 |
-
macd_yesterday = data['MACD'].iloc[i
|
91 |
-
signal_yesterday = data['MACD_signal'].iloc[i
|
92 |
|
93 |
buy_condition = (macd_yesterday < signal_yesterday) and (macd_today > signal_today) and (rsi_today < 50)
|
94 |
|
95 |
-
|
96 |
-
if last_buy_date is not None and (today - last_buy_date).days < min_days_between_buys:
|
97 |
-
buy_condition = False
|
98 |
-
|
99 |
-
if buy_condition:
|
100 |
allocation = cash * buy_fraction
|
101 |
if allocation > 0:
|
102 |
shares_bought = allocation / price
|
103 |
cash -= allocation
|
104 |
last_buy_date = today
|
105 |
-
|
106 |
-
position = {
|
107 |
"entry_date": today,
|
108 |
"entry_price": price,
|
109 |
"allocated": allocation,
|
110 |
"shares": shares_bought,
|
111 |
-
"highest": price,
|
112 |
"trailing_stop": price * (1 - trailing_stop_pct)
|
113 |
-
}
|
114 |
-
open_positions.append(position)
|
115 |
data.at[today, 'Buy'] = True
|
116 |
-
st.write(f"Buy: {today.date()} | Price: {price:.2f} | Shares: {shares_bought:.2f}")
|
117 |
|
118 |
-
#
|
119 |
positions_to_remove = []
|
120 |
for idx, pos in enumerate(open_positions):
|
121 |
-
# Update the highest price if the current price is higher.
|
122 |
if price > pos["highest"]:
|
123 |
pos["highest"] = price
|
124 |
-
# Update trailing stop: trailing stop is highest price * (1 - trailing_stop_pct)
|
125 |
pos["trailing_stop"] = pos["highest"] * (1 - trailing_stop_pct)
|
126 |
|
127 |
-
#
|
128 |
-
|
129 |
-
if price >= target_price and rsi_today > 50:
|
130 |
-
# Sell a fraction of this position.
|
131 |
shares_to_sell = pos["shares"] * sell_fraction
|
132 |
-
|
133 |
-
cash += sell_value
|
134 |
-
pos["allocated"] -= shares_to_sell * pos["entry_price"]
|
135 |
pos["shares"] -= shares_to_sell
|
|
|
136 |
data.at[today, 'Sell'] = True
|
137 |
-
st.write(f"Partial Sell: {today.date()} | Price: {price:.2f} | Shares Sold: {shares_to_sell:.2f}")
|
138 |
-
# If the position is nearly closed, mark it for complete removal.
|
139 |
if pos["shares"] < 0.001:
|
140 |
completed_trades.append({
|
141 |
"entry_date": pos["entry_date"],
|
@@ -145,14 +121,11 @@ for i in range(1, len(data)):
|
|
145 |
"allocated": pos["allocated"]
|
146 |
})
|
147 |
positions_to_remove.append(idx)
|
148 |
-
# Continue to next position without checking trailing stop.
|
149 |
continue
|
150 |
|
151 |
-
#
|
152 |
if price < pos["trailing_stop"]:
|
153 |
-
|
154 |
-
cash += sell_value
|
155 |
-
st.write(f"Trailing Stop Hit: {today.date()} | Price: {price:.2f} | Shares Sold: {pos['shares']:.2f}")
|
156 |
completed_trades.append({
|
157 |
"entry_date": pos["entry_date"],
|
158 |
"exit_date": today,
|
@@ -162,21 +135,20 @@ for i in range(1, len(data)):
|
|
162 |
})
|
163 |
positions_to_remove.append(idx)
|
164 |
|
165 |
-
|
166 |
-
for idx in sorted(positions_to_remove, reverse=True):
|
167 |
del open_positions[idx]
|
168 |
|
169 |
-
#
|
170 |
-
position_value = sum(
|
171 |
-
|
172 |
-
equity_curve.append(total_equity)
|
173 |
|
174 |
-
# Build performance DataFrame
|
175 |
performance = pd.DataFrame({
|
176 |
'Date': data.index[1:len(equity_curve)+1],
|
177 |
'Equity': equity_curve
|
178 |
}).set_index('Date')
|
179 |
|
|
|
180 |
st.subheader("Equity Curve")
|
181 |
fig, ax = plt.subplots(figsize=(10, 4))
|
182 |
ax.plot(performance.index, performance['Equity'], label="Total Equity")
|
@@ -195,13 +167,14 @@ ax2.set_ylabel("Price ($)")
|
|
195 |
ax2.legend()
|
196 |
st.pyplot(fig2)
|
197 |
|
198 |
-
|
199 |
final_equity = equity_curve[-1]
|
200 |
return_pct = ((final_equity - initial_capital) / initial_capital) * 100
|
|
|
201 |
st.write(f"**Initial Capital:** ${initial_capital:,.2f}")
|
202 |
st.write(f"**Final Equity:** ${final_equity:,.2f}")
|
203 |
st.write(f"**Return:** {return_pct:.2f}%")
|
204 |
|
205 |
st.markdown("""
|
206 |
*This extended demo is for educational purposes only and does not constitute financial advice. Always test your strategies extensively before trading with real money.*
|
207 |
-
""")
|
|
|
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")
|
|
|
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
|
|
|
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
|
|
|
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 |
|
85 |
+
if buy_condition and (last_buy_date is None or (today - last_buy_date).days >= min_days_between_buys):
|
|
|
|
|
|
|
|
|
86 |
allocation = cash * buy_fraction
|
87 |
if allocation > 0:
|
88 |
shares_bought = allocation / price
|
89 |
cash -= allocation
|
90 |
last_buy_date = today
|
91 |
+
open_positions.append({
|
|
|
92 |
"entry_date": today,
|
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
|
|
|
100 |
|
101 |
+
# Update positions and check sell conditions
|
102 |
positions_to_remove = []
|
103 |
for idx, pos in enumerate(open_positions):
|
|
|
104 |
if price > pos["highest"]:
|
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
|
113 |
+
pos["allocated"] -= shares_to_sell * pos["entry_price"]
|
114 |
data.at[today, 'Sell'] = True
|
|
|
|
|
115 |
if pos["shares"] < 0.001:
|
116 |
completed_trades.append({
|
117 |
"entry_date": pos["entry_date"],
|
|
|
121 |
"allocated": pos["allocated"]
|
122 |
})
|
123 |
positions_to_remove.append(idx)
|
|
|
124 |
continue
|
125 |
|
126 |
+
# Trailing stop exit
|
127 |
if price < pos["trailing_stop"]:
|
128 |
+
cash += pos["shares"] * price
|
|
|
|
|
129 |
completed_trades.append({
|
130 |
"entry_date": pos["entry_date"],
|
131 |
"exit_date": today,
|
|
|
135 |
})
|
136 |
positions_to_remove.append(idx)
|
137 |
|
138 |
+
for idx in reversed(positions_to_remove):
|
|
|
139 |
del open_positions[idx]
|
140 |
|
141 |
+
# Update equity curve
|
142 |
+
position_value = sum(pos["shares"] * price for pos in open_positions)
|
143 |
+
equity_curve.append(cash + position_value)
|
|
|
144 |
|
145 |
+
# Build performance DataFrame
|
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")
|
|
|
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 |
+
""")
|