Spaces:
Running
Running
Upload app.py
Browse files
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 |
-
|
| 753 |
-
|
| 754 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
else:
|
| 756 |
-
long_95 = long_score_95_percentile
|
| 757 |
|
| 758 |
if short_score_95_percentile is None:
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
| 767 |
-
df['short_confidence_score'] = (raw_short_score / short_95 * 100).clip(0, 100)
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 3939 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 4015 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
)
|