RSI als Filter auf dem GEGATETEN Signal - die neue Achse traegt auch nicht
User: "neue achse" -> "messe". Das war die einzige nie gepruefte RSI-Population: alle bisherigen Laeufe massen RSI als EIGENSTAENDIGEN Einstieg (backtest_rsi_v2.py heute) oder als Konfluenz-Beitrag auf dem UNGEGATETEN _build-Signal mit altem Exit (backtest_confluence.py). Hier laeuft er auf den Signalen, die den VOLLEN Live-Gate-Stack passieren - 1344/2038 statt ~1700/2500. AUFBAU: echte Gate-Pipeline aus backtest_entryroom_level.sammle (kein Nachbau, inkl. set_entry_room - die Lehre vom 05.08.), RSI-Serie gegen calc_rsi verifiziert, kanonischer Exit, Echtkosten, 2 Halbjahre, 95-%-KI, beide Ausfuehrungen, 3 Schwellen als Nachbar-Robustheit. ERGEBNIS: KEINE der 12 Zellen erfuellt die vorab fixierte Regel. Die Effekte sind winzig - alle Deltas gegen die Basis liegen bei ±0,06 R, ausser in den Extrem-Baendern, und die haben n=10 bis 50 (Rauschen). ⚠ Die Projekt-Hypothese "RSI GEGEN die Richtung = doppelter Edge" reproduziert NICHT: am Markt +0,040 in H2 aber -0,021 in H1, an der Order genau umgekehrt (+0,010 / -0,041). Vorzeichen kippt in beiden Ausfuehrungen. Der Pullback-Effekt existiert, aber der RSI ist nicht das Werkzeug, ihn zu greifen - im Bot laeuft er ueber die EMA-Distanz. ✅ EIN robuster Befund bleibt, und er ist die Bestaetigung einer vorhandenen Regel: MIT RSI>70 an der ruhenden Order ist in H2 OeR -0,683 mit KI [-1,041 .. -0,310], also signifikant NEGATIV. In eine Richtung einzusteigen, waehrend der RSI dort schon extrem steht, ist messbar schaedlich - genau das, was die Anti-Ueberdehnung (_STRETCH_MAX 3,5) bereits abfaengt. Kein neuer Filter noetig, die Wirkung ist schon da. 30. verworfener Signal-Eingriff. RSI bleibt reine Anzeige. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3839fd5dc0
commit
e3e8a6b9fd
@@ -0,0 +1,168 @@
|
||||
"""RSI als FILTER auf dem gegateten Wellensignal (2026-08-19) — die neue Achse.
|
||||
|
||||
User: "neue achse" → "messe".
|
||||
|
||||
WAS DARAN NEU IST. Alle bisherigen RSI-Laeufe des Projekts massen RSI als
|
||||
EIGENSTAENDIGEN Einstieg (`backtest_rsi_v2.py` heute: 24 Zellen, keine besteht)
|
||||
oder als Konfluenz-Beitrag auf dem UNGEGATETEN `_build`-Signal mit einem alten
|
||||
Exit (`backtest_confluence.py`). Nie gemessen wurde er auf der Population, die
|
||||
live tatsaechlich handelt: den Signalen, die den VOLLEN Gate-Stack passieren
|
||||
(Totband · M30-Filter · Anti-Ueberdehnung · Winkel · min_conf 55 ·
|
||||
Breakout-Bestaetigung · Entry-Raum). Das sind nur ~300-500 je Halbjahr statt
|
||||
~800 - eine andere Grundgesamtheit, und damit eine faire neue Achse.
|
||||
|
||||
DIE HYPOTHESE STAMMT AUS DEM PROJEKT SELBST und ist kontraintuitiv: dokumentiert
|
||||
ist "RSI *bestaetigend* ist kontraproduktiv, RSI *gegen* die Richtung = doppelter
|
||||
Edge" - der Pullback-Effekt. Gemessen wird deshalb BEIDES, gegen dieselbe Basis.
|
||||
|
||||
BASIS alle gegateten Signale, kein Filter
|
||||
MIT nur wenn RSI die Richtung BESTAETIGT (LONG: RSI > x)
|
||||
GEGEN nur wenn RSI DAGEGEN steht (LONG: RSI < 100-x) <- Pullback
|
||||
|
||||
x in 50 / 60 / 70 = Nachbar-Robustheit. Beide Ausfuehrungen (Markt am Bar-Close /
|
||||
ruhende Order am Bestaetigungs-Level mit Fill bei BERUEHRUNG).
|
||||
|
||||
⚠ KEIN NACHBAU: der Gate-Stack kommt aus `backtest_entryroom_level.sammle`
|
||||
(dieselbe Pipeline wie `backtest_conf_gated.py`), die RSI-Serie ist gegen die
|
||||
echte `core.analysis.calc_rsi` verifiziert (s. `backtest_rsi_v2.pruefe`).
|
||||
|
||||
REGEL VORAB FIXIERT: ein Filter traegt nur, wenn die BEHALTENE Gruppe die Basis
|
||||
in BEIDEN Halbjahren im OeR schlaegt, ihr 95-%-KI die Null ausschliesst UND die
|
||||
beiden Nachbar-Schwellen dasselbe Vorzeichen zeigen. Zusaetzlich muss die
|
||||
ENTFERNTE Gruppe schlechter sein als die behaltene - sonst wirft der Filter
|
||||
nicht die schlechten Signale weg, sondern nur irgendwelche.
|
||||
|
||||
⚠ GRENZE: ein Filter verkleinert die Stichprobe zwangslaeufig. Ein besseres OeR
|
||||
bei halber Trade-Zahl ist noch kein Ertrag - deshalb steht SR (Summe) daneben.
|
||||
|
||||
Aufruf: python backtest_rsi_filter.py [bars]
|
||||
"""
|
||||
import sys
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
import core.wave_rec as WR
|
||||
from backtest_breakout_gated import _PIV_K, _TU, _atr_series, _htf_sign
|
||||
from backtest_cost_gate import kennzahlen
|
||||
from backtest_entryroom_level import _GATE, _K, handeln, sammle
|
||||
from backtest_rsi_v2 import pruefe, rsi_serie
|
||||
from core.analysis import calc_trend_angle
|
||||
from core.exit_model import LIVE
|
||||
from core.wave_rec import (WaveRecommender, _ANGLE_LR, _EMA_FAST, _EMA_SLOW,
|
||||
_ema_series)
|
||||
|
||||
SCHWELLEN = [50, 60, 70]
|
||||
|
||||
|
||||
def zeig(lbl, k1, k2, b1=None, b2=None):
|
||||
def f(k, b):
|
||||
if not k:
|
||||
return " --"
|
||||
ci = k.get("ci"); m = ""
|
||||
if ci:
|
||||
m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1])
|
||||
+ ("" if ci[0] * ci[1] > 0 else " ~0"))
|
||||
dlt = ""
|
||||
if b:
|
||||
dlt = " Δ{:+.3f}".format(k["oer"] - b["oer"])
|
||||
return "n={:>4} OeR={:+.3f} PF={:.2f} SR={:+7.1f}{}{}".format(
|
||||
k["n"], k["oer"], k["pf"], k["sr"], m, dlt)
|
||||
print(" {:<20} H1 {}".format(lbl, f(k1, b1)))
|
||||
print(" {:<20} H2 {}".format("", f(k2, b2)))
|
||||
if not (k1 and k2 and b1 and b2):
|
||||
return False
|
||||
return (k1["oer"] > b1["oer"] and k2["oer"] > b2["oer"]
|
||||
and k1["oer"] > 0 and k2["oer"] > 0
|
||||
and (k1.get("ci") or (0, 0))[0] > 0 and (k2.get("ci") or (0, 0))[0] > 0)
|
||||
|
||||
|
||||
def main():
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
||||
mt5.initialize()
|
||||
sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD")
|
||||
if mt5.symbol_info(c)), None)
|
||||
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
||||
point = mt5.symbol_info(sym).point
|
||||
mt5.shutdown()
|
||||
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars]
|
||||
C = [float(b["close"]) for b in bars]
|
||||
SP = [float(b["spread"]) * point for b in bars]
|
||||
A = _atr_series(H, L, C)
|
||||
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW)
|
||||
R = rsi_serie(C)
|
||||
N = len(C); mid = N // 2; ende = N - LIVE.max_hold - 1
|
||||
|
||||
fehler = pruefe(C, R)
|
||||
assert fehler < 1e-6, "RSI-Serie weicht von calc_rsi ab (%.2e)" % fehler
|
||||
|
||||
print("Vorberechnung ...", flush=True)
|
||||
AD = [90.0] * N
|
||||
for i in range(_ANGLE_LR + 2, N):
|
||||
AD[i] = calc_trend_angle(C[i - (_ANGLE_LR + 1):i + 1], _ANGLE_LR)
|
||||
HTF = [0] * N; H1 = [0] * N
|
||||
for i in range(N):
|
||||
if A[i]:
|
||||
HTF[i] = _htf_sign(C, i, 6, A[i]); H1[i] = _htf_sign(C, i, 12, A[i])
|
||||
PH = [j for j in range(_PIV_K, N - _PIV_K)
|
||||
if H[j] == max(H[j - _PIV_K:j + _PIV_K + 1])]
|
||||
PL = [j for j in range(_PIV_K, N - _PIV_K)
|
||||
if L[j] == min(L[j - _PIV_K:j + _PIV_K + 1])]
|
||||
|
||||
w = WaveRecommender(_TU(), mt5.TIMEFRAME_M5)
|
||||
w.set_reversal_enabled(False)
|
||||
w.set_entry_room(_GATE) # ⚠ ohne das ist das Gate ein No-op (Lehre 05.08.)
|
||||
w.set_dead_hours([])
|
||||
w.set_breakout_k(_K)
|
||||
|
||||
s1 = sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, 0, mid)
|
||||
s2 = sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, mid, ende)
|
||||
|
||||
print("=" * 96)
|
||||
print(" RSI als FILTER auf dem GEGATETEN Signal — {} M5 ({} Bars)".format(sym, N))
|
||||
print(" Gegatete Signale: H1 {} H2 {} (WR/PF/KI je Gruppe)".format(len(s1), len(s2)))
|
||||
print("=" * 96)
|
||||
|
||||
treffer = []
|
||||
for modus, titel in (("markt", "A) MARKT am Bar-Close"),
|
||||
("level", "B) ruhende Order am Level, Fill bei BERUEHRUNG")):
|
||||
print("\n " + titel)
|
||||
print(" " + "-" * 92)
|
||||
|
||||
def rs(sigs, keep):
|
||||
out = []
|
||||
for s in sigs:
|
||||
if not keep(s):
|
||||
continue
|
||||
r = handeln(s, H, L, C, SP, ende, modus, 0.0, 12)
|
||||
if r is not None:
|
||||
out.append(r)
|
||||
return out
|
||||
|
||||
b1 = kennzahlen(rs(s1, lambda s: True))
|
||||
b2 = kennzahlen(rs(s2, lambda s: True))
|
||||
zeig("BASIS (kein Filter)", b1, b2)
|
||||
print("")
|
||||
for x in SCHWELLEN:
|
||||
for lbl, keep in (
|
||||
("MIT RSI>{}".format(x),
|
||||
lambda s, x=x: (R[s["i"]] > x) if s["d"] > 0 else (R[s["i"]] < 100 - x)),
|
||||
("GEGEN RSI<{}".format(100 - x),
|
||||
lambda s, x=x: (R[s["i"]] < 100 - x) if s["d"] > 0 else (R[s["i"]] > x)),
|
||||
):
|
||||
k1 = kennzahlen(rs(s1, keep)); k2 = kennzahlen(rs(s2, keep))
|
||||
if zeig(lbl, k1, k2, b1, b2):
|
||||
treffer.append((modus, lbl))
|
||||
print("")
|
||||
|
||||
print(" " + "=" * 92)
|
||||
print(" Zellen, die die vorab fixierte Regel erfuellen: {}".format(
|
||||
len(treffer) or "KEINE"))
|
||||
for t in treffer:
|
||||
print(" {}".format(t))
|
||||
print(" ⚠ SR (Summe) mitlesen: ein besseres OeR bei halber Trade-Zahl ist")
|
||||
print(" Handelsvermeidung, kein Ertrag.")
|
||||
print(" " + "=" * 92)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user