critical: Fix v11 missing use_quality_filters parameter + RSI index bug

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.
This commit is contained in:
mindesbunister
2025-12-06 22:26:50 +01:00
parent 0ebbdbeb0d
commit c7f2df09b9

View File

@@ -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,6 +284,25 @@ 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:
# 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,
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
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)
@@ -306,6 +330,25 @@ def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs
cooldown_remaining = inputs.cooldown_bars
elif flip_short:
# 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,
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
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)