saadrizvi09 commited on
Commit
3e1a1b1
·
verified ·
1 Parent(s): 70d5df2

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +108 -118
src/streamlit_app.py CHANGED
@@ -7,16 +7,17 @@ from sklearn.svm import SVR
7
  from sklearn.preprocessing import StandardScaler
8
  import plotly.graph_objects as go
9
  import plotly.express as px
10
- from datetime import datetime, timedelta, date
 
11
  # --- Config ---
12
- st.set_page_config(page_title="Hybrid HMM-SVR Strategy Backtester", layout="wide")
13
 
14
  # --- Helper Functions ---
15
 
16
  @st.cache_data(ttl=3600)
17
  def fetch_data(ticker, start_date, end_date):
18
  """
19
- Robust data fetching with caching, error handling, and string conversion.
20
  """
21
  ticker = ticker.strip().upper()
22
 
@@ -49,7 +50,7 @@ def calculate_metrics(df, strategy_col='Strategy_Value', benchmark_col='Buy_Hold
49
  """Calculates CAG, Sharpe, Drawdown, etc."""
50
  stats = {}
51
 
52
- for col, name in [(strategy_col, 'Hybrid Strategy'), (benchmark_col, 'Buy & Hold')]:
53
  initial = df[col].iloc[0]
54
  final = df[col].iloc[-1]
55
  total_return = (final - initial) / initial
@@ -71,7 +72,7 @@ def calculate_metrics(df, strategy_col='Strategy_Value', benchmark_col='Buy_Hold
71
  return pd.DataFrame(stats)
72
 
73
  def train_hmm_model(train_df, n_states):
74
- """Trains HMM on historical data (In-Sample)."""
75
  X_train = train_df[['Log_Returns', 'Volatility']].values * 100
76
 
77
  model = GaussianHMM(n_components=n_states, covariance_type="full", n_iter=100, random_state=42)
@@ -82,6 +83,8 @@ def train_hmm_model(train_df, n_states):
82
  for i in range(n_states):
83
  avg_vol = X_train[hidden_states == i, 1].mean()
84
  state_vol.append((i, avg_vol))
 
 
85
  state_vol.sort(key=lambda x: x[1])
86
 
87
  mapping = {old: new for new, (old, _) in enumerate(state_vol)}
@@ -105,61 +108,60 @@ def train_svr_model(train_df):
105
  return model, scaler
106
 
107
  def generate_trade_log(df):
108
- """
109
- Scans the backtest dataframe to identify individual trade cycles.
110
- A 'Trade' is defined as a period where Position Size > 0.
111
- """
112
  trades = []
113
  in_trade = False
114
  entry_date = None
115
  entry_price = 0
116
  trade_returns = []
 
117
 
118
- # We iterate through the dataframe
119
  for date, row in df.iterrows():
120
  pos = row['Final_Position']
121
  close_price = row['Close']
 
122
 
123
- # Check for Entry (Position goes from 0 to > 0)
124
  if pos > 0 and not in_trade:
125
  in_trade = True
126
  entry_date = date
127
- entry_price = close_price # Approximation for log visualization
128
- trade_returns = [row['Strategy_Returns']] # Start tracking returns for this specific trade
 
129
 
130
- # Check for adjustments while in trade
131
  elif pos > 0 and in_trade:
132
  trade_returns.append(row['Strategy_Returns'])
 
133
 
134
- # Check for Exit (Position goes to 0 while we were in a trade)
135
  elif pos == 0 and in_trade:
136
  in_trade = False
137
  exit_date = date
138
  exit_price = close_price
139
 
140
- # Calculate compounded return for this specific trade period
141
- # (1+r1)*(1+r2)... - 1
142
  cum_trade_ret = np.prod([1 + r for r in trade_returns]) - 1
 
143
 
144
  trades.append({
145
  'Entry Date': entry_date,
146
  'Exit Date': exit_date,
147
- 'Entry Price (Approx)': entry_price,
148
  'Exit Price': exit_price,
149
- 'Duration (Days)': len(trade_returns),
 
150
  'Trade PnL': cum_trade_ret
151
  })
152
  trade_returns = []
 
153
 
154
- # Handle case where trade is still open at end of data
155
  if in_trade:
156
  cum_trade_ret = np.prod([1 + r for r in trade_returns]) - 1
 
157
  trades.append({
158
  'Entry Date': entry_date,
159
  'Exit Date': df.index[-1],
160
- 'Entry Price (Approx)': entry_price,
161
  'Exit Price': df.iloc[-1]['Close'],
162
- 'Duration (Days)': len(trade_returns),
 
163
  'Trade PnL': cum_trade_ret
164
  })
165
 
@@ -167,57 +169,51 @@ def generate_trade_log(df):
167
 
168
  # --- Main Logic ---
169
 
170
- st.title("🧠 Hybrid HMM-SVR Strategy Backtester")
171
  st.markdown("""
172
- **The Hybrid Strategy:**
173
- 1. **Driver:** EMA Crossover (Fast > Slow = Bullish).
174
- 2. **Filter (HMM):** If Regime is "High Vol/Crash", **Block Trade** (Size = 0).
175
- 3. **Sizing (SVR):** If Regime is Safe, adjust size based on predicted risk.
176
- * *If SVR predicts higher risk -> Reduce Position Size.*
177
- * *If SVR predicts lower risk -> Increase Position Size.*
178
  """)
179
 
180
  # Sidebar Inputs
181
  with st.sidebar:
182
  st.header("Settings")
183
 
184
- # Added key='ticker_select'
185
  ticker = st.selectbox(
186
  "Ticker",
187
- ["BTC-USD", "BNB-USD"," ETH-USD","SOL-USD"],
188
  key="ticker_select"
189
  )
190
 
191
- # Added key='start_date'
192
  backtest_start = st.date_input(
193
  "Backtest Start Date",
194
  date(2022, 1, 1),
195
  key="start_date"
196
  )
197
 
198
- # Added key='end_date'
199
  backtest_end = st.date_input(
200
  "Backtest End Date",
201
  datetime.now(),
202
  key="end_date"
203
  )
204
 
205
- st.caption("Note: Models will automatically train on the **4 years** of data prior to your selected Start Date.")
206
-
207
  st.divider()
208
 
209
- # Added keys for these as well just to be safe
210
- short_window = st.number_input("Fast EMA", 12, key="fast_ema")
211
- long_window = st.number_input("Slow EMA", 26, key="slow_ema")
212
- n_states = st.slider("HMM States", 2, 4, 3, key="hmm_slider")
213
 
214
- if st.button("Run Hybrid Backtest"):
215
  train_start_date = pd.Timestamp(backtest_start) - pd.DateOffset(years=4)
216
 
217
  df = fetch_data(ticker, train_start_date, backtest_end)
218
 
219
  if df is None or len(df) < 200:
220
- st.error(f"Not enough data found for {ticker}. Ensure the ticker existed 4 years prior to {backtest_start}.")
221
  else:
222
  # 1. Feature Engineering
223
  df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1))
@@ -226,8 +222,9 @@ if st.button("Run Hybrid Backtest"):
226
  df['Downside_Returns'] = df['Log_Returns'].apply(lambda x: x if x < 0 else 0)
227
  df['Downside_Vol'] = df['Downside_Returns'].rolling(window=10).std()
228
 
229
- df['EMA_Short'] = df['Close'].ewm(span=short_window, adjust=False).mean()
230
- df['EMA_Long'] = df['Close'].ewm(span=long_window, adjust=False).mean()
 
231
 
232
  df['Target_Next_Vol'] = df['Volatility'].shift(-1)
233
 
@@ -238,133 +235,126 @@ if st.button("Run Hybrid Backtest"):
238
  test_df = df[df.index >= pd.Timestamp(backtest_start)].copy()
239
 
240
  if len(train_df) < 365:
241
- st.warning(f"Warning: Only {len(train_df)} days found for training. HMM performs best with >2 years of data.")
242
 
243
  if len(test_df) < 10:
244
  st.error("Not enough data for backtesting range.")
245
  else:
246
- st.info(f"Training on {len(train_df)} days ({train_df.index[0].date()} to {train_df.index[-1].date()}). Backtesting on {len(test_df)} days.")
247
 
248
- with st.spinner("Training HMM (Regime Detection)..."):
 
249
  hmm_model, state_map = train_hmm_model(train_df, n_states)
250
 
 
251
  X_train_hmm = train_df[['Log_Returns', 'Volatility']].values * 100
252
  train_raw_states = hmm_model.predict(X_train_hmm)
253
  train_df['Regime'] = [state_map.get(s, s) for s in train_raw_states]
254
 
255
- with st.spinner("Training SVR (Volatility Forecasting)..."):
256
  svr_model, svr_scaler = train_svr_model(train_df)
257
 
258
- with st.spinner("Running Backtest Loop..."):
259
  # --- OUT OF SAMPLE BACKTEST ---
260
 
 
261
  X_test_hmm = test_df[['Log_Returns', 'Volatility']].values * 100
262
  test_raw_states = hmm_model.predict(X_test_hmm)
263
  test_df['Regime'] = [state_map.get(s, s) for s in test_raw_states]
264
 
 
265
  X_test_svr = test_df[['Log_Returns', 'Volatility', 'Downside_Vol', 'Regime']].values
266
  X_test_svr_scaled = svr_scaler.transform(X_test_svr)
267
  test_df['Predicted_Vol'] = svr_model.predict(X_test_svr_scaled)
268
 
269
- high_vol_state = n_states - 1
270
 
 
271
  test_df['Signal'] = np.where(test_df['EMA_Short'] > test_df['EMA_Long'], 1, 0)
272
 
 
273
  avg_train_vol = train_df['Volatility'].mean()
274
-
275
  test_df['Risk_Ratio'] = test_df['Predicted_Vol'] / avg_train_vol
276
- test_df['Position_Size'] = (1.0 / test_df['Risk_Ratio']).clip(upper=1.0, lower=0.0)
 
 
 
 
 
 
 
 
 
 
277
 
278
  test_df['Position_Size'] = np.where(
279
- test_df['Regime'] == high_vol_state ,
280
- 0.0,
281
  test_df['Position_Size']
282
  )
283
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  test_df['Final_Position'] = (test_df['Signal'] * test_df['Position_Size']).shift(1)
285
 
 
286
  test_df['Simple_Returns'] = test_df['Close'].pct_change()
287
  test_df['Strategy_Returns'] = test_df['Final_Position'] * test_df['Simple_Returns']
288
- test_df['Buy_Hold_Returns'] = test_df['Simple_Returns']
289
 
290
- test_df['Strategy_Value'] = (1 + test_df['Strategy_Returns'].fillna(0)).cumprod()
291
- test_df['Buy_Hold_Value'] = (1 + test_df['Buy_Hold_Returns'].fillna(0)).cumprod()
292
 
 
 
293
  test_df.dropna(inplace=True)
294
 
295
- # --- EXTRACT TRADES ---
296
- trade_log = generate_trade_log(test_df)
297
-
298
- # --- RESULTS ---
299
-
300
  metrics_df = calculate_metrics(test_df)
301
- st.subheader("Performance Metrics")
 
302
  st.table(metrics_df)
303
 
304
- # Charts
305
- col1, col2 = st.columns([2, 1])
 
 
 
 
306
 
307
- with col1:
308
- st.subheader("Equity Curve & Trade Executions")
309
- fig = go.Figure()
310
-
311
- # 1. Equity Curves
312
- fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Buy_Hold_Value'], name='Buy & Hold', line=dict(color='gray', dash='dot')))
313
- fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Strategy_Value'], name='Hybrid Strategy', line=dict(color='#00CC96', width=2)))
314
-
315
- # 2. Add Trade Markers
316
- # Filter Entry Points (Buy)
317
- if not trade_log.empty:
318
- # Map dates to Strategy Value for Y-axis placement
319
- buy_points = trade_log.set_index('Entry Date')
320
- buy_vals = test_df.loc[buy_points.index]['Strategy_Value']
321
-
322
- sell_points = trade_log.set_index('Exit Date')
323
- sell_vals = test_df.loc[sell_points.index]['Strategy_Value']
324
-
325
- fig.add_trace(go.Scatter(
326
- x=buy_points.index,
327
- y=buy_vals,
328
- mode='markers',
329
- name='Buy Signal',
330
- marker=dict(symbol='triangle-up', size=10, color='lime')
331
- ))
332
-
333
- fig.add_trace(go.Scatter(
334
- x=sell_points.index,
335
- y=sell_vals,
336
- mode='markers',
337
- name='Sell Signal',
338
- marker=dict(symbol='triangle-down', size=10, color='red')
339
- ))
340
-
341
- st.plotly_chart(fig, use_container_width=True)
342
-
343
- with col2:
344
- st.subheader("Position Sizing (SVR Effect)")
345
- st.caption("How SVR adjusted trade size over time (0.0 to 1.0)")
346
- fig_size = px.area(test_df, x=test_df.index, y='Position_Size', title="Dynamic Exposure")
347
- st.plotly_chart(fig_size, use_container_width=True)
348
 
349
- # --- NEW: Trade Log Table ---
 
 
 
 
 
 
 
 
 
 
 
350
  st.divider()
351
- st.subheader("📝 Detailed Trade Log")
 
352
  if not trade_log.empty:
353
- # Formatting for cleaner display
354
  display_log = trade_log.copy()
355
  display_log['Entry Date'] = display_log['Entry Date'].dt.date
356
  display_log['Exit Date'] = display_log['Exit Date'].dt.date
357
  display_log['Trade PnL'] = display_log['Trade PnL'].map('{:.2%}'.format)
358
- display_log['Entry Price (Approx)'] = display_log['Entry Price (Approx)'].map('{:.2f}'.format)
359
  display_log['Exit Price'] = display_log['Exit Price'].map('{:.2f}'.format)
360
-
361
  st.dataframe(display_log, use_container_width=True)
362
  else:
363
- st.write("No trades executed in this period.")
364
-
365
- st.subheader("SVR Prediction Accuracy (Test Set)")
366
- fig_svr = go.Figure()
367
- slice_df = test_df.iloc[-100:]
368
- fig_svr.add_trace(go.Scatter(x=slice_df.index, y=slice_df['Target_Next_Vol'], name='Actual Volatility'))
369
- fig_svr.add_trace(go.Scatter(x=slice_df.index, y=slice_df['Predicted_Vol'], name='SVR Prediction', line=dict(dash='dot')))
370
- st.plotly_chart(fig_svr, use_container_width=True)
 
7
  from sklearn.preprocessing import StandardScaler
8
  import plotly.graph_objects as go
9
  import plotly.express as px
10
+ from datetime import datetime, date
11
+
12
  # --- Config ---
13
+ st.set_page_config(page_title="HMM-SVR Leverage Sniper", layout="wide")
14
 
15
  # --- Helper Functions ---
16
 
17
  @st.cache_data(ttl=3600)
18
  def fetch_data(ticker, start_date, end_date):
19
  """
20
+ Robust data fetching with caching.
21
  """
22
  ticker = ticker.strip().upper()
23
 
 
50
  """Calculates CAG, Sharpe, Drawdown, etc."""
51
  stats = {}
52
 
53
+ for col, name in [(strategy_col, 'Smart Leverage Strategy'), (benchmark_col, 'Buy & Hold')]:
54
  initial = df[col].iloc[0]
55
  final = df[col].iloc[-1]
56
  total_return = (final - initial) / initial
 
72
  return pd.DataFrame(stats)
73
 
74
  def train_hmm_model(train_df, n_states):
75
+ """Trains HMM on historical data and sorts states by volatility (0=Low, n=High)."""
76
  X_train = train_df[['Log_Returns', 'Volatility']].values * 100
77
 
78
  model = GaussianHMM(n_components=n_states, covariance_type="full", n_iter=100, random_state=42)
 
83
  for i in range(n_states):
84
  avg_vol = X_train[hidden_states == i, 1].mean()
85
  state_vol.append((i, avg_vol))
86
+
87
+ # Sort states: State 0 = Lowest Volatility (Safe), State N = Highest Volatility (Crash)
88
  state_vol.sort(key=lambda x: x[1])
89
 
90
  mapping = {old: new for new, (old, _) in enumerate(state_vol)}
 
108
  return model, scaler
109
 
110
  def generate_trade_log(df):
111
+ """Generates a log of trades including leverage used."""
 
 
 
112
  trades = []
113
  in_trade = False
114
  entry_date = None
115
  entry_price = 0
116
  trade_returns = []
117
+ avg_leverage = []
118
 
 
119
  for date, row in df.iterrows():
120
  pos = row['Final_Position']
121
  close_price = row['Close']
122
+ lev = row['Position_Size'] # Capture leverage used
123
 
 
124
  if pos > 0 and not in_trade:
125
  in_trade = True
126
  entry_date = date
127
+ entry_price = close_price
128
+ trade_returns = [row['Strategy_Returns']]
129
+ avg_leverage = [lev]
130
 
 
131
  elif pos > 0 and in_trade:
132
  trade_returns.append(row['Strategy_Returns'])
133
+ avg_leverage.append(lev)
134
 
 
135
  elif pos == 0 and in_trade:
136
  in_trade = False
137
  exit_date = date
138
  exit_price = close_price
139
 
 
 
140
  cum_trade_ret = np.prod([1 + r for r in trade_returns]) - 1
141
+ mean_lev = np.mean(avg_leverage)
142
 
143
  trades.append({
144
  'Entry Date': entry_date,
145
  'Exit Date': exit_date,
146
+ 'Entry Price': entry_price,
147
  'Exit Price': exit_price,
148
+ 'Duration': len(trade_returns),
149
+ 'Avg Leverage': f"{mean_lev:.1f}x",
150
  'Trade PnL': cum_trade_ret
151
  })
152
  trade_returns = []
153
+ avg_leverage = []
154
 
 
155
  if in_trade:
156
  cum_trade_ret = np.prod([1 + r for r in trade_returns]) - 1
157
+ mean_lev = np.mean(avg_leverage)
158
  trades.append({
159
  'Entry Date': entry_date,
160
  'Exit Date': df.index[-1],
161
+ 'Entry Price': entry_price,
162
  'Exit Price': df.iloc[-1]['Close'],
163
+ 'Duration': len(trade_returns),
164
+ 'Avg Leverage': f"{mean_lev:.1f}x",
165
  'Trade PnL': cum_trade_ret
166
  })
167
 
 
169
 
170
  # --- Main Logic ---
171
 
172
+ st.title(" HMM-SVR Leverage Backtester")
173
  st.markdown("""
174
+ **The "Strict Rules" Strategy:**
175
+ 1. **Baseline:** Buy when Fast EMA > Slow EMA.
176
+ 2. **Safety (HMM):** If Regime = **High Volatility (Crash)** -> **Exit (0x)**.
177
+ 3. **Leverage Boost (SVR + HMM):** * IF Regime is **Lowest Volatility (State 0)**
178
+ * AND SVR predicts volatility **< 50% of average** (Risk Ratio < 0.5)
179
+ * THEN **Leverage = 3x**.
180
  """)
181
 
182
  # Sidebar Inputs
183
  with st.sidebar:
184
  st.header("Settings")
185
 
 
186
  ticker = st.selectbox(
187
  "Ticker",
188
+ ["BNB-USD", "ETH-USD", "SOL-USD", "BTC-USD"],
189
  key="ticker_select"
190
  )
191
 
 
192
  backtest_start = st.date_input(
193
  "Backtest Start Date",
194
  date(2022, 1, 1),
195
  key="start_date"
196
  )
197
 
 
198
  backtest_end = st.date_input(
199
  "Backtest End Date",
200
  datetime.now(),
201
  key="end_date"
202
  )
203
 
 
 
204
  st.divider()
205
 
206
+ st.subheader("Leverage Rules")
207
+ leverage_mult = st.number_input("Boost Leverage (Certainty Multiplier)", value=3.0, step=0.5)
208
+ risk_threshold = st.slider("Certainty Threshold (Risk Ratio < X)", 0.1, 1.0, 0.5, help="Lower = Stricter. Only boost leverage when predicted risk is extremely low.")
 
209
 
210
+ if st.button("Run Leverage Backtest"):
211
  train_start_date = pd.Timestamp(backtest_start) - pd.DateOffset(years=4)
212
 
213
  df = fetch_data(ticker, train_start_date, backtest_end)
214
 
215
  if df is None or len(df) < 200:
216
+ st.error(f"Not enough data found for {ticker}.")
217
  else:
218
  # 1. Feature Engineering
219
  df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1))
 
222
  df['Downside_Returns'] = df['Log_Returns'].apply(lambda x: x if x < 0 else 0)
223
  df['Downside_Vol'] = df['Downside_Returns'].rolling(window=10).std()
224
 
225
+ # 12/26 EMA standard
226
+ df['EMA_Short'] = df['Close'].ewm(span=12, adjust=False).mean()
227
+ df['EMA_Long'] = df['Close'].ewm(span=26, adjust=False).mean()
228
 
229
  df['Target_Next_Vol'] = df['Volatility'].shift(-1)
230
 
 
235
  test_df = df[df.index >= pd.Timestamp(backtest_start)].copy()
236
 
237
  if len(train_df) < 365:
238
+ st.warning(f"Warning: Only {len(train_df)} days found for training.")
239
 
240
  if len(test_df) < 10:
241
  st.error("Not enough data for backtesting range.")
242
  else:
243
+ n_states = 3 # Fixed 3 states: Low, Neutral, High
244
 
245
+ with st.spinner("Training AI Models (HMM & SVR)..."):
246
+ # Train HMM
247
  hmm_model, state_map = train_hmm_model(train_df, n_states)
248
 
249
+ # Get HMM Regimes for Train set (needed for SVR training)
250
  X_train_hmm = train_df[['Log_Returns', 'Volatility']].values * 100
251
  train_raw_states = hmm_model.predict(X_train_hmm)
252
  train_df['Regime'] = [state_map.get(s, s) for s in train_raw_states]
253
 
254
+ # Train SVR
255
  svr_model, svr_scaler = train_svr_model(train_df)
256
 
 
257
  # --- OUT OF SAMPLE BACKTEST ---
258
 
259
+ # Predict Regimes
260
  X_test_hmm = test_df[['Log_Returns', 'Volatility']].values * 100
261
  test_raw_states = hmm_model.predict(X_test_hmm)
262
  test_df['Regime'] = [state_map.get(s, s) for s in test_raw_states]
263
 
264
+ # Predict Next Day Volatility
265
  X_test_svr = test_df[['Log_Returns', 'Volatility', 'Downside_Vol', 'Regime']].values
266
  X_test_svr_scaled = svr_scaler.transform(X_test_svr)
267
  test_df['Predicted_Vol'] = svr_model.predict(X_test_svr_scaled)
268
 
269
+ # --- STRICT LEVERAGE LOGIC ---
270
 
271
+ # 1. Base Signal (Trend)
272
  test_df['Signal'] = np.where(test_df['EMA_Short'] > test_df['EMA_Long'], 1, 0)
273
 
274
+ # 2. Calculate Confidence
275
  avg_train_vol = train_df['Volatility'].mean()
 
276
  test_df['Risk_Ratio'] = test_df['Predicted_Vol'] / avg_train_vol
277
+
278
+ # 3. Apply "The Rules"
279
+
280
+ # Rule A: Default Size
281
+ test_df['Position_Size'] = 1.0
282
+
283
+ # Rule B: The "Certainty" Boost
284
+ # If Regime is lowest volatility (State 0) AND Risk Ratio is low (< threshold)
285
+ # Then apply leverage
286
+ condition_safe_regime = (test_df['Regime'] == 0)
287
+ condition_low_risk_prediction = (test_df['Risk_Ratio'] < risk_threshold)
288
 
289
  test_df['Position_Size'] = np.where(
290
+ condition_safe_regime & condition_low_risk_prediction,
291
+ leverage_mult, # User selected leverage (e.g., 2.0x)
292
  test_df['Position_Size']
293
  )
294
 
295
+ # Rule C: The "Danger" Cut
296
+ # If Regime is Highest Volatility (State n-1) -> Go to 0
297
+ condition_crash_regime = (test_df['Regime'] == (n_states - 1))
298
+
299
+ test_df['Position_Size'] = np.where(
300
+ condition_crash_regime,
301
+ 0.0,
302
+ test_df['Position_Size']
303
+ )
304
+
305
+ # Final Position Calculation
306
+ # Shift by 1 because we act on Today's close for Tomorrow's return
307
  test_df['Final_Position'] = (test_df['Signal'] * test_df['Position_Size']).shift(1)
308
 
309
+ # Returns Calculation
310
  test_df['Simple_Returns'] = test_df['Close'].pct_change()
311
  test_df['Strategy_Returns'] = test_df['Final_Position'] * test_df['Simple_Returns']
 
312
 
313
+ # --- METRICS & VISUALS ---
 
314
 
315
+ test_df['Strategy_Value'] = (1 + test_df['Strategy_Returns'].fillna(0)).cumprod()
316
+ test_df['Buy_Hold_Value'] = (1 + test_df['Simple_Returns'].fillna(0)).cumprod()
317
  test_df.dropna(inplace=True)
318
 
 
 
 
 
 
319
  metrics_df = calculate_metrics(test_df)
320
+
321
+ st.subheader("Performance vs Benchmark")
322
  st.table(metrics_df)
323
 
324
+ # Plot 1: Equity Curve
325
+ st.subheader("Equity Curve")
326
+ fig = go.Figure()
327
+ fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Buy_Hold_Value'], name='Buy & Hold', line=dict(color='gray', dash='dot')))
328
+ fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Strategy_Value'], name='Smart Leverage Strategy', line=dict(color='#00CC96', width=2)))
329
+ st.plotly_chart(fig, use_container_width=True)
330
 
331
+ # Plot 2: Leverage Deployment
332
+ st.subheader("Leverage Deployment (0x, 1x, or 2x)")
333
+ st.caption("Notice how it shifts to 2x (Green Fill) only during smooth uptrends.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
+ fig_lev = go.Figure()
336
+ fig_lev.add_trace(go.Scatter(
337
+ x=test_df.index,
338
+ y=test_df['Position_Size'],
339
+ mode='lines',
340
+ fill='tozeroy',
341
+ name='Leverage Used',
342
+ line=dict(color='#636EFA')
343
+ ))
344
+ st.plotly_chart(fig_lev, use_container_width=True)
345
+
346
+ # Trade Log
347
  st.divider()
348
+ trade_log = generate_trade_log(test_df)
349
+ st.subheader("📝 Leverage Trade Log")
350
  if not trade_log.empty:
351
+ # Formatting
352
  display_log = trade_log.copy()
353
  display_log['Entry Date'] = display_log['Entry Date'].dt.date
354
  display_log['Exit Date'] = display_log['Exit Date'].dt.date
355
  display_log['Trade PnL'] = display_log['Trade PnL'].map('{:.2%}'.format)
356
+ display_log['Entry Price'] = display_log['Entry Price'].map('{:.2f}'.format)
357
  display_log['Exit Price'] = display_log['Exit Price'].map('{:.2f}'.format)
 
358
  st.dataframe(display_log, use_container_width=True)
359
  else:
360
+ st.write("No trades generated.")