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

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +145 -196
src/streamlit_app.py CHANGED
@@ -6,21 +6,16 @@ from hmmlearn.hmm import GaussianHMM
6
  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, 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
-
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)):
@@ -28,35 +23,23 @@ def fetch_data(ticker, start_date, end_date):
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
-
43
  return df
44
-
45
  except Exception as e:
46
- print(f"Error fetching data: {e}")
47
  return None
48
 
49
  def calculate_metrics(df, strategy_col='Strategy_Value', benchmark_col='Buy_Hold_Value'):
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
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()
@@ -68,13 +51,10 @@ def calculate_metrics(df, strategy_col='Strategy_Value', benchmark_col='Buy_Hold
68
  "Sharpe Ratio": f"{sharpe:.2f}",
69
  "Max Drawdown": f"{max_drawdown:.2%}"
70
  }
71
-
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)
79
  model.fit(X_train)
80
 
@@ -84,31 +64,22 @@ def train_hmm_model(train_df, 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)}
91
-
92
  return model, mapping
93
 
94
  def train_svr_model(train_df):
95
- """Trains SVR to predict next day's volatility."""
96
  feature_cols = ['Log_Returns', 'Volatility', 'Downside_Vol', 'Regime']
97
  target_col = 'Target_Next_Vol'
98
-
99
  X = train_df[feature_cols].values
100
  y = train_df[target_col].values
101
-
102
  scaler = StandardScaler()
103
  X_scaled = scaler.fit_transform(X)
104
-
105
  model = SVR(kernel='rbf', C=100, gamma=0.1, epsilon=0.01)
106
  model.fit(X_scaled, y)
107
-
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
@@ -119,7 +90,7 @@ def generate_trade_log(df):
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
@@ -127,26 +98,19 @@ def generate_trade_log(df):
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 = []
@@ -156,60 +120,36 @@ def generate_trade_log(df):
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
-
168
  return pd.DataFrame(trades)
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:
@@ -218,143 +158,152 @@ if st.button("Run Leverage Backtest"):
218
  # 1. Feature Engineering
219
  df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1))
220
  df['Volatility'] = df['Log_Returns'].rolling(window=10).std()
221
-
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
-
231
  df = df.dropna()
232
 
233
  # 2. Split Data
234
  train_df = df[df.index < pd.Timestamp(backtest_start)].copy()
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.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  from sklearn.svm import SVR
7
  from sklearn.preprocessing import StandardScaler
8
  import plotly.graph_objects as go
 
9
  from datetime import datetime, date
10
 
11
  # --- Config ---
12
+ st.set_page_config(page_title="HMM-SVR Honest Leverage Sniper", layout="wide")
13
 
14
  # --- Helper Functions ---
15
 
16
  @st.cache_data(ttl=3600)
17
  def fetch_data(ticker, start_date, end_date):
 
 
 
18
  ticker = ticker.strip().upper()
 
19
  if isinstance(start_date, (datetime, pd.Timestamp)):
20
  start_date = start_date.strftime('%Y-%m-%d')
21
  if isinstance(end_date, (datetime, pd.Timestamp)):
 
23
 
24
  try:
25
  df = yf.download(ticker, start=start_date, end=end_date, progress=False)
26
+ if df.empty: return None
 
 
 
27
  if isinstance(df.columns, pd.MultiIndex):
28
  df.columns = df.columns.get_level_values(0)
 
29
  df = df.dropna(how='all')
30
+ if len(df) < 10: return None
 
 
 
31
  return df
 
32
  except Exception as e:
33
+ st.error(f"Error: {e}")
34
  return None
35
 
36
  def calculate_metrics(df, strategy_col='Strategy_Value', benchmark_col='Buy_Hold_Value'):
 
37
  stats = {}
 
38
  for col, name in [(strategy_col, 'Smart Leverage Strategy'), (benchmark_col, 'Buy & Hold')]:
39
  initial = df[col].iloc[0]
40
  final = df[col].iloc[-1]
41
  total_return = (final - initial) / initial
 
42
  daily_ret = df[col].pct_change().dropna()
 
43
  sharpe = (daily_ret.mean() / daily_ret.std()) * np.sqrt(365) if daily_ret.std() != 0 else 0
44
 
45
  rolling_max = df[col].cummax()
 
51
  "Sharpe Ratio": f"{sharpe:.2f}",
52
  "Max Drawdown": f"{max_drawdown:.2%}"
53
  }
 
54
  return pd.DataFrame(stats)
55
 
56
  def train_hmm_model(train_df, n_states):
 
57
  X_train = train_df[['Log_Returns', 'Volatility']].values * 100
 
58
  model = GaussianHMM(n_components=n_states, covariance_type="full", n_iter=100, random_state=42)
59
  model.fit(X_train)
60
 
 
64
  avg_vol = X_train[hidden_states == i, 1].mean()
65
  state_vol.append((i, avg_vol))
66
 
 
67
  state_vol.sort(key=lambda x: x[1])
 
68
  mapping = {old: new for new, (old, _) in enumerate(state_vol)}
 
69
  return model, mapping
70
 
71
  def train_svr_model(train_df):
 
72
  feature_cols = ['Log_Returns', 'Volatility', 'Downside_Vol', 'Regime']
73
  target_col = 'Target_Next_Vol'
 
74
  X = train_df[feature_cols].values
75
  y = train_df[target_col].values
 
76
  scaler = StandardScaler()
77
  X_scaled = scaler.fit_transform(X)
 
78
  model = SVR(kernel='rbf', C=100, gamma=0.1, epsilon=0.01)
79
  model.fit(X_scaled, y)
 
80
  return model, scaler
81
 
82
  def generate_trade_log(df):
 
83
  trades = []
84
  in_trade = False
85
  entry_date = None
 
90
  for date, row in df.iterrows():
91
  pos = row['Final_Position']
92
  close_price = row['Close']
93
+ lev = row['Position_Size']
94
 
95
  if pos > 0 and not in_trade:
96
  in_trade = True
 
98
  entry_price = close_price
99
  trade_returns = [row['Strategy_Returns']]
100
  avg_leverage = [lev]
 
101
  elif pos > 0 and in_trade:
102
  trade_returns.append(row['Strategy_Returns'])
103
  avg_leverage.append(lev)
 
104
  elif pos == 0 and in_trade:
105
  in_trade = False
106
  exit_date = date
107
  exit_price = close_price
 
108
  cum_trade_ret = np.prod([1 + r for r in trade_returns]) - 1
109
  mean_lev = np.mean(avg_leverage)
 
110
  trades.append({
111
+ 'Entry Date': entry_date, 'Exit Date': exit_date,
112
+ 'Entry Price': entry_price, 'Exit Price': exit_price,
113
+ 'Duration': len(trade_returns), 'Avg Leverage': f"{mean_lev:.1f}x",
 
 
 
114
  'Trade PnL': cum_trade_ret
115
  })
116
  trade_returns = []
 
120
  cum_trade_ret = np.prod([1 + r for r in trade_returns]) - 1
121
  mean_lev = np.mean(avg_leverage)
122
  trades.append({
123
+ 'Entry Date': entry_date, 'Exit Date': df.index[-1],
124
+ 'Entry Price': entry_price, 'Exit Price': df.iloc[-1]['Close'],
125
+ 'Duration': len(trade_returns), 'Avg Leverage': f"{mean_lev:.1f}x",
 
 
 
126
  'Trade PnL': cum_trade_ret
127
  })
 
128
  return pd.DataFrame(trades)
129
 
130
  # --- Main Logic ---
131
 
132
+ st.title("⚡ HMM-SVR Honest Leverage Backtester")
133
  st.markdown("""
134
+ **The "Strict Rules" Strategy (No Lookahead Bias):**
135
+ 1. **Baseline:** Buy when Fast EMA > Slow EMA.
136
+ 2. **Safety (HMM):** Calculates market regime using ONLY past data.
137
+ 3. **Leverage Boost:** Uses SVR to predict *tomorrow's* volatility based on *today's* data.
138
+ **Timing:** Uses End-of-Day (EOD) data to make decisions for the next trading day.
 
139
  """)
140
 
 
141
  with st.sidebar:
142
  st.header("Settings")
143
+ ticker = st.selectbox("Ticker", ["BNB-USD", "ETH-USD", "SOL-USD", "BTC-USD"])
144
+ backtest_start = st.date_input("Backtest Start Date", date(2022, 1, 1))
145
+ backtest_end = st.date_input("Backtest End Date", datetime.now())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  st.divider()
 
147
  st.subheader("Leverage Rules")
148
+ leverage_mult = st.number_input("Boost Leverage", value=3.0, step=0.5)
149
+ risk_threshold = st.slider("Certainty Threshold", 0.1, 1.0, 0.5)
150
 
151
+ if st.button("Run Honest Backtest"):
152
  train_start_date = pd.Timestamp(backtest_start) - pd.DateOffset(years=4)
 
153
  df = fetch_data(ticker, train_start_date, backtest_end)
154
 
155
  if df is None or len(df) < 200:
 
158
  # 1. Feature Engineering
159
  df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1))
160
  df['Volatility'] = df['Log_Returns'].rolling(window=10).std()
 
161
  df['Downside_Returns'] = df['Log_Returns'].apply(lambda x: x if x < 0 else 0)
162
  df['Downside_Vol'] = df['Downside_Returns'].rolling(window=10).std()
 
 
 
 
 
163
  df['Target_Next_Vol'] = df['Volatility'].shift(-1)
 
164
  df = df.dropna()
165
 
166
  # 2. Split Data
167
  train_df = df[df.index < pd.Timestamp(backtest_start)].copy()
168
  test_df = df[df.index >= pd.Timestamp(backtest_start)].copy()
169
 
170
+ if len(train_df) < 365 or len(test_df) < 10:
171
+ st.error("Data split error. Adjust dates.")
 
 
 
172
  else:
173
+ n_states = 3
174
 
175
+ with st.spinner("1. Training Models on History..."):
176
+ # Train HMM on Past Data
177
  hmm_model, state_map = train_hmm_model(train_df, n_states)
178
 
179
+ # Get Regimes for Train set to train SVR
180
  X_train_hmm = train_df[['Log_Returns', 'Volatility']].values * 100
181
  train_raw_states = hmm_model.predict(X_train_hmm)
182
  train_df['Regime'] = [state_map.get(s, s) for s in train_raw_states]
183
 
184
  # Train SVR
185
  svr_model, svr_scaler = train_svr_model(train_df)
186
+
187
+ # --- HONEST WALK-FORWARD BACKTEST ---
188
+
189
+ st.info("2. Running Walk-Forward Simulation (Step-by-Step)... This simulates real-time trading.")
190
+ progress_bar = st.progress(0)
191
+
192
+ # Prepare lists for storing honest predictions
193
+ honest_regimes = []
194
+ honest_predicted_vols = []
195
+
196
+ # Concatenate for sliding window access
197
+ all_data = pd.concat([train_df, test_df])
198
+ start_idx = len(train_df)
199
+ total_steps = len(test_df)
200
+
201
+ # We use a fixed lookback window for HMM inference to keep it fast enough
202
+ # Looking back 252 days (1 year) is usually sufficient for regime detection
203
+ lookback_window = 252
204
+
205
+ for i in range(total_steps):
206
+ # Update UI
207
+ if i % 10 == 0: progress_bar.progress((i + 1) / total_steps)
208
 
209
+ # Define the window: From (Now - Lookback) to Now
210
+ curr_pointer = start_idx + i
211
+ window_start = max(0, curr_pointer - lookback_window)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
+ # Slice data strictly up to the current day 'i'
214
+ # We include 'i' because we are making a decision at Close of day 'i' for the next day
215
+ history_slice = all_data.iloc[window_start : curr_pointer + 1] # Remove the +1
216
 
217
+ # --- A. Honest Regime Detection ---
218
+ # HMM determines the path of states that best fits this specific history
219
+ X_slice = history_slice[['Log_Returns', 'Volatility']].values * 100
220
 
221
+ try:
222
+ # Predict sequence
223
+ hidden_states_slice = hmm_model.predict(X_slice)
224
+ # We only care about the LAST state (the state of "Today")
225
+ current_state_raw = hidden_states_slice[-1]
226
+ current_state = state_map.get(current_state_raw, current_state_raw)
227
+ except:
228
+ current_state = 1 # Fallback to Neutral if error
229
 
230
+ honest_regimes.append(current_state)
 
 
 
 
 
231
 
232
+ # --- B. Honest Volatility Prediction ---
233
+ # Prepare single row input for SVR: [Log_Ret, Vol, Down_Vol, Regime]
234
+ # Note: We use the 'current_state' we just calculated
235
+ row = test_df.iloc[i]
236
+ svr_features = np.array([[
237
+ row['Log_Returns'],
238
+ row['Volatility'],
239
+ row['Downside_Vol'],
240
+ current_state
241
+ ]])
242
 
243
+ # Scale and Predict
244
+ svr_feat_scaled = svr_scaler.transform(svr_features)
245
+ pred_vol = svr_model.predict(svr_feat_scaled)[0]
246
+ honest_predicted_vols.append(pred_vol)
 
 
 
 
 
 
247
 
248
+ # --- Fix 1: Calculate EMAs properly in walk-forward ---
249
+ # Calculate EMAs using only the history up to current day
250
+ test_df.loc[test_df.index[i], 'EMA_Short'] = history_slice['Close'].ewm(span=12).mean().iloc[-1]
251
+ test_df.loc[test_df.index[i], 'EMA_Long'] = history_slice['Close'].ewm(span=26).mean().iloc[-1]
252
+
253
+ # Assign the honest predictions back to dataframe
254
+ test_df['Regime'] = honest_regimes
255
+ test_df['Predicted_Vol'] = honest_predicted_vols
256
+
257
+ progress_bar.empty()
258
+
259
+ # --- STRATEGY LOGIC (Same as before) ---
260
+
261
+ test_df['Signal'] = np.where(test_df['EMA_Short'] > test_df['EMA_Long'], 1, 0)
262
+ avg_train_vol = train_df['Volatility'].mean()
263
+ test_df['Risk_Ratio'] = test_df['Predicted_Vol'] / avg_train_vol
264
+
265
+ test_df['Position_Size'] = 1.0
266
+
267
+ # Logic
268
+ cond_safe = (test_df['Regime'] == 0)
269
+ cond_low_risk = (test_df['Risk_Ratio'] < risk_threshold)
270
+ cond_crash = (test_df['Regime'] == (n_states - 1))
271
+
272
+ # Boost
273
+ test_df['Position_Size'] = np.where(cond_safe & cond_low_risk, leverage_mult, test_df['Position_Size'])
274
+ # Cut
275
+ test_df['Position_Size'] = np.where(cond_crash, 0.0, test_df['Position_Size'])
276
+
277
+ # Calculate Returns
278
+ test_df['Final_Position'] = (test_df['Signal'] * test_df['Position_Size']).shift(1)
279
+ test_df['Simple_Returns'] = test_df['Close'].pct_change()
280
+ test_df['Strategy_Returns'] = test_df['Final_Position'] * test_df['Simple_Returns']
281
+
282
+ # Metrics & Plots
283
+ test_df['Strategy_Value'] = (1 + test_df['Strategy_Returns'].fillna(0)).cumprod()
284
+ test_df['Buy_Hold_Value'] = (1 + test_df['Simple_Returns'].fillna(0)).cumprod()
285
+ test_df.dropna(inplace=True)
286
+
287
+ metrics_df = calculate_metrics(test_df)
288
+ st.subheader("Performance vs Benchmark")
289
+ st.table(metrics_df)
290
+
291
+ st.subheader("Equity Curve")
292
+ fig = go.Figure()
293
+ fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Buy_Hold_Value'], name='Buy & Hold', line=dict(color='gray', dash='dot')))
294
+ fig.add_trace(go.Scatter(x=test_df.index, y=test_df['Strategy_Value'], name='Smart Leverage', line=dict(color='#00CC96', width=2)))
295
+ st.plotly_chart(fig, use_container_width=True)
296
+
297
+ st.subheader("Leverage Deployment")
298
+ fig_lev = go.Figure()
299
+ fig_lev.add_trace(go.Scatter(x=test_df.index, y=test_df['Position_Size'], mode='lines', fill='tozeroy', name='Lev', line=dict(color='#636EFA')))
300
+ st.plotly_chart(fig_lev, use_container_width=True)
301
+
302
+ trade_log = generate_trade_log(test_df)
303
+ st.subheader("📝 Trade Log")
304
+ if not trade_log.empty:
305
+ display_log = trade_log.copy()
306
+ display_log['Trade PnL'] = display_log['Trade PnL'].map('{:.2%}'.format)
307
+ st.dataframe(display_log, use_container_width=True)
308
+ else:
309
+ st.write("No trades generated.")