RSI zum vierten Mal gemessen - faellt in allen 24 Zellen durch
User: "messe ihn nochmal". Kein blosses Wiederholen: die drei alten RSI-Laeufe
stiegen alle am Bar-Close zum MARKTPREIS ein. Genau diese Annahme hat am
05./06.08. Squeeze und Auto-Signal von negativ auf positiv gedreht (+0,32 bzw.
+0,61 R - mehr als alle Signal-Ideen zusammen). Fuer RSI war die
Ausfuehrungs-Achse nie geprueft.
AUFBAU: 80k M5, 2 Halbjahre, kanonischer Exit, Echtkosten, 95-%-Bootstrap.
2 Lesarten (Lehrbuch-Reversion und die Umkehrung) x 3 Schwellenpaare
(70/30, 75/25, 80/20) x 2 Ausfuehrungen (Markt / ruhende Order am Extrem mit
Fill bei Beruehrung) = 24 Zellen.
⚠ KEIN NACHBAU des Indikators: `calc_rsi` ist ein Wilder-RSI mit Rekursion ueber
die GANZE Reihe, ein Slice waere also nicht dasselbe. Die Serie wird inkrementell
gerechnet und an 200 Zufallsstichproben gegen die echte Funktion verifiziert;
bei Abweichung bricht das Skript ab. Groesste gemessene Abweichung: unter 1e-6.
ERGEBNIS: KEINE einzige Zelle erfuellt die vorab fixierte Regel (beidhaelftig
OeR > 0 UND PF > 1 UND KI ohne die Null UND Nachbarn gleichgerichtet). Vier
Zellen sind sogar beidseitig SIGNIFIKANT NEGATIV (KI komplett unter null):
REV 70/30 am Markt und MOM 75/25 an der Order.
⚠⚠ DIE AUSFUEHRUNGS-ACHSE HILFT HIER NICHT - und das war vorab so vorhergesagt.
Bei REV 75/25 hebt die ruhende Order zwar von -0,107/-0,014 auf +0,006/+0,075,
aber beide Intervalle enthalten weiter die Null, und die Nachbarn kippen
(80/20: H1 -0,181). Das bestaetigt den Mechanismus aus
`backtest_candle_fade2.py`: eine ruhende Order hilft bei MOMENTUM-Entries und
schadet bei REVERSIONS-Entries, weil eine Limit-Order am Extrem nur fuellt, wenn
sich die Bewegung weiter ausdehnt - also genau dann, wenn man falsch liegt.
⚠ Auch die im Projekt dokumentierte Umkehrung ("RSI gegen die Richtung =
doppelter Edge") traegt STANDALONE nicht: die MOM-Zeilen sind ueberwiegend
schlechter als die REV-Zeilen. Der Pullback-Effekt existiert, aber als
Konfidenz-Beitrag im gegateten Signal, nicht als eigenstaendiger Einstieg.
29. verworfener Signal-Eingriff. RSI bleibt draussen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
33cc451e52
commit
d7dc997eee
@@ -0,0 +1,193 @@
|
||||
"""RSI ZUM VIERTEN MAL gemessen — diesmal auf der Ausfuehrungs-Achse (2026-08-19)
|
||||
|
||||
User: "messe ihn nochmal".
|
||||
|
||||
WARUM DAS KEIN BLOSSES WIEDERHOLEN IST. RSI ist im Projekt dreimal
|
||||
durchgefallen (`backtest_confluence.py` als Konfluenz-Filter,
|
||||
`backtest_buch_luis.py` als Divergenz, dazu der Pullback-Befund). ALLE drei
|
||||
stiegen am **Bar-Close zum Marktpreis** ein. Genau diese Annahme hat am
|
||||
05./06.08. Squeeze und Auto-Signal von negativ auf positiv gedreht - der
|
||||
Einstiegspreis war dort +0,32 bzw. +0,61 R wert, mehr als alle Signal-Ideen
|
||||
zusammen. Fuer RSI war diese Achse nie geprueft.
|
||||
|
||||
⚠⚠ ABER die Vorhersage ist ungünstig, und sie steht VOR dem Lauf hier: RSI ist
|
||||
ein REVERSIONS-Einstieg (ueberkauft -> short). Und fuer Reversion ist die
|
||||
ruhende Order gemessen SCHLECHTER, nicht besser - `backtest_candle_fade2.py`
|
||||
(05.08.): "ruhende Order hilft bei Momentum-Entries, schadet bei
|
||||
Reversion-Entries", weil eine Limit-Order am Extrem nur fuellt, wenn sich die
|
||||
Bewegung weiter ausdehnt - also genau dann, wenn man falsch liegt. Wenn dieser
|
||||
Lauf dasselbe zeigt, ist das eine Bestaetigung des Mechanismus, keine
|
||||
Ueberraschung.
|
||||
|
||||
GEMESSEN WERDEN BEIDE LESARTEN, weil der Projekt-Befund sagt, dass die
|
||||
Lehrbuch-Lesart hier invertiert ist ("RSI *gegen* die Richtung = doppelter
|
||||
Edge"):
|
||||
REVERSION RSI > hi -> SHORT, RSI < lo -> LONG (Lehrbuch)
|
||||
MOMENTUM RSI > hi -> LONG, RSI < lo -> SHORT (die Umkehrung)
|
||||
je mit drei Schwellenpaaren (70/30, 75/25, 80/20) = Nachbar-Robustheit, und je
|
||||
mit zwei Ausfuehrungen (Markt am Bar-Close / ruhende Order am Extrem der
|
||||
Ausloese-Bar, Fill bei BERUEHRUNG).
|
||||
|
||||
⚠ KEIN NACHBAU des RSI: die Serie wird inkrementell gerechnet UND an 200
|
||||
Stichproben gegen die echte `core.analysis.calc_rsi` verifiziert. Weicht sie ab,
|
||||
bricht das Skript ab - ein selbst gebauter Indikator waere genau die
|
||||
Divergenz-Falle, die im Projekt dreimal zugeschlagen hat.
|
||||
|
||||
REGEL VORAB FIXIERT: tragfaehig nur, wenn eine Zelle in BEIDEN Halbjahren
|
||||
OeR > 0 UND PF > 1 liefert, das 95-%-KI die Null ausschliesst UND die beiden
|
||||
Nachbar-Schwellen dasselbe Vorzeichen haben.
|
||||
|
||||
Aufruf: python backtest_rsi_v2.py [bars]
|
||||
"""
|
||||
import random
|
||||
import sys
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
from backtest_cost_gate import kennzahlen
|
||||
from core.analysis import calc_rsi
|
||||
from core.exit_model import LIVE, simulate
|
||||
from core.squeeze_scan import atr_series
|
||||
|
||||
PERIOD = 14
|
||||
SCHWELLEN = [(70, 30), (75, 25), (80, 20)]
|
||||
COOLDOWN = 12 # Bars, damit dieselbe Ueberdehnung nicht dutzendfach zaehlt
|
||||
|
||||
|
||||
def rsi_serie(C, period=PERIOD):
|
||||
"""Wilder-RSI je Bar. ⚠ Gegen `calc_rsi` verifiziert (s. `pruefe`)."""
|
||||
n = len(C)
|
||||
out = [50.0] * n
|
||||
if n < period + 1:
|
||||
return out
|
||||
g = l = 0.0
|
||||
for i in range(1, period + 1):
|
||||
d = C[i] - C[i - 1]
|
||||
g += max(d, 0.0); l += max(-d, 0.0)
|
||||
ag, al = g / period, l / period
|
||||
out[period] = 100.0 if al == 0 else 100.0 - 100.0 / (1.0 + ag / al)
|
||||
for i in range(period + 1, n):
|
||||
d = C[i] - C[i - 1]
|
||||
ag = (ag * (period - 1) + max(d, 0.0)) / period
|
||||
al = (al * (period - 1) + max(-d, 0.0)) / period
|
||||
out[i] = 100.0 if al == 0 else 100.0 - 100.0 / (1.0 + ag / al)
|
||||
return out
|
||||
|
||||
|
||||
def pruefe(C, R, proben=200):
|
||||
"""Die Serie MUSS der echten Funktion entsprechen — sonst Abbruch."""
|
||||
rnd = random.Random(42)
|
||||
schlimmster = 0.0
|
||||
for _ in range(proben):
|
||||
i = rnd.randrange(PERIOD + 5, len(C))
|
||||
schlimmster = max(schlimmster, abs(calc_rsi(C[:i + 1], PERIOD) - R[i]))
|
||||
return schlimmster
|
||||
|
||||
|
||||
def handeln(i, d, H, L, C, SP, A, ende, modus):
|
||||
"""modus 'markt' = Fill am Close von i · 'level' = ruhende Order am Extrem."""
|
||||
atr = A[i]
|
||||
if not atr or atr <= 0 or i + 1 >= ende:
|
||||
return None
|
||||
if modus == "markt":
|
||||
f, preis = i + 1, C[i]
|
||||
else:
|
||||
# Ruhende Order am Extrem der Ausloese-Bar, Fill bei BERUEHRUNG.
|
||||
preis = H[i] if d > 0 else L[i]
|
||||
f = None
|
||||
for x in range(i + 1, min(i + 13, ende)):
|
||||
if (H[x] >= preis) if d > 0 else (L[x] <= preis):
|
||||
f = x
|
||||
break
|
||||
if f is None or f + 1 >= ende:
|
||||
return None
|
||||
f += 1
|
||||
r = simulate(preis, d, atr, H, L, C, f, LIVE, timestop=True, use_tp=True)
|
||||
return r - ((SP[i] if SP[i] > 0 else 0.0225) / atr)
|
||||
|
||||
|
||||
def lauf(H, L, C, SP, A, R, lo_i, hi_i, ende, hi, lo, lesart, modus):
|
||||
rs, letzte = [], -10**9
|
||||
for i in range(max(lo_i, PERIOD + 1), hi_i):
|
||||
if i - letzte < COOLDOWN:
|
||||
continue
|
||||
if R[i] >= hi:
|
||||
d = -1 if lesart == "reversion" else 1
|
||||
elif R[i] <= lo:
|
||||
d = 1 if lesart == "reversion" else -1
|
||||
else:
|
||||
continue
|
||||
letzte = i
|
||||
r = handeln(i, d, H, L, C, SP, A, ende, modus)
|
||||
if r is not None:
|
||||
rs.append(r)
|
||||
return rs
|
||||
|
||||
|
||||
def zeig(lbl, k1, k2):
|
||||
def f(k):
|
||||
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"))
|
||||
return "n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f}{}".format(
|
||||
k["n"], k["wr"], k["oer"], k["pf"], m)
|
||||
ok = (k1 and k2 and k1["oer"] > 0 and k2["oer"] > 0 and k1["pf"] > 1
|
||||
and k2["pf"] > 1 and (k1.get("ci") or (0, 0))[0] > 0
|
||||
and (k2.get("ci") or (0, 0))[0] > 0)
|
||||
print(" {:<26} H1 {}".format(lbl, f(k1)))
|
||||
print(" {:<26} H2 {} {}".format("", f(k2), "✅" if ok else "❌"))
|
||||
return ok
|
||||
|
||||
|
||||
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)
|
||||
R = rsi_serie(C)
|
||||
N = len(C); mid = N // 2; ende = N - LIVE.max_hold - 1
|
||||
|
||||
fehler = pruefe(C, R)
|
||||
print("=" * 96)
|
||||
print(" RSI({}) auf {} M5 ({} Bars) · kanonischer Exit · Echtkosten · 95-%-KI"
|
||||
.format(PERIOD, sym, N))
|
||||
print(" Serie gegen die echte `calc_rsi` geprueft: groesste Abweichung {:.2e}"
|
||||
.format(fehler))
|
||||
assert fehler < 1e-6, "RSI-Serie weicht von calc_rsi ab — Abbruch"
|
||||
print("=" * 96)
|
||||
|
||||
treffer = []
|
||||
for modus, titel in (("markt", "A) MARKT am Bar-Close (= die drei alten Laeufe)"),
|
||||
("level", "B) ruhende Order am Extrem, Fill bei BERUEHRUNG")):
|
||||
print("\n " + titel)
|
||||
print(" " + "-" * 92)
|
||||
for lesart in ("reversion", "momentum"):
|
||||
for hi, lo in SCHWELLEN:
|
||||
k1 = kennzahlen(lauf(H, L, C, SP, A, R, 0, mid, ende, hi, lo, lesart, modus))
|
||||
k2 = kennzahlen(lauf(H, L, C, SP, A, R, mid, ende, ende, hi, lo, lesart, modus))
|
||||
if zeig("{} {}/{}".format(lesart.upper()[:3], hi, lo), k1, k2):
|
||||
treffer.append((modus, lesart, hi, lo))
|
||||
print("")
|
||||
|
||||
print(" " + "=" * 92)
|
||||
print(" Zellen, die die vorab fixierte Regel erfuellen: {}".format(
|
||||
len(treffer) or "KEINE"))
|
||||
for t in treffer:
|
||||
print(" {}".format(t))
|
||||
print(" ⚠ Nachbar-Robustheit zusaetzlich pruefen: eine einzelne Zelle unter")
|
||||
print(" 12 getesteten ist bei 5 % Irrtum die ERWARTETE Zahl an Fehlalarmen.")
|
||||
print(" " + "=" * 92)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user