Spaces:
Running
Running
Upload app.py
Browse files
app.py
CHANGED
|
@@ -10,7 +10,8 @@ import json
|
|
| 10 |
# --- CORRECTED IMPORTS: Moved MACD ---
|
| 11 |
from ta.volatility import BollingerBands
|
| 12 |
from ta.momentum import RSIIndicator # Removed MACD from here
|
| 13 |
-
from ta.trend import ADXIndicator, MACD
|
|
|
|
| 14 |
# --- [NEW] ADDED ATR FOR INTELLIGENT EXIT ---
|
| 15 |
from ta.volatility import AverageTrueRange
|
| 16 |
# --- [END NEW] ---
|
|
@@ -473,10 +474,12 @@ def clean_data_and_report_outliers(df):
|
|
| 473 |
|
| 474 |
# --- 2. Custom Backtesting Engine ---
|
| 475 |
|
| 476 |
-
# --- [
|
| 477 |
-
def calculate_confidence_score(df, primary_driver,
|
| 478 |
-
use_rsi, use_volatility, use_trend, use_volume, use_macd, use_ma_slope, use_markov,
|
| 479 |
-
|
|
|
|
|
|
|
| 480 |
bband_params,
|
| 481 |
best_markov_setup=None):
|
| 482 |
|
|
@@ -489,92 +492,83 @@ def calculate_confidence_score(df, primary_driver, # <-- This exists
|
|
| 489 |
df['RunUp_Return'] = df['Close'].pct_change(periods=run_up_period)
|
| 490 |
df['RunUp_State'] = df['RunUp_Return'].apply(lambda x: 'Up' if x > 0 else 'Down')
|
| 491 |
|
| 492 |
-
# --- BBand Factor
|
| 493 |
if primary_driver != 'Bollinger Bands' and 'bband_lower' in df.columns:
|
| 494 |
-
bb_weight = 1.0
|
| 495 |
long_entry_pct = bband_params.get('long_entry_threshold_pct', 0.0)
|
| 496 |
short_entry_pct = bband_params.get('short_entry_threshold_pct', 0.0)
|
| 497 |
long_bb_trigger_price = df['bband_lower'] * (1 - long_entry_pct)
|
| 498 |
short_bb_trigger_price = df['bband_upper'] * (1 + short_entry_pct)
|
| 499 |
long_score_range = (df['large_ma'] - long_bb_trigger_price).replace(0, np.nan)
|
| 500 |
short_score_range = (short_bb_trigger_price - df['large_ma']).replace(0, np.nan)
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
long_score += long_score_pct * bb_weight
|
| 504 |
-
short_score += short_score_pct * bb_weight
|
| 505 |
-
|
| 506 |
-
# --- [RESTORED] Gradient Factor Scoring ---
|
| 507 |
|
| 508 |
# Factor 1: RSI
|
| 509 |
-
if primary_driver != 'RSI Crossover' and use_rsi and 'RSI' in df.columns
|
| 510 |
long_score += ((30 - df['RSI']) / 30).clip(0, 1).fillna(0) * rsi_w
|
| 511 |
short_score += ((df['RSI'] - 70) / 30).clip(0, 1).fillna(0) * rsi_w
|
| 512 |
|
| 513 |
# Factor 2: Volatility
|
| 514 |
-
if use_volatility and 'Volatility_p' in df.columns
|
| 515 |
-
# This one is binary
|
| 516 |
vol_signal = (df['Volatility_p'] > 0.025).astype(float) * vol_w
|
| 517 |
-
long_score += vol_signal
|
| 518 |
-
short_score += vol_signal
|
| 519 |
|
| 520 |
-
# Factor 3: Trend
|
| 521 |
-
if use_trend and 'SMA_200' in df.columns and 'Close' in df.columns
|
| 522 |
-
|
| 523 |
-
valid_sma = (df['SMA_200'] != 0) & df['SMA_200'].notna() & df['Close'].notna()
|
| 524 |
pct_dist = pd.Series(0.0, index=df.index)
|
| 525 |
pct_dist.loc[valid_sma] = df.loc[valid_sma].apply(lambda row: (row['Close'] - row['SMA_200']) / row['SMA_200'] if row['SMA_200'] != 0 else 0, axis=1)
|
| 526 |
long_score += (pct_dist / 0.10).clip(0, 1).fillna(0) * trend_w
|
| 527 |
short_score += (-pct_dist / 0.10).clip(0, 1).fillna(0) * trend_w
|
| 528 |
|
| 529 |
# Factor 4: Volume
|
| 530 |
-
if use_volume and 'Volume_Ratio' in df.columns
|
| 531 |
-
# This one is a gradient
|
| 532 |
vol_spike_signal = ((df['Volume_Ratio'] - 1.75) / 2.25).clip(0, 1).fillna(0) * vol_w_val
|
| 533 |
-
long_score += vol_spike_signal
|
| 534 |
-
short_score += vol_spike_signal
|
| 535 |
|
| 536 |
-
# Factor 5: MACD
|
| 537 |
-
if primary_driver != 'MACD Crossover' and use_macd and 'MACD_line' in df.columns
|
| 538 |
-
and not df['MACD_line'].isna().all() and not df['MACD_signal'].isna().all() and not df['MACD_hist'].isna().all():
|
| 539 |
-
# This is a hybrid gradient/binary
|
| 540 |
macd_cross_long = (df['MACD_line'].shift(1) < df['MACD_signal'].shift(1)) & (df['MACD_line'] >= df['MACD_signal'])
|
| 541 |
macd_cross_short = (df['MACD_line'].shift(1) > df['MACD_signal'].shift(1)) & (df['MACD_line'] <= df['MACD_signal'])
|
| 542 |
long_score += macd_cross_long.astype(float) * macd_w * 0.6
|
| 543 |
short_score += macd_cross_short.astype(float) * macd_w * 0.6
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
long_score += hist_positive.astype(float) * macd_w * 0.4
|
| 547 |
-
short_score += hist_negative.astype(float) * macd_w * 0.4
|
| 548 |
|
| 549 |
# Factor 6: MA Slope
|
| 550 |
-
if primary_driver != 'MA Slope' and use_ma_slope and 'ma_slope' in df.columns
|
| 551 |
-
# This is binary
|
| 552 |
long_score += (df['ma_slope'] > 0).astype(float) * ma_slope_w
|
| 553 |
short_score += (df['ma_slope'] < 0).astype(float) * ma_slope_w
|
| 554 |
|
| 555 |
# Factor 7: Markov State
|
| 556 |
if primary_driver != 'Markov State' and use_markov and best_markov_setup and 'RunUp_State' in df.columns:
|
| 557 |
strategy = best_markov_setup.get('Strategy')
|
| 558 |
-
if strategy == 'Down -> Up':
|
| 559 |
-
|
| 560 |
-
elif strategy == 'Up -> Up'
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
|
| 568 |
-
# --- [REMOVED NORMALIZATION] ---
|
| 569 |
-
# Return the raw, un-normalized scores
|
| 570 |
return long_score.fillna(0), short_score.fillna(0)
|
| 571 |
|
| 572 |
-
# --- FINAL VERSION: Merged Intelligent Exit + All Missing Features ---
|
| 573 |
-
|
| 574 |
# --- FULL REPLACEMENT FOR run_backtest FUNCTION ---
|
| 575 |
def run_backtest(data, params,
|
| 576 |
-
use_rsi, use_volatility, use_trend, use_volume, use_macd, use_ma_slope, use_markov,
|
| 577 |
-
rsi_w, vol_w, trend_w, vol_w_val, macd_w, ma_slope_w, markov_w,
|
| 578 |
use_adx_filter, adx_threshold, rsi_logic,
|
| 579 |
adx_period=14,
|
| 580 |
veto_setups_list=None,
|
|
@@ -643,6 +637,78 @@ def run_backtest(data, params,
|
|
| 643 |
else:
|
| 644 |
df['RSI'], df['Volatility_p'], df['ADX'], df['ATR'] = np.nan, np.nan, np.nan, np.nan
|
| 645 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
df['SMA_200'] = df['Close'].rolling(window=200, min_periods=1).mean()
|
| 647 |
|
| 648 |
if 'Volume' in df.columns:
|
|
@@ -672,7 +738,9 @@ def run_backtest(data, params,
|
|
| 672 |
raw_long_score, raw_short_score = calculate_confidence_score(df,
|
| 673 |
primary_driver,
|
| 674 |
use_rsi, use_volatility, use_trend, use_volume, use_macd, use_ma_slope, use_markov,
|
|
|
|
| 675 |
rsi_w, vol_w, trend_w, vol_w_val, macd_w, ma_slope_w, markov_w,
|
|
|
|
| 676 |
bband_params_for_score,
|
| 677 |
best_markov_setup=markov_setup
|
| 678 |
)
|
|
@@ -759,7 +827,11 @@ def run_backtest(data, params,
|
|
| 759 |
|
| 760 |
# [SURGICAL PATCH START] --------------------------------------------------
|
| 761 |
# Check if ALL confidence indicators are disabled
|
| 762 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
|
| 764 |
# If indicators are OFF, we bypass the confidence check (allow raw signal)
|
| 765 |
# Otherwise, we enforce the confidence threshold as usual
|
|
@@ -1019,18 +1091,34 @@ def generate_long_plot(df, trades, ticker):
|
|
| 1019 |
fig.add_trace(go.Scatter(x=df.index, y=df['bband_upper'], mode='lines', name='Upper Band', line=dict(color='gray', width=0.5)))
|
| 1020 |
fig.add_trace(go.Scatter(x=df.index, y=df['bband_lower'], mode='lines', name='Lower Band', line=dict(color='gray', width=0.5), fill='tonexty', fillcolor='rgba(211,211,211,0.2)'))
|
| 1021 |
|
| 1022 |
-
#
|
| 1023 |
long_entries_log, long_exits, _, _ = trades
|
|
|
|
|
|
|
| 1024 |
if long_entries_log:
|
| 1025 |
dates = [t['date'] for t in long_entries_log]
|
| 1026 |
prices = [t['price'] for t in long_entries_log]
|
| 1027 |
scores = [f"Confidence: {t['confidence']:.0f}%" for t in long_entries_log]
|
| 1028 |
-
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1034 |
|
| 1035 |
def generate_short_plot(df, trades, ticker):
|
| 1036 |
fig = go.Figure()
|
|
@@ -3269,11 +3357,14 @@ def main():
|
|
| 3269 |
for key, val in defaults.items():
|
| 3270 |
if key not in st.session_state: st.session_state[key] = val
|
| 3271 |
|
| 3272 |
-
# 4. Helper to update state from widgets
|
| 3273 |
def update_state():
|
| 3274 |
-
|
| 3275 |
-
|
| 3276 |
-
if widget_key
|
|
|
|
|
|
|
|
|
|
| 3277 |
|
| 3278 |
st.set_page_config(page_title="Dave's Quant System", page_icon="🔴", layout="wide")
|
| 3279 |
|
|
@@ -3470,13 +3561,20 @@ def main():
|
|
| 3470 |
st.sidebar.radio("Mode:", ("Analyse Single Ticker", full_list_label), key='run_mode')
|
| 3471 |
|
| 3472 |
if st.session_state.get('run_mode') == "Analyse Single Ticker":
|
| 3473 |
-
|
| 3474 |
-
|
| 3475 |
-
|
| 3476 |
-
|
| 3477 |
-
|
| 3478 |
-
|
| 3479 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3480 |
|
| 3481 |
# --- UPDATED: Date Inputs (Fully Unrestricted 2000-2030) ---
|
| 3482 |
st.sidebar.date_input(
|
|
@@ -3813,9 +3911,22 @@ def main():
|
|
| 3813 |
if 'widget_markov_w' not in st.session_state: st.session_state.widget_markov_w = st.session_state.markov_w
|
| 3814 |
st.sidebar.number_input("Markov Weight", 0.1, 5.0, step=0.1, key='widget_markov_w', on_change=update_state, disabled=not st.session_state.get('use_markov', True))
|
| 3815 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3816 |
if 'widget_confidence_slider' not in st.session_state: st.session_state.widget_confidence_slider = st.session_state.confidence_slider
|
| 3817 |
st.sidebar.slider("Minimum Confidence Threshold (%)", 0, 100, step=5, key='widget_confidence_slider', on_change=update_state)
|
| 3818 |
-
|
| 3819 |
st.sidebar.markdown("---")
|
| 3820 |
|
| 3821 |
st.sidebar.header("3. Strategy Parameters")
|
|
@@ -4243,38 +4354,61 @@ def main():
|
|
| 4243 |
|
| 4244 |
# A) Single Ticker Analysis
|
| 4245 |
if st.session_state.run_mode == "Analyse Single Ticker":
|
|
|
|
| 4246 |
selected_ticker = st.session_state.get('ticker_select', ticker_list[0] if ticker_list else None)
|
| 4247 |
-
if not selected_ticker:
|
| 4248 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4249 |
cols_to_use = [selected_ticker]
|
| 4250 |
if f'{selected_ticker}_High' in master_df.columns: cols_to_use.append(f'{selected_ticker}_High')
|
| 4251 |
if f'{selected_ticker}_Low' in master_df.columns: cols_to_use.append(f'{selected_ticker}_Low')
|
| 4252 |
if f'{selected_ticker}_Volume' in master_df.columns: cols_to_use.append(f'{selected_ticker}_Volume')
|
|
|
|
| 4253 |
existing_cols = [col for col in cols_to_use if col in master_df.columns]
|
| 4254 |
-
|
| 4255 |
-
|
| 4256 |
-
|
| 4257 |
-
|
| 4258 |
-
|
|
|
|
|
|
|
| 4259 |
rename_dict = {selected_ticker: 'Close', f'{selected_ticker}_High': 'High', f'{selected_ticker}_Low': 'Low', f'{selected_ticker}_Volume': 'Volume'}
|
| 4260 |
rename_dict_filtered = {k: v for k, v in rename_dict.items() if k in existing_cols}
|
| 4261 |
data_for_backtest = data_for_backtest.rename(columns=rename_dict_filtered)
|
| 4262 |
-
|
| 4263 |
if not data_for_backtest.empty and 'Close' in data_for_backtest.columns and not data_for_backtest['Close'].isna().all():
|
|
|
|
|
|
|
|
|
|
| 4264 |
long_pnl, short_pnl, avg_long_trade, avg_short_trade, results_df, trades, open_trades, trade_counts, durations, trade_dates, exit_breakdown = run_backtest(
|
| 4265 |
-
|
| 4266 |
-
|
| 4267 |
-
|
| 4268 |
-
|
| 4269 |
-
|
| 4270 |
-
|
| 4271 |
-
|
| 4272 |
-
|
| 4273 |
-
|
| 4274 |
-
|
| 4275 |
-
|
| 4276 |
-
|
| 4277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4278 |
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}
|
| 4279 |
st.session_state.open_trades_df = pd.DataFrame(open_trades) if open_trades else pd.DataFrame()
|
| 4280 |
st.session_state.exit_breakdown_totals = {'long_profit_take_count': exit_breakdown[0], 'long_tsl_count': exit_breakdown[1], 'long_time_exit_count': exit_breakdown[2], 'short_profit_take_count': exit_breakdown[3], 'short_tsl_count': exit_breakdown[4], 'short_time_exit_count': exit_breakdown[5]}
|
|
@@ -4306,18 +4440,20 @@ def main():
|
|
| 4306 |
ticker_data_series = ticker_data_series.rename(columns={k:v for k,v in rename_dict.items() if k in existing_cols})
|
| 4307 |
|
| 4308 |
if not ticker_data_series.empty and 'Close' in ticker_data_series.columns and not ticker_data_series['Close'].isna().all():
|
| 4309 |
-
long_pnl, short_pnl, avg_long_trade, avg_short_trade,
|
| 4310 |
-
ticker_data_series, manual_params,
|
| 4311 |
st.session_state.use_rsi, st.session_state.use_vol, st.session_state.use_trend, st.session_state.use_volume,
|
| 4312 |
-
st.session_state.use_macd, st.session_state.use_ma_slope, st.session_state.use_markov,
|
|
|
|
| 4313 |
st.session_state.rsi_w, st.session_state.vol_w, st.session_state.trend_w, st.session_state.volume_w,
|
| 4314 |
-
st.session_state.macd_w, st.session_state.ma_slope_w, st.session_state.markov_w,
|
|
|
|
| 4315 |
st.session_state.use_adx_filter, st.session_state.adx_threshold, st.session_state.get('rsi_logic', 'Crossover'), st.session_state.adx_period,
|
| 4316 |
veto_setups_list=veto_list_to_use, primary_driver=st.session_state.primary_driver,
|
| 4317 |
markov_setup=markov_setup_to_use, exit_logic_type=exit_logic, exit_confidence_threshold=exit_thresh,
|
| 4318 |
smart_trailing_stop_pct=smart_trailing_stop, smart_exit_atr_period=smart_atr_p,
|
| 4319 |
smart_exit_atr_multiplier=smart_atr_m, intelligent_tsl_pct=intelligent_tsl,
|
| 4320 |
-
analysis_start_date=user_start_date
|
| 4321 |
)
|
| 4322 |
|
| 4323 |
if abs(long_pnl) > PROFIT_THRESHOLD or abs(short_pnl) > PROFIT_THRESHOLD or \
|
|
@@ -4467,7 +4603,7 @@ def main():
|
|
| 4467 |
if st.button("💾 Add these settings to User-Defined List", key="save_setup_from_analysis", on_click=add_setup_to_user_list): pass
|
| 4468 |
|
| 4469 |
# 5. Open Positions Table (With Filter)
|
| 4470 |
-
st.subheader("
|
| 4471 |
help="This table displays all currently ACTIVE trades, plus any trades that closed within the last 30 days.")
|
| 4472 |
|
| 4473 |
if st.session_state.get('open_trades_df') is not None and not st.session_state.open_trades_df.empty:
|
|
|
|
| 10 |
# --- CORRECTED IMPORTS: Moved MACD ---
|
| 11 |
from ta.volatility import BollingerBands
|
| 12 |
from ta.momentum import RSIIndicator # Removed MACD from here
|
| 13 |
+
from ta.trend import ADXIndicator, MACD
|
| 14 |
+
from ta.volume import MFIIndicator # Added MFI here
|
| 15 |
# --- [NEW] ADDED ATR FOR INTELLIGENT EXIT ---
|
| 16 |
from ta.volatility import AverageTrueRange
|
| 17 |
# --- [END NEW] ---
|
|
|
|
| 474 |
|
| 475 |
# --- 2. Custom Backtesting Engine ---
|
| 476 |
|
| 477 |
+
# --- [UPDATED] 9-Factor Version (with MFI & SuperTrend) ---
|
| 478 |
+
def calculate_confidence_score(df, primary_driver,
|
| 479 |
+
use_rsi, use_volatility, use_trend, use_volume, use_macd, use_ma_slope, use_markov,
|
| 480 |
+
use_mfi, use_supertrend,
|
| 481 |
+
rsi_w, vol_w, trend_w, vol_w_val, macd_w, ma_slope_w, markov_w,
|
| 482 |
+
mfi_w, supertrend_w,
|
| 483 |
bband_params,
|
| 484 |
best_markov_setup=None):
|
| 485 |
|
|
|
|
| 492 |
df['RunUp_Return'] = df['Close'].pct_change(periods=run_up_period)
|
| 493 |
df['RunUp_State'] = df['RunUp_Return'].apply(lambda x: 'Up' if x > 0 else 'Down')
|
| 494 |
|
| 495 |
+
# --- BBand Factor ---
|
| 496 |
if primary_driver != 'Bollinger Bands' and 'bband_lower' in df.columns:
|
| 497 |
+
bb_weight = 1.0
|
| 498 |
long_entry_pct = bband_params.get('long_entry_threshold_pct', 0.0)
|
| 499 |
short_entry_pct = bband_params.get('short_entry_threshold_pct', 0.0)
|
| 500 |
long_bb_trigger_price = df['bband_lower'] * (1 - long_entry_pct)
|
| 501 |
short_bb_trigger_price = df['bband_upper'] * (1 + short_entry_pct)
|
| 502 |
long_score_range = (df['large_ma'] - long_bb_trigger_price).replace(0, np.nan)
|
| 503 |
short_score_range = (short_bb_trigger_price - df['large_ma']).replace(0, np.nan)
|
| 504 |
+
long_score += ((df['large_ma'] - df['Close']) / long_score_range).clip(0, 1).fillna(0) * bb_weight
|
| 505 |
+
short_score += ((df['Close'] - df['large_ma']) / short_score_range).clip(0, 1).fillna(0) * bb_weight
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
|
| 507 |
# Factor 1: RSI
|
| 508 |
+
if primary_driver != 'RSI Crossover' and use_rsi and 'RSI' in df.columns:
|
| 509 |
long_score += ((30 - df['RSI']) / 30).clip(0, 1).fillna(0) * rsi_w
|
| 510 |
short_score += ((df['RSI'] - 70) / 30).clip(0, 1).fillna(0) * rsi_w
|
| 511 |
|
| 512 |
# Factor 2: Volatility
|
| 513 |
+
if use_volatility and 'Volatility_p' in df.columns:
|
|
|
|
| 514 |
vol_signal = (df['Volatility_p'] > 0.025).astype(float) * vol_w
|
| 515 |
+
long_score += vol_signal; short_score += vol_signal
|
|
|
|
| 516 |
|
| 517 |
+
# Factor 3: Trend
|
| 518 |
+
if use_trend and 'SMA_200' in df.columns and 'Close' in df.columns:
|
| 519 |
+
valid_sma = (df['SMA_200'] != 0) & df['SMA_200'].notna()
|
|
|
|
| 520 |
pct_dist = pd.Series(0.0, index=df.index)
|
| 521 |
pct_dist.loc[valid_sma] = df.loc[valid_sma].apply(lambda row: (row['Close'] - row['SMA_200']) / row['SMA_200'] if row['SMA_200'] != 0 else 0, axis=1)
|
| 522 |
long_score += (pct_dist / 0.10).clip(0, 1).fillna(0) * trend_w
|
| 523 |
short_score += (-pct_dist / 0.10).clip(0, 1).fillna(0) * trend_w
|
| 524 |
|
| 525 |
# Factor 4: Volume
|
| 526 |
+
if use_volume and 'Volume_Ratio' in df.columns:
|
|
|
|
| 527 |
vol_spike_signal = ((df['Volume_Ratio'] - 1.75) / 2.25).clip(0, 1).fillna(0) * vol_w_val
|
| 528 |
+
long_score += vol_spike_signal; short_score += vol_spike_signal
|
|
|
|
| 529 |
|
| 530 |
+
# Factor 5: MACD
|
| 531 |
+
if primary_driver != 'MACD Crossover' and use_macd and 'MACD_line' in df.columns:
|
|
|
|
|
|
|
| 532 |
macd_cross_long = (df['MACD_line'].shift(1) < df['MACD_signal'].shift(1)) & (df['MACD_line'] >= df['MACD_signal'])
|
| 533 |
macd_cross_short = (df['MACD_line'].shift(1) > df['MACD_signal'].shift(1)) & (df['MACD_line'] <= df['MACD_signal'])
|
| 534 |
long_score += macd_cross_long.astype(float) * macd_w * 0.6
|
| 535 |
short_score += macd_cross_short.astype(float) * macd_w * 0.6
|
| 536 |
+
long_score += (df['MACD_hist'] > 0).astype(float) * macd_w * 0.4
|
| 537 |
+
short_score += (df['MACD_hist'] < 0).astype(float) * macd_w * 0.4
|
|
|
|
|
|
|
| 538 |
|
| 539 |
# Factor 6: MA Slope
|
| 540 |
+
if primary_driver != 'MA Slope' and use_ma_slope and 'ma_slope' in df.columns:
|
|
|
|
| 541 |
long_score += (df['ma_slope'] > 0).astype(float) * ma_slope_w
|
| 542 |
short_score += (df['ma_slope'] < 0).astype(float) * ma_slope_w
|
| 543 |
|
| 544 |
# Factor 7: Markov State
|
| 545 |
if primary_driver != 'Markov State' and use_markov and best_markov_setup and 'RunUp_State' in df.columns:
|
| 546 |
strategy = best_markov_setup.get('Strategy')
|
| 547 |
+
if strategy == 'Down -> Up': long_score += (df['RunUp_State'] == 'Down').astype(float) * markov_w
|
| 548 |
+
elif strategy == 'Up -> Up': long_score += (df['RunUp_State'] == 'Up').astype(float) * markov_w
|
| 549 |
+
elif strategy == 'Up -> Down': short_score += (df['RunUp_State'] == 'Up').astype(float) * markov_w
|
| 550 |
+
elif strategy == 'Down -> Down': short_score += (df['RunUp_State'] == 'Down').astype(float) * markov_w
|
| 551 |
+
|
| 552 |
+
# Factor 8: MFI (Money Flow Index) [IMPROVED]
|
| 553 |
+
if use_mfi and 'MFI' in df.columns:
|
| 554 |
+
# Binary Logic: If MFI < 20 (Oversold), give full weight.
|
| 555 |
+
# This fixes the "tiny score" issue.
|
| 556 |
+
long_score += (df['MFI'] < 25).astype(float) * mfi_w
|
| 557 |
+
short_score += (df['MFI'] > 75).astype(float) * mfi_w
|
| 558 |
+
|
| 559 |
+
# Factor 9: SuperTrend [IMPROVED]
|
| 560 |
+
if use_supertrend and 'SuperTrend' in df.columns:
|
| 561 |
+
# Long Score: Price > SuperTrend (Trend is Bullish)
|
| 562 |
+
long_score += (df['Close'] > df['SuperTrend']).astype(float) * supertrend_w
|
| 563 |
+
# Short Score: Price < SuperTrend (Trend is Bearish)
|
| 564 |
+
short_score += (df['Close'] < df['SuperTrend']).astype(float) * supertrend_w
|
| 565 |
|
|
|
|
|
|
|
| 566 |
return long_score.fillna(0), short_score.fillna(0)
|
| 567 |
|
|
|
|
|
|
|
| 568 |
# --- FULL REPLACEMENT FOR run_backtest FUNCTION ---
|
| 569 |
def run_backtest(data, params,
|
| 570 |
+
use_rsi, use_volatility, use_trend, use_volume, use_macd, use_ma_slope, use_markov, use_mfi, use_supertrend, # Added MFI/ST
|
| 571 |
+
rsi_w, vol_w, trend_w, vol_w_val, macd_w, ma_slope_w, markov_w, mfi_w, supertrend_w, # Added MFI/ST weights
|
| 572 |
use_adx_filter, adx_threshold, rsi_logic,
|
| 573 |
adx_period=14,
|
| 574 |
veto_setups_list=None,
|
|
|
|
| 637 |
else:
|
| 638 |
df['RSI'], df['Volatility_p'], df['ADX'], df['ATR'] = np.nan, np.nan, np.nan, np.nan
|
| 639 |
|
| 640 |
+
# --- [NEW] MFI & SUPERTREND CALCULATIONS (Fixed) ---
|
| 641 |
+
if len(df) >= 14:
|
| 642 |
+
# 1. MFI Calculation
|
| 643 |
+
try:
|
| 644 |
+
mfi_ind = MFIIndicator(high=df['High'], low=df['Low'], close=df['Close'], volume=df['Volume'], window=14, fillna=True)
|
| 645 |
+
df['MFI'] = mfi_ind.money_flow_index()
|
| 646 |
+
except Exception: df['MFI'] = 50.0
|
| 647 |
+
|
| 648 |
+
# 2. Proper Recursive SuperTrend Calculation
|
| 649 |
+
try:
|
| 650 |
+
st_period = 10
|
| 651 |
+
st_multiplier = 3.0
|
| 652 |
+
|
| 653 |
+
# Calculate ATR
|
| 654 |
+
high_low = df['High'] - df['Low']
|
| 655 |
+
high_close = np.abs(df['High'] - df['Close'].shift())
|
| 656 |
+
low_close = np.abs(df['Low'] - df['Close'].shift())
|
| 657 |
+
ranges = pd.concat([high_low, high_close, low_close], axis=1)
|
| 658 |
+
true_range = np.max(ranges, axis=1)
|
| 659 |
+
atr = true_range.rolling(st_period).mean().fillna(0)
|
| 660 |
+
|
| 661 |
+
# Basic Bands
|
| 662 |
+
hl2 = (df['High'] + df['Low']) / 2
|
| 663 |
+
basic_upper = hl2 + (st_multiplier * atr)
|
| 664 |
+
basic_lower = hl2 - (st_multiplier * atr)
|
| 665 |
+
|
| 666 |
+
# Initialize Final Bands
|
| 667 |
+
final_upper = basic_upper.copy()
|
| 668 |
+
final_lower = basic_lower.copy()
|
| 669 |
+
trend = np.zeros(len(df), dtype=int)
|
| 670 |
+
supertrend = np.zeros(len(df))
|
| 671 |
+
|
| 672 |
+
# Recursive Loop (Accurate Logic)
|
| 673 |
+
close = df['Close'].values
|
| 674 |
+
bu = basic_upper.values
|
| 675 |
+
bl = basic_lower.values
|
| 676 |
+
fu = final_upper.values
|
| 677 |
+
fl = final_lower.values
|
| 678 |
+
|
| 679 |
+
# 1 = Uptrend, -1 = Downtrend
|
| 680 |
+
curr_trend = 1
|
| 681 |
+
|
| 682 |
+
for i in range(1, len(df)):
|
| 683 |
+
# Calculate Final Upper Band
|
| 684 |
+
if bu[i] < fu[i-1] or close[i-1] > fu[i-1]:
|
| 685 |
+
fu[i] = bu[i]
|
| 686 |
+
else:
|
| 687 |
+
fu[i] = fu[i-1]
|
| 688 |
+
|
| 689 |
+
# Calculate Final Lower Band
|
| 690 |
+
if bl[i] > fl[i-1] or close[i-1] < fl[i-1]:
|
| 691 |
+
fl[i] = bl[i]
|
| 692 |
+
else:
|
| 693 |
+
fl[i] = fl[i-1]
|
| 694 |
+
|
| 695 |
+
# Determine Trend
|
| 696 |
+
if curr_trend == 1 and close[i] < fl[i]:
|
| 697 |
+
curr_trend = -1
|
| 698 |
+
elif curr_trend == -1 and close[i] > fu[i]:
|
| 699 |
+
curr_trend = 1
|
| 700 |
+
|
| 701 |
+
trend[i] = curr_trend
|
| 702 |
+
supertrend[i] = fl[i] if curr_trend == 1 else fu[i]
|
| 703 |
+
|
| 704 |
+
df['SuperTrend'] = supertrend
|
| 705 |
+
|
| 706 |
+
except Exception as e:
|
| 707 |
+
# print(f"ST Error: {e}") # Debug if needed
|
| 708 |
+
df['SuperTrend'] = np.nan
|
| 709 |
+
else:
|
| 710 |
+
df['MFI'], df['SuperTrend'] = 50.0, np.nan
|
| 711 |
+
|
| 712 |
df['SMA_200'] = df['Close'].rolling(window=200, min_periods=1).mean()
|
| 713 |
|
| 714 |
if 'Volume' in df.columns:
|
|
|
|
| 738 |
raw_long_score, raw_short_score = calculate_confidence_score(df,
|
| 739 |
primary_driver,
|
| 740 |
use_rsi, use_volatility, use_trend, use_volume, use_macd, use_ma_slope, use_markov,
|
| 741 |
+
use_mfi, use_supertrend, # <--- NEW
|
| 742 |
rsi_w, vol_w, trend_w, vol_w_val, macd_w, ma_slope_w, markov_w,
|
| 743 |
+
mfi_w, supertrend_w, # <--- NEW
|
| 744 |
bband_params_for_score,
|
| 745 |
best_markov_setup=markov_setup
|
| 746 |
)
|
|
|
|
| 827 |
|
| 828 |
# [SURGICAL PATCH START] --------------------------------------------------
|
| 829 |
# Check if ALL confidence indicators are disabled
|
| 830 |
+
# CORRECTED: Added use_mfi and use_supertrend to this list so the score isn't ignored when they are On.
|
| 831 |
+
all_indicators_off = not any([
|
| 832 |
+
use_rsi, use_volatility, use_trend, use_volume, use_macd, use_ma_slope, use_markov,
|
| 833 |
+
use_mfi, use_supertrend
|
| 834 |
+
])
|
| 835 |
|
| 836 |
# If indicators are OFF, we bypass the confidence check (allow raw signal)
|
| 837 |
# Otherwise, we enforce the confidence threshold as usual
|
|
|
|
| 1091 |
fig.add_trace(go.Scatter(x=df.index, y=df['bband_upper'], mode='lines', name='Upper Band', line=dict(color='gray', width=0.5)))
|
| 1092 |
fig.add_trace(go.Scatter(x=df.index, y=df['bband_lower'], mode='lines', name='Lower Band', line=dict(color='gray', width=0.5), fill='tonexty', fillcolor='rgba(211,211,211,0.2)'))
|
| 1093 |
|
| 1094 |
+
# Unpack Trades Tuple: (Long Entries Log, Long Exits Index, Short Entries Log, Short Exits Index)
|
| 1095 |
long_entries_log, long_exits, _, _ = trades
|
| 1096 |
+
|
| 1097 |
+
# 1. Plot Long Entries
|
| 1098 |
if long_entries_log:
|
| 1099 |
dates = [t['date'] for t in long_entries_log]
|
| 1100 |
prices = [t['price'] for t in long_entries_log]
|
| 1101 |
scores = [f"Confidence: {t['confidence']:.0f}%" for t in long_entries_log]
|
| 1102 |
+
|
| 1103 |
+
# Filter out entries that fall outside the dataframe's date range (just in case)
|
| 1104 |
+
valid_points = [(d, p, s) for d, p, s in zip(dates, prices, scores) if d in df.index]
|
| 1105 |
+
if valid_points:
|
| 1106 |
+
v_dates, v_prices, v_scores = zip(*valid_points)
|
| 1107 |
+
fig.add_trace(go.Scatter(x=v_dates, y=v_prices, mode='markers', name='Long Entry',
|
| 1108 |
+
marker=dict(color='green', symbol='triangle-up', size=12),
|
| 1109 |
+
text=v_scores, hoverinfo='text'))
|
| 1110 |
+
|
| 1111 |
+
# 2. Plot Long Exits
|
| 1112 |
+
# Ensure long_exits is not empty and contains valid dates found in df
|
| 1113 |
+
if not long_exits.empty:
|
| 1114 |
+
valid_exits = [date for date in long_exits if date in df.index]
|
| 1115 |
+
if valid_exits:
|
| 1116 |
+
exit_prices = df.loc[valid_exits, 'Close']
|
| 1117 |
+
fig.add_trace(go.Scatter(x=exit_prices.index, y=exit_prices, mode='markers', name='Long Exit',
|
| 1118 |
+
marker=dict(color='darkgreen', symbol='x', size=8)))
|
| 1119 |
+
|
| 1120 |
+
fig.update_layout(title=f'Long Trades for {ticker}', xaxis_title='Date', yaxis_title='Price', legend_title="Indicator")
|
| 1121 |
+
return fig
|
| 1122 |
|
| 1123 |
def generate_short_plot(df, trades, ticker):
|
| 1124 |
fig = go.Figure()
|
|
|
|
| 3357 |
for key, val in defaults.items():
|
| 3358 |
if key not in st.session_state: st.session_state[key] = val
|
| 3359 |
|
| 3360 |
+
# 4. Helper to update state from widgets (Fixed)
|
| 3361 |
def update_state():
|
| 3362 |
+
keys = list(st.session_state.keys())
|
| 3363 |
+
for widget_key in keys:
|
| 3364 |
+
if widget_key.startswith('widget_'):
|
| 3365 |
+
main_key = widget_key[len('widget_'):]
|
| 3366 |
+
if main_key in st.session_state:
|
| 3367 |
+
st.session_state[main_key] = st.session_state[widget_key]
|
| 3368 |
|
| 3369 |
st.set_page_config(page_title="Dave's Quant System", page_icon="🔴", layout="wide")
|
| 3370 |
|
|
|
|
| 3561 |
st.sidebar.radio("Mode:", ("Analyse Single Ticker", full_list_label), key='run_mode')
|
| 3562 |
|
| 3563 |
if st.session_state.get('run_mode') == "Analyse Single Ticker":
|
| 3564 |
+
if not ticker_list:
|
| 3565 |
+
st.sidebar.warning("No tickers loaded.")
|
| 3566 |
+
else:
|
| 3567 |
+
# Ensure valid selection logic (only reset if invalid/None)
|
| 3568 |
+
if st.session_state.ticker_select not in ticker_list:
|
| 3569 |
+
st.session_state.ticker_select = ticker_list[0]
|
| 3570 |
+
|
| 3571 |
+
try:
|
| 3572 |
+
ticker_index = ticker_list.index(st.session_state.ticker_select)
|
| 3573 |
+
except ValueError:
|
| 3574 |
+
ticker_index = 0
|
| 3575 |
+
|
| 3576 |
+
# [FIXED] Removed the unconditional reset line that was here
|
| 3577 |
+
st.sidebar.selectbox("Select a Ticker:", ticker_list, index=ticker_index, key='widget_ticker_select', on_change=update_state )
|
| 3578 |
|
| 3579 |
# --- UPDATED: Date Inputs (Fully Unrestricted 2000-2030) ---
|
| 3580 |
st.sidebar.date_input(
|
|
|
|
| 3911 |
if 'widget_markov_w' not in st.session_state: st.session_state.widget_markov_w = st.session_state.markov_w
|
| 3912 |
st.sidebar.number_input("Markov Weight", 0.1, 5.0, step=0.1, key='widget_markov_w', on_change=update_state, disabled=not st.session_state.get('use_markov', True))
|
| 3913 |
|
| 3914 |
+
# --- NEW: MFI Controls ---
|
| 3915 |
+
if 'widget_use_mfi' not in st.session_state: st.session_state.widget_use_mfi = st.session_state.use_mfi
|
| 3916 |
+
st.sidebar.toggle("Use Money Flow (MFI)", key='widget_use_mfi', on_change=update_state)
|
| 3917 |
+
|
| 3918 |
+
if 'widget_mfi_w' not in st.session_state: st.session_state.widget_mfi_w = st.session_state.mfi_w
|
| 3919 |
+
st.sidebar.number_input("MFI Weight", 0.1, 5.0, step=0.1, key='widget_mfi_w', on_change=update_state, disabled=not st.session_state.get('use_mfi', True))
|
| 3920 |
+
|
| 3921 |
+
# --- NEW: SuperTrend Controls ---
|
| 3922 |
+
if 'widget_use_supertrend' not in st.session_state: st.session_state.widget_use_supertrend = st.session_state.use_supertrend
|
| 3923 |
+
st.sidebar.toggle("Use SuperTrend", key='widget_use_supertrend', on_change=update_state)
|
| 3924 |
+
|
| 3925 |
+
if 'widget_supertrend_w' not in st.session_state: st.session_state.widget_supertrend_w = st.session_state.supertrend_w
|
| 3926 |
+
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))
|
| 3927 |
+
|
| 3928 |
if 'widget_confidence_slider' not in st.session_state: st.session_state.widget_confidence_slider = st.session_state.confidence_slider
|
| 3929 |
st.sidebar.slider("Minimum Confidence Threshold (%)", 0, 100, step=5, key='widget_confidence_slider', on_change=update_state)
|
|
|
|
| 3930 |
st.sidebar.markdown("---")
|
| 3931 |
|
| 3932 |
st.sidebar.header("3. Strategy Parameters")
|
|
|
|
| 4354 |
|
| 4355 |
# A) Single Ticker Analysis
|
| 4356 |
if st.session_state.run_mode == "Analyse Single Ticker":
|
| 4357 |
+
# --- [FIX START] Logic integrated from old_but_working_app.py ---
|
| 4358 |
selected_ticker = st.session_state.get('ticker_select', ticker_list[0] if ticker_list else None)
|
| 4359 |
+
if not selected_ticker:
|
| 4360 |
+
st.error("No ticker selected.")
|
| 4361 |
+
st.stop()
|
| 4362 |
+
|
| 4363 |
+
user_start_date = pd.Timestamp(st.session_state.start_date)
|
| 4364 |
+
|
| 4365 |
cols_to_use = [selected_ticker]
|
| 4366 |
if f'{selected_ticker}_High' in master_df.columns: cols_to_use.append(f'{selected_ticker}_High')
|
| 4367 |
if f'{selected_ticker}_Low' in master_df.columns: cols_to_use.append(f'{selected_ticker}_Low')
|
| 4368 |
if f'{selected_ticker}_Volume' in master_df.columns: cols_to_use.append(f'{selected_ticker}_Volume')
|
| 4369 |
+
|
| 4370 |
existing_cols = [col for col in cols_to_use if col in master_df.columns]
|
| 4371 |
+
if selected_ticker not in existing_cols:
|
| 4372 |
+
st.error(f"Ticker '{selected_ticker}' not found.")
|
| 4373 |
+
st.stop()
|
| 4374 |
+
|
| 4375 |
+
# We use the full dataframe here (without date slicing) to allow run_backtest to handle warm-up
|
| 4376 |
+
data_for_backtest = master_df.loc[:, existing_cols].copy()
|
| 4377 |
+
|
| 4378 |
rename_dict = {selected_ticker: 'Close', f'{selected_ticker}_High': 'High', f'{selected_ticker}_Low': 'Low', f'{selected_ticker}_Volume': 'Volume'}
|
| 4379 |
rename_dict_filtered = {k: v for k, v in rename_dict.items() if k in existing_cols}
|
| 4380 |
data_for_backtest = data_for_backtest.rename(columns=rename_dict_filtered)
|
| 4381 |
+
|
| 4382 |
if not data_for_backtest.empty and 'Close' in data_for_backtest.columns and not data_for_backtest['Close'].isna().all():
|
| 4383 |
+
# --- [FIX END] ---
|
| 4384 |
+
|
| 4385 |
+
# --- CORRECTED CALL (Matches Analyse Full List) ---
|
| 4386 |
long_pnl, short_pnl, avg_long_trade, avg_short_trade, results_df, trades, open_trades, trade_counts, durations, trade_dates, exit_breakdown = run_backtest(
|
| 4387 |
+
data_for_backtest, manual_params,
|
| 4388 |
+
# 1. Toggles (Added mfi and supertrend)
|
| 4389 |
+
st.session_state.use_rsi, st.session_state.use_vol, st.session_state.use_trend, st.session_state.use_volume,
|
| 4390 |
+
st.session_state.use_macd, st.session_state.use_ma_slope, st.session_state.use_markov,
|
| 4391 |
+
st.session_state.use_mfi, st.session_state.use_supertrend,
|
| 4392 |
+
# 2. Weights (Added mfi and supertrend)
|
| 4393 |
+
st.session_state.rsi_w, st.session_state.vol_w, st.session_state.trend_w, st.session_state.volume_w,
|
| 4394 |
+
st.session_state.macd_w, st.session_state.ma_slope_w, st.session_state.markov_w,
|
| 4395 |
+
st.session_state.mfi_w, st.session_state.supertrend_w,
|
| 4396 |
+
# 3. Settings
|
| 4397 |
+
st.session_state.use_adx_filter, st.session_state.adx_threshold,
|
| 4398 |
+
st.session_state.get('rsi_logic', 'Crossover'),
|
| 4399 |
+
st.session_state.adx_period,
|
| 4400 |
+
veto_setups_list=veto_list_to_use,
|
| 4401 |
+
primary_driver=st.session_state.primary_driver,
|
| 4402 |
+
markov_setup=markov_setup_to_use,
|
| 4403 |
+
exit_logic_type=exit_logic,
|
| 4404 |
+
exit_confidence_threshold=exit_thresh,
|
| 4405 |
+
smart_trailing_stop_pct=smart_trailing_stop,
|
| 4406 |
+
smart_exit_atr_period=smart_atr_p,
|
| 4407 |
+
smart_exit_atr_multiplier=smart_atr_m,
|
| 4408 |
+
intelligent_tsl_pct=intelligent_tsl,
|
| 4409 |
+
# 4. Start Date Filter (Crucial for matching warmup logic)
|
| 4410 |
+
analysis_start_date=user_start_date
|
| 4411 |
+
)
|
| 4412 |
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}
|
| 4413 |
st.session_state.open_trades_df = pd.DataFrame(open_trades) if open_trades else pd.DataFrame()
|
| 4414 |
st.session_state.exit_breakdown_totals = {'long_profit_take_count': exit_breakdown[0], 'long_tsl_count': exit_breakdown[1], 'long_time_exit_count': exit_breakdown[2], 'short_profit_take_count': exit_breakdown[3], 'short_tsl_count': exit_breakdown[4], 'short_time_exit_count': exit_breakdown[5]}
|
|
|
|
| 4440 |
ticker_data_series = ticker_data_series.rename(columns={k:v for k,v in rename_dict.items() if k in existing_cols})
|
| 4441 |
|
| 4442 |
if not ticker_data_series.empty and 'Close' in ticker_data_series.columns and not ticker_data_series['Close'].isna().all():
|
| 4443 |
+
long_pnl, short_pnl, avg_long_trade, avg_short_trade, results_df, trades, open_trades, trade_counts, durations, trade_dates, exit_breakdown = run_backtest(
|
| 4444 |
+
ticker_data_series, manual_params, # <--- THIS WAS THE ERROR SOURCE
|
| 4445 |
st.session_state.use_rsi, st.session_state.use_vol, st.session_state.use_trend, st.session_state.use_volume,
|
| 4446 |
+
st.session_state.use_macd, st.session_state.use_ma_slope, st.session_state.use_markov,
|
| 4447 |
+
st.session_state.use_mfi, st.session_state.use_supertrend, # Added MFI/ST
|
| 4448 |
st.session_state.rsi_w, st.session_state.vol_w, st.session_state.trend_w, st.session_state.volume_w,
|
| 4449 |
+
st.session_state.macd_w, st.session_state.ma_slope_w, st.session_state.markov_w,
|
| 4450 |
+
st.session_state.mfi_w, st.session_state.supertrend_w, # Added MFI/ST weights
|
| 4451 |
st.session_state.use_adx_filter, st.session_state.adx_threshold, st.session_state.get('rsi_logic', 'Crossover'), st.session_state.adx_period,
|
| 4452 |
veto_setups_list=veto_list_to_use, primary_driver=st.session_state.primary_driver,
|
| 4453 |
markov_setup=markov_setup_to_use, exit_logic_type=exit_logic, exit_confidence_threshold=exit_thresh,
|
| 4454 |
smart_trailing_stop_pct=smart_trailing_stop, smart_exit_atr_period=smart_atr_p,
|
| 4455 |
smart_exit_atr_multiplier=smart_atr_m, intelligent_tsl_pct=intelligent_tsl,
|
| 4456 |
+
analysis_start_date=user_start_date
|
| 4457 |
)
|
| 4458 |
|
| 4459 |
if abs(long_pnl) > PROFIT_THRESHOLD or abs(short_pnl) > PROFIT_THRESHOLD or \
|
|
|
|
| 4603 |
if st.button("💾 Add these settings to User-Defined List", key="save_setup_from_analysis", on_click=add_setup_to_user_list): pass
|
| 4604 |
|
| 4605 |
# 5. Open Positions Table (With Filter)
|
| 4606 |
+
st.subheader("👨🏻💼 Open Positions & Recently Closed",
|
| 4607 |
help="This table displays all currently ACTIVE trades, plus any trades that closed within the last 30 days.")
|
| 4608 |
|
| 4609 |
if st.session_state.get('open_trades_df') is not None and not st.session_state.open_trades_df.empty:
|