mgbam commited on
Commit
f023109
·
1 Parent(s): d7c0a81

Add application file

Browse files
Files changed (1) hide show
  1. app.py +197 -0
app.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 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")
11
+ st.markdown("""
12
+ This app demonstrates an extended MACD-RSI based trading strategy on SPY with the following features:
13
+ - **Multiple Simultaneous Positions:** Each buy signal creates a new position.
14
+ - **Dynamic Trailing Stop:** Each open position is updated with a trailing stop.
15
+ - **Configurable Parameters:** Adjust strategy parameters via the sidebar.
16
+ - **Buy Rule:**
17
+ Buy 15% of available cash when:
18
+ - The MACD line crosses above its signal line.
19
+ - RSI is below 50.
20
+ - No buy has been executed in the last 2 days.
21
+ - **Sell Rule:**
22
+ For each position:
23
+ - **Partial Sell:** Sell 40% of the position when the price reaches 1.08× the entry price and RSI is above 50.
24
+ - **Trailing Stop Exit:** If the price falls below the position’s dynamic trailing stop, sell the entire position.
25
+ """)
26
+
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
+ @st.cache_data
35
+ def load_data(ticker, period="1y"):
36
+ data = yf.download(ticker, period=period)
37
+ data.dropna(inplace=True)
38
+ return data
39
+
40
+ 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
+ # Calculate technical indicators: MACD and RSI
45
+ macd_indicator = MACD(close=data['Close'])
46
+ data['MACD'] = macd_indicator.macd()
47
+ data['MACD_signal'] = macd_indicator.macd_signal()
48
+
49
+ rsi_indicator = RSIIndicator(close=data['Close'], window=14)
50
+ data['RSI'] = rsi_indicator.rsi()
51
+
52
+ # Initialize signal flags for plotting
53
+ data['Buy'] = False
54
+ data['Sell'] = False
55
+
56
+ # Backtesting parameters
57
+ initial_capital = 100000
58
+ cash = initial_capital
59
+ equity_curve = []
60
+
61
+ # To enforce the buy cooldown, track the last buy date.
62
+ last_buy_date = None
63
+
64
+ # List to track open positions; each position is a dictionary with details.
65
+ open_positions = [] # keys: entry_date, entry_price, shares, allocated, highest, trailing_stop
66
+
67
+ # Lists to store completed trades for analysis
68
+ completed_trades = []
69
+
70
+ # Backtesting simulation loop
71
+ for i in range(1, len(data)):
72
+ today = data.index[i]
73
+ price = data['Close'].iloc[i]
74
+ rsi_today = data['RSI'].iloc[i]
75
+
76
+ # --- Check for a buy signal ---
77
+ # Signal: MACD crossover (yesterday below signal, today above signal) and RSI below 50.
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
+ # Enforce cooldown: if a buy occurred recently, skip.
86
+ if last_buy_date is not None and (today - last_buy_date).days < min_days_between_buys:
87
+ buy_condition = False
88
+
89
+ if buy_condition:
90
+ allocation = cash * buy_fraction
91
+ if allocation > 0:
92
+ shares_bought = allocation / price
93
+ cash -= allocation
94
+ last_buy_date = today
95
+ # Initialize the open position with its own trailing stop.
96
+ position = {
97
+ "entry_date": today,
98
+ "entry_price": price,
99
+ "allocated": allocation,
100
+ "shares": shares_bought,
101
+ "highest": price, # track highest price achieved for this position
102
+ "trailing_stop": price * (1 - trailing_stop_pct)
103
+ }
104
+ open_positions.append(position)
105
+ data.at[today, 'Buy'] = True
106
+ st.write(f"Buy: {today.date()} | Price: {price:.2f} | Shares: {shares_bought:.2f}")
107
+
108
+ # --- Update open positions for trailing stops and partial sell targets ---
109
+ positions_to_remove = []
110
+ for idx, pos in enumerate(open_positions):
111
+ # Update the highest price if the current price is higher.
112
+ if price > pos["highest"]:
113
+ pos["highest"] = price
114
+ # Update trailing stop: trailing stop is highest price * (1 - trailing_stop_pct)
115
+ pos["trailing_stop"] = pos["highest"] * (1 - trailing_stop_pct)
116
+
117
+ # Check for partial sell condition:
118
+ target_price = pos["entry_price"] * target_multiplier
119
+ if price >= target_price and rsi_today > 50:
120
+ # Sell a fraction of this position.
121
+ shares_to_sell = pos["shares"] * sell_fraction
122
+ sell_value = shares_to_sell * price
123
+ cash += sell_value
124
+ pos["allocated"] -= shares_to_sell * pos["entry_price"]
125
+ pos["shares"] -= shares_to_sell
126
+ data.at[today, 'Sell'] = True
127
+ st.write(f"Partial Sell: {today.date()} | Price: {price:.2f} | Shares Sold: {shares_to_sell:.2f}")
128
+ # If the position is nearly closed, mark it for complete removal.
129
+ if pos["shares"] < 0.001:
130
+ completed_trades.append({
131
+ "entry_date": pos["entry_date"],
132
+ "exit_date": today,
133
+ "entry_price": pos["entry_price"],
134
+ "exit_price": price,
135
+ "allocated": pos["allocated"]
136
+ })
137
+ positions_to_remove.append(idx)
138
+ # Continue to next position without checking trailing stop.
139
+ continue
140
+
141
+ # Check trailing stop: if current price falls below the trailing stop, sell the entire position.
142
+ if price < pos["trailing_stop"]:
143
+ sell_value = pos["shares"] * price
144
+ cash += sell_value
145
+ st.write(f"Trailing Stop Hit: {today.date()} | Price: {price:.2f} | Shares Sold: {pos['shares']:.2f}")
146
+ completed_trades.append({
147
+ "entry_date": pos["entry_date"],
148
+ "exit_date": today,
149
+ "entry_price": pos["entry_price"],
150
+ "exit_price": price,
151
+ "allocated": pos["allocated"]
152
+ })
153
+ positions_to_remove.append(idx)
154
+
155
+ # Remove positions that have been fully closed (reverse sort indices to remove safely)
156
+ for idx in sorted(positions_to_remove, reverse=True):
157
+ del open_positions[idx]
158
+
159
+ # Calculate the current value of all open positions.
160
+ position_value = sum([pos["shares"] * price for pos in open_positions])
161
+ total_equity = cash + position_value
162
+ equity_curve.append(total_equity)
163
+
164
+ # Build performance DataFrame for visualization.
165
+ performance = pd.DataFrame({
166
+ 'Date': data.index[1:len(equity_curve)+1],
167
+ 'Equity': equity_curve
168
+ }).set_index('Date')
169
+
170
+ st.subheader("Equity Curve")
171
+ fig, ax = plt.subplots(figsize=(10, 4))
172
+ ax.plot(performance.index, performance['Equity'], label="Total Equity")
173
+ ax.set_xlabel("Date")
174
+ ax.set_ylabel("Equity ($)")
175
+ ax.legend()
176
+ st.pyplot(fig)
177
+
178
+ st.subheader("SPY Price with Buy/Sell Signals")
179
+ fig2, ax2 = plt.subplots(figsize=(10, 4))
180
+ ax2.plot(data.index, data['Close'], label="SPY Close Price", color='black')
181
+ ax2.scatter(data.index[data['Buy']], data['Close'][data['Buy']], marker="^", color="green", label="Buy Signal", s=100)
182
+ ax2.scatter(data.index[data['Sell']], data['Close'][data['Sell']], marker="v", color="red", label="Sell Signal", s=100)
183
+ ax2.set_xlabel("Date")
184
+ ax2.set_ylabel("Price ($)")
185
+ ax2.legend()
186
+ st.pyplot(fig2)
187
+
188
+ st.subheader("Strategy Performance Metrics")
189
+ final_equity = equity_curve[-1]
190
+ return_pct = ((final_equity - initial_capital) / initial_capital) * 100
191
+ st.write(f"**Initial Capital:** ${initial_capital:,.2f}")
192
+ st.write(f"**Final Equity:** ${final_equity:,.2f}")
193
+ st.write(f"**Return:** {return_pct:.2f}%")
194
+
195
+ st.markdown("""
196
+ *This extended demo is for educational purposes only and does not constitute financial advice. Always test your strategies extensively before trading with real money.*
197
+ """)