M15-Auto-Entry gebaut (Default AUS) -- gegen die Messung, auf User-Wunsch

User wollte den Auto-Trade testen, nach vollstaendiger Information ueber die
Beleglage. backtest_m15_auto.py (13.08.): die gehandelte Lesart ist in BEIDEN
Halbjahren negativ (OeR -0,162/-0,094, PF 0,72/0,84), beide KI schliessen die
Null aus, Nachbarn monoton schlechter, und sie ist schlechter als die
HTF-Richtung UND als ihr eigenes Gegenteil. Live entkoppelt 46,8 % Treffer.

_check_auto_m15 im _pos_loop: Ausloeser gruen + gerichtet, Richtung exakt wie im
Backtest (auf->LONG, ab->SHORT), Dedup 1x je Episode ueber (Richtung, Level),
nur FLAT, Setup-Tag AUTOM15_*. Erbt die VOLLE Guard-Kette ueber
_auto_guard('m15') -- der Parameter war laut Docstring genau dafuer vorgesehen,
nachgebaut wurde nichts.

Abweichung ausdruecklich benannt: gemessen wurde der Einstieg AM Level mit Fill
bei Beruehrung, gebaut ist der Markt-Einstieg. Der ist hier mehrfach als der
schlechtere gemessen -- die Live-Zahlen sind eher noch unter -0,16/-0,09 zu
erwarten.

Abbruchregel VORAB fixiert: nach >=20 AUTOM15_*-Trades Verhaeltnis < 1,0 ODER
PF < 1 -> auto_m15 = false. Dieselbe Latte wie B5, damit sie nicht nachtraeglich
wandert.

Verifiziert: Toggle, Snapshot-Felder, Deploy mit --feld auto_m15, ruff F821
(neuer Code sitzt in einem except -- der dokumentierte blinde Fleck), 85 Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-19 18:01:52 +02:00
co-authored by Claude Opus 5
parent cc3a710752
commit bd65bdaad0
6 changed files with 5185 additions and 4 deletions
+111 -2
View File
@@ -382,6 +382,19 @@ class TradingEngine:
# ⚠ ECHTE Order — Default AUS (Autonomie nie stillschweigend an). `[trading]
# auto_squeeze=true` aktiviert. Dedup über (dir, level) des Ausbruchs.
self._auto_squeeze = self.cfg["trading"].get("auto_squeeze", "false").lower() == "true"
# ── M15-KARTEN-AUTO-ENTRY (User-Wunsch 2026-08-19, "ich moechte das
# auto trade testen"). ⚠⚠ GEGEN DIE MESSUNG gebaut und deshalb
# Default AUS: `backtest_m15_auto.py` (13.08.) hat genau diese Regel
# gemessen -- die Lesart ist in BEIDEN Halbjahren negativ
# (OeR -0,162/-0,094, PF 0,72/0,84), das 95-%-KI schliesst beide Male
# die Null aus, und sie ist schlechter als die HTF-Richtung UND als
# ihr eigenes Gegenteil. Live bestaetigt (m15_states, entkoppelt):
# 46,8 % Treffer auf 30 min. Der User hat das nach vollstaendiger
# Information ausdruecklich zum TESTEN gewuenscht.
self._auto_m15 = self.cfg["trading"].get("auto_m15", "false").lower() == "true"
self._m15_last_entry_sig = None # Dedup (Richtung, Level) wie im Backtest
self._m15_entry_count = 0
self._last_m15 = None # letzter berechneter Kartenstand
self._squeeze_skip_night = self.cfg["trading"].get("auto_squeeze_skip_night", "true").lower() == "true"
# ── WELCHE Stunden ausgesetzt werden (2026-08-05, gemessen `backtest_cost_gate.py`).
# War fest 07. Die Messung zeigt: KEINE Stunde ist in beiden Halbjahren negativ —
@@ -825,6 +838,7 @@ class TradingEngine:
self._check_pending_fill() # gefuellte Pending-Order zuordnen
self._manage_squeeze_pending()
self._check_auto_squeeze() # autonomer Entry auf Squeeze-Ausbruch
self._check_auto_m15() # autonomer Entry auf die gruene M15-Karte
except Exception as e:
log.error(f"_pos_loop: {e}", exc_info=True)
self._sleep(_POS_S)
@@ -1158,8 +1172,13 @@ class TradingEngine:
# den Squeeze. Der Parameter `pfad` bleibt in der Signatur — die
# Guard-Kette ist bewusst EINE Quelle für alle autonomen Wege, und ein
# künftiger zweiter Weg soll sie wieder benutzen, nicht nachbauen.
if not self._auto_squeeze:
return "Auto-Squeeze aus"
# ⚠ Pfad-abhaengig seit 2026-08-19: der M15-Auto-Entry nutzt DIESELBE
# Guard-Kette (Nacht, Circuit, News) statt sie nachzubauen -- genau das,
# wofuer der Parameter laut Docstring gedacht ist.
_an = {"squeeze": self._auto_squeeze,
"m15": getattr(self, "_auto_m15", False)}.get(pfad, self._auto_squeeze)
if not _an:
return ("Auto-M15 aus" if pfad == "m15" else "Auto-Squeeze aus")
skip = self._squeeze_skip_night
if self._circuit_halted():
return "Circuit-Breaker"
@@ -1179,6 +1198,90 @@ class TradingEngine:
"""Alias — `_check_auto_squeeze` ruft weiterhin diesen Namen."""
return self._auto_guard("squeeze")
def set_auto_m15(self, on: bool) -> bool:
"""M15-Karten-Auto-Entry an/aus. Rueckgabe = neuer Zustand."""
self._auto_m15 = bool(on)
self._m15_last_entry_sig = None
log.info(f"Auto-M15-Entry {'AN' if self._auto_m15 else 'AUS'}")
self._save_runtime_state()
return self._auto_m15
def _check_auto_m15(self):
"""Autonomer Entry auf die GRUENE, gerichtete M15-Karte (Default AUS).
GEGEN DIE MESSUNG. `backtest_m15_auto.py` (13.08.): die Lesart ist in
BEIDEN Halbjahren negativ (OeR -0,162 / -0,094, PF 0,72 / 0,84), das
95-%-KI schliesst beide Male die Null aus, alle Nachbar-Schwellen werden
monoton schlechter, und sie ist schlechter als die HTF-Richtung UND als
ihr eigenes Gegenteil. Auf User-Wunsch zum TESTEN gebaut.
ABWEICHUNG VON DER GEMESSENEN VARIANTE, ausdruecklich benannt: gemessen
wurde der Einstieg AM Level mit Fill bei Beruehrung; hier wird zum MARKT
eingestiegen. Im Projekt ist der Markt-Einstieg mehrfach als der
schlechtere gemessen (Squeeze +0,456 -> -0,158 allein durch den Preis).
Die Live-Zahlen sind also eher noch unter den -0,16/-0,09 zu erwarten.
Eine ruhende Order haette den geteilten `_pending_ziel`-Manager
gebraucht, in dem der Squeeze Vorrang hat -- das ist ein eigener Umbau.
Dedup 1x je Episode ueber (Richtung, Level) -- exakt der Backtest-
Schluessel `(d_les, nah["level"])`. Erbt die volle Guard-Kette
(`_auto_guard("m15")`: Nacht, Circuit-Breaker, News-Blackout) sowie
Startup-Schonfrist, Flat-Bedingung und die Wiedereinstiegs-Sperren.
"""
if not self._auto_m15:
return
try:
if self._in_startup_grace():
return
s = getattr(self, "_last_m15", None)
if not s or s.get("status") != "gruen":
self._m15_last_entry_sig = None # re-armt bei Nicht-Gruen
return
les = s.get("lesart")
if les not in ("auf", "ab"):
self._m15_last_entry_sig = None
return
nah = s.get("nah") or {}
lvl = nah.get("level")
if lvl is None:
return
d = 1 if les == "auf" else -1 # wie im Backtest
sig = (d, round(float(lvl), 3))
if sig == self._m15_last_entry_sig:
return # dieselbe Episode
if (self.trader.snapshot() or {}).get("lots"):
return # nur FLAT
grund = self._auto_guard("m15")
if grund:
return
# Wiedereinstiegs-Sperren teilen wir mit dem Squeeze (gleiche Logik,
# gleiche Begruendung: Open-Close-Kaskaden nach einem Auto-Close).
now = time.time()
if (self._sr_close_dir == d
and now - self._sr_close_ts < _SQUEEZE_SR_COOLDOWN_S):
return
if (self._pos_close_dir == d
and now - self._pos_close_ts < _SQUEEZE_REENTRY_COOLDOWN_S):
return
self._m15_last_entry_sig = sig
want = "long" if d > 0 else "short"
# ⚠ Setup-TAG ist Pflicht, nicht Kosmetik: ohne ihn sind die Trades
# in der DB von manuellen NICHT unterscheidbar und der Test spaeter
# nicht auswertbar (Deployment-Drift Fall 8, 06.08.: vier reale
# Squeeze-Trades landeten als setup=NULL, damit zaehlten B4-Monitor
# und Abbruchregel NULL Bot-Trades).
self._last_rec["setup"] = f"AUTOM15_{want.upper()}"
fn = self.trader.open_long if d > 0 else self.trader.open_short
err = self._open(fn, want, source="auto_m15")
if err:
log.warning(f"Auto-M15-Entry fehlgeschlagen: {err}")
return
self._m15_entry_count += 1
log.info(f"AUTO-M15-ENTRY: {want.upper()} auf gruene Karte "
f"(Lesart {les}, Level {lvl}) — selbstaendig eroeffnet")
except Exception as e:
log.warning(f"_check_auto_m15: {e}")
def _check_pending_fill(self):
"""Hat eine von UNS platzierte Pending-Order gefuellt? Dann nachtragen.
@@ -2161,6 +2264,8 @@ class TradingEngine:
if "manual_margin" in st:
from core.config import set_manual_margin as _set
self._manual_margin = _set(st["manual_margin"] or 0)
if "auto_m15" in st:
self._auto_m15 = bool(st["auto_m15"])
if "cb_remember" in st:
self._cb_remember = float(st["cb_remember"] or 8.0)
if "cb_limit_pct" in st:
@@ -2200,6 +2305,7 @@ class TradingEngine:
# 0 = aus. So ueberlebt auch die Hoehe den Neustart.
"cb_limit_pct": getattr(self, "_cb_limit_pct", 0.0),
"cb_remember": getattr(self, "_cb_remember", 8.0),
"auto_m15": getattr(self, "_auto_m15", False),
# Kegel-ACI-Skalen (2026-08-08): ohne Persistenz waere
# die muehsam erlernte Kalibrierung nach jedem Neustart
# weg und liefe von 1,0 neu an. ⚠ JSON-Schluessel sind
@@ -4181,6 +4287,7 @@ class TradingEngine:
# die Fehlerklasse, fuer die der Linter am 07.08. eingebaut wurde.
_m15 = self._m15_setup(market, wave_snap, _cone,
self._alignment_cached(), wave_signal)
self._last_m15 = _m15 # fuer `_check_auto_m15` im 1-s-Loop
self._log_m15_state(_m15, market)
self._check_m15_alert(_m15) # Telegram bei Flanke auf GRUEN
self._log_cone_check(_cone)
@@ -4352,6 +4459,8 @@ class TradingEngine:
# ⚠ Eigenes Fenster (ab 01.08.) — NICHT dasselbe wie
# `squeeze_monitor.live` (alle Trades). s. `_squeeze_b5_cached`.
"squeeze_b5": self._squeeze_b5_cached(),
"auto_m15": self._auto_m15,
"m15_entry_count": self._m15_entry_count,
# Kennzahlen der MANUELLEN Trades für die gleichnamige Karte
# (2026-08-04). Rein deskriptiv; ~120 s gecacht, damit der heiße
# Snapshot-Pfad nicht je Abruf die DB aggregiert.
File diff suppressed because it is too large Load Diff