Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +34 -41
src/streamlit_app.py
CHANGED
|
@@ -258,52 +258,45 @@ if st.button("Run Honest Backtest"):
|
|
| 258 |
|
| 259 |
# --- STRATEGY LOGIC (Same as before) ---
|
| 260 |
|
| 261 |
-
test_df['Signal'] = np.where(test_df['EMA_Short'] > test_df['EMA_Long'], 1,
|
|
|
|
| 262 |
avg_train_vol = train_df['Volatility'].mean()
|
| 263 |
test_df['Risk_Ratio'] = test_df['Predicted_Vol'] / avg_train_vol
|
| 264 |
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
-
#
|
| 268 |
-
|
| 269 |
-
cond_low_risk
|
| 270 |
-
|
|
|
|
| 271 |
|
| 272 |
-
#
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
test_df['Position_Size'] = np.where(cond_crash, 0.0, test_df['Position_Size'])
|
| 276 |
|
| 277 |
-
#
|
| 278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 303 |
-
|
| 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.")
|
|
|
|
| 258 |
|
| 259 |
# --- STRATEGY LOGIC (Same as before) ---
|
| 260 |
|
| 261 |
+
test_df['Signal'] = np.where(test_df['EMA_Short'] > test_df['EMA_Long'], 1, -1)
|
| 262 |
+
|
| 263 |
avg_train_vol = train_df['Volatility'].mean()
|
| 264 |
test_df['Risk_Ratio'] = test_df['Predicted_Vol'] / avg_train_vol
|
| 265 |
|
| 266 |
+
# Initialize Position Column
|
| 267 |
+
test_df['Target_Position'] = 0.0
|
| 268 |
+
|
| 269 |
+
# --- CONDITIONS ---
|
| 270 |
+
# BULLISH Conditions
|
| 271 |
+
cond_bull_trend = (test_df['Signal'] == 1)
|
| 272 |
+
cond_safe_regime = (test_df['Regime'] == 0) # HMM says "Calm"
|
| 273 |
+
cond_low_risk = (test_df['Risk_Ratio'] < risk_threshold) # SVR says "Low Vol"
|
| 274 |
+
|
| 275 |
+
# BEARISH Conditions (The "Destined to go down" setup)
|
| 276 |
+
cond_bear_trend = (test_df['Signal'] == -1)
|
| 277 |
+
cond_crash_regime = (test_df['Regime'] == (n_states - 1)) # HMM says "Crash"
|
| 278 |
+
# We use Risk Ratio > 1.0 to confirm high volatility momentum (Panic Selling)
|
| 279 |
+
cond_high_vol_momentum = (test_df['Risk_Ratio'] > 1.0)
|
| 280 |
+
|
| 281 |
+
# --- LOGIC EXECUTION ---
|
| 282 |
|
| 283 |
+
# 1. LONG LOGIC (Sniper Longs)
|
| 284 |
+
# If Bull Trend + Safe + Low Risk -> Boost Leverage
|
| 285 |
+
test_df.loc[cond_bull_trend & cond_safe_regime & cond_low_risk, 'Target_Position'] = leverage_mult
|
| 286 |
+
# If Bull Trend but Risk is normal -> 1x Long (Base case)
|
| 287 |
+
test_df.loc[cond_bull_trend & (~cond_safe_regime | ~cond_low_risk), 'Target_Position'] = 1.0
|
| 288 |
|
| 289 |
+
# 2. SHORT LOGIC (Sniper Shorts)
|
| 290 |
+
# ONLY Short if: Trend Down + HMM Crash Detected + Volatility is Expanding
|
| 291 |
+
test_df.loc[cond_bear_trend & cond_crash_regime & cond_high_vol_momentum, 'Target_Position'] = -1.0
|
|
|
|
| 292 |
|
| 293 |
+
# Note: If Bear Trend exists but HMM says "Calm" (State 0) or Vol is low,
|
| 294 |
+
# we stay in CASH (0.0) because it might just be a sideways chop, not a crash.
|
| 295 |
+
|
| 296 |
+
# 3. Calculate Returns (Shift Logic)
|
| 297 |
+
# We apply the position chosen 'Today' to 'Tomorrow's' returns
|
| 298 |
+
test_df['Final_Position'] = test_df['Target_Position'].shift(1)
|
| 299 |
test_df['Simple_Returns'] = test_df['Close'].pct_change()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
|
| 301 |
+
# Math: If Final_Position is -1.0 and Simple_Returns is -5%, Strategy makes +5%
|
| 302 |
+
test_df['Strategy_Returns'] = test_df['Final_Position'] * test_df['Simple_Returns']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|