saadrizvi09 commited on
Commit
b4f22a3
·
verified ·
1 Parent(s): 3f3c968

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +111 -48
src/streamlit_app.py CHANGED
@@ -19,31 +19,24 @@ def fetch_data(ticker, start_date, end_date):
19
  """
20
  Robust data fetching with caching, error handling, and string conversion.
21
  """
22
- # Clean ticker input
23
  ticker = ticker.strip().upper()
24
 
25
- # Convert dates to strings to avoid Streamlit/yfinance type conflicts
26
  if isinstance(start_date, (datetime, pd.Timestamp)):
27
  start_date = start_date.strftime('%Y-%m-%d')
28
  if isinstance(end_date, (datetime, pd.Timestamp)):
29
  end_date = end_date.strftime('%Y-%m-%d')
30
 
31
  try:
32
- # thread=False can sometimes help stability in cloud envs
33
  df = yf.download(ticker, start=start_date, end=end_date, progress=False)
34
 
35
- # Immediate check for empty data
36
  if df.empty:
37
  return None
38
 
39
- # Handle MultiIndex columns (yfinance structure change)
40
  if isinstance(df.columns, pd.MultiIndex):
41
  df.columns = df.columns.get_level_values(0)
42
 
43
- # Remove rows that are completely empty
44
  df = df.dropna(how='all')
45
 
46
- # Check length again after cleanup
47
  if len(df) < 10:
48
  return None
49
 
@@ -58,18 +51,14 @@ def calculate_metrics(df, strategy_col='Strategy_Value', benchmark_col='Buy_Hold
58
  stats = {}
59
 
60
  for col, name in [(strategy_col, 'Hybrid Strategy'), (benchmark_col, 'Buy & Hold')]:
61
- # Returns
62
  initial = df[col].iloc[0]
63
  final = df[col].iloc[-1]
64
  total_return = (final - initial) / initial
65
 
66
- # Daily Returns
67
  daily_ret = df[col].pct_change().dropna()
68
 
69
- # Sharpe (Annualized, assuming 365 trading days for crypto)
70
  sharpe = (daily_ret.mean() / daily_ret.std()) * np.sqrt(365) if daily_ret.std() != 0 else 0
71
 
72
- # Max Drawdown
73
  rolling_max = df[col].cummax()
74
  drawdown = (df[col] - rolling_max) / rolling_max
75
  max_drawdown = drawdown.min()
@@ -84,13 +73,11 @@ def calculate_metrics(df, strategy_col='Strategy_Value', benchmark_col='Buy_Hold
84
 
85
  def train_hmm_model(train_df, n_states):
86
  """Trains HMM on historical data (In-Sample)."""
87
- # Features: Log Returns and Volatility
88
  X_train = train_df[['Log_Returns', 'Volatility']].values * 100
89
 
90
  model = GaussianHMM(n_components=n_states, covariance_type="full", n_iter=100, random_state=42)
91
  model.fit(X_train)
92
 
93
- # Sort states by Volatility (State 0 = Lowest Risk)
94
  hidden_states = model.predict(X_train)
95
  state_vol = []
96
  for i in range(n_states):
@@ -98,30 +85,87 @@ def train_hmm_model(train_df, n_states):
98
  state_vol.append((i, avg_vol))
99
  state_vol.sort(key=lambda x: x[1])
100
 
101
- # Create mapping: {Random_ID: Sorted_ID}
102
  mapping = {old: new for new, (old, _) in enumerate(state_vol)}
103
 
104
  return model, mapping
105
 
106
  def train_svr_model(train_df):
107
  """Trains SVR to predict next day's volatility."""
108
- # Features for SVR: Returns, Current Vol, Downside Vol, Regime
109
  feature_cols = ['Log_Returns', 'Volatility', 'Downside_Vol', 'Regime']
110
  target_col = 'Target_Next_Vol'
111
 
112
  X = train_df[feature_cols].values
113
  y = train_df[target_col].values
114
 
115
- # Scale features
116
  scaler = StandardScaler()
117
  X_scaled = scaler.fit_transform(X)
118
 
119
- # SVR with RBF kernel
120
  model = SVR(kernel='rbf', C=100, gamma=0.1, epsilon=0.01)
121
  model.fit(X_scaled, y)
122
 
123
  return model, scaler
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  # --- Main Logic ---
126
 
127
  st.title("🧠 Hybrid HMM-SVR Strategy Backtester")
@@ -138,13 +182,11 @@ st.markdown("""
138
  with st.sidebar:
139
  st.header("Settings")
140
 
141
- # Changed from text input to dropdown for easier selection
142
  ticker = st.selectbox(
143
  "Ticker",
144
  ["BTC-USD", "BNB-USD", "BABA", "TSM", "AAPL", "SOL-USD", "NVDA", "GOOGL", "META", "MSFT", "AMZN", "TSLA", "AVGO", "ORCL", "AMD", "ETH-USD", "XRP-USD", "ADA-USD", "DOGE-USD"]
145
  )
146
 
147
- # Modified Date Logic: User selects Trading Period
148
  backtest_start = st.date_input("Backtest Start Date", datetime.now() - timedelta(days=1425))
149
  backtest_end = st.date_input("Backtest End Date", datetime.now())
150
 
@@ -156,10 +198,8 @@ with st.sidebar:
156
  n_states = st.slider("HMM States", 2, 4, 3)
157
 
158
  if st.button("Run Hybrid Backtest"):
159
- # Calculate the Training Start Date (4 Years before Backtest Start)
160
  train_start_date = pd.Timestamp(backtest_start) - pd.DateOffset(years=4)
161
 
162
- # Fetch ALL data (Training Period + Backtest Period)
163
  df = fetch_data(ticker, train_start_date, backtest_end)
164
 
165
  if df is None or len(df) < 200:
@@ -169,20 +209,17 @@ if st.button("Run Hybrid Backtest"):
169
  df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1))
170
  df['Volatility'] = df['Log_Returns'].rolling(window=10).std()
171
 
172
- # Downside Volatility (Leverage Effect Feature)
173
  df['Downside_Returns'] = df['Log_Returns'].apply(lambda x: x if x < 0 else 0)
174
  df['Downside_Vol'] = df['Downside_Returns'].rolling(window=10).std()
175
 
176
- # Strategy Indicators
177
  df['EMA_Short'] = df['Close'].ewm(span=short_window, adjust=False).mean()
178
  df['EMA_Long'] = df['Close'].ewm(span=long_window, adjust=False).mean()
179
 
180
- # Target for SVR (Next Day Volatility)
181
  df['Target_Next_Vol'] = df['Volatility'].shift(-1)
182
 
183
  df = df.dropna()
184
 
185
- # 2. Split Data based on Dates
186
  train_df = df[df.index < pd.Timestamp(backtest_start)].copy()
187
  test_df = df[df.index >= pd.Timestamp(backtest_start)].copy()
188
 
@@ -197,7 +234,6 @@ if st.button("Run Hybrid Backtest"):
197
  with st.spinner("Training HMM (Regime Detection)..."):
198
  hmm_model, state_map = train_hmm_model(train_df, n_states)
199
 
200
- # Predict Train Regimes (Needed for SVR training input)
201
  X_train_hmm = train_df[['Log_Returns', 'Volatility']].values * 100
202
  train_raw_states = hmm_model.predict(X_train_hmm)
203
  train_df['Regime'] = [state_map.get(s, s) for s in train_raw_states]
@@ -208,60 +244,43 @@ if st.button("Run Hybrid Backtest"):
208
  with st.spinner("Running Backtest Loop..."):
209
  # --- OUT OF SAMPLE BACKTEST ---
210
 
211
- # 1. Predict Regimes for Test Data
212
  X_test_hmm = test_df[['Log_Returns', 'Volatility']].values * 100
213
  test_raw_states = hmm_model.predict(X_test_hmm)
214
  test_df['Regime'] = [state_map.get(s, s) for s in test_raw_states]
215
 
216
- # 2. Predict Volatility for Test Data (Using SVR)
217
  X_test_svr = test_df[['Log_Returns', 'Volatility', 'Downside_Vol', 'Regime']].values
218
  X_test_svr_scaled = svr_scaler.transform(X_test_svr)
219
  test_df['Predicted_Vol'] = svr_model.predict(X_test_svr_scaled)
220
 
221
- # 3. Calculate Strategy Logic
222
  high_vol_state = n_states - 1
223
 
224
- # Base Signal (EMA)
225
  test_df['Signal'] = np.where(test_df['EMA_Short'] > test_df['EMA_Long'], 1, 0)
226
 
227
- # Calculate Baseline Risk (Average Volatility seen in Training)
228
  avg_train_vol = train_df['Volatility'].mean()
229
 
230
- # Calculate Position Size (The "Dimmer Switch")
231
- # Logic: Size = Average_Vol / Predicted_Vol
232
- # If Predicted > Average, Size < 1.0 (Reduce Risk)
233
- # If Predicted < Average, Size > 1.0 (Increase Risk) -> Capped at 1.0 for safety
234
  test_df['Risk_Ratio'] = test_df['Predicted_Vol'] / avg_train_vol
235
  test_df['Position_Size'] = (1.0 / test_df['Risk_Ratio']).clip(upper=1.0, lower=0.0)
236
 
237
- # Override: If HMM says CRASH, Size = 0
238
  test_df['Position_Size'] = np.where(
239
  test_df['Regime'] == high_vol_state ,
240
  0.0,
241
  test_df['Position_Size']
242
  )
243
 
244
- # Final Position: Signal * Size
245
- # We shift(1) because we calculate size today for tomorrow's return
246
  test_df['Final_Position'] = (test_df['Signal'] * test_df['Position_Size']).shift(1)
247
 
248
- # 4. Returns (Using Simple Returns for accurate Equity Curve)
249
- # We calculate simple % change for the portfolio value
250
  test_df['Simple_Returns'] = test_df['Close'].pct_change()
251
-
252
- # Strategy Returns: Position * Simple Return
253
  test_df['Strategy_Returns'] = test_df['Final_Position'] * test_df['Simple_Returns']
254
-
255
- # Buy & Hold Returns: Just Simple Return
256
  test_df['Buy_Hold_Returns'] = test_df['Simple_Returns']
257
 
258
- # Cumulative Equity Curve (Accurate Money Growth)
259
- # We fill NaN with 0 for the first day to avoid breaking the cumprod
260
  test_df['Strategy_Value'] = (1 + test_df['Strategy_Returns'].fillna(0)).cumprod()
261
  test_df['Buy_Hold_Value'] = (1 + test_df['Buy_Hold_Returns'].fillna(0)).cumprod()
262
 
263
  test_df.dropna(inplace=True)
264
 
 
 
 
265
  # --- RESULTS ---
266
 
267
  metrics_df = calculate_metrics(test_df)
@@ -272,10 +291,39 @@ if st.button("Run Hybrid Backtest"):
272
  col1, col2 = st.columns([2, 1])
273
 
274
  with col1:
275
- st.subheader("Equity Curve")
276
  fig = go.Figure()
 
 
277
  fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Buy_Hold_Value'], name='Buy & Hold', line=dict(color='gray', dash='dot')))
278
  fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Strategy_Value'], name='Hybrid Strategy', line=dict(color='#00CC96', width=2)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  st.plotly_chart(fig, use_container_width=True)
280
 
281
  with col2:
@@ -284,9 +332,24 @@ if st.button("Run Hybrid Backtest"):
284
  fig_size = px.area(test_df, x=test_df.index, y='Position_Size', title="Dynamic Exposure")
285
  st.plotly_chart(fig_size, use_container_width=True)
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  st.subheader("SVR Prediction Accuracy (Test Set)")
288
  fig_svr = go.Figure()
289
- # Show a slice to avoid clutter
290
  slice_df = test_df.iloc[-100:]
291
  fig_svr.add_trace(go.Scatter(x=slice_df.index, y=slice_df['Target_Next_Vol'], name='Actual Volatility'))
292
  fig_svr.add_trace(go.Scatter(x=slice_df.index, y=slice_df['Predicted_Vol'], name='SVR Prediction', line=dict(dash='dot')))
 
19
  """
20
  Robust data fetching with caching, error handling, and string conversion.
21
  """
 
22
  ticker = ticker.strip().upper()
23
 
 
24
  if isinstance(start_date, (datetime, pd.Timestamp)):
25
  start_date = start_date.strftime('%Y-%m-%d')
26
  if isinstance(end_date, (datetime, pd.Timestamp)):
27
  end_date = end_date.strftime('%Y-%m-%d')
28
 
29
  try:
 
30
  df = yf.download(ticker, start=start_date, end=end_date, progress=False)
31
 
 
32
  if df.empty:
33
  return None
34
 
 
35
  if isinstance(df.columns, pd.MultiIndex):
36
  df.columns = df.columns.get_level_values(0)
37
 
 
38
  df = df.dropna(how='all')
39
 
 
40
  if len(df) < 10:
41
  return None
42
 
 
51
  stats = {}
52
 
53
  for col, name in [(strategy_col, 'Hybrid Strategy'), (benchmark_col, 'Buy & Hold')]:
 
54
  initial = df[col].iloc[0]
55
  final = df[col].iloc[-1]
56
  total_return = (final - initial) / initial
57
 
 
58
  daily_ret = df[col].pct_change().dropna()
59
 
 
60
  sharpe = (daily_ret.mean() / daily_ret.std()) * np.sqrt(365) if daily_ret.std() != 0 else 0
61
 
 
62
  rolling_max = df[col].cummax()
63
  drawdown = (df[col] - rolling_max) / rolling_max
64
  max_drawdown = drawdown.min()
 
73
 
74
  def train_hmm_model(train_df, n_states):
75
  """Trains HMM on historical data (In-Sample)."""
 
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)
79
  model.fit(X_train)
80
 
 
81
  hidden_states = model.predict(X_train)
82
  state_vol = []
83
  for i in range(n_states):
 
85
  state_vol.append((i, avg_vol))
86
  state_vol.sort(key=lambda x: x[1])
87
 
 
88
  mapping = {old: new for new, (old, _) in enumerate(state_vol)}
89
 
90
  return model, mapping
91
 
92
  def train_svr_model(train_df):
93
  """Trains SVR to predict next day's volatility."""
 
94
  feature_cols = ['Log_Returns', 'Volatility', 'Downside_Vol', 'Regime']
95
  target_col = 'Target_Next_Vol'
96
 
97
  X = train_df[feature_cols].values
98
  y = train_df[target_col].values
99
 
 
100
  scaler = StandardScaler()
101
  X_scaled = scaler.fit_transform(X)
102
 
 
103
  model = SVR(kernel='rbf', C=100, gamma=0.1, epsilon=0.01)
104
  model.fit(X_scaled, y)
105
 
106
  return model, scaler
107
 
108
+ def generate_trade_log(df):
109
+ """
110
+ Scans the backtest dataframe to identify individual trade cycles.
111
+ A 'Trade' is defined as a period where Position Size > 0.
112
+ """
113
+ trades = []
114
+ in_trade = False
115
+ entry_date = None
116
+ entry_price = 0
117
+ trade_returns = []
118
+
119
+ # We iterate through the dataframe
120
+ for date, row in df.iterrows():
121
+ pos = row['Final_Position']
122
+ close_price = row['Close']
123
+
124
+ # Check for Entry (Position goes from 0 to > 0)
125
+ if pos > 0 and not in_trade:
126
+ in_trade = True
127
+ entry_date = date
128
+ entry_price = close_price # Approximation for log visualization
129
+ trade_returns = [row['Strategy_Returns']] # Start tracking returns for this specific trade
130
+
131
+ # Check for adjustments while in trade
132
+ elif pos > 0 and in_trade:
133
+ trade_returns.append(row['Strategy_Returns'])
134
+
135
+ # Check for Exit (Position goes to 0 while we were in a trade)
136
+ elif pos == 0 and in_trade:
137
+ in_trade = False
138
+ exit_date = date
139
+ exit_price = close_price
140
+
141
+ # Calculate compounded return for this specific trade period
142
+ # (1+r1)*(1+r2)... - 1
143
+ cum_trade_ret = np.prod([1 + r for r in trade_returns]) - 1
144
+
145
+ trades.append({
146
+ 'Entry Date': entry_date,
147
+ 'Exit Date': exit_date,
148
+ 'Entry Price (Approx)': entry_price,
149
+ 'Exit Price': exit_price,
150
+ 'Duration (Days)': len(trade_returns),
151
+ 'Trade PnL': cum_trade_ret
152
+ })
153
+ trade_returns = []
154
+
155
+ # Handle case where trade is still open at end of data
156
+ if in_trade:
157
+ cum_trade_ret = np.prod([1 + r for r in trade_returns]) - 1
158
+ trades.append({
159
+ 'Entry Date': entry_date,
160
+ 'Exit Date': df.index[-1],
161
+ 'Entry Price (Approx)': entry_price,
162
+ 'Exit Price': df.iloc[-1]['Close'],
163
+ 'Duration (Days)': len(trade_returns),
164
+ 'Trade PnL': cum_trade_ret
165
+ })
166
+
167
+ return pd.DataFrame(trades)
168
+
169
  # --- Main Logic ---
170
 
171
  st.title("🧠 Hybrid HMM-SVR Strategy Backtester")
 
182
  with st.sidebar:
183
  st.header("Settings")
184
 
 
185
  ticker = st.selectbox(
186
  "Ticker",
187
  ["BTC-USD", "BNB-USD", "BABA", "TSM", "AAPL", "SOL-USD", "NVDA", "GOOGL", "META", "MSFT", "AMZN", "TSLA", "AVGO", "ORCL", "AMD", "ETH-USD", "XRP-USD", "ADA-USD", "DOGE-USD"]
188
  )
189
 
 
190
  backtest_start = st.date_input("Backtest Start Date", datetime.now() - timedelta(days=1425))
191
  backtest_end = st.date_input("Backtest End Date", datetime.now())
192
 
 
198
  n_states = st.slider("HMM States", 2, 4, 3)
199
 
200
  if st.button("Run Hybrid Backtest"):
 
201
  train_start_date = pd.Timestamp(backtest_start) - pd.DateOffset(years=4)
202
 
 
203
  df = fetch_data(ticker, train_start_date, backtest_end)
204
 
205
  if df is None or len(df) < 200:
 
209
  df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1))
210
  df['Volatility'] = df['Log_Returns'].rolling(window=10).std()
211
 
 
212
  df['Downside_Returns'] = df['Log_Returns'].apply(lambda x: x if x < 0 else 0)
213
  df['Downside_Vol'] = df['Downside_Returns'].rolling(window=10).std()
214
 
 
215
  df['EMA_Short'] = df['Close'].ewm(span=short_window, adjust=False).mean()
216
  df['EMA_Long'] = df['Close'].ewm(span=long_window, adjust=False).mean()
217
 
 
218
  df['Target_Next_Vol'] = df['Volatility'].shift(-1)
219
 
220
  df = df.dropna()
221
 
222
+ # 2. Split Data
223
  train_df = df[df.index < pd.Timestamp(backtest_start)].copy()
224
  test_df = df[df.index >= pd.Timestamp(backtest_start)].copy()
225
 
 
234
  with st.spinner("Training HMM (Regime Detection)..."):
235
  hmm_model, state_map = train_hmm_model(train_df, n_states)
236
 
 
237
  X_train_hmm = train_df[['Log_Returns', 'Volatility']].values * 100
238
  train_raw_states = hmm_model.predict(X_train_hmm)
239
  train_df['Regime'] = [state_map.get(s, s) for s in train_raw_states]
 
244
  with st.spinner("Running Backtest Loop..."):
245
  # --- OUT OF SAMPLE BACKTEST ---
246
 
 
247
  X_test_hmm = test_df[['Log_Returns', 'Volatility']].values * 100
248
  test_raw_states = hmm_model.predict(X_test_hmm)
249
  test_df['Regime'] = [state_map.get(s, s) for s in test_raw_states]
250
 
 
251
  X_test_svr = test_df[['Log_Returns', 'Volatility', 'Downside_Vol', 'Regime']].values
252
  X_test_svr_scaled = svr_scaler.transform(X_test_svr)
253
  test_df['Predicted_Vol'] = svr_model.predict(X_test_svr_scaled)
254
 
 
255
  high_vol_state = n_states - 1
256
 
 
257
  test_df['Signal'] = np.where(test_df['EMA_Short'] > test_df['EMA_Long'], 1, 0)
258
 
 
259
  avg_train_vol = train_df['Volatility'].mean()
260
 
 
 
 
 
261
  test_df['Risk_Ratio'] = test_df['Predicted_Vol'] / avg_train_vol
262
  test_df['Position_Size'] = (1.0 / test_df['Risk_Ratio']).clip(upper=1.0, lower=0.0)
263
 
 
264
  test_df['Position_Size'] = np.where(
265
  test_df['Regime'] == high_vol_state ,
266
  0.0,
267
  test_df['Position_Size']
268
  )
269
 
 
 
270
  test_df['Final_Position'] = (test_df['Signal'] * test_df['Position_Size']).shift(1)
271
 
 
 
272
  test_df['Simple_Returns'] = test_df['Close'].pct_change()
 
 
273
  test_df['Strategy_Returns'] = test_df['Final_Position'] * test_df['Simple_Returns']
 
 
274
  test_df['Buy_Hold_Returns'] = test_df['Simple_Returns']
275
 
 
 
276
  test_df['Strategy_Value'] = (1 + test_df['Strategy_Returns'].fillna(0)).cumprod()
277
  test_df['Buy_Hold_Value'] = (1 + test_df['Buy_Hold_Returns'].fillna(0)).cumprod()
278
 
279
  test_df.dropna(inplace=True)
280
 
281
+ # --- EXTRACT TRADES ---
282
+ trade_log = generate_trade_log(test_df)
283
+
284
  # --- RESULTS ---
285
 
286
  metrics_df = calculate_metrics(test_df)
 
291
  col1, col2 = st.columns([2, 1])
292
 
293
  with col1:
294
+ st.subheader("Equity Curve & Trade Executions")
295
  fig = go.Figure()
296
+
297
+ # 1. Equity Curves
298
  fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Buy_Hold_Value'], name='Buy & Hold', line=dict(color='gray', dash='dot')))
299
  fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Strategy_Value'], name='Hybrid Strategy', line=dict(color='#00CC96', width=2)))
300
+
301
+ # 2. Add Trade Markers
302
+ # Filter Entry Points (Buy)
303
+ if not trade_log.empty:
304
+ # Map dates to Strategy Value for Y-axis placement
305
+ buy_points = trade_log.set_index('Entry Date')
306
+ buy_vals = test_df.loc[buy_points.index]['Strategy_Value']
307
+
308
+ sell_points = trade_log.set_index('Exit Date')
309
+ sell_vals = test_df.loc[sell_points.index]['Strategy_Value']
310
+
311
+ fig.add_trace(go.Scatter(
312
+ x=buy_points.index,
313
+ y=buy_vals,
314
+ mode='markers',
315
+ name='Buy Signal',
316
+ marker=dict(symbol='triangle-up', size=10, color='lime')
317
+ ))
318
+
319
+ fig.add_trace(go.Scatter(
320
+ x=sell_points.index,
321
+ y=sell_vals,
322
+ mode='markers',
323
+ name='Sell Signal',
324
+ marker=dict(symbol='triangle-down', size=10, color='red')
325
+ ))
326
+
327
  st.plotly_chart(fig, use_container_width=True)
328
 
329
  with col2:
 
332
  fig_size = px.area(test_df, x=test_df.index, y='Position_Size', title="Dynamic Exposure")
333
  st.plotly_chart(fig_size, use_container_width=True)
334
 
335
+ # --- NEW: Trade Log Table ---
336
+ st.divider()
337
+ st.subheader("📝 Detailed Trade Log")
338
+ if not trade_log.empty:
339
+ # Formatting for cleaner display
340
+ display_log = trade_log.copy()
341
+ display_log['Entry Date'] = display_log['Entry Date'].dt.date
342
+ display_log['Exit Date'] = display_log['Exit Date'].dt.date
343
+ display_log['Trade PnL'] = display_log['Trade PnL'].map('{:.2%}'.format)
344
+ display_log['Entry Price (Approx)'] = display_log['Entry Price (Approx)'].map('{:.2f}'.format)
345
+ display_log['Exit Price'] = display_log['Exit Price'].map('{:.2f}'.format)
346
+
347
+ st.dataframe(display_log, use_container_width=True)
348
+ else:
349
+ st.write("No trades executed in this period.")
350
+
351
  st.subheader("SVR Prediction Accuracy (Test Set)")
352
  fig_svr = go.Figure()
 
353
  slice_df = test_df.iloc[-100:]
354
  fig_svr.add_trace(go.Scatter(x=slice_df.index, y=slice_df['Target_Next_Vol'], name='Actual Volatility'))
355
  fig_svr.add_trace(go.Scatter(x=slice_df.index, y=slice_df['Predicted_Vol'], name='SVR Prediction', line=dict(dash='dot')))