Spaces:
Running
Running
Upload 2 files
Browse files- app.py +48 -25
- config.json +2 -0
app.py
CHANGED
|
@@ -60,6 +60,7 @@ def load_settings():
|
|
| 60 |
"smart_exit_atr_multiplier": 3.0,
|
| 61 |
"intelligent_tsl_pct": 0.2,
|
| 62 |
"norm_lookback_years": 1,
|
|
|
|
| 63 |
"benchmark_rank": 99,
|
| 64 |
"use_ma_floor_filter": True,
|
| 65 |
"catcher_stop_pct": 0.03,
|
|
@@ -587,7 +588,8 @@ def run_backtest(data, params,
|
|
| 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,7 +644,7 @@ def run_backtest(data, params,
|
|
| 642 |
else:
|
| 643 |
df['RSI'], df['Volatility_p'], df['ADX'], df['ATR'] = np.nan, np.nan, np.nan, np.nan
|
| 644 |
|
| 645 |
-
|
| 646 |
if len(df) >= 14:
|
| 647 |
# 1. MFI Calculation
|
| 648 |
try:
|
|
@@ -750,36 +752,47 @@ def run_backtest(data, params,
|
|
| 750 |
best_markov_setup=markov_setup
|
| 751 |
)
|
| 752 |
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|
|
@@ -3728,6 +3741,13 @@ def main():
|
|
| 3728 |
"• **1-2 Years:** Adaptive. Judges trades only against recent market volatility (good for changing regimes)."
|
| 3729 |
"• **Default is set to 1 year as we are usually most interested in the recent year.")
|
| 3730 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3731 |
|
| 3732 |
# 2. Grade Benchmark - HIDDEN IN PRODUCTION MODE
|
| 3733 |
if not production_mode:
|
|
@@ -4332,6 +4352,7 @@ def main():
|
|
| 4332 |
"smart_exit_atr_multiplier": st.session_state.get('smart_exit_atr_multiplier', 3.0),
|
| 4333 |
"intelligent_tsl_pct": st.session_state.intelligent_tsl_pct / 100.0,
|
| 4334 |
"norm_lookback_years": norm_lookback_years,
|
|
|
|
| 4335 |
"benchmark_rank": benchmark_percentile_setting,
|
| 4336 |
"use_vol": st.session_state.use_vol, "vol_w": st.session_state.vol_w,
|
| 4337 |
"use_trend": st.session_state.use_trend, "trend_w": st.session_state.trend_w,
|
|
@@ -4592,9 +4613,10 @@ def main():
|
|
| 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 |
)
|
| 4599 |
st.session_state.single_ticker_results = {"long_pnl": long_pnl, "short_pnl": short_pnl, "avg_long_trade": avg_long_trade, "avg_short_trade": avg_short_trade, "results_df": results_df, "trades": trades}
|
| 4600 |
st.session_state.open_trades_df = pd.DataFrame(open_trades) if open_trades else pd.DataFrame()
|
|
@@ -4659,9 +4681,10 @@ def main():
|
|
| 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 |
)
|
| 4666 |
|
| 4667 |
if abs(long_pnl) > PROFIT_THRESHOLD or abs(short_pnl) > PROFIT_THRESHOLD or \
|
|
|
|
| 60 |
"smart_exit_atr_multiplier": 3.0,
|
| 61 |
"intelligent_tsl_pct": 0.2,
|
| 62 |
"norm_lookback_years": 1,
|
| 63 |
+
'use_rolling_benchmark': False,
|
| 64 |
"benchmark_rank": 99,
|
| 65 |
"use_ma_floor_filter": True,
|
| 66 |
"catcher_stop_pct": 0.03,
|
|
|
|
| 588 |
intelligent_tsl_pct=1.0,
|
| 589 |
analysis_start_date=None,
|
| 590 |
analysis_end_date=None,
|
| 591 |
+
benchmark_lookback_years=None,
|
| 592 |
+
use_rolling_benchmark=True):
|
| 593 |
|
| 594 |
df = data.copy()
|
| 595 |
required_cols = ['Close']
|
|
|
|
| 644 |
else:
|
| 645 |
df['RSI'], df['Volatility_p'], df['ADX'], df['ATR'] = np.nan, np.nan, np.nan, np.nan
|
| 646 |
|
| 647 |
+
# --- [NEW] MFI & SUPERTREND CALCULATIONS (Fixed) ---
|
| 648 |
if len(df) >= 14:
|
| 649 |
# 1. MFI Calculation
|
| 650 |
try:
|
|
|
|
| 752 |
best_markov_setup=markov_setup
|
| 753 |
)
|
| 754 |
|
| 755 |
+
# 1. GLOBAL (STATIC) MODE - "The Crystal Ball"
|
| 756 |
+
# Used for "Elite Filtering" of open trades.
|
| 757 |
+
if not use_rolling_benchmark:
|
| 758 |
+
if long_score_95_percentile is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 759 |
long_scores_gt_zero = raw_long_score[raw_long_score > 0]
|
| 760 |
val = long_scores_gt_zero.quantile(benchmark_rank) if not long_scores_gt_zero.empty else 1.0
|
| 761 |
long_95 = pd.Series(val, index=df.index)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
else:
|
| 763 |
+
long_95 = pd.Series(long_score_95_percentile, index=df.index)
|
| 764 |
+
|
| 765 |
+
if short_score_95_percentile is None:
|
| 766 |
short_scores_gt_zero = raw_short_score[raw_short_score > 0]
|
| 767 |
val = short_scores_gt_zero.quantile(benchmark_rank) if not short_scores_gt_zero.empty else 1.0
|
| 768 |
+
short_95 = pd.Series(val, index=df.index)
|
| 769 |
+
else:
|
| 770 |
+
short_95 = pd.Series(short_score_95_percentile, index=df.index)
|
| 771 |
+
|
| 772 |
+
# 2. ADAPTIVE (ROLLING) MODE - "Real World"
|
| 773 |
+
# Used for realistic historical simulation.
|
| 774 |
else:
|
| 775 |
+
if long_score_95_percentile is None:
|
| 776 |
+
# Default to 1 year if lookback is missing
|
| 777 |
+
years = benchmark_lookback_years if (benchmark_lookback_years is not None and benchmark_lookback_years > 0) else 1
|
| 778 |
+
window_days = int(years * 252)
|
| 779 |
+
# Calculate Rolling Percentile
|
| 780 |
+
long_95 = raw_long_score.rolling(window=window_days, min_periods=50).quantile(benchmark_rank).fillna(method='bfill')
|
| 781 |
+
else:
|
| 782 |
+
long_95 = pd.Series(long_score_95_percentile, index=df.index)
|
| 783 |
+
|
| 784 |
+
if short_score_95_percentile is None:
|
| 785 |
+
years = benchmark_lookback_years if (benchmark_lookback_years is not None and benchmark_lookback_years > 0) else 1
|
| 786 |
+
window_days = int(years * 252)
|
| 787 |
+
short_95 = raw_short_score.rolling(window=window_days, min_periods=50).quantile(benchmark_rank).fillna(method='bfill')
|
| 788 |
+
else:
|
| 789 |
+
short_95 = pd.Series(short_score_95_percentile, index=df.index)
|
| 790 |
|
| 791 |
# Safety: Ensure we don't divide by zero
|
| 792 |
long_95 = long_95.replace(0, 1.0)
|
| 793 |
short_95 = short_95.replace(0, 1.0)
|
| 794 |
+
|
| 795 |
+
# Final Calculation (Removing 'if' check to avoid Series Ambiguity Error)
|
| 796 |
df['long_confidence_score'] = (raw_long_score / long_95 * 100).clip(0, 100).fillna(0.0)
|
| 797 |
df['short_confidence_score'] = (raw_short_score / short_95 * 100).clip(0, 100).fillna(0.0)
|
| 798 |
|
|
|
|
| 3741 |
"• **1-2 Years:** Adaptive. Judges trades only against recent market volatility (good for changing regimes)."
|
| 3742 |
"• **Default is set to 1 year as we are usually most interested in the recent year.")
|
| 3743 |
)
|
| 3744 |
+
use_rolling_benchmark = st.sidebar.checkbox(
|
| 3745 |
+
"Use Rolling Benchmark (Adaptive)",
|
| 3746 |
+
value=st.session_state.get('use_rolling_benchmark', True),
|
| 3747 |
+
key='widget_use_rolling_benchmark',
|
| 3748 |
+
on_change=update_state,
|
| 3749 |
+
help="CHECKED: Adaptive Mode. Benchmarks adapt to recent history. Realistic backtest.\nUNCHECKED: Global Mode (Crystal Ball). Uses ALL history to set one strict benchmark. Good for filtering Open Trades."
|
| 3750 |
+
)
|
| 3751 |
|
| 3752 |
# 2. Grade Benchmark - HIDDEN IN PRODUCTION MODE
|
| 3753 |
if not production_mode:
|
|
|
|
| 4352 |
"smart_exit_atr_multiplier": st.session_state.get('smart_exit_atr_multiplier', 3.0),
|
| 4353 |
"intelligent_tsl_pct": st.session_state.intelligent_tsl_pct / 100.0,
|
| 4354 |
"norm_lookback_years": norm_lookback_years,
|
| 4355 |
+
"use_rolling_benchmark": use_rolling_benchmark,
|
| 4356 |
"benchmark_rank": benchmark_percentile_setting,
|
| 4357 |
"use_vol": st.session_state.use_vol, "vol_w": st.session_state.vol_w,
|
| 4358 |
"use_trend": st.session_state.use_trend, "trend_w": st.session_state.trend_w,
|
|
|
|
| 4613 |
smart_exit_atr_multiplier=smart_atr_m,
|
| 4614 |
intelligent_tsl_pct=intelligent_tsl,
|
| 4615 |
benchmark_rank=benchmark_percentile_setting / 100.0,
|
|
|
|
| 4616 |
analysis_start_date=user_start_date,
|
| 4617 |
+
analysis_end_date=end_date,
|
| 4618 |
+
benchmark_lookback_years=norm_lookback_years,
|
| 4619 |
+
use_rolling_benchmark=use_rolling_benchmark
|
| 4620 |
)
|
| 4621 |
st.session_state.single_ticker_results = {"long_pnl": long_pnl, "short_pnl": short_pnl, "avg_long_trade": avg_long_trade, "avg_short_trade": avg_short_trade, "results_df": results_df, "trades": trades}
|
| 4622 |
st.session_state.open_trades_df = pd.DataFrame(open_trades) if open_trades else pd.DataFrame()
|
|
|
|
| 4681 |
smart_trailing_stop_pct=smart_trailing_stop, smart_exit_atr_period=smart_atr_p,
|
| 4682 |
smart_exit_atr_multiplier=smart_atr_m, intelligent_tsl_pct=intelligent_tsl,
|
| 4683 |
benchmark_rank=benchmark_percentile_setting / 100.0,
|
|
|
|
| 4684 |
analysis_start_date=user_start_date,
|
| 4685 |
+
analysis_end_date=end_date,
|
| 4686 |
+
benchmark_lookback_years=norm_lookback_years,
|
| 4687 |
+
use_rolling_benchmark=use_rolling_benchmark
|
| 4688 |
)
|
| 4689 |
|
| 4690 |
if abs(long_pnl) > PROFIT_THRESHOLD or abs(short_pnl) > PROFIT_THRESHOLD or \
|
config.json
CHANGED
|
@@ -17,12 +17,14 @@
|
|
| 17 |
"primary_driver": "Bollinger Bands",
|
| 18 |
"exit_logic_type": "Intelligent (ADX/MACD/ATR)",
|
| 19 |
"use_ma_floor_filter": true,
|
|
|
|
| 20 |
"exit_confidence_threshold": 40,
|
| 21 |
"smart_trailing_stop_pct": 5.0,
|
| 22 |
"smart_exit_atr_period": 14,
|
| 23 |
"smart_exit_atr_multiplier": 3.0,
|
| 24 |
"intelligent_tsl_pct": 0.2,
|
| 25 |
"norm_lookback_years": 1,
|
|
|
|
| 26 |
"benchmark_rank": 99,
|
| 27 |
"use_vol": false,
|
| 28 |
"vol_w": 0.5,
|
|
|
|
| 17 |
"primary_driver": "Bollinger Bands",
|
| 18 |
"exit_logic_type": "Intelligent (ADX/MACD/ATR)",
|
| 19 |
"use_ma_floor_filter": true,
|
| 20 |
+
"catcher_stop_pct": 0.03,
|
| 21 |
"exit_confidence_threshold": 40,
|
| 22 |
"smart_trailing_stop_pct": 5.0,
|
| 23 |
"smart_exit_atr_period": 14,
|
| 24 |
"smart_exit_atr_multiplier": 3.0,
|
| 25 |
"intelligent_tsl_pct": 0.2,
|
| 26 |
"norm_lookback_years": 1,
|
| 27 |
+
"use_rolling_benchmark": false,
|
| 28 |
"benchmark_rank": 99,
|
| 29 |
"use_vol": false,
|
| 30 |
"vol_w": 0.5,
|