From c7f2df09b9940e9700771cbcc1240665397e0f82 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sat, 6 Dec 2025 22:26:50 +0100 Subject: [PATCH] critical: Fix v11 missing use_quality_filters parameter + RSI index bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TWO CRITICAL BUGS FIXED: 1. Missing use_quality_filters parameter (Pine Script parity): - Added use_quality_filters: bool = True to MoneyLineV11Inputs - Implemented bypass logic in signal generation for both long/short - When False: only trend flips generate signals (no filtering) - When True: all filters must pass (original v11 behavior) - Matches Pine Script: finalSignal = buyReady and (not useQualityFilters or (...filters...)) 2. RSI index misalignment causing 100% NaN values: - np.where() returns numpy arrays without indices - pd.Series(gain/loss) created NEW integer indices (0,1,2...) - Result: RSI values misaligned with original datetime index - Fix: pd.Series(gain/loss, index=series.index) preserves alignment - Impact: RSI NaN count 100 → 0, all filters now work correctly VERIFICATION: - Test 1 (no filters): 1,424 signals ✓ - Test 2 (permissive RSI): 1,308 signals ✓ - Test 3 (moderate RSI 25-70/30-80): 1,157 signals ✓ Progressive sweep can now proceed with corrected signal generation. --- backtester/v11_moneyline_all_filters.py | 105 +++++++++++++++++------- 1 file changed, 74 insertions(+), 31 deletions(-) diff --git a/backtester/v11_moneyline_all_filters.py b/backtester/v11_moneyline_all_filters.py index 31deaed..281e0b9 100644 --- a/backtester/v11_moneyline_all_filters.py +++ b/backtester/v11_moneyline_all_filters.py @@ -49,6 +49,9 @@ class MoneyLineV11Inputs: confirm_bars: int = 0 # Immediate signals cooldown_bars: int = 3 # Prevent overtrading + # Master filter toggle (matches Pine Script useQualityFilters) + use_quality_filters: bool = True # When False, only trend flips generate signals (no filtering) + # ATR profile (fixed for test - 5-minute chart defaults) atr_period: int = 12 # ATR calculation length multiplier: float = 3.8 # ATR band multiplier @@ -107,8 +110,10 @@ def rsi(series: pd.Series, length: int) -> pd.Series: delta = series.diff() gain = np.where(delta > 0, delta, 0.0) loss = np.where(delta < 0, -delta, 0.0) - avg_gain = rma(pd.Series(gain), length) - avg_loss = rma(pd.Series(loss), length) + # CRITICAL FIX (Dec 6, 2025): Preserve index from input series + # np.where returns array, pd.Series without index=series.index creates misaligned data + avg_gain = rma(pd.Series(gain, index=series.index), length) + avg_loss = rma(pd.Series(loss, index=series.index), length) rs = avg_gain / avg_loss.replace(0, np.nan) rsi_series = 100 - (100 / (1 + rs)) return rsi_series.fillna(50.0) @@ -279,18 +284,10 @@ def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs volume_ok = row.volume_ratio <= inputs.vol_max # Only check upper bound if flip_long: - # Entry buffer check (longBufferOk) - disabled if entry_buffer_atr == 0 - if inputs.entry_buffer_atr > 0: - entry_buffer_ok = row.close > (row.supertrend + inputs.entry_buffer_atr * row.atr) - else: - entry_buffer_ok = True # Filter disabled - - # Long filters - rsi_ok = inputs.rsi_long_min <= row.rsi <= inputs.rsi_long_max # rsiLongOk - pos_ok = row.price_position < inputs.long_pos_max # longPositionOk - - # V11: ALL filters must pass (this is the fix from v9) - if adx_ok and volume_ok and rsi_ok and pos_ok and entry_buffer_ok: + # CRITICAL FIX (Dec 6, 2025): Add use_quality_filters bypass to match Pine Script + # Pine Script: finalLongSignal = buyReady and (not useQualityFilters or (...filters...)) + if not inputs.use_quality_filters: + # No filters - just the trend flip (like v9 behavior) signals.append( MoneyLineV11Signal( timestamp=row.name, @@ -304,25 +301,39 @@ def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs ) ) cooldown_remaining = inputs.cooldown_bars + else: + # Original v11 filter logic - ALL filters must pass + # Entry buffer check (longBufferOk) - disabled if entry_buffer_atr == 0 + if inputs.entry_buffer_atr > 0: + entry_buffer_ok = row.close > (row.supertrend + inputs.entry_buffer_atr * row.atr) + else: + entry_buffer_ok = True # Filter disabled + + # Long filters + rsi_ok = inputs.rsi_long_min <= row.rsi <= inputs.rsi_long_max # rsiLongOk + pos_ok = row.price_position < inputs.long_pos_max # longPositionOk + + # V11: ALL filters must pass (this is the fix from v9) + if adx_ok and volume_ok and rsi_ok and pos_ok and entry_buffer_ok: + signals.append( + MoneyLineV11Signal( + timestamp=row.name, + direction="long", + entry_price=float(row.close), + adx=float(row.adx), + atr=float(row.atr), + rsi=float(row.rsi), + volume_ratio=float(row.volume_ratio), + price_position=float(row.price_position), + ) + ) + cooldown_remaining = inputs.cooldown_bars elif flip_short: - # Entry buffer check (shortBufferOk) - disabled if entry_buffer_atr == 0 - if inputs.entry_buffer_atr > 0: - entry_buffer_ok = row.close < (row.supertrend - inputs.entry_buffer_atr * row.atr) - else: - entry_buffer_ok = True # Filter disabled - - # Short filters - rsi_ok = inputs.rsi_short_min <= row.rsi <= inputs.rsi_short_max # rsiShortOk - - # Price position filter - disabled if short_pos_min == 0 - if inputs.short_pos_min > 0: - pos_ok = row.price_position > inputs.short_pos_min # shortPositionOk - else: - pos_ok = True # Filter disabled - - # V11: ALL filters must pass (this is the fix from v9) - if adx_ok and volume_ok and rsi_ok and pos_ok and entry_buffer_ok: + # CRITICAL FIX (Dec 6, 2025): Add use_quality_filters bypass to match Pine Script + # Pine Script: finalShortSignal = sellReady and (not useQualityFilters or (...filters...)) + if not inputs.use_quality_filters: + # No filters - just the trend flip (like v9 behavior) signals.append( MoneyLineV11Signal( timestamp=row.name, @@ -336,5 +347,37 @@ def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs ) ) cooldown_remaining = inputs.cooldown_bars + else: + # Original v11 filter logic - ALL filters must pass + # Entry buffer check (shortBufferOk) - disabled if entry_buffer_atr == 0 + if inputs.entry_buffer_atr > 0: + entry_buffer_ok = row.close < (row.supertrend - inputs.entry_buffer_atr * row.atr) + else: + entry_buffer_ok = True # Filter disabled + + # Short filters + rsi_ok = inputs.rsi_short_min <= row.rsi <= inputs.rsi_short_max # rsiShortOk + + # Price position filter - disabled if short_pos_min == 0 + if inputs.short_pos_min > 0: + pos_ok = row.price_position > inputs.short_pos_min # shortPositionOk + else: + pos_ok = True # Filter disabled + + # V11: ALL filters must pass (this is the fix from v9) + if adx_ok and volume_ok and rsi_ok and pos_ok and entry_buffer_ok: + signals.append( + MoneyLineV11Signal( + timestamp=row.name, + direction="short", + entry_price=float(row.close), + adx=float(row.adx), + atr=float(row.atr), + rsi=float(row.rsi), + volume_ratio=float(row.volume_ratio), + price_position=float(row.price_position), + ) + ) + cooldown_remaining = inputs.cooldown_bars return signals