Elliott entmachtet (v=144)
Vier Verbesserungen, alle aus dem Gemessenen abgeleitet — kein neues Signal.
Ausgangspunkt: die Karte hat genau eine belegte Funktion, das Veto; als Prognose
ist sie ein Muenzwurf (48-51 % ueber 1.071 Episoden).
(1) SELBST-KALIBRIERUNG. Neue Tabelle rec_outcomes: eine Zeile je EPISODE
(Richtungswechsel, nicht je Minute), Auswertung ~alle 5 min gegen candles_m1
nach 30/60 min, Snapshot rec_track, Zeile #vd-selfcal. Gegen 50 % zu lesen.
Warum der groesste Hebel: die Qualitaet war bis heute unsichtbar und musste
auf Nachfrage rueckwirkend gemessen werden. Dasselbe Muster
(pbreak_predictions) hat den Ausfall des P(break)-Modells LIVE aufgedeckt.
Mit 4 Szenarien getestet (tests_rec_outcomes.py). Zwei Fehlschlaege dabei
waren Testfehler, keine Code-Fehler — dokumentiert.
(2) alignment_stats auf die VOLLE Historie (1.216 statt 40 Trades) und
zusaetzlich je Lot. Absolute Euro-Vergleiche sind bei wechselnder
Positionsgroesse ungueltig (Lehre 04.08.). Damit steht die einzige
beidhaelftig robuste Aussage der Karte auf ihrer vollen Stichprobe.
(3) Block-Gruende sichtbar (block_mix -> #vd-blocks): "heute X % ohne Block ·
entry_room … · min_conf …". Bisher nur per DB-Abfrage zu beantworten.
(4) ELLIOTT: Verdict-Gewicht 1,0 -> 0. Gemessen ueber 15.544 Zeilen hatte es mit
1.384 Episoden die groesste Stichprobe der Tabelle und ist darin flach
(-0,075/+0,032, 50 % Treffer) — bei 25,9 % Einfluss auf die Nadel, weil es
in nur 1 % der Zeilen schweigt. Chip bleibt, Stimme entfaellt.
⚠ Folge: die Nadel schlaegt staerker aus (real +0,33 -> +1,00).
⚠ Namenskollision beim Bau gefunden und behoben: die neue Klasse hiess zuerst
.vd-track — so heisst bereits die Schiene der Bias-Nadel; sie waere
ueberschrieben worden. Jetzt .vd-selfcal.
Live verifiziert: alignment je Lot, block_mix (93 von 1381 ohne Block),
rec_outcomes angelegt, Elliott weight=0 bei erhaltenem Chip, v=144 ausgeliefert,
eine Instanz auf Port 8000.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
164 lines
6.2 KiB
Python
164 lines
6.2 KiB
Python
"""Szenario-Test der Selbst-Kalibrierung (rec_outcomes) — ohne MT5, auf DB-Kopie.
|
|
|
|
Geprueft werden die Faelle, die live schiefgehen koennen:
|
|
1) Episode wird EINMAL geloggt, nicht je Minute
|
|
2) WARTEN re-armt → die naechste Richtung ist eine neue Episode
|
|
3) Auswertung rechnet den Forward-Return korrekt (inkl. Broker-Offset)
|
|
4) fehlende M1-Bars → Zeile bleibt offen statt falsch bewertet
|
|
5) rec_accuracy aggregiert Trefferquote und Ø-Return richtig
|
|
"""
|
|
import os
|
|
import shutil
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import types
|
|
|
|
sys.path.insert(0, r"c:\Users\ah\Downloads\tradingrobot")
|
|
|
|
from core.engine import TradingEngine
|
|
from core.history import HistoryLogger
|
|
|
|
QUELL = r"c:\Users\ah\Downloads\tradingrobot\oil_widget_history.db"
|
|
TMP = os.path.join(tempfile.mkdtemp(), "t.db")
|
|
shutil.copy(QUELL, TMP)
|
|
con = sqlite3.connect(TMP)
|
|
con.execute("DELETE FROM rec_outcomes")
|
|
con.commit()
|
|
|
|
h = HistoryLogger(TMP)
|
|
fehler = []
|
|
|
|
|
|
def pruef(lbl, ist, soll):
|
|
ok = ist == soll
|
|
if not ok:
|
|
fehler.append(lbl)
|
|
print(f" {'OK ' if ok else 'FEHLER'} {lbl:<50} ist={ist!r:<14} soll={soll!r}")
|
|
|
|
|
|
print("=" * 86)
|
|
print(" 1) Episoden-Logging: eine Zeile je Richtungswechsel, nicht je Minute")
|
|
print("=" * 86)
|
|
|
|
|
|
class E:
|
|
"""Nur der Episoden-Teil aus `_run_analysis`, isoliert nachgestellt."""
|
|
def __init__(self):
|
|
self._rec_epi_sig = None
|
|
self.history = h
|
|
|
|
def tick(self, sig):
|
|
if sig in ("LONG", "SHORT") and sig != self._rec_epi_sig:
|
|
self.history.log_rec_outcome(signal=sig, conf_pct=70, tf="M5",
|
|
price=75.0, atr=0.2)
|
|
if sig in ("LONG", "SHORT", "WARTEN"):
|
|
self._rec_epi_sig = sig if sig != "WARTEN" else None
|
|
|
|
|
|
e = E()
|
|
for s in ["LONG"] * 5: # dieselbe Episode, 5 Minuten lang
|
|
e.tick(s)
|
|
pruef("5x LONG hintereinander → 1 Zeile",
|
|
con.execute("SELECT COUNT(*) FROM rec_outcomes").fetchone()[0], 1)
|
|
for s in ["SHORT"] * 3: # Flip = neue Episode
|
|
e.tick(s)
|
|
pruef("Flip auf SHORT → 2 Zeilen",
|
|
con.execute("SELECT COUNT(*) FROM rec_outcomes").fetchone()[0], 2)
|
|
e.tick("WARTEN"); e.tick("WARTEN")
|
|
pruef("WARTEN erzeugt keine Zeile",
|
|
con.execute("SELECT COUNT(*) FROM rec_outcomes").fetchone()[0], 2)
|
|
e.tick("SHORT")
|
|
pruef("nach WARTEN ist SHORT eine NEUE Episode",
|
|
con.execute("SELECT COUNT(*) FROM rec_outcomes").fetchone()[0], 3)
|
|
|
|
print()
|
|
print("=" * 86)
|
|
print(" 2) Auswertung gegen candles_m1 (mit Broker-Offset)")
|
|
print("=" * 86)
|
|
con.execute("DELETE FROM rec_outcomes")
|
|
con.execute("DELETE FROM candles_m1 WHERE symbol='TESTSYM'")
|
|
OFF = 3 * 3600
|
|
t0 = int(time.time()) - 7200 # vor 2 h → beide Horizonte faellig
|
|
# LONG @ 75.00, ATR 0.20; nach 30 min steht 75.10 (+0,5 ATR), nach 60 min 74.90
|
|
con.execute("INSERT INTO rec_outcomes (ts,signal,conf_pct,tf,price,atr) "
|
|
"VALUES (?,?,?,?,?,?)", (t0, "LONG", 70, "M5", 75.00, 0.20))
|
|
for minuten, kurs in ((30, 75.10), (60, 74.90)):
|
|
con.execute("INSERT OR REPLACE INTO candles_m1 (time,o,h,l,c,spread,tick_volume,symbol)"
|
|
" VALUES (?,?,?,?,?,?,?,?)",
|
|
(t0 + minuten * 60 + OFF, kurs, kurs, kurs, kurs, 20, 1, "TESTSYM"))
|
|
con.commit()
|
|
|
|
eng = TradingEngine.__new__(TradingEngine)
|
|
eng._rec_eval_ts = 0.0
|
|
eng.trader = types.SimpleNamespace(_broker_offset_s=lambda: OFF)
|
|
import core.engine as CE
|
|
_alt = CE.HISTORY_DB_FILE
|
|
CE.HISTORY_DB_FILE = TMP
|
|
eng._evaluate_rec_outcomes()
|
|
CE.HISTORY_DB_FILE = _alt
|
|
|
|
r = con.execute("SELECT ret30,hit30,ret60,hit60,done FROM rec_outcomes").fetchone()
|
|
pruef("ret30 = (75.10-75.00)/0.20 = +0.5", round(r[0], 3), 0.5)
|
|
pruef("hit30 = 1 (lief in Signalrichtung)", r[1], 1)
|
|
pruef("ret60 = (74.90-75.00)/0.20 = -0.5", round(r[2], 3), -0.5)
|
|
pruef("hit60 = 0", r[3], 0)
|
|
pruef("done = 1", r[4], 1)
|
|
|
|
print()
|
|
print("=" * 86)
|
|
print(" 3) SHORT-Richtung spiegelbildlich · fehlende Bars → bleibt offen")
|
|
print("=" * 86)
|
|
con.execute("DELETE FROM rec_outcomes")
|
|
# ⚠ Test-Kerzen aus Abschnitt 2 MUESSEN weg — sonst faellt die dortige
|
|
# 60-Minuten-Bar in das Fenster dieses Abschnitts (beide Anker liegen 2 h
|
|
# zurueck) und der Fall "keine Bar" waere gar nicht getestet.
|
|
con.execute("DELETE FROM candles_m1 WHERE symbol='TESTSYM'")
|
|
t1 = int(time.time()) - 7200
|
|
con.execute("INSERT INTO rec_outcomes (ts,signal,conf_pct,tf,price,atr) "
|
|
"VALUES (?,?,?,?,?,?)", (t1, "SHORT", 70, "M5", 75.00, 0.20))
|
|
con.execute("INSERT OR REPLACE INTO candles_m1 (time,o,h,l,c,spread,tick_volume,symbol)"
|
|
" VALUES (?,?,?,?,?,?,?,?)",
|
|
(t1 + 1800 + OFF, 74.9, 74.9, 74.9, 74.9, 20, 1, "TESTSYM"))
|
|
# fuer 60 min BEWUSST keine Bar → Zeile darf nicht faelschlich bewertet werden.
|
|
# ⚠ Dafuer muss das 60-Minuten-Fenster WIRKLICH leer sein: die DB-Kopie enthaelt
|
|
# ECHTE candles_m1 aus dem Live-Betrieb, und die liegen genau hier. Ohne dieses
|
|
# DELETE testet man nicht "keine Bar", sondern "echte Bar" (erster Versuch lieferte
|
|
# ret60 = -14,985 = der reale Kurs ~78).
|
|
con.execute("DELETE FROM candles_m1 WHERE time > ? AND time <= ?",
|
|
(t1 + 3600 + OFF - 900, t1 + 3600 + OFF))
|
|
con.commit()
|
|
eng._rec_eval_ts = 0.0
|
|
CE.HISTORY_DB_FILE = TMP
|
|
eng._evaluate_rec_outcomes()
|
|
CE.HISTORY_DB_FILE = _alt
|
|
r = con.execute("SELECT ret30,hit30,ret60,done FROM rec_outcomes").fetchone()
|
|
pruef("SHORT: Kurs faellt → ret30 = +0.5", round(r[0], 3), 0.5)
|
|
pruef("SHORT: hit30 = 1", r[1], 1)
|
|
pruef("ret60 ohne Bar bleibt NULL", r[2], None)
|
|
pruef("done bleibt offen (0/None)", bool(r[3]), False)
|
|
|
|
print()
|
|
print("=" * 86)
|
|
print(" 4) rec_accuracy aggregiert")
|
|
print("=" * 86)
|
|
con.execute("DELETE FROM rec_outcomes")
|
|
for ret, hit in ((0.5, 1), (0.3, 1), (-0.4, 0), (-0.2, 0)):
|
|
con.execute("INSERT INTO rec_outcomes (ts,signal,conf_pct,tf,price,atr,"
|
|
"ret30,hit30,ret60,hit60,done) VALUES (?,?,?,?,?,?,?,?,?,?,1)",
|
|
(int(time.time()), "LONG", 70, "M5", 75.0, 0.2, ret, hit, ret, hit))
|
|
con.commit()
|
|
a = h.rec_accuracy(200)
|
|
pruef("n = 4", a["n"], 4)
|
|
pruef("Trefferquote 30' = 50 %", a["wr30"], 50.0)
|
|
pruef("Ø-Return 30' = +0.05", a["avg30"], 0.05)
|
|
|
|
con.close()
|
|
print()
|
|
print("=" * 86)
|
|
print(" ERGEBNIS: " + ("alle Szenarien korrekt" if not fehler
|
|
else f"{len(fehler)} FEHLER: {fehler}"))
|
|
print("=" * 86)
|
|
sys.exit(1 if fehler else 0)
|