Spaces:
Running
Running
Upload app.py
Browse files
app.py
CHANGED
|
@@ -3169,7 +3169,105 @@ def update_state():
|
|
| 3169 |
if main_key in st.session_state:
|
| 3170 |
st.session_state[main_key] = st.session_state[widget_key]
|
| 3171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3172 |
def main():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3173 |
st.set_page_config(page_title="Dave's Quant System", page_icon="🔴", layout="wide")
|
| 3174 |
|
| 3175 |
# --- [FIX 1: Robust Initialization] ---
|
|
@@ -3305,7 +3403,7 @@ def main():
|
|
| 3305 |
st.session_state.ticker_select = st.session_state.ticker_list[0]
|
| 3306 |
st.session_state.load_message = load_message
|
| 3307 |
st.session_state.outlier_report = outlier_report
|
| 3308 |
-
|
| 3309 |
master_df = st.session_state.master_df
|
| 3310 |
ticker_list = st.session_state.ticker_list
|
| 3311 |
|
|
@@ -3354,20 +3452,7 @@ def main():
|
|
| 3354 |
div[data-testid="stSidebar"] button[kind="primary"] { width: 100%; }
|
| 3355 |
div[data-testid="stSidebar"] div[data-testid="stButton"] button { width: 100%; }
|
| 3356 |
</style>""", unsafe_allow_html=True)
|
| 3357 |
-
|
| 3358 |
-
# --- DISPLAY LOAD MESSAGES (Moved here to check Production Mode) ---
|
| 3359 |
-
if 'load_message' in st.session_state:
|
| 3360 |
-
with main_content_placeholder.container():
|
| 3361 |
-
# Always show the main load success/fail message
|
| 3362 |
-
st.info(st.session_state.load_message)
|
| 3363 |
-
|
| 3364 |
-
# Only show Data Cleaning details if NOT in Production Mode
|
| 3365 |
-
if st.session_state.outlier_report and not production_mode:
|
| 3366 |
-
st.info(f"Data Cleaning: Found and removed price spikes >100% in {len(st.session_state.outlier_report)} tickers.")
|
| 3367 |
-
|
| 3368 |
-
del st.session_state.load_message
|
| 3369 |
-
if 'outlier_report' in st.session_state: del st.session_state.outlier_report
|
| 3370 |
-
|
| 3371 |
|
| 3372 |
# --- SIDEBAR SECTION 1: Select Test Mode & Dates (Common to both) ---
|
| 3373 |
st.sidebar.header("1. Select Test Mode & Dates")
|
|
@@ -4334,112 +4419,170 @@ def main():
|
|
| 4334 |
else: st.info("No chart data available.")
|
| 4335 |
|
| 4336 |
# 13. Display Full Analysis Summary
|
| 4337 |
-
|
| 4338 |
-
|
| 4339 |
-
|
| 4340 |
-
|
| 4341 |
-
|
| 4342 |
-
|
| 4343 |
-
|
| 4344 |
-
|
| 4345 |
-
|
| 4346 |
-
|
| 4347 |
-
|
| 4348 |
|
| 4349 |
-
|
| 4350 |
-
|
| 4351 |
-
|
| 4352 |
-
|
| 4353 |
-
|
| 4354 |
-
|
| 4355 |
-
|
| 4356 |
-
|
| 4357 |
-
|
| 4358 |
-
|
| 4359 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4360 |
|
| 4361 |
-
|
| 4362 |
-
|
| 4363 |
-
if
|
| 4364 |
-
|
| 4365 |
-
|
| 4366 |
-
|
|
|
|
|
|
|
| 4367 |
|
| 4368 |
-
|
| 4369 |
-
|
| 4370 |
-
|
| 4371 |
-
|
| 4372 |
-
|
|
|
|
| 4373 |
|
| 4374 |
-
|
| 4375 |
-
|
| 4376 |
-
|
| 4377 |
-
# 5. Open Positions Table (With Filter)
|
| 4378 |
-
st.subheader("👨🏻💼 Open Positions & Recently Closed",
|
| 4379 |
-
help="This table displays all currently ACTIVE trades, plus any trades that closed within the last 30 days.")
|
| 4380 |
|
| 4381 |
-
|
| 4382 |
-
|
| 4383 |
-
|
| 4384 |
-
|
| 4385 |
-
|
| 4386 |
-
# Ensure date column is datetime
|
| 4387 |
-
full_df['Date Closed'] = pd.to_datetime(full_df['Date Closed'], errors='coerce')
|
| 4388 |
-
|
| 4389 |
-
mask_open = (full_df['Status'] == 'Open')
|
| 4390 |
-
mask_recent = (full_df['Status'] == 'Closed') & (full_df['Date Closed'] >= cutoff)
|
| 4391 |
-
|
| 4392 |
-
display_open_df = full_df[mask_open | mask_recent].copy()
|
| 4393 |
-
# -------------------------------------------------------
|
| 4394 |
|
| 4395 |
-
|
| 4396 |
-
|
| 4397 |
-
|
| 4398 |
-
|
| 4399 |
-
|
| 4400 |
-
|
| 4401 |
-
|
| 4402 |
-
|
| 4403 |
-
|
| 4404 |
-
|
| 4405 |
-
|
| 4406 |
-
|
| 4407 |
-
else:
|
| 4408 |
-
st.info("No Open or Recent trades found (older trades are hidden).")
|
| 4409 |
else:
|
| 4410 |
-
st.info("No trades found.")
|
| 4411 |
-
|
| 4412 |
-
# 14. Default Message
|
| 4413 |
else:
|
| 4414 |
-
|
| 4415 |
-
st.info("Click a 'Run' button in the sidebar to start.")
|
| 4416 |
|
| 4417 |
-
#
|
| 4418 |
-
|
| 4419 |
-
|
| 4420 |
-
|
| 4421 |
-
|
| 4422 |
-
|
| 4423 |
-
|
| 4424 |
-
|
| 4425 |
-
|
| 4426 |
-
|
| 4427 |
-
|
| 4428 |
-
|
| 4429 |
-
|
| 4430 |
-
|
| 4431 |
-
|
| 4432 |
-
|
| 4433 |
-
|
| 4434 |
-
|
| 4435 |
-
|
| 4436 |
-
|
| 4437 |
-
|
| 4438 |
-
|
| 4439 |
-
|
| 4440 |
-
|
| 4441 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4442 |
|
|
|
|
| 4443 |
def generate_trades_timeline_histogram(trades_df, start_date, end_date):
|
| 4444 |
"""
|
| 4445 |
Creates a stacked histogram showing trade results over time.
|
|
|
|
| 3169 |
if main_key in st.session_state:
|
| 3170 |
st.session_state[main_key] = st.session_state[widget_key]
|
| 3171 |
|
| 3172 |
+
def convert_results_to_csv(summary_df, params):
|
| 3173 |
+
"""
|
| 3174 |
+
Combines the Performance Summary and the Configuration Settings into a single CSV string.
|
| 3175 |
+
"""
|
| 3176 |
+
if summary_df is None or summary_df.empty:
|
| 3177 |
+
return ""
|
| 3178 |
+
|
| 3179 |
+
# 1. Get Performance Data (First Row)
|
| 3180 |
+
perf_data = summary_df.iloc[0].to_dict()
|
| 3181 |
+
|
| 3182 |
+
# 2. Get Settings Data (From Session State/Params)
|
| 3183 |
+
settings_data = {
|
| 3184 |
+
"--- SETTINGS ---": "", # Separator
|
| 3185 |
+
"Start Date": params.get('start_date'),
|
| 3186 |
+
"End Date": params.get('end_date'),
|
| 3187 |
+
"Primary Driver": params.get('primary_driver'),
|
| 3188 |
+
"Confidence Threshold": params.get('confidence_threshold'),
|
| 3189 |
+
"RSI Weight": params.get('rsi_w') if params.get('use_rsi') else "Off",
|
| 3190 |
+
"Volatility Weight": params.get('vol_w') if params.get('use_volatility') else "Off",
|
| 3191 |
+
"Trend Weight": params.get('trend_w') if params.get('use_trend') else "Off",
|
| 3192 |
+
"Volume Weight": params.get('vol_w_val') if params.get('use_volume') else "Off",
|
| 3193 |
+
"MACD Weight": params.get('macd_w') if params.get('use_macd') else "Off",
|
| 3194 |
+
"MA Slope Weight": params.get('ma_slope_w') if params.get('use_ma_slope') else "Off",
|
| 3195 |
+
"Markov Weight": params.get('markov_w') if params.get('use_markov') else "Off",
|
| 3196 |
+
"MFI Weight": params.get('mfi_w') if params.get('use_mfi') else "Off",
|
| 3197 |
+
"SuperTrend Weight": params.get('supertrend_w') if params.get('use_supertrend') else "Off",
|
| 3198 |
+
"ADX Filter": f"On (< {params.get('adx_threshold')})" if params.get('use_adx_filter') else "Off",
|
| 3199 |
+
"BB Period": params.get('bband_period'),
|
| 3200 |
+
"BB Std Dev": params.get('bband_std_dev'),
|
| 3201 |
+
"MA Period": params.get('large_ma_period'),
|
| 3202 |
+
"Long Entry Thresh %": params.get('long_entry_threshold_pct'),
|
| 3203 |
+
"Short Entry Thresh %": params.get('short_entry_threshold_pct'),
|
| 3204 |
+
"Exit Logic": params.get('exit_logic_type'),
|
| 3205 |
+
"Smart TSL %": params.get('smart_trailing_stop_pct')
|
| 3206 |
+
}
|
| 3207 |
+
|
| 3208 |
+
# 3. Combine into one dictionary
|
| 3209 |
+
combined_data = {**perf_data, **settings_data}
|
| 3210 |
+
|
| 3211 |
+
# 4. Create DataFrame and convert to CSV
|
| 3212 |
+
export_df = pd.DataFrame([combined_data])
|
| 3213 |
+
return export_df.to_csv(index=False).encode('utf-8')
|
| 3214 |
+
|
| 3215 |
def main():
|
| 3216 |
+
if 'run_advanced_advisor' not in st.session_state: st.session_state.run_advanced_advisor = False
|
| 3217 |
+
if 'run_user_advisor_setup' not in st.session_state: st.session_state.run_user_advisor_setup = False
|
| 3218 |
+
|
| 3219 |
+
# 2. Define ALL Defaults (Prevents "AttributeError" crashes)
|
| 3220 |
+
defaults = {
|
| 3221 |
+
'start_date': date(2024, 1, 1), 'end_date': date.today(),
|
| 3222 |
+
'bband_period': 20, 'bband_std_dev': 2.0,
|
| 3223 |
+
'long_entry_threshold_pct': 0.0, 'short_entry_threshold_pct': 0.0,
|
| 3224 |
+
'long_exit_ma_threshold_pct': 0.0, 'short_exit_ma_threshold_pct': 0.0,
|
| 3225 |
+
'confidence_threshold': 50, 'max_long_duration': 60, 'max_short_duration': 60,
|
| 3226 |
+
'long_trailing_stop_loss_pct': 0.0, 'short_trailing_stop_loss_pct': 0.0,
|
| 3227 |
+
'primary_driver': 'Bollinger Bands',
|
| 3228 |
+
'use_rsi': True, 'rsi_w': 0.5,
|
| 3229 |
+
'use_volatility': True, 'vol_w': 0.5,
|
| 3230 |
+
'use_trend': True, 'trend_w': 0.5,
|
| 3231 |
+
'use_volume': True, 'vol_w_val': 0.5,
|
| 3232 |
+
'use_macd': False, 'macd_w': 0.5,
|
| 3233 |
+
'use_ma_slope': False, 'ma_slope_w': 0.5,
|
| 3234 |
+
'use_markov': False, 'markov_w': 0.5,
|
| 3235 |
+
# New Indicators
|
| 3236 |
+
'use_mfi': False, 'mfi_w': 0.5,
|
| 3237 |
+
'use_supertrend': False, 'supertrend_w': 0.5,
|
| 3238 |
+
# Filters & Exits
|
| 3239 |
+
'use_adx_filter': False, 'adx_threshold': 25, 'rsi_logic': 'Level',
|
| 3240 |
+
'exit_logic_type': 'Standard (Price-Based)',
|
| 3241 |
+
'smart_trailing_stop_pct': 0.05,
|
| 3242 |
+
'smart_exit_atr_period': 14, 'smart_exit_atr_multiplier': 3.0,
|
| 3243 |
+
'intelligent_tsl_pct': 1.0,
|
| 3244 |
+
'use_ma_floor_filter': False,
|
| 3245 |
+
'long_delay_days': 0, 'short_delay_days': 0,
|
| 3246 |
+
'long_score_95_percentile': None, 'short_score_95_percentile': None,
|
| 3247 |
+
'veto_setup_list': None,
|
| 3248 |
+
'advisor_df': None, 'confidence_results_df': None,
|
| 3249 |
+
'single_ticker_results': None, 'summary_df': None,
|
| 3250 |
+
'open_trades_df': None, 'load_message': None,
|
| 3251 |
+
'outlier_report': None,
|
| 3252 |
+
'best_markov_setup': None,
|
| 3253 |
+
# Optimization
|
| 3254 |
+
'ma_period': 50, 'bb_period': 20, 'bb_std': 2.0,
|
| 3255 |
+
'confidence_slider': 50, 'long_entry': 0.0, 'long_exit': 0.0,
|
| 3256 |
+
'long_sl': 0.0, 'long_delay': 0, 'short_entry': 0.0,
|
| 3257 |
+
'short_exit': 0.0, 'short_sl': 0.0, 'short_delay': 0, 'max_duration': 60,
|
| 3258 |
+
'volume_w': 0.5 # fallback for vol_w_val naming differences
|
| 3259 |
+
}
|
| 3260 |
+
|
| 3261 |
+
# 3. Apply Defaults if Missing
|
| 3262 |
+
for key, val in defaults.items():
|
| 3263 |
+
if key not in st.session_state: st.session_state[key] = val
|
| 3264 |
+
|
| 3265 |
+
# 4. Helper to update state from widgets
|
| 3266 |
+
def update_state():
|
| 3267 |
+
for key in defaults.keys():
|
| 3268 |
+
widget_key = f"widget_{key}"
|
| 3269 |
+
if widget_key in st.session_state: st.session_state[key] = st.session_state[widget_key]
|
| 3270 |
+
|
| 3271 |
st.set_page_config(page_title="Dave's Quant System", page_icon="🔴", layout="wide")
|
| 3272 |
|
| 3273 |
# --- [FIX 1: Robust Initialization] ---
|
|
|
|
| 3403 |
st.session_state.ticker_select = st.session_state.ticker_list[0]
|
| 3404 |
st.session_state.load_message = load_message
|
| 3405 |
st.session_state.outlier_report = outlier_report
|
| 3406 |
+
|
| 3407 |
master_df = st.session_state.master_df
|
| 3408 |
ticker_list = st.session_state.ticker_list
|
| 3409 |
|
|
|
|
| 3452 |
div[data-testid="stSidebar"] button[kind="primary"] { width: 100%; }
|
| 3453 |
div[data-testid="stSidebar"] div[data-testid="stButton"] button { width: 100%; }
|
| 3454 |
</style>""", unsafe_allow_html=True)
|
| 3455 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3456 |
|
| 3457 |
# --- SIDEBAR SECTION 1: Select Test Mode & Dates (Common to both) ---
|
| 3458 |
st.sidebar.header("1. Select Test Mode & Dates")
|
|
|
|
| 4419 |
else: st.info("No chart data available.")
|
| 4420 |
|
| 4421 |
# 13. Display Full Analysis Summary
|
| 4422 |
+
if st.session_state.get('summary_df') is not None and not st.session_state.summary_df.empty:
|
| 4423 |
+
# 1. Top Cards
|
| 4424 |
+
display_summary_analytics(st.session_state.summary_df)
|
| 4425 |
+
|
| 4426 |
+
# 2. Histogram 1: Profit Distribution
|
| 4427 |
+
st.markdown("---")
|
| 4428 |
+
try:
|
| 4429 |
+
if 'generate_profit_distribution_chart' in globals():
|
| 4430 |
+
dist_fig = generate_profit_distribution_chart(st.session_state.summary_df)
|
| 4431 |
+
if dist_fig: st.plotly_chart(dist_fig, use_container_width=True)
|
| 4432 |
+
except Exception: pass
|
| 4433 |
|
| 4434 |
+
# 3. Histogram 2: Trades Over Time (NEW)
|
| 4435 |
+
try:
|
| 4436 |
+
if 'generate_trades_timeline_histogram' in globals() and st.session_state.get('open_trades_df') is not None:
|
| 4437 |
+
timeline_fig = generate_trades_timeline_histogram(
|
| 4438 |
+
st.session_state.open_trades_df,
|
| 4439 |
+
st.session_state.start_date,
|
| 4440 |
+
st.session_state.end_date
|
| 4441 |
+
)
|
| 4442 |
+
if timeline_fig: st.plotly_chart(timeline_fig, use_container_width=True)
|
| 4443 |
+
except Exception as e: st.error(f"Chart Error: {e}")
|
| 4444 |
+
st.markdown("---")
|
| 4445 |
+
|
| 4446 |
+
# 4. Results Per Ticker Table
|
| 4447 |
+
st.subheader("Results per Ticker")
|
| 4448 |
+
if st.checkbox("Only show tickers with trades", value=True):
|
| 4449 |
+
df_to_display = st.session_state.summary_df[(st.session_state.summary_df['Num Long Trades'] > 0) | (st.session_state.summary_df['Num Short Trades'] > 0)].copy()
|
| 4450 |
+
else:
|
| 4451 |
+
df_to_display = st.session_state.summary_df.copy()
|
| 4452 |
|
| 4453 |
+
date_cols = ["First Long Entry", "Last Long Exit", "First Short Entry", "Last Short Exit"]
|
| 4454 |
+
for col in date_cols:
|
| 4455 |
+
if col in df_to_display.columns: df_to_display[col] = pd.to_datetime(df_to_display[col], errors='coerce').dt.strftime('%Y-%m-%d')
|
| 4456 |
+
df_to_display.fillna('-', inplace=True)
|
| 4457 |
+
st.dataframe(df_to_display.style.format({ "Cumulative Long P&L": "{:.2%}", "Avg Long Profit per Trade": "{:.2%}", "Avg Long Duration (Days)": "{:.1f}", "Cumulative Short P&L": "{:.2%}", "Avg Short Profit per Trade": "{:.2%}", "Avg Short Duration (Days)": "{:.1f}", "Avg Long Confidence": "{:.0f}%", "Avg Short Confidence": "{:.0f}%" }, na_rep='-'))
|
| 4458 |
+
|
| 4459 |
+
if not production_mode:
|
| 4460 |
+
if st.button("💾 Add these settings to User-Defined List", key="save_setup_from_analysis", on_click=add_setup_to_user_list): pass
|
| 4461 |
|
| 4462 |
+
# 5. Open Positions Table (With Filter)
|
| 4463 |
+
st.subheader("👨🏽💼 Open Positions & Recently Closed",
|
| 4464 |
+
help="This table displays all currently ACTIVE trades, plus any trades that closed within the last 30 days.")
|
| 4465 |
+
|
| 4466 |
+
if st.session_state.get('open_trades_df') is not None and not st.session_state.open_trades_df.empty:
|
| 4467 |
+
full_df = st.session_state.open_trades_df.copy()
|
| 4468 |
|
| 4469 |
+
# --- FILTER: Show 'Open' OR 'Closed in last 30 days' ---
|
| 4470 |
+
cutoff = pd.Timestamp.now() - pd.Timedelta(days=30)
|
| 4471 |
+
full_df['Date Closed'] = pd.to_datetime(full_df['Date Closed'], errors='coerce')
|
|
|
|
|
|
|
|
|
|
| 4472 |
|
| 4473 |
+
mask_open = (full_df['Status'] == 'Open')
|
| 4474 |
+
mask_recent = (full_df['Status'] == 'Closed') & (full_df['Date Closed'] >= cutoff)
|
| 4475 |
+
|
| 4476 |
+
display_open_df = full_df[mask_open | mask_recent].copy()
|
| 4477 |
+
# -------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4478 |
|
| 4479 |
+
display_open_df.sort_values(by=['Status', 'Date Open'], ascending=[True, False], inplace=True)
|
| 4480 |
+
|
| 4481 |
+
cols_order_manual = ['Ticker', 'Status', 'Final % P/L', 'Side', 'Date Open', 'Date Closed', 'Start Confidence']
|
| 4482 |
+
existing_cols_open = [col for col in cols_order_manual if col in display_open_df.columns]
|
| 4483 |
+
|
| 4484 |
+
if existing_cols_open and not display_open_df.empty:
|
| 4485 |
+
st.dataframe(display_open_df[existing_cols_open].style.format({
|
| 4486 |
+
"Final % P/L": lambda x: f"{x:.2%}" if pd.notna(x) else '-',
|
| 4487 |
+
"Date Open": lambda x: x.strftime('%Y-%m-%d') if pd.notna(x) else '-',
|
| 4488 |
+
"Date Closed": lambda x: x.strftime('%Y-%m-%d') if pd.notna(x) else '-',
|
| 4489 |
+
"Start Confidence": lambda x: f"{x:.0f}%" if pd.notna(x) else '-'
|
| 4490 |
+
}, na_rep='-'))
|
|
|
|
|
|
|
| 4491 |
else:
|
| 4492 |
+
st.info("No Open or Recent trades found (older trades are hidden).")
|
|
|
|
|
|
|
| 4493 |
else:
|
| 4494 |
+
st.info("No trades found.")
|
|
|
|
| 4495 |
|
| 4496 |
+
# --- NEW: SAVE RESULTS BUTTON ---
|
| 4497 |
+
st.markdown("---")
|
| 4498 |
+
st.subheader("💾 Save Analysis")
|
| 4499 |
+
|
| 4500 |
+
current_config = {
|
| 4501 |
+
'start_date': st.session_state.start_date, 'end_date': st.session_state.end_date,
|
| 4502 |
+
'primary_driver': st.session_state.primary_driver, 'confidence_threshold': st.session_state.confidence_threshold,
|
| 4503 |
+
'use_rsi': st.session_state.use_rsi, 'rsi_w': st.session_state.rsi_w,
|
| 4504 |
+
'use_volatility': st.session_state.use_volatility, 'vol_w': st.session_state.vol_w,
|
| 4505 |
+
'use_trend': st.session_state.use_trend, 'trend_w': st.session_state.trend_w,
|
| 4506 |
+
'use_volume': st.session_state.use_volume, 'vol_w_val': st.session_state.vol_w_val,
|
| 4507 |
+
'use_macd': st.session_state.use_macd, 'macd_w': st.session_state.macd_w,
|
| 4508 |
+
'use_ma_slope': st.session_state.use_ma_slope, 'ma_slope_w': st.session_state.ma_slope_w,
|
| 4509 |
+
'use_markov': st.session_state.use_markov, 'markov_w': st.session_state.markov_w,
|
| 4510 |
+
'use_mfi': st.session_state.use_mfi, 'mfi_w': st.session_state.mfi_w,
|
| 4511 |
+
'use_supertrend': st.session_state.use_supertrend, 'supertrend_w': st.session_state.supertrend_w,
|
| 4512 |
+
'use_adx_filter': st.session_state.use_adx_filter, 'adx_threshold': st.session_state.adx_threshold,
|
| 4513 |
+
'bband_period': st.session_state.bband_period, 'bband_std_dev': st.session_state.bband_std_dev,
|
| 4514 |
+
'large_ma_period': st.session_state.get('large_ma_period', 50),
|
| 4515 |
+
'long_entry_threshold_pct': st.session_state.long_entry_threshold_pct,
|
| 4516 |
+
'short_entry_threshold_pct': st.session_state.short_entry_threshold_pct,
|
| 4517 |
+
'exit_logic_type': st.session_state.exit_logic_type,
|
| 4518 |
+
'smart_trailing_stop_pct': st.session_state.smart_trailing_stop_pct
|
| 4519 |
+
}
|
| 4520 |
+
|
| 4521 |
+
# Generate CSV
|
| 4522 |
+
if 'convert_results_to_csv' in globals():
|
| 4523 |
+
csv_data = convert_results_to_csv(st.session_state.summary_df, current_config)
|
| 4524 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
|
| 4525 |
+
file_name = f"Backtest_Results_{timestamp}.csv"
|
| 4526 |
+
|
| 4527 |
+
st.download_button(
|
| 4528 |
+
label="⬇️ Download Results & Settings as CSV",
|
| 4529 |
+
data=csv_data,
|
| 4530 |
+
file_name=file_name,
|
| 4531 |
+
mime='text/csv',
|
| 4532 |
+
use_container_width=True
|
| 4533 |
+
)
|
| 4534 |
+
|
| 4535 |
+
# 14. Default Message
|
| 4536 |
+
else:
|
| 4537 |
+
if not any([st.session_state.get(k) for k in ['run_advanced_advisor','run_user_advisor_setup','advisor_df','confidence_results_df','single_ticker_results','summary_df','load_message']]):
|
| 4538 |
+
st.info("Click a 'Run' button in the sidebar to start.")
|
| 4539 |
+
|
| 4540 |
+
# 15. Footer Buttons (Hidden in Production)
|
| 4541 |
+
if not production_mode:
|
| 4542 |
+
st.markdown("---")
|
| 4543 |
+
col_load1, col_load2, col_load3 = st.columns(3)
|
| 4544 |
+
with col_load1:
|
| 4545 |
+
if 'apply_best_params_to_widgets' in locals() and st.session_state.get('best_params'):
|
| 4546 |
+
st.button("⬇️ Load Optimal Parameters", on_click=apply_best_params_to_widgets, use_container_width=True)
|
| 4547 |
+
with col_load2:
|
| 4548 |
+
if 'apply_best_weights_to_widgets' in locals() and st.session_state.get('best_weights'):
|
| 4549 |
+
st.button("⬇️ Load Optimal Weights", on_click=apply_best_weights_to_widgets, use_container_width=True)
|
| 4550 |
+
with col_load3:
|
| 4551 |
+
if st.session_state.get('worst_confidence_setups_list'):
|
| 4552 |
+
if st.button("Apply Top Worst Setups as Veto Filter", use_container_width=True):
|
| 4553 |
+
st.session_state.veto_setup_list = st.session_state.worst_confidence_setups_list
|
| 4554 |
+
st.session_state.worst_confidence_setups_list = None
|
| 4555 |
+
st.sidebar.info(f"Applying {len(st.session_state.veto_setup_list)} Veto filters.")
|
| 4556 |
+
st.rerun()
|
| 4557 |
+
|
| 4558 |
+
col_load4 = st.columns(1)[0]
|
| 4559 |
+
with col_load4:
|
| 4560 |
+
is_markov_relevant = (st.session_state.primary_driver == 'Markov State') or st.session_state.use_markov
|
| 4561 |
+
if st.session_state.get('best_markov_setup') and is_markov_relevant:
|
| 4562 |
+
if st.button("⬇️ Load Best Markov Setup", on_click=None, use_container_width=True):
|
| 4563 |
+
st.session_state.primary_driver = "Markov State"
|
| 4564 |
+
st.session_state.use_markov = True
|
| 4565 |
+
st.sidebar.success("Best Markov setup loaded!")
|
| 4566 |
+
st.rerun()
|
| 4567 |
+
|
| 4568 |
+
# --- LOGIC HANDLERS (Hidden execution) ---
|
| 4569 |
+
if st.session_state.run_advanced_advisor:
|
| 4570 |
+
with st.spinner("Running Optimization..."):
|
| 4571 |
+
run_optimization()
|
| 4572 |
+
st.session_state.run_advanced_advisor = False
|
| 4573 |
+
|
| 4574 |
+
if st.session_state.run_user_advisor_setup:
|
| 4575 |
+
with st.spinner("Running User Setup..."):
|
| 4576 |
+
current_params = {k: st.session_state[k] for k in defaults.keys() if k in st.session_state}
|
| 4577 |
+
current_params['use_mfi'] = st.session_state.use_mfi
|
| 4578 |
+
current_params['mfi_w'] = st.session_state.mfi_w
|
| 4579 |
+
current_params['use_supertrend'] = st.session_state.use_supertrend
|
| 4580 |
+
current_params['supertrend_w'] = st.session_state.supertrend_w
|
| 4581 |
+
|
| 4582 |
+
run_user_defined_setup(current_params)
|
| 4583 |
+
st.session_state.run_user_advisor_setup = False
|
| 4584 |
|
| 4585 |
+
# --- CHART FUNCTION (Must be outside main) ---
|
| 4586 |
def generate_trades_timeline_histogram(trades_df, start_date, end_date):
|
| 4587 |
"""
|
| 4588 |
Creates a stacked histogram showing trade results over time.
|