Daveabc12 commited on
Commit
dc3da35
·
verified ·
1 Parent(s): 29d780e

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -23
app.py CHANGED
@@ -62,7 +62,7 @@ def load_settings():
62
  "norm_lookback_years": 1,
63
  "benchmark_rank": 99,
64
  "use_ma_floor_filter": True,
65
-
66
  "use_vol": False, "vol_w": 0.5,
67
  "use_trend": False, "trend_w": 2.0,
68
  "use_volume": False, "volume_w": 0.5,
@@ -217,6 +217,7 @@ def load_user_setups():
217
  "ADX Filter": "Off",
218
  "Conf. Threshold": 50,
219
  "Large MA Period": 50, "Bollinger Band Period": 20, "Bollinger Band Std Dev": 2.0,
 
220
  "Long Entry Threshold (%)": 0.0, "Long Exit Threshold (%)": 0.0, "Long Stop Loss (%)": 8.0, "Long Delay (Days)": 0,
221
  "Short Entry Threshold (%)": 0.0, "Short Exit Threshold (%)": 0.0, "Short Stop Loss (%)": 8.0, "Short Delay (Days)": 0,
222
  "Z_Avg_Profit": 0.0, "Z_Num_Trades": 0, "Z_WL_Ratio": 0.0
@@ -585,7 +586,8 @@ def run_backtest(data, params,
585
  smart_exit_atr_multiplier=3.0,
586
  intelligent_tsl_pct=1.0,
587
  analysis_start_date=None,
588
- analysis_end_date=None):
 
589
 
590
  df = data.copy()
591
  required_cols = ['Close']
@@ -640,7 +642,7 @@ def run_backtest(data, params,
640
  else:
641
  df['RSI'], df['Volatility_p'], df['ADX'], df['ATR'] = np.nan, np.nan, np.nan, np.nan
642
 
643
- # --- [NEW] MFI & SUPERTREND CALCULATIONS (Fixed) ---
644
  if len(df) >= 14:
645
  # 1. MFI Calculation
646
  try:
@@ -749,22 +751,37 @@ def run_backtest(data, params,
749
  )
750
 
751
  if long_score_95_percentile is None:
752
- long_scores_gt_zero = raw_long_score[raw_long_score > 0]
753
- # [FIX] Use the variable 'benchmark_rank' instead of hardcoded 0.95
754
- long_95 = long_scores_gt_zero.quantile(benchmark_rank) if not long_scores_gt_zero.empty else 1.0
 
 
 
 
 
 
 
755
  else:
756
- long_95 = long_score_95_percentile
757
 
758
  if short_score_95_percentile is None:
759
- short_scores_gt_zero = raw_short_score[raw_short_score > 0]
760
- # [FIX] Use the variable 'benchmark_rank' instead of hardcoded 0.95
761
- short_95 = short_scores_gt_zero.quantile(benchmark_rank) if not short_scores_gt_zero.empty else 1.0
 
 
 
 
 
762
  else:
763
- short_95 = short_score_95_percentile
764
 
 
 
 
765
 
766
- df['long_confidence_score'] = (raw_long_score / long_95 * 100).clip(0, 100) if long_95 > 0 else 0.0
767
- df['short_confidence_score'] = (raw_short_score / short_95 * 100).clip(0, 100) if short_95 > 0 else 0.0
768
 
769
  apply_veto = bool(veto_setups_list)
770
  if apply_veto:
@@ -904,7 +921,17 @@ def run_backtest(data, params,
904
  df['long_entry_price_static'] = df['Close'].where(df['long_signal'].shift(1) == 0).ffill().bfill()
905
  df['short_entry_price_static'] = df['Close'].where(df['short_signal'].shift(1) == 0).ffill().bfill()
906
 
907
- long_breakeven_floor = df['long_entry_price_static']; short_breakeven_floor = df['short_entry_price_static']
 
 
 
 
 
 
 
 
 
 
908
  use_ma_floor_filter = params.get('use_ma_floor_filter', False)
909
 
910
  standard_tsl_pct_long = params.get('long_trailing_stop_loss_pct', 0)
@@ -1264,7 +1291,8 @@ def display_summary_analytics(summary_df):
1264
 
1265
  st.metric("Strategy Score", f"{display_score:.2f}%")
1266
  st.metric("Avg Profit per Trade (Active Tickers)", f"{avg_trade_profit:.2%}")
1267
- st.metric(f"Average Entry Confidence", f"{avg_confidence:.0f}%")
 
1268
 
1269
  st.text(f"Profitable Tickers: {good_tickers}")
1270
  st.text(f"Losing Tickers: {bad_tickers}")
@@ -1538,6 +1566,7 @@ def run_single_parameter_test(task_data):
1538
  "Ticker G/B Ratio": good_bad_ratio,
1539
  "Trade G/B Ratio": trade_good_bad_ratio,
1540
  "Total Trades": total_trades,
 
1541
  "Avg Entry Conf.": avg_entry_confidence,
1542
  "Winning Tickers": winning_tickers,
1543
  "Losing Tickers": losing_tickers,
@@ -2314,11 +2343,16 @@ def run_advisor_scan(main_df, setups_to_run, advisor_type="Advisor"):
2314
  except: long_d = 0
2315
  try: short_d = int(setup.get("Short Delay (Days)", 0))
2316
  except: short_d = 0
 
 
 
 
2317
 
2318
  params_for_run = {
2319
  "large_ma_period": ma_p, "bband_period": bb_p, "long_delay_days": long_d, "short_delay_days": short_d,
2320
  "bband_std_dev": setup.get("Bollinger Band Std Dev", 2.0),
2321
  "confidence_threshold": setup.get("Conf. Threshold", 50),
 
2322
  "long_entry_threshold_pct": setup.get("Long Entry Threshold (%)", 0.0) / 100.0,
2323
  "long_exit_ma_threshold_pct": setup.get("Long Exit Threshold (%)", 0.0) / 100.0,
2324
  "long_trailing_stop_loss_pct": setup.get("Long Stop Loss (%)", 0.0) / 100.0,
@@ -2660,6 +2694,9 @@ def apply_best_params_to_widgets():
2660
 
2661
  # --- NEW: Load Max Duration ---
2662
  if 'max_trading_days' in params: st.session_state.max_duration = params['max_trading_days']
 
 
 
2663
 
2664
  st.sidebar.success("Optimal parameters loaded into sidebar!")
2665
  st.rerun()
@@ -3118,7 +3155,18 @@ def run_confidence_optimisation(optimise_for, find_mode, master_df, main_content
3118
 
3119
  # --- Display the table ---
3120
  display_df_conf = results_df.head(60)
3121
- conf_formatters = { "Avg Profit/Trade": "{:.2%}", "Ticker G/B Ratio": "{:.2f}", "Trade G/B Ratio": "{:.2f}", "Avg Entry Conf.": "{:.1f}%", "Good Score": "{:.4f}", "Bad Score": "{:.4f}", "Norm. Score %": "{:.2f}%" }
 
 
 
 
 
 
 
 
 
 
 
3122
  if 'MACD' in display_df_conf.columns: conf_formatters['MACD'] = '{}'
3123
  if 'MA Slope' in display_df_conf.columns: conf_formatters['MA Slope'] = '{}'
3124
  if 'Markov' in display_df_conf.columns: conf_formatters['Markov'] = '{}'
@@ -3165,6 +3213,7 @@ def generate_user_advisor_ui_and_run(main_df):
3165
  "Large MA Period": st.column_config.NumberColumn("MA", width="small"),
3166
  "Bollinger Band Period": st.column_config.NumberColumn("BB", width="small"),
3167
  "Bollinger Band Std Dev": st.column_config.NumberColumn("Std", format="%.1f", width="small"),
 
3168
  "Long Entry Threshold (%)": st.column_config.NumberColumn("L Entry", format="%.1f", width="small"),
3169
  "Long Exit Threshold (%)": st.column_config.NumberColumn("L Exit", format="%.1f", width="small"),
3170
  "Long Stop Loss (%)": st.column_config.NumberColumn("L TSL", format="%.1f", width="small"),
@@ -3187,7 +3236,7 @@ def generate_user_advisor_ui_and_run(main_df):
3187
  "Run", "Notes", # <--- Notes column appears here (2nd)
3188
  "RSI", "Volatility", "TREND", "Volume", "MACD", "MA Slope", "Markov", "ADX Filter",
3189
  "Conf. Threshold",
3190
- "Large MA Period", "Bollinger Band Period", "Bollinger Band Std Dev",
3191
  "Long Entry Threshold (%)", "Long Exit Threshold (%)", "Long Stop Loss (%)", "Long Delay (Days)",
3192
  "Short Entry Threshold (%)", "Short Exit Threshold (%)", "Short Stop Loss (%)", "Short Delay (Days)",
3193
  "Z_Avg_Profit", "Z_Num_Trades", "Z_WL_Ratio"
@@ -3381,6 +3430,7 @@ def main():
3381
  'smart_exit_atr_period': 14, 'smart_exit_atr_multiplier': 3.0,
3382
  'intelligent_tsl_pct': 1.0,
3383
  'use_ma_floor_filter': False,
 
3384
  'long_delay_days': 0, 'short_delay_days': 0,
3385
  'long_score_95_percentile': None, 'short_score_95_percentile': None,
3386
  'veto_setup_list': None,
@@ -3457,7 +3507,8 @@ def main():
3457
  st.session_state.smart_exit_atr_period = defaults.get("smart_exit_atr_period", 14)
3458
  st.session_state.smart_exit_atr_multiplier = defaults.get("smart_exit_atr_multiplier", 3.0)
3459
  st.session_state.intelligent_tsl_pct = defaults.get("intelligent_tsl_pct", 0.60) * 100
3460
-
 
3461
  # Section 3 Defaults
3462
  st.session_state.ma_period = defaults.get("large_ma_period", 50)
3463
  st.session_state.bb_period = defaults.get("bband_period", 20)
@@ -3935,8 +3986,18 @@ def main():
3935
  st.sidebar.number_input("MACD Weight", 0.1, 5.0, step=0.1, key='widget_macd_w', on_change=update_state, disabled=not st.session_state.get('use_macd', True))
3936
 
3937
  # Confidence Slider
3938
- if 'widget_confidence_slider' not in st.session_state: st.session_state.widget_confidence_slider = st.session_state.confidence_slider
3939
- st.sidebar.slider("Minimum Confidence Threshold (%)", 0, 100, step=5, key='widget_confidence_slider', on_change=update_state, help="Acts as a quality filter. Higher values increase trade quality but reduce the number of trades found.")
 
 
 
 
 
 
 
 
 
 
3940
 
3941
  else:
3942
  # --- DEVELOPER VIEW (Original) ---
@@ -4011,8 +4072,17 @@ def main():
4011
  if 'widget_supertrend_w' not in st.session_state: st.session_state.widget_supertrend_w = st.session_state.supertrend_w
4012
  st.sidebar.number_input("SuperTrend Weight", 0.1, 5.0, step=0.1, key='widget_supertrend_w', on_change=update_state, disabled=not st.session_state.get('use_supertrend', True))
4013
 
4014
- if 'widget_confidence_slider' not in st.session_state: st.session_state.widget_confidence_slider = st.session_state.confidence_slider
4015
- st.sidebar.slider("Minimum Confidence Threshold (%)", 0, 100, step=5, key='widget_confidence_slider', on_change=update_state)
 
 
 
 
 
 
 
 
 
4016
  st.sidebar.markdown("---")
4017
 
4018
  st.sidebar.header("3. Strategy Parameters")
@@ -4067,6 +4137,17 @@ def main():
4067
 
4068
  st.sidebar.toggle("Apply MA/BB Price Lock Floor (Catcher)", value=st.session_state.use_ma_floor_filter, key='widget_use_ma_floor_filter', on_change=update_state,
4069
  help="If ON, the Trailing Stop Loss is prevented from dropping below the Mean Reversion price.")
 
 
 
 
 
 
 
 
 
 
 
4070
  st.sidebar.markdown("---")
4071
 
4072
  st.sidebar.subheader("Short Trade Logic")
@@ -4243,7 +4324,8 @@ def main():
4243
  "rsi_logic": st.session_state.get('rsi_logic', 'Crossover'),
4244
  "primary_driver": st.session_state.get('primary_driver', 'Bollinger Bands'),
4245
  "exit_logic_type": st.session_state.get('exit_logic_type', 'Standard (Price-Based)'),
4246
- "use_ma_floor_filter": st.session_state.use_ma_floor_filter,
 
4247
  "exit_confidence_threshold": st.session_state.get('exit_confidence_threshold', 50),
4248
  "smart_trailing_stop_pct": st.session_state.get('smart_trailing_stop_pct', 5.0),
4249
  "smart_exit_atr_period": st.session_state.get('smart_exit_atr_period', 14),
@@ -4414,6 +4496,7 @@ def main():
4414
  "short_trailing_stop_loss_pct": st.session_state.get('short_sl', 8.0) / 100,
4415
  "short_delay_days": st.session_state.get('short_delay', 0),
4416
  "use_ma_floor_filter": st.session_state.get('use_ma_floor_filter', True),
 
4417
  "max_long_duration": st.session_state.get('max_long_duration', 60),
4418
  "max_short_duration": st.session_state.get('max_short_duration', 10)
4419
  }
@@ -4509,6 +4592,7 @@ def main():
4509
  smart_exit_atr_multiplier=smart_atr_m,
4510
  intelligent_tsl_pct=intelligent_tsl,
4511
  benchmark_rank=benchmark_percentile_setting / 100.0,
 
4512
  analysis_start_date=user_start_date,
4513
  analysis_end_date=end_date
4514
  )
@@ -4575,6 +4659,7 @@ def main():
4575
  smart_trailing_stop_pct=smart_trailing_stop, smart_exit_atr_period=smart_atr_p,
4576
  smart_exit_atr_multiplier=smart_atr_m, intelligent_tsl_pct=intelligent_tsl,
4577
  benchmark_rank=benchmark_percentile_setting / 100.0,
 
4578
  analysis_start_date=user_start_date,
4579
  analysis_end_date=end_date
4580
  )
 
62
  "norm_lookback_years": 1,
63
  "benchmark_rank": 99,
64
  "use_ma_floor_filter": True,
65
+ "catcher_stop_pct": 0.03,
66
  "use_vol": False, "vol_w": 0.5,
67
  "use_trend": False, "trend_w": 2.0,
68
  "use_volume": False, "volume_w": 0.5,
 
217
  "ADX Filter": "Off",
218
  "Conf. Threshold": 50,
219
  "Large MA Period": 50, "Bollinger Band Period": 20, "Bollinger Band Std Dev": 2.0,
220
+ "Catcher Offset (%)": 3.0,
221
  "Long Entry Threshold (%)": 0.0, "Long Exit Threshold (%)": 0.0, "Long Stop Loss (%)": 8.0, "Long Delay (Days)": 0,
222
  "Short Entry Threshold (%)": 0.0, "Short Exit Threshold (%)": 0.0, "Short Stop Loss (%)": 8.0, "Short Delay (Days)": 0,
223
  "Z_Avg_Profit": 0.0, "Z_Num_Trades": 0, "Z_WL_Ratio": 0.0
 
586
  smart_exit_atr_multiplier=3.0,
587
  intelligent_tsl_pct=1.0,
588
  analysis_start_date=None,
589
+ analysis_end_date=None,
590
+ benchmark_lookback_years=None):
591
 
592
  df = data.copy()
593
  required_cols = ['Close']
 
642
  else:
643
  df['RSI'], df['Volatility_p'], df['ADX'], df['ATR'] = np.nan, np.nan, np.nan, np.nan
644
 
645
+ # --- [NEW] MFI & SUPERTREND CALCULATIONS (Fixed) ---
646
  if len(df) >= 14:
647
  # 1. MFI Calculation
648
  try:
 
751
  )
752
 
753
  if long_score_95_percentile is None:
754
+ # If user specified a lookback (e.g. 1 year), use Rolling Window
755
+ if benchmark_lookback_years is not None and benchmark_lookback_years > 0:
756
+ window_days = int(benchmark_lookback_years * 252) # Approx trading days
757
+ # Calculate Rolling Percentile. 'min_periods' ensures we get data early in the chart.
758
+ long_95 = raw_long_score.rolling(window=window_days, min_periods=50).quantile(benchmark_rank).fillna(method='bfill')
759
+ else:
760
+ # Fallback to old "Global" method if no lookback specified
761
+ long_scores_gt_zero = raw_long_score[raw_long_score > 0]
762
+ val = long_scores_gt_zero.quantile(benchmark_rank) if not long_scores_gt_zero.empty else 1.0
763
+ long_95 = pd.Series(val, index=df.index)
764
  else:
765
+ long_95 = pd.Series(long_score_95_percentile, index=df.index)
766
 
767
  if short_score_95_percentile is None:
768
+ # Same logic for Short
769
+ if benchmark_lookback_years is not None and benchmark_lookback_years > 0:
770
+ window_days = int(benchmark_lookback_years * 252)
771
+ short_95 = raw_short_score.rolling(window=window_days, min_periods=50).quantile(benchmark_rank).fillna(method='bfill')
772
+ else:
773
+ short_scores_gt_zero = raw_short_score[raw_short_score > 0]
774
+ val = short_scores_gt_zero.quantile(benchmark_rank) if not short_scores_gt_zero.empty else 1.0
775
+ short_95 = pd.Series(val, index=df.index)
776
  else:
777
+ short_95 = pd.Series(short_score_95_percentile, index=df.index)
778
 
779
+ # Safety: Ensure we don't divide by zero
780
+ long_95 = long_95.replace(0, 1.0)
781
+ short_95 = short_95.replace(0, 1.0)
782
 
783
+ df['long_confidence_score'] = (raw_long_score / long_95 * 100).clip(0, 100).fillna(0.0)
784
+ df['short_confidence_score'] = (raw_short_score / short_95 * 100).clip(0, 100).fillna(0.0)
785
 
786
  apply_veto = bool(veto_setups_list)
787
  if apply_veto:
 
921
  df['long_entry_price_static'] = df['Close'].where(df['long_signal'].shift(1) == 0).ffill().bfill()
922
  df['short_entry_price_static'] = df['Close'].where(df['short_signal'].shift(1) == 0).ffill().bfill()
923
 
924
+ # [SURGICAL UPDATE START] --- Catcher Offset Logic ---
925
+ # We retrieve the offset percentage from params (default to 0.0 if missing)
926
+ catcher_offset = params.get('catcher_stop_pct', 0.0)
927
+
928
+ # Calculate the Adjusted Floor/Ceiling
929
+ # Positive Offset = Higher Floor (Profit for Long, Profit for Short)
930
+ # Negative Offset = Lower Floor (Loss allowance for Long, Loss allowance for Short)
931
+ long_breakeven_floor = df['long_entry_price_static'] * (1 + catcher_offset)
932
+ short_breakeven_floor = df['short_entry_price_static'] * (1 - catcher_offset)
933
+ # [SURGICAL UPDATE END] -----------------------------
934
+
935
  use_ma_floor_filter = params.get('use_ma_floor_filter', False)
936
 
937
  standard_tsl_pct_long = params.get('long_trailing_stop_loss_pct', 0)
 
1291
 
1292
  st.metric("Strategy Score", f"{display_score:.2f}%")
1293
  st.metric("Avg Profit per Trade (Active Tickers)", f"{avg_trade_profit:.2%}")
1294
+ net_return_pct = avg_trade_profit * total_trades * 100
1295
+ st.metric("Net % Return", f"{net_return_pct:.1f}%", help="The total 'Pile of Money'. Sum of all trade percentages.")
1296
 
1297
  st.text(f"Profitable Tickers: {good_tickers}")
1298
  st.text(f"Losing Tickers: {bad_tickers}")
 
1566
  "Ticker G/B Ratio": good_bad_ratio,
1567
  "Trade G/B Ratio": trade_good_bad_ratio,
1568
  "Total Trades": total_trades,
1569
+ "Net % Return": total_profit_weighted_avg * 100,
1570
  "Avg Entry Conf.": avg_entry_confidence,
1571
  "Winning Tickers": winning_tickers,
1572
  "Losing Tickers": losing_tickers,
 
2343
  except: long_d = 0
2344
  try: short_d = int(setup.get("Short Delay (Days)", 0))
2345
  except: short_d = 0
2346
+ catcher_pct = setup.get("Catcher Offset (%)", 3.0)
2347
+ try: catcher_decimal = float(catcher_pct) / 100.0
2348
+ except: catcher_decimal = 0.03
2349
+
2350
 
2351
  params_for_run = {
2352
  "large_ma_period": ma_p, "bband_period": bb_p, "long_delay_days": long_d, "short_delay_days": short_d,
2353
  "bband_std_dev": setup.get("Bollinger Band Std Dev", 2.0),
2354
  "confidence_threshold": setup.get("Conf. Threshold", 50),
2355
+ "catcher_stop_pct": catcher_decimal,
2356
  "long_entry_threshold_pct": setup.get("Long Entry Threshold (%)", 0.0) / 100.0,
2357
  "long_exit_ma_threshold_pct": setup.get("Long Exit Threshold (%)", 0.0) / 100.0,
2358
  "long_trailing_stop_loss_pct": setup.get("Long Stop Loss (%)", 0.0) / 100.0,
 
2694
 
2695
  # --- NEW: Load Max Duration ---
2696
  if 'max_trading_days' in params: st.session_state.max_duration = params['max_trading_days']
2697
+
2698
+ # --- NEW: Load Catcher Offset ---
2699
+ if 'catcher_stop_pct' in params: st.session_state.catcher_stop_pct = params['catcher_stop_pct'] * 100
2700
 
2701
  st.sidebar.success("Optimal parameters loaded into sidebar!")
2702
  st.rerun()
 
3155
 
3156
  # --- Display the table ---
3157
  display_df_conf = results_df.head(60)
3158
+
3159
+ # [UPDATED] Added "Net % Return" to the formatters
3160
+ conf_formatters = {
3161
+ "Avg Profit/Trade": "{:.2%}",
3162
+ "Ticker G/B Ratio": "{:.2f}",
3163
+ "Trade G/B Ratio": "{:.2f}",
3164
+ "Net % Return": "{:.1f}%",
3165
+ "Avg Entry Conf.": "{:.1f}%",
3166
+ "Good Score": "{:.4f}",
3167
+ "Bad Score": "{:.4f}",
3168
+ "Norm. Score %": "{:.2f}%"
3169
+ }
3170
  if 'MACD' in display_df_conf.columns: conf_formatters['MACD'] = '{}'
3171
  if 'MA Slope' in display_df_conf.columns: conf_formatters['MA Slope'] = '{}'
3172
  if 'Markov' in display_df_conf.columns: conf_formatters['Markov'] = '{}'
 
3213
  "Large MA Period": st.column_config.NumberColumn("MA", width="small"),
3214
  "Bollinger Band Period": st.column_config.NumberColumn("BB", width="small"),
3215
  "Bollinger Band Std Dev": st.column_config.NumberColumn("Std", format="%.1f", width="small"),
3216
+ "Catcher Offset (%)": st.column_config.NumberColumn("Catcher %", format="%.1f", width="small"),
3217
  "Long Entry Threshold (%)": st.column_config.NumberColumn("L Entry", format="%.1f", width="small"),
3218
  "Long Exit Threshold (%)": st.column_config.NumberColumn("L Exit", format="%.1f", width="small"),
3219
  "Long Stop Loss (%)": st.column_config.NumberColumn("L TSL", format="%.1f", width="small"),
 
3236
  "Run", "Notes", # <--- Notes column appears here (2nd)
3237
  "RSI", "Volatility", "TREND", "Volume", "MACD", "MA Slope", "Markov", "ADX Filter",
3238
  "Conf. Threshold",
3239
+ "Large MA Period", "Bollinger Band Period", "Bollinger Band Std Dev","Catcher Offset (%)",
3240
  "Long Entry Threshold (%)", "Long Exit Threshold (%)", "Long Stop Loss (%)", "Long Delay (Days)",
3241
  "Short Entry Threshold (%)", "Short Exit Threshold (%)", "Short Stop Loss (%)", "Short Delay (Days)",
3242
  "Z_Avg_Profit", "Z_Num_Trades", "Z_WL_Ratio"
 
3430
  'smart_exit_atr_period': 14, 'smart_exit_atr_multiplier': 3.0,
3431
  'intelligent_tsl_pct': 1.0,
3432
  'use_ma_floor_filter': False,
3433
+ 'catcher_stop_pct': 0.03,
3434
  'long_delay_days': 0, 'short_delay_days': 0,
3435
  'long_score_95_percentile': None, 'short_score_95_percentile': None,
3436
  'veto_setup_list': None,
 
3507
  st.session_state.smart_exit_atr_period = defaults.get("smart_exit_atr_period", 14)
3508
  st.session_state.smart_exit_atr_multiplier = defaults.get("smart_exit_atr_multiplier", 3.0)
3509
  st.session_state.intelligent_tsl_pct = defaults.get("intelligent_tsl_pct", 0.60) * 100
3510
+ st.session_state.catcher_stop_pct = defaults.get("catcher_stop_pct", 0.0) * 100
3511
+
3512
  # Section 3 Defaults
3513
  st.session_state.ma_period = defaults.get("large_ma_period", 50)
3514
  st.session_state.bb_period = defaults.get("bband_period", 20)
 
3986
  st.sidebar.number_input("MACD Weight", 0.1, 5.0, step=0.1, key='widget_macd_w', on_change=update_state, disabled=not st.session_state.get('use_macd', True))
3987
 
3988
  # Confidence Slider
3989
+ # [FIX] Force the widget key to match the stored variable BEFORE creating the widget
3990
+ st.session_state['widget_confidence_slider'] = st.session_state.confidence_slider
3991
+
3992
+ # [FIX] Remove 'value=' argument. Streamlit will use the key we just populated.
3993
+ st.sidebar.slider(
3994
+ "Minimum Confidence Threshold (%)",
3995
+ 0, 100,
3996
+ step=5,
3997
+ key='widget_confidence_slider',
3998
+ on_change=update_state,
3999
+ help="Acts as a quality filter. Higher values increase trade quality but reduce the number of trades found."
4000
+ )
4001
 
4002
  else:
4003
  # --- DEVELOPER VIEW (Original) ---
 
4072
  if 'widget_supertrend_w' not in st.session_state: st.session_state.widget_supertrend_w = st.session_state.supertrend_w
4073
  st.sidebar.number_input("SuperTrend Weight", 0.1, 5.0, step=0.1, key='widget_supertrend_w', on_change=update_state, disabled=not st.session_state.get('use_supertrend', True))
4074
 
4075
+ # [FIX] Force the widget key to match the stored variable
4076
+ st.session_state['widget_confidence_slider'] = st.session_state.confidence_slider
4077
+
4078
+ # [FIX] Remove 'value=' argument.
4079
+ st.sidebar.slider(
4080
+ "Minimum Confidence Threshold (%)",
4081
+ 0, 100,
4082
+ step=5,
4083
+ key='widget_confidence_slider',
4084
+ on_change=update_state
4085
+ )
4086
  st.sidebar.markdown("---")
4087
 
4088
  st.sidebar.header("3. Strategy Parameters")
 
4137
 
4138
  st.sidebar.toggle("Apply MA/BB Price Lock Floor (Catcher)", value=st.session_state.use_ma_floor_filter, key='widget_use_ma_floor_filter', on_change=update_state,
4139
  help="If ON, the Trailing Stop Loss is prevented from dropping below the Mean Reversion price.")
4140
+ # [NEW SLIDER] Catcher Offset
4141
+ # Only visible in Developer Mode. Disabled if Catcher is OFF.
4142
+ st.sidebar.slider(
4143
+ "Catcher Offset (%)",
4144
+ min_value=-10.0, max_value=10.0, value=st.session_state.catcher_stop_pct, step=0.1,
4145
+ key='widget_catcher_stop_pct', on_change=update_state,
4146
+ format="%.1f%%",
4147
+ disabled=not st.session_state.use_ma_floor_filter,
4148
+ help="Adjusts the hard floor level relative to Entry Price.\nPositive = Secure Profit (e.g. +1% Floor).\nNegative = Allow wiggle room (e.g. -1% Floor)."
4149
+ )
4150
+
4151
  st.sidebar.markdown("---")
4152
 
4153
  st.sidebar.subheader("Short Trade Logic")
 
4324
  "rsi_logic": st.session_state.get('rsi_logic', 'Crossover'),
4325
  "primary_driver": st.session_state.get('primary_driver', 'Bollinger Bands'),
4326
  "exit_logic_type": st.session_state.get('exit_logic_type', 'Standard (Price-Based)'),
4327
+ "use_ma_floor_filter": st.session_state.use_ma_floor_filter,
4328
+ "catcher_stop_pct": st.session_state.catcher_stop_pct / 100.0,
4329
  "exit_confidence_threshold": st.session_state.get('exit_confidence_threshold', 50),
4330
  "smart_trailing_stop_pct": st.session_state.get('smart_trailing_stop_pct', 5.0),
4331
  "smart_exit_atr_period": st.session_state.get('smart_exit_atr_period', 14),
 
4496
  "short_trailing_stop_loss_pct": st.session_state.get('short_sl', 8.0) / 100,
4497
  "short_delay_days": st.session_state.get('short_delay', 0),
4498
  "use_ma_floor_filter": st.session_state.get('use_ma_floor_filter', True),
4499
+ "catcher_stop_pct": st.session_state.get('catcher_stop_pct', 0.0) / 100.0,
4500
  "max_long_duration": st.session_state.get('max_long_duration', 60),
4501
  "max_short_duration": st.session_state.get('max_short_duration', 10)
4502
  }
 
4592
  smart_exit_atr_multiplier=smart_atr_m,
4593
  intelligent_tsl_pct=intelligent_tsl,
4594
  benchmark_rank=benchmark_percentile_setting / 100.0,
4595
+ benchmark_lookback_years=norm_lookback_years, # <--- NEW PASSED VALUE
4596
  analysis_start_date=user_start_date,
4597
  analysis_end_date=end_date
4598
  )
 
4659
  smart_trailing_stop_pct=smart_trailing_stop, smart_exit_atr_period=smart_atr_p,
4660
  smart_exit_atr_multiplier=smart_atr_m, intelligent_tsl_pct=intelligent_tsl,
4661
  benchmark_rank=benchmark_percentile_setting / 100.0,
4662
+ benchmark_lookback_years=norm_lookback_years, # <--- NEW PASSED VALUE
4663
  analysis_start_date=user_start_date,
4664
  analysis_end_date=end_date
4665
  )