Auto-Signal-Entry (SIG) komplett entfernt inkl. Button (v=166)
User-Entscheidung. Der Pfad war seit 01.08. aus und auf BEIDEN Ausfuehrungs-Achsen gemessen negativ: Markt-Einstieg in allen 8 Feldern, ruhende Order mit Touch-Fill -0,392/-0,338 in allen 6 und damit sogar schlechter als Markt. Die +0,35/+0,32 aus backtest_auto_signal_v3.py galten in Variante B (Fill beim Close-Bruch) und waren Look-ahead - Aufschlag 0,77-0,85 R. Ursache strukturell: das Bestaetigungs-Level _pend WANDERT. Nebenbei entfaellt der offene Churn-Defekt (4x setzen/stornieren in 76 s bei identischem Level, gefunden 06.08., nie behoben). Entfernt: _check_auto_signal, set_auto_signal, der Signal-Zweig in _pending_ziel, /api/autosignal, Button + Handler, vier Snapshot-Felder, die auto_signal*/signal_pending_entry-Leser. Geblieben: AUTOSIG_*-Trades in der DB, die Backtests, wave_rec.breakout (speist block_reason) und _confirm_breakout selbst. Sicherung: .removed_backup/auto_signal_2026-08-12.py + Git. Das Risiko war nicht SIG, sondern der GETEILTE Pending-Manager (zwei getrennte Manager wuerden sich gegenseitig stornieren). Deshalb wurde das Squeeze-Verhalten VOR dem Eingriff in tests/test_pending_ziel.py festgenagelt - 7 Tests, vorher gruen, laufen unveraendert weiter. Zwei Folgefehler fing die Pruefkette sofort: ruff F821 ein verwaistes _t, py_compile ein dangling if. Zwei Tests wurden mitgezogen statt geloescht - test_signal_fill_long ist jetzt umgedreht (unbekannte Quelle darf NICHT als Squeeze durchgehen). Live verifiziert: alle vier Snapshot-Felder weg, Endpunkt tot, auto_squeeze/squeeze_pending unveraendert, 67 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b85dd010ab
commit
bf7e382818
@@ -6161,6 +6161,52 @@ Neumessung. Sie bleiben unverändert und reproduzierbar; wo ihre Schlussfolgerun
|
|||||||
relevant wird, gehört sie live-treu **neu gerechnet** (Muster:
|
relevant wird, gehört sie live-treu **neu gerechnet** (Muster:
|
||||||
`backtest_auto_signal_norev.py`).
|
`backtest_auto_signal_norev.py`).
|
||||||
|
|
||||||
|
## ✅ AUTO-SIGNAL-ENTRY (🎯 SIG) KOMPLETT ENTFERNT (User-Entscheidung 2026-08-12)
|
||||||
|
|
||||||
|
User: „den Signal-Autotrader brauchen wir im Moment nicht mehr — sollten wir den
|
||||||
|
inkl. Button entfernen?" **Ja.** Er war seit dem 01.08. aus und ist auf **beiden**
|
||||||
|
Ausführungs-Achsen gemessen negativ:
|
||||||
|
| Variante | H1 / H2 |
|
||||||
|
|---|---|
|
||||||
|
| Markt-Einstieg | negativ in **allen 8** Feldern (`backtest_auto_signal.py` 30.07., `_norev` 04.08.) |
|
||||||
|
| ruhende Order, Fill bei BERÜHRUNG | **−0,392/−0,338**, negativ in **allen 6** Feldern (`backtest_signal_touchfill.py` 06.08.) |
|
||||||
|
⚠⚠ **Der zweite Punkt gibt den Ausschlag:** die ruhende Order — genau der Umbau,
|
||||||
|
der den Squeeze gerettet hat — macht SIG sogar **schlechter als den
|
||||||
|
Markt-Einstieg**. Die +0,35/+0,32 aus `backtest_auto_signal_v3.py` galten in
|
||||||
|
Variante B (Fill beim Close-Bruch) und waren **Look-ahead**; der Aufschlag betrug
|
||||||
|
0,77–0,85 R. Ursache ist strukturell und nicht reparierbar: `_pend` **wandert**
|
||||||
|
(neu verankert bei jeder k×ATR-Gegenbewegung, nach Timeout, bei Richtungswechsel)
|
||||||
|
— „ein Level ist nur dann ein Ausführungs-Vorteil, wenn es STILLSTEHT".
|
||||||
|
✅ Nebenbei entfällt der **offene Churn-Defekt** (4× setzen/stornieren in 76 s bei
|
||||||
|
identischem Level, gefunden 06.08., nie behoben).
|
||||||
|
|
||||||
|
**Entfernt:** `_check_auto_signal` · `set_auto_signal` · der Signal-Zweig in
|
||||||
|
`_pending_ziel` · `POST /api/autosignal` · Button 🎯 SIG (HTML + Handler) · die
|
||||||
|
vier Snapshot-Felder · `signal_pending_entry` und die `auto_signal*`-Leser.
|
||||||
|
**Geblieben:** die **AUTOSIG_\*-Trades in der DB** (Historie), die Backtests (sie
|
||||||
|
dokumentieren das Warum), `wave_rec.breakout` (speist `block_reason`) und
|
||||||
|
`_confirm_breakout` selbst (das ist das Gate, nicht der Trader).
|
||||||
|
Sicherung: **`.removed_backup/auto_signal_2026-08-12.py`** + Git-Historie
|
||||||
|
(Vorbild: der Wave-Auto-Trader, 23.07. genauso entfernt).
|
||||||
|
|
||||||
|
⚠⚠ **DAS RISIKO WAR NICHT SIG, SONDERN DER GETEILTE MANAGER.** Squeeze und Signal
|
||||||
|
teilten sich `_pending_ziel` — bewusst, weil zwei getrennte Manager über
|
||||||
|
`trader.pending_orders()` die Orders des jeweils anderen sehen und sich
|
||||||
|
**gegenseitig stornieren** würden. Deshalb wurde das Squeeze-Verhalten **VOR** dem
|
||||||
|
Eingriff in `tests/test_pending_ziel.py` festgenagelt (7 Tests: beide Box-Grenzen ·
|
||||||
|
nicht-armed · Guard · Feature aus · kein ATR · fehlende Box · ATR-Skalierung) —
|
||||||
|
sie liefen vorher grün und laufen **unverändert** weiter.
|
||||||
|
✅ **Zwei Folgefehler fing die Prüfkette sofort:** `ruff F821` meldete ein
|
||||||
|
verwaistes `_t` (im entfernten Block definiert, zwei Zeilen später noch benutzt),
|
||||||
|
und `py_compile` ein dangling `if` im runtime_state-Laden. Beide behoben.
|
||||||
|
✅ **Zwei Tests mitgezogen statt gelöscht:** `test_signal_fill_long` wurde
|
||||||
|
**umgedreht** — ein Pending-Fill mit unbekannter Quelle darf jetzt NICHT
|
||||||
|
stillschweigend als Squeeze durchgehen (das wäre Deployment-Drift Fall 8 mit
|
||||||
|
umgekehrtem Vorzeichen); und die Systemzustand-Zeile prüft nun ausdrücklich, dass
|
||||||
|
„SIG" **nicht wieder auftaucht**.
|
||||||
|
✅ **Live verifiziert:** alle vier Snapshot-Felder weg, `/api/autosignal`
|
||||||
|
antwortet nicht mehr, `auto_squeeze`/`squeeze_pending` unverändert, 67 Tests grün.
|
||||||
|
|
||||||
## ⚠⚠ WIEDEREINSTIEG NACH S/R-CLOSE — 2-HALBJAHRES-BACKTEST: FÄLLT DURCH (2026-08-12)
|
## ⚠⚠ WIEDEREINSTIEG NACH S/R-CLOSE — 2-HALBJAHRES-BACKTEST: FÄLLT DURCH (2026-08-12)
|
||||||
|
|
||||||
`backtest_srclose_reentry.py`, 80k M5-Bars. Nachfolger des Live-Laufs (n=14, von
|
`backtest_srclose_reentry.py`, 80k M5-Bars. Nachfolger des Live-Laufs (n=14, von
|
||||||
|
|||||||
+53
-162
@@ -354,11 +354,6 @@ class TradingEngine:
|
|||||||
"squeeze_max_chase_atr", "0.20"))
|
"squeeze_max_chase_atr", "0.20"))
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
self._squeeze_max_chase = 0.20
|
self._squeeze_max_chase = 0.20
|
||||||
# Signal-Pfad bekommt dieselbe Mechanik (2026-08-05, gemessen
|
|
||||||
# `backtest_auto_signal_v3.py`: am Bestätigungs-Level ØR +0,35/+0,32 statt
|
|
||||||
# −0,09/−0,11 am Markt — SIG besteht damit ERSTMALS die 2-Stichproben-Regel).
|
|
||||||
self._signal_pending = self.cfg["trading"].get(
|
|
||||||
"signal_pending_entry", "true").lower() == "true"
|
|
||||||
self._pending_levels = None
|
self._pending_levels = None
|
||||||
self._pending_quelle = ""
|
self._pending_quelle = ""
|
||||||
# Welche Pending-Order stammt von welchem Pfad? Bei einem FILL ist die
|
# Welche Pending-Order stammt von welchem Pfad? Bei einem FILL ist die
|
||||||
@@ -368,22 +363,25 @@ class TradingEngine:
|
|||||||
self._pending_tagged: set = set()
|
self._pending_tagged: set = set()
|
||||||
self._squeeze_pending_err = None
|
self._squeeze_pending_err = None
|
||||||
self._squeeze_chase_logged = None
|
self._squeeze_chase_logged = None
|
||||||
self._signal_chase_logged = None
|
|
||||||
self._squeeze_reverse_sig = None # Dedup Reverse-Close (Gegen-Trade)
|
self._squeeze_reverse_sig = None # Dedup Reverse-Close (Gegen-Trade)
|
||||||
self._squeeze_entry_count = 0
|
self._squeeze_entry_count = 0
|
||||||
# ── Autonomer Entry auf die EMPFEHLUNG (User-Wunsch 2026-07-30) ───────────
|
# ⚠⚠ DER AUTONOME ENTRY AUF DIE EMPFEHLUNG (Auto-Signal / „SIG") IST AM
|
||||||
# ⚠ GEMESSEN NICHT tragfähig (H1 negativ, s. backtest_auto_signal.py) — auf
|
# 2026-08-12 KOMPLETT ENTFERNT (User-Entscheidung). Er war seit dem
|
||||||
# ausdrücklichen User-Wunsch gebaut, Default AUS, mit den besten Parametern.
|
# 01.08. aus und auf BEIDEN Ausfuehrungs-Achsen gemessen negativ —
|
||||||
_t = self.cfg["trading"]
|
# Markt-Einstieg in allen 8 Feldern, ruhende Order mit Touch-Fill in
|
||||||
self._auto_signal = _t.get("auto_signal", "false").lower() == "true"
|
# allen 6 und dabei sogar SCHLECHTER als Markt. Ursache strukturell:
|
||||||
try:
|
# das Bestaetigungs-Level `_pend` WANDERT.
|
||||||
self._auto_signal_min_conf = int(float(_t.get("auto_signal_min_conf", "75")))
|
# Weg sind: `_check_auto_signal`, `set_auto_signal`, der Signal-Zweig in
|
||||||
except (TypeError, ValueError):
|
# `_pending_ziel`, `/api/autosignal`, der Dashboard-Button und die
|
||||||
self._auto_signal_min_conf = 75
|
# Snapshot-Felder. Die ini-Schluessel (`auto_signal*`,
|
||||||
self._auto_signal_skip_night = _t.get("auto_signal_skip_night", "true").lower() == "true"
|
# `signal_pending_entry`) werden nicht mehr gelesen.
|
||||||
self._signal_last_sig = None # Dedup: (dir, Signal-Episode) 1× handeln
|
# GEBLIEBEN: die AUTOSIG_*-Trades in der DB (Historie) und die
|
||||||
self._signal_entry_count = 0
|
# Backtests (sie dokumentieren das Warum).
|
||||||
|
# Sicherung: `.removed_backup/auto_signal_2026-08-12.py` + Git.
|
||||||
# 15-Minuten-Regel für BOT-eröffnete Trades (gemessen kalibriert: 0,5×ATR)
|
# 15-Minuten-Regel für BOT-eröffnete Trades (gemessen kalibriert: 0,5×ATR)
|
||||||
|
# ⚠ `_t` wurde bis 2026-08-12 im entfernten Auto-Signal-Block gesetzt; der
|
||||||
|
# Linter (`ruff F821`) hat den Bruch beim Entfernen sofort gemeldet.
|
||||||
|
_t = self.cfg["trading"]
|
||||||
try:
|
try:
|
||||||
self._adverse15_atr = max(0.0, float(_t.get("adverse_15min_atr", "0.5")))
|
self._adverse15_atr = max(0.0, float(_t.get("adverse_15min_atr", "0.5")))
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
@@ -765,7 +763,6 @@ class TradingEngine:
|
|||||||
self._check_pending_fill() # gefuellte Pending-Order zuordnen
|
self._check_pending_fill() # gefuellte Pending-Order zuordnen
|
||||||
self._manage_squeeze_pending()
|
self._manage_squeeze_pending()
|
||||||
self._check_auto_squeeze() # autonomer Entry auf Squeeze-Ausbruch
|
self._check_auto_squeeze() # autonomer Entry auf Squeeze-Ausbruch
|
||||||
self._check_auto_signal() # autonomer Entry auf die Empfehlung
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"_pos_loop: {e}", exc_info=True)
|
log.error(f"_pos_loop: {e}", exc_info=True)
|
||||||
self._sleep(_POS_S)
|
self._sleep(_POS_S)
|
||||||
@@ -1092,14 +1089,13 @@ class TradingEngine:
|
|||||||
Divergenz, die dieses Projekt siebenmal als Deployment-Drift dokumentiert hat
|
Divergenz, die dieses Projekt siebenmal als Deployment-Drift dokumentiert hat
|
||||||
— und bei einer RUHENDEN Order wiegt sie schwerer: eine vergessene
|
— und bei einer RUHENDEN Order wiegt sie schwerer: eine vergessene
|
||||||
Pending-Order feuert im gesperrten Fenster, ohne dass jemand hinschaut."""
|
Pending-Order feuert im gesperrten Fenster, ohne dass jemand hinschaut."""
|
||||||
if pfad == "squeeze":
|
# ⚠ Seit dem Entfernen des Auto-Signal-Pfads (2026-08-12) gibt es nur noch
|
||||||
|
# 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:
|
if not self._auto_squeeze:
|
||||||
return "Auto-Squeeze aus"
|
return "Auto-Squeeze aus"
|
||||||
skip = self._squeeze_skip_night
|
skip = self._squeeze_skip_night
|
||||||
else:
|
|
||||||
if not self._auto_signal:
|
|
||||||
return "Auto-Signal aus"
|
|
||||||
skip = self._auto_signal_skip_night
|
|
||||||
if self._circuit_halted():
|
if self._circuit_halted():
|
||||||
return "Circuit-Breaker"
|
return "Circuit-Breaker"
|
||||||
if skip and datetime.now(_BERLIN).hour in self._squeeze_night_hours:
|
if skip and datetime.now(_BERLIN).hour in self._squeeze_night_hours:
|
||||||
@@ -1143,17 +1139,23 @@ class TradingEngine:
|
|||||||
if not quelle:
|
if not quelle:
|
||||||
return # fremde/manuelle Position
|
return # fremde/manuelle Position
|
||||||
d = "LONG" if ps.get("order_type") == 0 else "SHORT"
|
d = "LONG" if ps.get("order_type") == 0 else "SHORT"
|
||||||
setup = f"SQUEEZE_{d}" if quelle == "squeeze" else f"AUTOSIG_{d}"
|
# ⚠ Seit 2026-08-12 legt nur noch der Squeeze Pending-Orders (der
|
||||||
|
# Auto-Signal-Pfad ist entfernt). Die `quelle` bleibt trotzdem im
|
||||||
|
# Marker — ein künftiger zweiter Weg würde sie wieder brauchen, und
|
||||||
|
# ein Fill ohne bekannte Quelle darf NICHT stillschweigend als
|
||||||
|
# Squeeze gebucht werden (Deployment-Drift Fall 8).
|
||||||
|
if quelle != "squeeze":
|
||||||
|
log.warning(f"Pending-Fill mit unbekannter Quelle {quelle!r} "
|
||||||
|
f"(Ticket {tk}) — NICHT getaggt")
|
||||||
|
return
|
||||||
|
setup = f"SQUEEZE_{d}"
|
||||||
self._pending_tagged.add(tk)
|
self._pending_tagged.add(tk)
|
||||||
self._pending_tickets.pop(int(tk), None)
|
self._pending_tickets.pop(int(tk), None)
|
||||||
ok = self.history.tag_bot_trade(int(tk), setup) if self.history else False
|
ok = self.history.tag_bot_trade(int(tk), setup) if self.history else False
|
||||||
# Dieselbe Buchfuehrung wie beim Market-Pfad, damit B4/B5 zaehlen
|
# Dieselbe Buchfuehrung wie beim Market-Pfad, damit B4/B5 zaehlen
|
||||||
self._bot_open_ticket = tk
|
self._bot_open_ticket = tk
|
||||||
self._bot_open_source = "auto_squeeze" if quelle == "squeeze" else "auto_signal"
|
self._bot_open_source = "auto_squeeze"
|
||||||
if quelle == "squeeze":
|
|
||||||
self._squeeze_entry_count += 1
|
self._squeeze_entry_count += 1
|
||||||
else:
|
|
||||||
self._signal_entry_count += 1
|
|
||||||
log.info(f"🎯 PENDING-FILL ({quelle}): {d} @ {ps.get('entry_price')} "
|
log.info(f"🎯 PENDING-FILL ({quelle}): {d} @ {ps.get('entry_price')} "
|
||||||
f"— Position {tk} als {setup} getaggt"
|
f"— Position {tk} als {setup} getaggt"
|
||||||
+ ("" if ok else " ⚠ DB-Nachtrag fehlgeschlagen"))
|
+ ("" if ok else " ⚠ DB-Nachtrag fehlgeschlagen"))
|
||||||
@@ -1195,17 +1197,25 @@ class TradingEngine:
|
|||||||
return ({mt5.ORDER_TYPE_BUY_STOP: round(float(hi) + _SQ_K_ENG * atr, 3),
|
return ({mt5.ORDER_TYPE_BUY_STOP: round(float(hi) + _SQ_K_ENG * atr, 3),
|
||||||
mt5.ORDER_TYPE_SELL_STOP: round(float(lo) - _SQ_K_ENG * atr, 3)},
|
mt5.ORDER_TYPE_SELL_STOP: round(float(lo) - _SQ_K_ENG * atr, 3)},
|
||||||
"squeeze")
|
"squeeze")
|
||||||
# ── 2) Signal: EINE Order am Bestätigungs-Level ──────────────────
|
# ⚠⚠ DER SIGNAL-ZWEIG IST AM 2026-08-12 ENTFERNT (User-Entscheidung).
|
||||||
# Gemessen `backtest_auto_signal_v3.py`: am Level ØR +0,35/+0,32 (PF 2,2)
|
# Hier lag eine Order am Bestätigungs-Level von `_confirm_breakout`.
|
||||||
# gegen −0,09/−0,11 am Markt — dieselbe Mechanik, die den Squeeze rettet.
|
# Gemessen negativ auf BEIDEN Ausführungs-Achsen:
|
||||||
bo = ws.get("breakout") or {}
|
# · Markt-Einstieg — negativ in allen 8 Feldern
|
||||||
if (self._signal_pending and bo.get("pending") and bo.get("level")
|
# (`backtest_auto_signal.py` 30.07., `_norev` 04.08.)
|
||||||
and bo.get("dir") in ("LONG", "SHORT")
|
# · ruhende Order, Fill bei BERÜHRUNG — **−0,392/−0,338**, negativ in
|
||||||
and (bo.get("conf") or 0) >= self._auto_signal_min_conf
|
# allen 6 Feldern und damit sogar SCHLECHTER als der Markt-Einstieg
|
||||||
and self._auto_guard("signal") is None):
|
# (`backtest_signal_touchfill.py` 06.08.)
|
||||||
ot = (mt5.ORDER_TYPE_BUY_STOP if bo["dir"] == "LONG"
|
# ⚠ Die +0,35/+0,32 aus `backtest_auto_signal_v3.py` galten in Variante
|
||||||
else mt5.ORDER_TYPE_SELL_STOP)
|
# B (Fill beim Close-Bruch) und waren Look-ahead — der Aufschlag
|
||||||
return {ot: round(float(bo["level"]), 3)}, "signal"
|
# betrug 0,77–0,85 R.
|
||||||
|
# Der Grund ist STRUKTURELL und nicht reparierbar: `_pend` **wandert**
|
||||||
|
# (neu verankert bei jeder k×ATR-Gegenbewegung, nach Timeout und bei
|
||||||
|
# Richtungswechsel). Eine Order an einem wandernden Level fängt
|
||||||
|
# systematisch die Fehlausbrüche ein — „ein Level ist nur dann ein
|
||||||
|
# Ausführungs-Vorteil, wenn es STILLSTEHT".
|
||||||
|
# Damit entfällt auch der offene Churn-Defekt (4× setzen/stornieren in
|
||||||
|
# 76 s bei identischem Level, gefunden 06.08.).
|
||||||
|
# Sicherung: `.removed_backup/auto_signal_2026-08-12.py` + Git-Historie.
|
||||||
return {}, ""
|
return {}, ""
|
||||||
|
|
||||||
def _manage_squeeze_pending(self):
|
def _manage_squeeze_pending(self):
|
||||||
@@ -1418,120 +1428,6 @@ class TradingEngine:
|
|||||||
self._save_runtime_state()
|
self._save_runtime_state()
|
||||||
return self._auto_squeeze
|
return self._auto_squeeze
|
||||||
|
|
||||||
def _check_auto_signal(self):
|
|
||||||
"""Autonomer Entry auf die EMPFEHLUNG (Wave-Signal), nur wenn FLAT.
|
|
||||||
⚠ GEMESSEN NICHT tragfähig (`backtest_auto_signal.py`: H1 ØR −0,05…−0,18 in
|
|
||||||
BEIDEN Konfidenz-Stufen, H2 positiv = Regime-Kippen; Live-Dry-Run 2026-07-23
|
|
||||||
WR 29 %/PF 0,64). Auf ausdrücklichen User-Wunsch (2026-07-30) gebaut, Default
|
|
||||||
AUS, mit den gemessen besten Parametern (Konf≥75 + Nacht-Sperre) und dem
|
|
||||||
15-Min-Adverse-Schutz. B4/B5: Live-Perf. gegen die Erwartung halten — driftet
|
|
||||||
es weg, zurückbauen. Dedup: 1× je Signal-Episode (re-armt bei WARTEN/Flip)."""
|
|
||||||
if not self._auto_signal:
|
|
||||||
return
|
|
||||||
if self._circuit_halted(): # Tagesverlust-Stopp → heute keine Auto-Trades
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
sig_snap = self.wave.signal() or {}
|
|
||||||
d = sig_snap.get("signal")
|
|
||||||
if d not in ("LONG", "SHORT"):
|
|
||||||
self._signal_last_sig = None # WARTEN → Episode zu Ende, re-armen
|
|
||||||
return
|
|
||||||
conf = sig_snap.get("conf_pct") or 0
|
|
||||||
if conf < self._auto_signal_min_conf:
|
|
||||||
return # zu schwach (Dedup NICHT setzen)
|
|
||||||
# Nacht-Sperre — dasselbe Fenster wie beim Auto-Squeeze
|
|
||||||
# (`auto_squeeze_night_hours`, Default 3–6). Kein Dedup-Marker, damit das
|
|
||||||
# Signal danach noch feuern kann, falls es dann noch steht.
|
|
||||||
# ⚠ Anders als beim Squeeze ist die Kostenfalle für die WELLEN-Population
|
|
||||||
# gemessen einschlägig (alle Nacht-Bars 0,333×ATR) — hier ist der Guard also
|
|
||||||
# auch ertrags-begründet. Der Pfad ist ohnehin aus (`auto_signal=false`).
|
|
||||||
if (self._auto_signal_skip_night
|
|
||||||
and datetime.now(_BERLIN).hour in self._squeeze_night_hours):
|
|
||||||
return
|
|
||||||
# News-Blackout (s. `_check_auto_squeeze`) — kein Dedup-Marker.
|
|
||||||
if self.calendar.blackout():
|
|
||||||
return
|
|
||||||
ps = self.trader.snapshot()
|
|
||||||
if ps.get("ticket"):
|
|
||||||
# Position offen: läuft sie in Signalrichtung, gilt die Episode als
|
|
||||||
# „schon dabei" (kein Nachkauf); Gegen-Position wird NICHT gedreht
|
|
||||||
# (unbelegt — dieselbe Begründung wie beim Squeeze-Reverse).
|
|
||||||
pos_dir = "LONG" if ps.get("order_type") == 0 else "SHORT"
|
|
||||||
if pos_dir == d:
|
|
||||||
self._signal_last_sig = d
|
|
||||||
return
|
|
||||||
if d == self._signal_last_sig:
|
|
||||||
return # diese Episode schon gehandelt
|
|
||||||
# Wiedereinstiegs-Sperren (wie beim Squeeze): nach S/R-Close bzw. nach
|
|
||||||
# JEDEM Close nicht sofort gleichgerichtet neu eröffnen.
|
|
||||||
now = time.time()
|
|
||||||
if (d == self._sr_close_dir and now - self._sr_close_ts < _SQUEEZE_SR_COOLDOWN_S):
|
|
||||||
return
|
|
||||||
if (d == self._pos_close_dir and now - self._pos_close_ts < _SQUEEZE_REENTRY_COOLDOWN_S):
|
|
||||||
return
|
|
||||||
# ── NACHJAGD-BREMSE (2026-08-05, gemessen `backtest_auto_signal_v3.py`).
|
|
||||||
# Der Market-Weg ist nur noch FALLBACK, wenn die ruhende Stop-Order am
|
|
||||||
# Bestätigungs-Level nicht gegriffen hat. Am Level ØR +0,35/+0,32, am
|
|
||||||
# Bar-Close −0,09/−0,11 — und der Härtetest zeigt den Kipp-Punkt bei
|
|
||||||
# ~0,25–0,30×ATR Abstand (bei +0,20 noch +0,148/+0,090). Also nur bis
|
|
||||||
# `squeeze_max_chase_atr` nachkaufen, sonst diese Episode auslassen.
|
|
||||||
# ⚠ `_signal_last_sig` wird NICHT gesetzt → kommt der Kurs ans Level
|
|
||||||
# zurück, darf der Entry noch kommen.
|
|
||||||
ws = self.wave.snapshot() or {}
|
|
||||||
bo = ws.get("breakout") or {}
|
|
||||||
atr_m5 = (ws.get("pb_feats") or {}).get("atr") or 0.0
|
|
||||||
lvl = bo.get("level") if bo.get("dir") == d else None
|
|
||||||
tick = self.data.snapshot() or {}
|
|
||||||
px = tick.get("ask") if d == "LONG" else tick.get("bid")
|
|
||||||
if lvl and atr_m5 > 0 and px:
|
|
||||||
weg = (float(px) - float(lvl)) * (1 if d == "LONG" else -1) / atr_m5
|
|
||||||
if weg > self._squeeze_max_chase:
|
|
||||||
if self._signal_chase_logged != d:
|
|
||||||
self._signal_chase_logged = d
|
|
||||||
log.info(f"⏭ Auto-Signal übersprungen: Kurs steht schon "
|
|
||||||
f"{weg:.2f}×ATR über dem Bestätigungs-Level {lvl} "
|
|
||||||
f"(Grenze {self._squeeze_max_chase:.2f}) — "
|
|
||||||
f"gemessen ab hier kein Edge mehr")
|
|
||||||
return
|
|
||||||
self._signal_chase_logged = None
|
|
||||||
self._signal_last_sig = d
|
|
||||||
if isinstance(self._last_rec, dict):
|
|
||||||
self._last_rec["setup"] = f"AUTOSIG_{d}"
|
|
||||||
log.info(f"🎯 AUTO-SIGNAL-ENTRY: {d} (Konfidenz {conf}% ≥ "
|
|
||||||
f"{self._auto_signal_min_conf}%) — eröffne selbständig")
|
|
||||||
err = (self.open_long(source="auto_signal") if d == "LONG"
|
|
||||||
else self.open_short(source="auto_signal"))
|
|
||||||
sym = self.data.symbol or ""
|
|
||||||
tg = self.cfg["telegram"]
|
|
||||||
tg_on = (tg.get("enabled", "false").lower() == "true" and tg.get("bot_token"))
|
|
||||||
if err:
|
|
||||||
log.error(f"Auto-Signal-Entry fehlgeschlagen: {err}")
|
|
||||||
self._signal_last_sig = None # Fehlschlag → nächster Tick erneut
|
|
||||||
if tg_on:
|
|
||||||
try: send_telegram(f"⚠ <b>Auto-Signal-Entry FEHLGESCHLAGEN</b>\n"
|
|
||||||
f"{sym} {d} (Konf {conf}%) — {err}",
|
|
||||||
tg["bot_token"], tg["chat_id"])
|
|
||||||
except Exception as e: log.warning(f"Signal-Telegram (Fehler): {e}")
|
|
||||||
return
|
|
||||||
self._signal_entry_count += 1
|
|
||||||
if tg_on:
|
|
||||||
try: send_telegram(f"🎯 <b>Auto-Signal-Entry</b>\n{sym} {d} selbständig "
|
|
||||||
f"eröffnet (Empfehlung, Konfidenz {conf}%).",
|
|
||||||
tg["bot_token"], tg["chat_id"])
|
|
||||||
except Exception as e: log.warning(f"Signal-Telegram: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
log.error(f"_check_auto_signal: {e}", exc_info=True)
|
|
||||||
|
|
||||||
def set_auto_signal(self, on: bool) -> bool:
|
|
||||||
"""Autonomen Signal-Entry an/aus (UI/Config). Rückgabe = neuer Zustand."""
|
|
||||||
self._auto_signal = bool(on)
|
|
||||||
self._signal_last_sig = None
|
|
||||||
log.info(f"Auto-Signal-Entry {'AN' if self._auto_signal else 'AUS'} "
|
|
||||||
f"(Konf≥{self._auto_signal_min_conf}%"
|
|
||||||
f"{', Nacht-Sperre' if self._auto_signal_skip_night else ''})")
|
|
||||||
self._save_runtime_state()
|
|
||||||
return self._auto_signal
|
|
||||||
|
|
||||||
def _check_adverse15(self):
|
def _check_adverse15(self):
|
||||||
"""15-Minuten-Regel (User-Wunsch 2026-07-30): steht ein **vom Bot eröffneter**
|
"""15-Minuten-Regel (User-Wunsch 2026-07-30): steht ein **vom Bot eröffneter**
|
||||||
Trade 15 min nach Entry mehr als `adverse_15min_atr`×ATR im Minus → schließen
|
Trade 15 min nach Entry mehr als `adverse_15min_atr`×ATR im Minus → schließen
|
||||||
@@ -2154,8 +2050,9 @@ class TradingEngine:
|
|||||||
st = json.load(f)
|
st = json.load(f)
|
||||||
if "auto_squeeze" in st:
|
if "auto_squeeze" in st:
|
||||||
self._auto_squeeze = bool(st["auto_squeeze"])
|
self._auto_squeeze = bool(st["auto_squeeze"])
|
||||||
if "auto_signal" in st:
|
# ⚠ `auto_signal` wird seit 2026-08-12 NICHT mehr gelesen (Pfad
|
||||||
self._auto_signal = bool(st["auto_signal"])
|
# entfernt). Ein Altbestand in der Datei schadet nicht — er wird
|
||||||
|
# beim naechsten Schreiben ohnehin nicht mehr mitgeschrieben.
|
||||||
if "auto_sr_close" in st:
|
if "auto_sr_close" in st:
|
||||||
self._auto_sr_close = bool(st["auto_sr_close"])
|
self._auto_sr_close = bool(st["auto_sr_close"])
|
||||||
if "sr_close_min_gain" in st:
|
if "sr_close_min_gain" in st:
|
||||||
@@ -2190,7 +2087,6 @@ class TradingEngine:
|
|||||||
tmp = self._runtime_state_path + ".tmp"
|
tmp = self._runtime_state_path + ".tmp"
|
||||||
with open(tmp, "w", encoding="utf-8") as f:
|
with open(tmp, "w", encoding="utf-8") as f:
|
||||||
json.dump({"auto_squeeze": self._auto_squeeze,
|
json.dump({"auto_squeeze": self._auto_squeeze,
|
||||||
"auto_signal": self._auto_signal,
|
|
||||||
"auto_sr_close": self._auto_sr_close,
|
"auto_sr_close": self._auto_sr_close,
|
||||||
"sr_close_min_gain": self._sr_close_min_gain,
|
"sr_close_min_gain": self._sr_close_min_gain,
|
||||||
"manual_margin": getattr(self, "_manual_margin", 0.0),
|
"manual_margin": getattr(self, "_manual_margin", 0.0),
|
||||||
@@ -4170,16 +4066,12 @@ class TradingEngine:
|
|||||||
# Einstieg über ruhende Stop-Orders (2026-08-05) + die aktuell gesetzten
|
# Einstieg über ruhende Stop-Orders (2026-08-05) + die aktuell gesetzten
|
||||||
# Trigger — macht im Snapshot prüfbar, ob wirklich Orders liegen.
|
# Trigger — macht im Snapshot prüfbar, ob wirklich Orders liegen.
|
||||||
"squeeze_pending": self._squeeze_pending,
|
"squeeze_pending": self._squeeze_pending,
|
||||||
"signal_pending": self._signal_pending,
|
|
||||||
"pending_levels": (
|
"pending_levels": (
|
||||||
sorted(round(v, 3) for v in self._pending_levels.values())
|
sorted(round(v, 3) for v in self._pending_levels.values())
|
||||||
if self._pending_levels else []),
|
if self._pending_levels else []),
|
||||||
"pending_quelle": self._pending_quelle,
|
"pending_quelle": self._pending_quelle,
|
||||||
"squeeze_max_chase_atr": self._squeeze_max_chase,
|
"squeeze_max_chase_atr": self._squeeze_max_chase,
|
||||||
"squeeze_entry_count": self._squeeze_entry_count,
|
"squeeze_entry_count": self._squeeze_entry_count,
|
||||||
"auto_signal": self._auto_signal,
|
|
||||||
"auto_signal_min_conf": self._auto_signal_min_conf,
|
|
||||||
"signal_entry_count": self._signal_entry_count,
|
|
||||||
"adverse15_atr": self._adverse15_atr,
|
"adverse15_atr": self._adverse15_atr,
|
||||||
"adverse15_count": self._adverse15_count,
|
"adverse15_count": self._adverse15_count,
|
||||||
# Auto-Flip-Close (gemessen negativ, auf User-Wunsch gebaut) — für den
|
# Auto-Flip-Close (gemessen negativ, auf User-Wunsch gebaut) — für den
|
||||||
@@ -4386,7 +4278,6 @@ class TradingEngine:
|
|||||||
try:
|
try:
|
||||||
an = lambda b: "AN" if b else "aus" # noqa: E731
|
an = lambda b: "AN" if b else "aus" # noqa: E731
|
||||||
z.append(f"Automatik: BRK {an(self._auto_squeeze)} · "
|
z.append(f"Automatik: BRK {an(self._auto_squeeze)} · "
|
||||||
f"SIG {an(self._auto_signal)} · "
|
|
||||||
f"S/R-Close {an(self._auto_sr_close)} · "
|
f"S/R-Close {an(self._auto_sr_close)} · "
|
||||||
f"Flip {an(getattr(self, '_flip_close', False))}")
|
f"Flip {an(getattr(self, '_flip_close', False))}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
+17
-8
@@ -396,15 +396,24 @@ CONFIG_DEPS = [
|
|||||||
"nur im Live-Pfad über diese Config.",
|
"nur im Live-Pfad über diese Config.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "auto_signal", "test": "backtest_auto_signal.py",
|
# ⚠⚠ GEGENSTANDSLOS seit 2026-08-12: der Auto-Signal-Pfad ist KOMPLETT
|
||||||
|
# entfernt (Code, Endpunkt, Button, Snapshot-Felder). Der Schlüssel
|
||||||
|
# wird nirgends mehr gelesen — ein Wächter darüber könnte also nur
|
||||||
|
# noch Fehlalarm oder falsche Beruhigung liefern.
|
||||||
|
# Der Eintrag bleibt als STUMME Notiz stehen (`validated` = der
|
||||||
|
# Ist-Zustand), damit die Historie lesbar bleibt und niemand den
|
||||||
|
# Schlüssel versehentlich reaktiviert, ohne die Messlage zu kennen.
|
||||||
|
"key": "auto_signal", "test": "backtest_signal_touchfill.py",
|
||||||
"validated": "false",
|
"validated": "false",
|
||||||
"why": "ABGESCHALTET 2026-08-01. Gemessen in H1 durchgehend negativ (alle 8 "
|
"why": "ENTFERNT 2026-08-12 (Pfad gelöscht, nicht nur abgeschaltet). "
|
||||||
"Varianten, n=718–1167/Hälfte), H2 positiv = Regime-Kippen. Zusätzlich: "
|
"Gemessen negativ auf BEIDEN Ausführungs-Achsen: Markt-Einstieg "
|
||||||
"beide Auto-Pfade konkurrieren um den EINEN Positions-Slot — läuft er "
|
"in allen 8 Feldern (backtest_auto_signal.py 30.07.), ruhende "
|
||||||
"mit, ist die B5-Abbruchprüfung des Auto-Squeeze nicht sauber messbar. "
|
"Order mit Touch-Fill −0,392/−0,338 in allen 6 Feldern und damit "
|
||||||
"⚠ Dieser Schlüssel lebt AUCH in runtime_state.json (UI-Toggle) und wird "
|
"SCHLECHTER als Markt (backtest_signal_touchfill.py 06.08.). "
|
||||||
"von dort überschrieben — genau so lief er am 31.07. unbemerkt weiter, "
|
"Ursache strukturell: das Bestätigungs-Level `_pend` WANDERT. "
|
||||||
"obwohl die ini 'false' sagte.",
|
"⚠ Die ini-Schlüssel bleiben stehen, werden aber nicht mehr "
|
||||||
|
"gelesen — Reaktivieren ginge nur über die Git-Historie bzw. "
|
||||||
|
".removed_backup/auto_signal_2026-08-12.py.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "auto_squeeze", "test": "backtest_breakout_squeeze.py",
|
"key": "auto_squeeze", "test": "backtest_breakout_squeeze.py",
|
||||||
|
|||||||
@@ -378,16 +378,12 @@ async def set_margin_pct(body: dict,
|
|||||||
return {"ok": True, "value": val}
|
return {"ok": True, "value": val}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/autosignal")
|
# ⚠⚠ `/api/autosignal` ENTFERNT am 2026-08-12 (User-Entscheidung). Der autonome
|
||||||
async def toggle_autosignal(x_auth_token: str | None = Header(default=None)):
|
# Entry auf die Empfehlung war seit dem 01.08. aus und auf BEIDEN
|
||||||
"""Autonomen Entry auf die EMPFEHLUNG an/aus (User-Wunsch 2026-07-30).
|
# Ausführungs-Achsen gemessen negativ — Markt-Einstieg in allen 8 Feldern,
|
||||||
⚠ Gemessen NICHT tragfähig (backtest_auto_signal.py: H1 negativ) — bewusst
|
# ruhende Order mit Touch-Fill in allen 6 und dabei sogar schlechter als
|
||||||
vom User gewollt, Default aus, mit 15-Min-Adverse-Schutz."""
|
# Markt. Ursache strukturell: das Bestätigungs-Level wandert.
|
||||||
_require_auth(x_auth_token)
|
# Sicherung: `.removed_backup/auto_signal_2026-08-12.py` + Git-Historie.
|
||||||
eng = app.state.engine
|
|
||||||
state = eng.set_auto_signal(not eng._auto_signal)
|
|
||||||
get_logger("server").info(f"[WEB] Auto-Signal-Entry {'AN' if state else 'AUS'}")
|
|
||||||
return {"ok": True, "enabled": state}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/srclose_min")
|
@app.post("/api/srclose_min")
|
||||||
|
|||||||
@@ -24,14 +24,24 @@ def test_squeeze_fill_wird_getaggt(engine):
|
|||||||
assert 49483981 not in engine._pending_tickets # Merker verbraucht
|
assert 49483981 not in engine._pending_tickets # Merker verbraucht
|
||||||
|
|
||||||
|
|
||||||
def test_signal_fill_long(engine):
|
def test_unbekannte_quelle_wird_NICHT_getaggt(engine):
|
||||||
engine._pending_tickets = {777: "signal"}
|
"""⚠ Ersetzt `test_signal_fill_long` (der Auto-Signal-Pfad ist am
|
||||||
|
2026-08-12 entfernt). Der Test wurde NICHT geloescht, sondern umgedreht:
|
||||||
|
seit es nur noch den Squeeze gibt, darf ein Fill mit einer FREMDEN Quelle
|
||||||
|
nicht stillschweigend als Squeeze durchgehen — das waere genau
|
||||||
|
Deployment-Drift Fall 8 (vier reale Trades landeten als `setup=NULL` und
|
||||||
|
waren von manuellen nicht unterscheidbar), nur mit umgekehrtem Vorzeichen:
|
||||||
|
ein Fremd-Trade wuerde die B4-/B5-Monitore verfaelschen."""
|
||||||
|
engine._pending_tickets = {777: "signal"} # Altbestand / unbekannt
|
||||||
engine.trader.snap = {"ticket": 777, "order_type": 0, "entry_price": 75.2}
|
engine.trader.snap = {"ticket": 777, "order_type": 0, "entry_price": 75.2}
|
||||||
engine._check_pending_fill()
|
engine._check_pending_fill()
|
||||||
|
|
||||||
assert engine._signal_entry_count == 1
|
|
||||||
assert engine._squeeze_entry_count == 0
|
assert engine._squeeze_entry_count == 0
|
||||||
assert engine._bot_open_source == "auto_signal"
|
assert engine._bot_open_source is None
|
||||||
|
# ⚠ Der Merker bleibt STEHEN — nur ein sauber zugeordneter Fill verbraucht
|
||||||
|
# ihn. So wird derselbe Fall beim naechsten Tick erneut gemeldet, statt
|
||||||
|
# still zu verschwinden.
|
||||||
|
assert 777 in engine._pending_tickets
|
||||||
|
|
||||||
|
|
||||||
def test_fremde_position_wird_nicht_getaggt(engine):
|
def test_fremde_position_wird_nicht_getaggt(engine):
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
"""`_pending_ziel`: das SQUEEZE-Verhalten darf sich beim Entfernen von SIG
|
||||||
|
NICHT aendern.
|
||||||
|
|
||||||
|
ANLASS (2026-08-12): der Auto-Signal-Pfad wird entfernt — gemessen negativ auf
|
||||||
|
BEIDEN Ausfuehrungs-Achsen (Markt: alle 8 Felder; ruhende Order mit Touch-Fill:
|
||||||
|
alle 6 Felder, sogar schlechter als der Markt-Einstieg). Grund strukturell:
|
||||||
|
`_pend` WANDERT, und „ein Level ist nur dann ein Ausfuehrungs-Vorteil, wenn es
|
||||||
|
STILLSTEHT".
|
||||||
|
|
||||||
|
⚠⚠ DAS RISIKO IST NICHT SIG, SONDERN DER GETEILTE MANAGER. Squeeze und Signal
|
||||||
|
teilen sich `_pending_ziel` — bewusst, weil zwei getrennte Manager ueber
|
||||||
|
`trader.pending_orders()` die Orders des jeweils anderen sehen und sich
|
||||||
|
**gegenseitig stornieren** wuerden (der Bug waere sporadisch und im Log kaum
|
||||||
|
erkennbar). Dieser Test nagelt den Squeeze-Zweig fest, BEVOR der Signal-Zweig
|
||||||
|
faellt, und laeuft danach unveraendert weiter.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.engine import TradingEngine
|
||||||
|
|
||||||
|
_SQ_K = 0.1 # `_SQ_K_ENG` — Ausbruchspuffer in ×ATR
|
||||||
|
|
||||||
|
|
||||||
|
class _Wave:
|
||||||
|
def __init__(self, snap):
|
||||||
|
self._snap = snap
|
||||||
|
|
||||||
|
def snapshot(self):
|
||||||
|
return self._snap
|
||||||
|
|
||||||
|
|
||||||
|
class _Stub:
|
||||||
|
"""Minimal-Engine — nur was `_pending_ziel` liest."""
|
||||||
|
|
||||||
|
_pending_ziel = TradingEngine._pending_ziel
|
||||||
|
|
||||||
|
def __init__(self, *, sq_state="armed", hi=80.5, lo=80.0, atr=0.2,
|
||||||
|
sq_pending=True, guard=None):
|
||||||
|
self._squeeze_pending = sq_pending
|
||||||
|
self._guard = guard
|
||||||
|
self.wave = _Wave({
|
||||||
|
"pb_feats": {"atr": atr},
|
||||||
|
"squeeze": {"state": sq_state, "box_hi": hi, "box_lo": lo},
|
||||||
|
})
|
||||||
|
# SIG-Felder bleiben gesetzt, damit der Test VOR und NACH dem Entfernen
|
||||||
|
# laeuft: existiert der Signal-Zweig nicht mehr, werden sie ignoriert.
|
||||||
|
self._signal_pending = False
|
||||||
|
self._auto_signal_min_conf = 75
|
||||||
|
|
||||||
|
def _auto_guard(self, quelle):
|
||||||
|
return self._guard
|
||||||
|
|
||||||
|
|
||||||
|
def test_armed_legt_beide_box_grenzen():
|
||||||
|
"""Der Kern: solange der Squeeze `armed` ist, liegen BEIDE Stop-Orders —
|
||||||
|
je einen Ausbruchspuffer ausserhalb der Box."""
|
||||||
|
ziel, quelle = _Stub(hi=80.5, lo=80.0, atr=0.2)._pending_ziel()
|
||||||
|
assert quelle == "squeeze"
|
||||||
|
assert ziel == {mt5.ORDER_TYPE_BUY_STOP: round(80.5 + _SQ_K * 0.2, 3),
|
||||||
|
mt5.ORDER_TYPE_SELL_STOP: round(80.0 - _SQ_K * 0.2, 3)}
|
||||||
|
|
||||||
|
|
||||||
|
def test_nicht_armed_legt_nichts():
|
||||||
|
for st in ("active", None):
|
||||||
|
ziel, quelle = _Stub(sq_state=st)._pending_ziel()
|
||||||
|
assert ziel == {} and quelle == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_guard_storniert():
|
||||||
|
"""Nacht-Fenster / News / Circuit-Breaker: der Guard ist die EINE Quelle fuer
|
||||||
|
beide Wege — schlaegt er zu, darf nichts liegen."""
|
||||||
|
ziel, quelle = _Stub(guard="nacht")._pending_ziel()
|
||||||
|
assert ziel == {} and quelle == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_feature_aus_legt_nichts():
|
||||||
|
ziel, quelle = _Stub(sq_pending=False)._pending_ziel()
|
||||||
|
assert ziel == {} and quelle == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_ohne_atr_kein_absturz():
|
||||||
|
s = _Stub()
|
||||||
|
s.wave = _Wave({"pb_feats": {"atr": 0.0}, "squeeze": {"state": "armed",
|
||||||
|
"box_hi": 80.5, "box_lo": 80.0}})
|
||||||
|
ziel, quelle = s._pending_ziel()
|
||||||
|
assert ziel == {} and quelle == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_fehlende_box_legt_nichts():
|
||||||
|
for hi, lo in ((None, 80.0), (80.5, None), (None, None)):
|
||||||
|
ziel, quelle = _Stub(hi=hi, lo=lo)._pending_ziel()
|
||||||
|
assert ziel == {} and quelle == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_preise_haengen_am_atr():
|
||||||
|
"""Der Puffer skaliert mit dem ATR — sonst waere er in ruhigen Phasen zu weit
|
||||||
|
und in schnellen zu eng."""
|
||||||
|
a, _ = _Stub(atr=0.1)._pending_ziel()
|
||||||
|
b, _ = _Stub(atr=0.4)._pending_ziel()
|
||||||
|
assert a[mt5.ORDER_TYPE_BUY_STOP] < b[mt5.ORDER_TYPE_BUY_STOP]
|
||||||
|
assert a[mt5.ORDER_TYPE_SELL_STOP] > b[mt5.ORDER_TYPE_SELL_STOP]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(pytest.main([__file__, "-v"]))
|
||||||
@@ -34,9 +34,8 @@ class _Hist:
|
|||||||
class _Stub:
|
class _Stub:
|
||||||
_system_status = TradingEngine._system_status
|
_system_status = TradingEngine._system_status
|
||||||
|
|
||||||
def __init__(self, *, brk=False, sig=False, src=True, flip=False, puls=None):
|
def __init__(self, *, brk=False, src=True, flip=False, puls=None):
|
||||||
self._auto_squeeze = brk
|
self._auto_squeeze = brk
|
||||||
self._auto_signal = sig
|
|
||||||
self._auto_sr_close = src
|
self._auto_sr_close = src
|
||||||
self._flip_close = flip
|
self._flip_close = flip
|
||||||
self.history = _Hist(puls if puls is not None else [])
|
self.history = _Hist(puls if puls is not None else [])
|
||||||
@@ -46,9 +45,15 @@ def _text(z):
|
|||||||
return "\n".join(z)
|
return "\n".join(z)
|
||||||
|
|
||||||
|
|
||||||
def test_automatik_zeile_nennt_alle_vier_schalter():
|
def test_automatik_zeile_nennt_alle_schalter():
|
||||||
z = _text(_Stub(brk=True, sig=False, src=True, flip=False)._system_status())
|
"""⚠ Seit dem 2026-08-12 sind es DREI — der Auto-Signal-Pfad (SIG) ist
|
||||||
assert "BRK AN" in z and "SIG aus" in z and "S/R-Close AN" in z and "Flip aus" in z
|
entfernt. Der Test zaehlt sie bewusst einzeln auf: verschwindet einer
|
||||||
|
stillschweigend aus der Zeile, faellt es hier auf und nicht erst, wenn
|
||||||
|
monatelang niemand merkt, dass ein Schalter aus ist (real geschehen:
|
||||||
|
`auto_sr_close` stand 5 Tage aus, gemessener Wert +350,81 EUR)."""
|
||||||
|
z = _text(_Stub(brk=True, src=True, flip=False)._system_status())
|
||||||
|
assert "BRK AN" in z and "S/R-Close AN" in z and "Flip aus" in z
|
||||||
|
assert "SIG" not in z, "der entfernte Pfad darf nicht wieder auftauchen"
|
||||||
|
|
||||||
|
|
||||||
def test_toter_logger_wird_gemeldet():
|
def test_toter_logger_wird_gemeldet():
|
||||||
|
|||||||
+6
-9
@@ -1058,14 +1058,11 @@ function render(d) {
|
|||||||
shortBtn.disabled = hasPos;
|
shortBtn.disabled = hasPos;
|
||||||
closeBtn.disabled = !hasPos;
|
closeBtn.disabled = !hasPos;
|
||||||
$("btn-squeeze").classList.toggle("on", !!d.auto_squeeze);
|
$("btn-squeeze").classList.toggle("on", !!d.auto_squeeze);
|
||||||
// Auto-Signal-Entry: bernstein-„warn" statt grün — er ist gemessen NICHT
|
// ⚠⚠ Der 🎯 SIG-Button ist am 2026-08-12 ENTFERNT (User-Entscheidung). Der
|
||||||
// tragfähig (H1 negativ) und läuft nur auf ausdrücklichen Wunsch.
|
// autonome Entry auf die Empfehlung war seit dem 01.08. aus und auf BEIDEN
|
||||||
const asBtn = $("btn-autosig");
|
// Ausführungs-Achsen gemessen negativ — Markt in allen 8 Feldern, ruhende
|
||||||
if (asBtn) {
|
// Order mit Touch-Fill in allen 6 und dabei schlechter als Markt. Grund
|
||||||
asBtn.classList.toggle("warn-on", !!d.auto_signal);
|
// strukturell: das Bestätigungs-Level `_pend` WANDERT.
|
||||||
asBtn.textContent = d.auto_signal
|
|
||||||
? `🎯 SIG ${d.auto_signal_min_conf || 75}%` : "🎯 SIG";
|
|
||||||
}
|
|
||||||
$("btn-trail").classList.toggle("on", !!(d.trail || {}).enabled);
|
$("btn-trail").classList.toggle("on", !!(d.trail || {}).enabled);
|
||||||
|
|
||||||
// Empfehlungs-Blinken: LONG/SHORT-Button blinkt, sobald die Welle eine
|
// Empfehlungs-Blinken: LONG/SHORT-Button blinkt, sobald die Welle eine
|
||||||
@@ -1661,7 +1658,7 @@ _updateMuteBtn();
|
|||||||
|
|
||||||
// Toggle-Buttons (AUTO/TRAIL/S-R-Close) — ohne Bestätigung
|
// Toggle-Buttons (AUTO/TRAIL/S-R-Close) — ohne Bestätigung
|
||||||
$("btn-squeeze").onclick = () => toggle("api/autosqueeze", "Auto-Squeeze (autonom!)");
|
$("btn-squeeze").onclick = () => toggle("api/autosqueeze", "Auto-Squeeze (autonom!)");
|
||||||
$("btn-autosig").onclick = () => toggle("api/autosignal", "Auto-Signal-Entry (autonom!)");
|
// (🎯 SIG entfernt 2026-08-12 — s. renderOrderButtons oben)
|
||||||
$("btn-trail").onclick = () => toggle("api/trail", "Trailing");
|
$("btn-trail").onclick = () => toggle("api/trail", "Trailing");
|
||||||
$("srclose-btn").onclick = () => toggle("api/srclose", "Auto-S/R-Close");
|
$("srclose-btn").onclick = () => toggle("api/srclose", "Auto-S/R-Close");
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -6,7 +6,7 @@
|
|||||||
<meta name="theme-color" content="#0d1117">
|
<meta name="theme-color" content="#0d1117">
|
||||||
<title>Oil · MT5</title>
|
<title>Oil · MT5</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="stylesheet" href="style.css?v=165">
|
<link rel="stylesheet" href="style.css?v=166">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Modul-Menü (ganz oben) -->
|
<!-- Modul-Menü (ganz oben) -->
|
||||||
@@ -335,7 +335,9 @@
|
|||||||
<button class="btn short" data-side="short">▼ SHORT</button>
|
<button class="btn short" data-side="short">▼ SHORT</button>
|
||||||
<button class="btn close" data-side="close">✕ CLOSE</button>
|
<button class="btn close" data-side="close">✕ CLOSE</button>
|
||||||
<button class="btn tog" id="btn-squeeze" title="Autonomer Entry auf Squeeze-Ausbruch">🚀 BRK</button>
|
<button class="btn tog" id="btn-squeeze" title="Autonomer Entry auf Squeeze-Ausbruch">🚀 BRK</button>
|
||||||
<button class="btn tog" id="btn-autosig" title="Autonomer Entry auf die Empfehlung — GEMESSEN NICHT tragfähig (H1 negativ), auf eigenen Wunsch aktiv">🎯 SIG</button>
|
<!-- 🎯 SIG (Auto-Signal-Entry) ENTFERNT 2026-08-12: seit 01.08. aus und auf
|
||||||
|
BEIDEN Ausfuehrungs-Achsen gemessen negativ (Markt alle 8 Felder,
|
||||||
|
ruhende Order mit Touch-Fill alle 6 und schlechter als Markt). -->
|
||||||
<button class="btn tog" id="btn-trail">🎯 TRAIL</button>
|
<button class="btn tog" id="btn-trail">🎯 TRAIL</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -354,6 +356,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="toast" class="toast hidden"></div>
|
<div id="toast" class="toast hidden"></div>
|
||||||
|
|
||||||
<script src="app.js?v=165"></script>
|
<script src="app.js?v=166"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user