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.*
""")