"""MRV07 — Consecutive-down buy in uptrend. After N+ consecutive lower closes AND close > SMA100 (uptrend filter), buy at close[i]; exit after max_bars or on the first green close (close > prev close). Grid: try (consec_n, max_bars) combinations on 1d. Total backtests: 3 configs x 2 assets = 6. """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt") import altlib as al import numpy as np def make_entries_fn(consec_n=3, sma_win=100, max_bars=10): """Factory for consecutive-down buy entries. Signal: close[i] < close[i-1] < ... < close[i-consec_n+1] (N consecutive lower closes) AND close[i] > SMA100 (uptrend filter). Entry: buy at close[i] (filled immediately). Exit: after max_bars bars (hard stop); no TP/SL (green-close exit not encodable causally in the entries-list format — green close requires next-bar data). """ def entries_fn(df): c = df["close"].values.astype(float) n = len(c) sma100 = al.sma(c, sma_win) entries = [] for i in range(n): # Need at least consec_n prior bars if i < consec_n: entries.append(None) continue # Check SMA100 (uptrend) if np.isnan(sma100[i]) or c[i] <= sma100[i]: entries.append(None) continue # Check N consecutive lower closes consecutive_down = True for k in range(consec_n): if k == 0: # close[i] < close[i-1] if c[i] >= c[i-1]: consecutive_down = False break else: # close[i-k] < close[i-k-1] if c[i-k] >= c[i-k-1]: consecutive_down = False break if consecutive_down: entries.append({ "dir": 1, "tp": None, "sl": None, "max_bars": max_bars, }) else: entries.append(None) return entries return entries_fn # Grid: 3 configs (consec_n, max_bars) # Hypothesis: after 3 consecutive dips in uptrend, expect mean-reversion bounce CONFIGS = [ dict(consec_n=3, max_bars=5, label="N3_mb5"), dict(consec_n=3, max_bars=10, label="N3_mb10"), dict(consec_n=4, max_bars=5, label="N4_mb5"), ] best_rep = None best_hold = -999.0 best_label = None for cfg in CONFIGS: label = cfg["label"] fn = make_entries_fn(consec_n=cfg["consec_n"], max_bars=cfg["max_bars"]) rep = al.study_signals(f"MRV07-{label}", fn, tfs=("1d",)) hold = rep["verdict"].get("best_holdout_sharpe", -999) full = rep["verdict"].get("best_full_sharpe", -999) print(f"\n[{label}] full={full:.3f} hold={hold:.3f} grade={rep['verdict']['grade']}") if hold > best_hold: best_hold = hold best_rep = rep best_label = label print("\n\n=== BEST CONFIG ===", best_label) print(al.fmt(best_rep)) print("JSON:", al.as_json(best_rep))