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:
@@ -49,6 +49,9 @@ class MoneyLineV11Inputs:
|
|||||||
confirm_bars: int = 0 # Immediate signals
|
confirm_bars: int = 0 # Immediate signals
|
||||||
cooldown_bars: int = 3 # Prevent overtrading
|
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 profile (fixed for test - 5-minute chart defaults)
|
||||||
atr_period: int = 12 # ATR calculation length
|
atr_period: int = 12 # ATR calculation length
|
||||||
multiplier: float = 3.8 # ATR band multiplier
|
multiplier: float = 3.8 # ATR band multiplier
|
||||||
@@ -107,8 +110,10 @@ def rsi(series: pd.Series, length: int) -> pd.Series:
|
|||||||
delta = series.diff()
|
delta = series.diff()
|
||||||
gain = np.where(delta > 0, delta, 0.0)
|
gain = np.where(delta > 0, delta, 0.0)
|
||||||
loss = np.where(delta < 0, -delta, 0.0)
|
loss = np.where(delta < 0, -delta, 0.0)
|
||||||
avg_gain = rma(pd.Series(gain), length)
|
# CRITICAL FIX (Dec 6, 2025): Preserve index from input series
|
||||||
avg_loss = rma(pd.Series(loss), length)
|
# 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)
|
rs = avg_gain / avg_loss.replace(0, np.nan)
|
||||||
rsi_series = 100 - (100 / (1 + rs))
|
rsi_series = 100 - (100 / (1 + rs))
|
||||||
return rsi_series.fillna(50.0)
|
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
|
volume_ok = row.volume_ratio <= inputs.vol_max # Only check upper bound
|
||||||
|
|
||||||
if flip_long:
|
if flip_long:
|
||||||
# Entry buffer check (longBufferOk) - disabled if entry_buffer_atr == 0
|
# CRITICAL FIX (Dec 6, 2025): Add use_quality_filters bypass to match Pine Script
|
||||||
if inputs.entry_buffer_atr > 0:
|
# Pine Script: finalLongSignal = buyReady and (not useQualityFilters or (...filters...))
|
||||||
entry_buffer_ok = row.close > (row.supertrend + inputs.entry_buffer_atr * row.atr)
|
if not inputs.use_quality_filters:
|
||||||
else:
|
# No filters - just the trend flip (like v9 behavior)
|
||||||
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(
|
signals.append(
|
||||||
MoneyLineV11Signal(
|
MoneyLineV11Signal(
|
||||||
timestamp=row.name,
|
timestamp=row.name,
|
||||||
@@ -304,25 +301,39 @@ def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
cooldown_remaining = inputs.cooldown_bars
|
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:
|
elif flip_short:
|
||||||
# Entry buffer check (shortBufferOk) - disabled if entry_buffer_atr == 0
|
# CRITICAL FIX (Dec 6, 2025): Add use_quality_filters bypass to match Pine Script
|
||||||
if inputs.entry_buffer_atr > 0:
|
# Pine Script: finalShortSignal = sellReady and (not useQualityFilters or (...filters...))
|
||||||
entry_buffer_ok = row.close < (row.supertrend - inputs.entry_buffer_atr * row.atr)
|
if not inputs.use_quality_filters:
|
||||||
else:
|
# No filters - just the trend flip (like v9 behavior)
|
||||||
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(
|
signals.append(
|
||||||
MoneyLineV11Signal(
|
MoneyLineV11Signal(
|
||||||
timestamp=row.name,
|
timestamp=row.name,
|
||||||
@@ -336,5 +347,37 @@ def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
cooldown_remaining = inputs.cooldown_bars
|
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
|
return signals
|
||||||
|
|||||||
Reference in New Issue
Block a user