Fix 3 bugs (swing NaN, ROC div-by-zero, currency matching) and add 77 new tests
Bug fixes:
- Swing point detection: false positives when all neighbors are NaN (all([]) == True)
- ROC indicator: division by zero when historical close price is zero
- News currency extraction: substring matching ("usd" in "usda") replaced with word-boundary regex
- ADX DX calculation: use epsilon instead of NaN to avoid cascade
- Signal aggregator: refactor ternary append, add pip_value<=0 guard
New tests (77) cover: swing point edge cases, ROC/ADX/RSI/MACD/BB boundaries,
regime classification, composite scoring, quality grade thresholds, EUR filter
pipeline, trade setup edge cases, MTF agreement, weekend filter, and integration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -488,7 +488,7 @@ class TechnicalEngine:
|
||||
plus_di = 100 * (plus_dm.ewm(alpha=1/c.adx_period, min_periods=c.adx_period, adjust=False).mean() / atr)
|
||||
minus_di = 100 * (minus_dm.ewm(alpha=1/c.adx_period, min_periods=c.adx_period, adjust=False).mean() / atr)
|
||||
|
||||
dx = 100 * (plus_di - minus_di).abs() / (plus_di + minus_di).replace(0, np.nan)
|
||||
dx = 100 * (plus_di - minus_di).abs() / np.maximum(plus_di + minus_di, 1e-10)
|
||||
adx = dx.ewm(alpha=1/c.adx_period, min_periods=c.adx_period, adjust=False).mean()
|
||||
|
||||
adx_val = adx.iloc[-1] if not adx.empty and not pd.isna(adx.iloc[-1]) else 0.0
|
||||
@@ -598,8 +598,12 @@ class TechnicalEngine:
|
||||
if len(close) < n + 5:
|
||||
return
|
||||
|
||||
roc_val = ((close.iloc[-1] - close.iloc[-n - 1]) / close.iloc[-n - 1]) * 100
|
||||
roc_prev = ((close.iloc[-2] - close.iloc[-n - 2]) / close.iloc[-n - 2]) * 100
|
||||
denom1 = close.iloc[-n - 1]
|
||||
denom2 = close.iloc[-n - 2]
|
||||
if denom1 == 0 or denom2 == 0:
|
||||
return
|
||||
roc_val = ((close.iloc[-1] - denom1) / denom1) * 100
|
||||
roc_prev = ((close.iloc[-2] - denom2) / denom2) * 100
|
||||
|
||||
if pd.isna(roc_val) or pd.isna(roc_prev):
|
||||
return
|
||||
@@ -1112,12 +1116,14 @@ class TechnicalEngine:
|
||||
for i in range(window, len(vals) - window):
|
||||
if np.isnan(vals[i]):
|
||||
continue
|
||||
left_vals = [vals[i-j] for j in range(1, window+1) if not np.isnan(vals[i-j])]
|
||||
right_vals = [vals[i+j] for j in range(1, window+1) if i+j < len(vals) and not np.isnan(vals[i+j])]
|
||||
if not left_vals or not right_vals:
|
||||
continue
|
||||
if mode == "low":
|
||||
if all(vals[i] <= vals[i-j] for j in range(1, window+1) if not np.isnan(vals[i-j])) and \
|
||||
all(vals[i] <= vals[i+j] for j in range(1, window+1) if i+j < len(vals) and not np.isnan(vals[i+j])):
|
||||
if all(vals[i] <= v for v in left_vals) and all(vals[i] <= v for v in right_vals):
|
||||
points.append((i, vals[i]))
|
||||
else:
|
||||
if all(vals[i] >= vals[i-j] for j in range(1, window+1) if not np.isnan(vals[i-j])) and \
|
||||
all(vals[i] >= vals[i+j] for j in range(1, window+1) if i+j < len(vals) and not np.isnan(vals[i+j])):
|
||||
if all(vals[i] >= v for v in left_vals) and all(vals[i] >= v for v in right_vals):
|
||||
points.append((i, vals[i]))
|
||||
return points
|
||||
|
||||
Reference in New Issue
Block a user