Auto-Signal-Entry + 15-Min-Regel (User-Wunsch, gegen die Messung, mit besten Params)
User will Auto-Eroeffnung bei Empfehlung trotz negativem Backtest -> gebaut, Default AUS, mit den GEMESSEN besten Parametern statt geratenen: Entry (_check_auto_signal, [trading] auto_signal=false): - min_conf=75 + Nacht-Sperre = beste Kombi (H1 -0.053 mildester Verlust, H2 +0.141 bestes Netto). NICHT weil "mehr Konfidenz besser" -- conf_pct ist unkalibriert, 75 ist empirisch. - Nur FLAT, 1x je Signal-Episode (Dedup, re-armt bei WARTEN/Flip), kein Nachkauf, kein Drehen einer Gegen-Position; erbt Circuit-Breaker + beide Cooldowns. - Setup-Tag AUTOSIG_<dir> -> in der DB separierbar fuer B4-Monitor. - Toggle POST /api/autosignal, Button "SIG" (bernstein = gemessen nicht tragfaehig), neustart-fest via runtime_state.json. 15-Min-Regel (_check_adverse15, adverse_15min_atr=0.5): - Schwelle GEMESSEN kalibriert: "sobald im Minus" ist die SCHLECHTESTE Variante (WR 39->31%, Netto -0.067 vs +0.026 ohne); 0.5xATR ist die beste (H1 -0.111 -> -0.079). Netto bringt die Regel ~nichts = Regime-Schutz. - Nur BOT-eroeffnete Trades (_bot_open_ticket) -- manuelle Diskretion bleibt. - 1x je Ticket geprueft, Startup-Grace respektiert, closed_by=adverse15. Verifiziert: 18 synthetische Szenarien (9 Entry + 9 Adverse) alle korrekt; Live-Neustart ohne Loop-Fehler, Toggle + Persistenz geprueft. v=119. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d40616fbeb
commit
2996eddd7e
@@ -747,6 +747,46 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
|||||||
(+118 €/WR 56 %, `analyze_entries`-Auswertung 2026-07-30): **menschliche AUSWAHL**
|
(+118 €/WR 56 %, `analyze_entries`-Auswertung 2026-07-30): **menschliche AUSWAHL**
|
||||||
unter den Signalen (Kontext/Tageszeit/Marktlage), nicht „alle nehmen" — die
|
unter den Signalen (Kontext/Tageszeit/Marktlage), nicht „alle nehmen" — die
|
||||||
Grundgesamtheit ALLER Signale ist der Verlierer. **14. verworfener Signal-Eingriff.**
|
Grundgesamtheit ALLER Signale ist der Verlierer. **14. verworfener Signal-Eingriff.**
|
||||||
|
- **Auto-Signal-Entry = TROTZDEM GEBAUT auf ausdrücklichen User-Wunsch (2026-07-30,
|
||||||
|
„probieren wir es trotzdem mal"), `[trading] auto_signal`, Default FALSE.** ⚠ **Der
|
||||||
|
Backtest direkt darüber sagt NEIN** (H1 durchgehend negativ) — der User hat das nach
|
||||||
|
vollständiger Information (inkl. Live-Dry-Run-Historie WR 29 %/PF 0,64) bewusst
|
||||||
|
entschieden. Gebaut mit den **gemessen BESTEN** Parametern, nicht mit Default-Raten:
|
||||||
|
`auto_signal_min_conf=75` (mit Nacht-Sperre H1 −0,053 = mildester Verlust, H2 +0,141
|
||||||
|
= bestes Netto; ⚠ NICHT weil „mehr Konfidenz besser" — conf_pct ist unkalibriert, 75
|
||||||
|
ist empirisch) und `auto_signal_skip_night=true` (verbessert JEDE Variante).
|
||||||
|
**Mechanik** (`engine._check_auto_signal` im `_pos_loop`, 1 s, NACH den Close-Checks):
|
||||||
|
eröffnet nur **FLAT**, nur bei `wave.signal()` LONG/SHORT mit `conf_pct ≥ min_conf`,
|
||||||
|
**1× je Signal-Episode** (Dedup `_signal_last_sig`, re-armt bei WARTEN/Flip — nicht
|
||||||
|
über den Preis wie beim Squeeze, weil das Signal keine Level-Marke hat); erbt die
|
||||||
|
Squeeze-Guards: Circuit-Breaker, Nacht-Sperre, S/R-Close-Cooldown, allgemeine
|
||||||
|
Wiedereinstiegs-Sperre. **Kein Nachkauf** bei gleichgerichteter Position, **kein
|
||||||
|
Drehen** einer Gegen-Position (unbelegt, wie Squeeze-Reverse). Log `🎯 AUTO-SIGNAL-
|
||||||
|
ENTRY` + Telegram, Setup-Tag `AUTOSIG_<dir>` (macht die Trades in der DB separierbar
|
||||||
|
→ B4-Monitor). Snapshot `auto_signal`/`auto_signal_min_conf`/`signal_entry_count`,
|
||||||
|
Toggle `POST /api/autosignal` = Dashboard-Button **🎯 SIG** (bernstein statt blau,
|
||||||
|
weil gemessen nicht tragfähig; zeigt die Schwelle an), neustart-fest via
|
||||||
|
`runtime_state.json`. **B5-Pflicht:** Live-ØR/PF gegen die Backtest-Erwartung halten
|
||||||
|
(H2-Regime +0,14 vs H1-Regime −0,05) — driftet es weg, **zurückbauen** (`=false`).
|
||||||
|
- **15-Minuten-Regel (`[trading] adverse_15min_atr=0.5`, AKTIV, User-Wunsch 2026-07-30
|
||||||
|
„wenn der Trade nach 15 Minuten gegen die Richtung läuft schließe ihn"):** steht ein
|
||||||
|
**vom BOT eröffneter** Trade 15 min nach Entry ≥ X×ATR_M5 gegen die Richtung →
|
||||||
|
schließen (`engine._check_adverse15`, `closed_by=adverse15`, Log ⏱ + Telegram).
|
||||||
|
⚠ **Schwelle GEMESSEN kalibriert, NICHT die ursprüngliche Vorgabe** (`backtest_auto_
|
||||||
|
signal.py`, 15-Min-Achse): „sobald im Minus" (0,0) ist die **SCHLECHTESTE** Variante
|
||||||
|
(WR bricht 39→31 %, Netto-ØR −0,067 vs +0,026 ohne Regel) — sie kappt Trades, die
|
||||||
|
sich erholt hätten. **0,5×ATR ist die beste Regel-Variante** (H1 −0,111→−0,079).
|
||||||
|
Netto über beide Hälften bringt die Regel ~nichts (+0,001 vs +0,026 **ohne**) → sie
|
||||||
|
ist ein **REGIME-Schutz** (hilft im schlechten Regime, kostet im guten), bewusst als
|
||||||
|
Sicherheit akzeptiert. **2. unabhängige Bestätigung** von `backtest_adverse_nosignal.py`
|
||||||
|
(dort fiel der Adverse-Exit bei JEDER Schwelle durch). Greift **NICHT bei manuellen
|
||||||
|
Trades** (`_bot_open_ticket`-Marker — dort bleibt die Diskretion beim User, gemessen
|
||||||
|
sein Vorteil) und nicht in der Startup-Schonfrist; **1× je Ticket** geprüft
|
||||||
|
(`_adverse15_done_ticket`) genau beim 15-min-Zeitpunkt. Snapshot `adverse15_atr`/
|
||||||
|
`adverse15_count`. 0 = aus. Mit **18 synthetischen Szenarien getestet** (9 Entry:
|
||||||
|
Konf-Gate, Dedup, WARTEN-Re-Arm, Nachkauf, Gegen-Position, Cooldown, Toggle,
|
||||||
|
Nacht-Sperre · 9 Adverse: über/unter Schwelle, zu früh, im Plus, SHORT-Richtung,
|
||||||
|
manueller Trade, aus, 1×-je-Ticket, Startup-Grace) — alle korrekt.
|
||||||
- **Auto-Squeeze-Entry = LIVE AUTONOM (User-Vorgabe 2026-07-16, `[trading]
|
- **Auto-Squeeze-Entry = LIVE AUTONOM (User-Vorgabe 2026-07-16, `[trading]
|
||||||
auto_squeeze=true`, Default FALSE):** der Bot eröffnet **selbständig eine echte
|
auto_squeeze=true`, Default FALSE):** der Bot eröffnet **selbständig eine echte
|
||||||
Order**, sobald der **gemessen-validierte** Squeeze-Ausbruch feuert (`wave.squeeze.
|
Order**, sobald der **gemessen-validierte** Squeeze-Ausbruch feuert (`wave.squeeze.
|
||||||
@@ -1331,7 +1371,7 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
|||||||
Signal → **Hinweis-Dialog „trotzdem eröffnen?"** (überschreibbar). CLOSE bei
|
Signal → **Hinweis-Dialog „trotzdem eröffnen?"** (überschreibbar). CLOSE bei
|
||||||
**negativem** P&L → Sicherheitsabfrage; im Plus/Breakeven direkt. Sonst keine
|
**negativem** P&L → Sicherheitsabfrage; im Plus/Breakeven direkt. Sonst keine
|
||||||
Token-Dialoge (`require_token=false`). Audio braucht 1× Nutzer-Tap.
|
Token-Dialoge (`require_token=false`). Audio braucht 1× Nutzer-Tap.
|
||||||
- Asset-Version aktuell **v=118** (in `web/index.html` hochzählen, siehe Workflows).
|
- Asset-Version aktuell **v=119** (in `web/index.html` hochzählen, siehe Workflows).
|
||||||
Schriftgrößen 2026-07-24 global **+4px** (2× je +2px auf User-Wunsch; Body-Basis
|
Schriftgrößen 2026-07-24 global **+4px** (2× je +2px auf User-Wunsch; Body-Basis
|
||||||
14→18px). ⚠ Betrifft in `style.css` sowohl `font-size:Npx` (71×) ALS AUCH die
|
14→18px). ⚠ Betrifft in `style.css` sowohl `font-size:Npx` (71×) ALS AUCH die
|
||||||
`font:<weight> Npx/…`-**Shorthand** (3×: `.ms-chip`/`.ms-bos-lbl`/`.sqm-badge` —
|
`font:<weight> Npx/…`-**Shorthand** (3×: `.ms-chip`/`.ms-bos-lbl`/`.sqm-badge` —
|
||||||
@@ -1341,7 +1381,7 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
|||||||
|
|
||||||
## Web-API (server.py)
|
## Web-API (server.py)
|
||||||
`GET /api/snapshot` (inkl. `verdict`-Aggregat) `/api/health` `/api/logs` `/api/stats` `/api/news` `/api/bars?tf=&n=` (Charts, read-only) `/api/squeeze_monitor` (B4-Monitor, read-only, Engine-Cache 60 s) · `WS /ws` ·
|
`GET /api/snapshot` (inkl. `verdict`-Aggregat) `/api/health` `/api/logs` `/api/stats` `/api/news` `/api/bars?tf=&n=` (Charts, read-only) `/api/squeeze_monitor` (B4-Monitor, read-only, Engine-Cache 60 s) · `WS /ws` ·
|
||||||
`POST /api/order|auto|trail|emergency|takeprofit|sltp|srclose|srclose_min|auth/check`.
|
`POST /api/order|auto|trail|emergency|takeprofit|sltp|srclose|srclose_min|autosqueeze|autosignal|auth/check`.
|
||||||
`/api/sltp {sl,tp}` = manuelles SL/TP der offenen Position → `engine.set_sltp` →
|
`/api/sltp {sl,tp}` = manuelles SL/TP der offenen Position → `engine.set_sltp` →
|
||||||
`trader.modify_sltp` (TRADE_ACTION_SLTP, prüft Seite/Mindestabstand) **und deaktiviert
|
`trader.modify_sltp` (TRADE_ACTION_SLTP, prüft Seite/Mindestabstand) **und deaktiviert
|
||||||
das Trailing** (`trail.deactivate` — sonst zöge es SL/TP beim nächsten Tick zurück,
|
das Trailing** (`trail.deactivate` — sonst zöge es SL/TP beim nächsten Tick zurück,
|
||||||
|
|||||||
+30
-7
@@ -50,6 +50,7 @@ _TIMESTOP = 24 # Time-Stop: 120 min = 24 M5-Bars in Phase „Init"
|
|||||||
_MAXH = 288 # Sim-Horizont (24 h auf M5)
|
_MAXH = 288 # Sim-Horizont (24 h auf M5)
|
||||||
_ATRMIN = 0.06 # ATR-Floor wie live (_ATR_MIN in trailing.py)
|
_ATRMIN = 0.06 # ATR-Floor wie live (_ATR_MIN in trailing.py)
|
||||||
_NIGHT = (0, 1, 2, 3, 4, 5, 6, 7) # Berlin-Stunden = gemessene Kostenfalle
|
_NIGHT = (0, 1, 2, 3, 4, 5, 6, 7) # Berlin-Stunden = gemessene Kostenfalle
|
||||||
|
_ADV_BARS = 3 # 15-Min-Regel: 3 M5-Bars = 15 min nach Entry prüfen
|
||||||
_HTF_N = 6 # 6 M5-Bars = 1 M30-Bar
|
_HTF_N = 6 # 6 M5-Bars = 1 M30-Bar
|
||||||
_H1_N = 12 # 12 M30-Bars … (H1 = 12 M5-Bars)
|
_H1_N = 12 # 12 M30-Bars … (H1 = 12 M5-Bars)
|
||||||
|
|
||||||
@@ -110,9 +111,13 @@ def _htf_signs(H, L, C, step):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _sim(entry, d, atr, H, L, C, j0):
|
def _sim(entry, d, atr, H, L, C, j0, adv15=None):
|
||||||
"""Live-Exit-Modell. Rückgabe R = Profit/ATR (brutto, ohne Kosten).
|
"""Live-Exit-Modell. Rückgabe R = Profit/ATR (brutto, ohne Kosten).
|
||||||
Phasen-Ratsche wie live: Time-Stop greift nur, solange nie Trail-Start erreicht."""
|
Phasen-Ratsche wie live: Time-Stop greift nur, solange nie Trail-Start erreicht.
|
||||||
|
|
||||||
|
`adv15` = 15-Minuten-Regel (User-Wunsch 2026-07-30): steht der Trade nach
|
||||||
|
_ADV_BARS (3 M5-Bars = 15 min) mehr als adv15×ATR im Minus → schließen.
|
||||||
|
None = aus. adv15=0.0 bedeutet „schließen wenn überhaupt im Minus"."""
|
||||||
eff = entry - d * _SL_ATR * atr
|
eff = entry - d * _SL_ATR * atr
|
||||||
hw = entry
|
hw = entry
|
||||||
trail_started = False
|
trail_started = False
|
||||||
@@ -125,6 +130,11 @@ def _sim(entry, d, atr, H, L, C, j0):
|
|||||||
exit_px = eff; break
|
exit_px = eff; break
|
||||||
hw = max(hw, hi) if d > 0 else min(hw, lo)
|
hw = max(hw, hi) if d > 0 else min(hw, lo)
|
||||||
prof = (C[j] - entry) * d
|
prof = (C[j] - entry) * d
|
||||||
|
# 15-Minuten-Regel: EINMALIGE Prüfung genau beim Bar _ADV_BARS nach Entry.
|
||||||
|
# Läuft der Trade dann gegen mich → raus (Einstieg war falsch).
|
||||||
|
if adv15 is not None and (j - j0) == _ADV_BARS - 1:
|
||||||
|
if prof <= -adv15 * atr:
|
||||||
|
exit_px = C[j]; break
|
||||||
if prof >= _TRAIL_ON * atr:
|
if prof >= _TRAIL_ON * atr:
|
||||||
trail_started = True
|
trail_started = True
|
||||||
cand = hw - d * _TRAIL * atr
|
cand = hw - d * _TRAIL * atr
|
||||||
@@ -149,7 +159,7 @@ def _rep(name, Rs, ref=None):
|
|||||||
return s / n
|
return s / n
|
||||||
|
|
||||||
|
|
||||||
def scan(bars, H, L, C, SP, A, HTF, H1, EF, ES, w, lo_i, hi_i, min_conf, skip_night):
|
def scan(bars, H, L, C, SP, A, HTF, H1, EF, ES, w, lo_i, hi_i, min_conf, skip_night, adv15=None):
|
||||||
"""Sequentielle 1-Positions-Simulation: nimmt das ERSTE Signal, wenn flat."""
|
"""Sequentielle 1-Positions-Simulation: nimmt das ERSTE Signal, wenn flat."""
|
||||||
Rs = []
|
Rs = []
|
||||||
i = max(lo_i, _EMA_SLOW + 20)
|
i = max(lo_i, _EMA_SLOW + 20)
|
||||||
@@ -172,7 +182,7 @@ def scan(bars, H, L, C, SP, A, HTF, H1, EF, ES, w, lo_i, hi_i, min_conf, skip_ni
|
|||||||
d = 1 if sig == "LONG" else -1
|
d = 1 if sig == "LONG" else -1
|
||||||
entry = C[i]
|
entry = C[i]
|
||||||
cost = (SP[i] if SP[i] > 0 else 0.0225) / atr
|
cost = (SP[i] if SP[i] > 0 else 0.0225) / atr
|
||||||
R = _sim(entry, d, atr, H, L, C, i + 1) - cost
|
R = _sim(entry, d, atr, H, L, C, i + 1, adv15) - cost
|
||||||
Rs.append(R)
|
Rs.append(R)
|
||||||
# nach dem Trade weiterspringen: der Exit fand irgendwo in [i+1, i+_MAXH]
|
# nach dem Trade weiterspringen: der Exit fand irgendwo in [i+1, i+_MAXH]
|
||||||
# statt — konservativ mit dem Time-Stop-Fenster weiterrücken (kein Overlap)
|
# statt — konservativ mit dem Time-Stop-Fenster weiterrücken (kein Overlap)
|
||||||
@@ -215,8 +225,17 @@ def main():
|
|||||||
print("=" * 92)
|
print("=" * 92)
|
||||||
|
|
||||||
verdict = {}
|
verdict = {}
|
||||||
|
# 15-Minuten-Regel (User-Wunsch): welche Schwelle ist die beste? Basis =
|
||||||
|
# Konf>=55 + Nacht-Sperre, damit nur die Regel selbst variiert.
|
||||||
|
ADV = [(None, "OHNE 15-Min-Regel (Basis)"), (0.0, "15min raus wenn im Minus"),
|
||||||
|
(0.15, "15min raus wenn < -0,15xATR"), (0.3, "15min raus wenn < -0,3xATR"),
|
||||||
|
(0.5, "15min raus wenn < -0,5xATR"), (0.8, "15min raus wenn < -0,8xATR")]
|
||||||
for label, lo, hi in (("H1 (alt)", 0, mid), ("H2 (neu)", mid, N)):
|
for label, lo, hi in (("H1 (alt)", 0, mid), ("H2 (neu)", mid, N)):
|
||||||
print(f"\n{label}:")
|
print(f"\n{label} — 15-MIN-REGEL (Basis Konf>=55 + Nacht-Sperre):")
|
||||||
|
for a, alab in ADV:
|
||||||
|
Rs = scan(bars, H, L, C, SP, A, HTF, H1, EF, ES, w, lo, hi, 55, True, a)
|
||||||
|
verdict.setdefault(("adv", a), []).append(_rep(alab, Rs))
|
||||||
|
print(f"\n{label} — Konfidenz-Achse (ohne 15-Min-Regel):")
|
||||||
for mc in (55, 65, 70, 75):
|
for mc in (55, 65, 70, 75):
|
||||||
for night in (False, True):
|
for night in (False, True):
|
||||||
tag = f"Konf>={mc}" + (" · Nacht-Sperre" if night else "")
|
tag = f"Konf>={mc}" + (" · Nacht-Sperre" if night else "")
|
||||||
@@ -227,7 +246,8 @@ def main():
|
|||||||
print("\n" + "=" * 92)
|
print("\n" + "=" * 92)
|
||||||
print(" URTEIL (trägt nur, wenn ØR in BEIDEN Hälften > 0 UND im Squeeze-Band):")
|
print(" URTEIL (trägt nur, wenn ØR in BEIDEN Hälften > 0 UND im Squeeze-Band):")
|
||||||
any_ok = False
|
any_ok = False
|
||||||
for (mc, night), vals in sorted(verdict.items()):
|
for key, vals in sorted(verdict.items(), key=lambda kv: str(kv[0])):
|
||||||
|
mc, night = key
|
||||||
if len(vals) != 2 or any(v is None for v in vals):
|
if len(vals) != 2 or any(v is None for v in vals):
|
||||||
continue
|
continue
|
||||||
ok = all(v > 0 for v in vals)
|
ok = all(v > 0 for v in vals)
|
||||||
@@ -235,7 +255,10 @@ def main():
|
|||||||
mark = "✅ TRÄGT" if (ok and band) else ("⚠ positiv, unter Squeeze-Band" if ok else "❌ fällt durch")
|
mark = "✅ TRÄGT" if (ok and band) else ("⚠ positiv, unter Squeeze-Band" if ok else "❌ fällt durch")
|
||||||
if ok and band:
|
if ok and band:
|
||||||
any_ok = True
|
any_ok = True
|
||||||
tag = f"Konf>={mc}" + (" +Nacht-Sperre" if night else "")
|
if mc == "adv":
|
||||||
|
tag = "15Min-Regel " + (str(night) if night is not None else "AUS")
|
||||||
|
else:
|
||||||
|
tag = f"Konf>={mc}" + (" +Nacht-Sperre" if night else "")
|
||||||
print(f" {tag:<28} H1 ØR {vals[0]:+.3f} · H2 ØR {vals[1]:+.3f} {mark}")
|
print(f" {tag:<28} H1 ØR {vals[0]:+.3f} · H2 ØR {vals[1]:+.3f} {mark}")
|
||||||
print("\n " + ("→ mindestens eine Variante trägt: Einbau vertretbar (klein starten, B4-Monitor)."
|
print("\n " + ("→ mindestens eine Variante trägt: Einbau vertretbar (klein starten, B4-Monitor)."
|
||||||
if any_ok else
|
if any_ok else
|
||||||
|
|||||||
@@ -357,6 +357,33 @@ DEFAULT_CONFIG = {
|
|||||||
# B4-Wochenmonitor prüfen, ob Nacht-Squeezes tragen. Code (`_SQUEEZE_NIGHT`,
|
# B4-Wochenmonitor prüfen, ob Nacht-Squeezes tragen. Code (`_SQUEEZE_NIGHT`,
|
||||||
# `_squeeze_skip_night`) bleibt dormant/reaktivierbar (true = wieder an).
|
# `_squeeze_skip_night`) bleibt dormant/reaktivierbar (true = wieder an).
|
||||||
"auto_squeeze_skip_night": "true",
|
"auto_squeeze_skip_night": "true",
|
||||||
|
# ── Autonomer Entry auf die EMPFEHLUNG (Wave-Signal) — User-Wunsch 2026-07-30
|
||||||
|
# („probieren wir es trotzdem mal"). ⚠ GEMESSEN NICHT TRAGFÄHIG
|
||||||
|
# (`backtest_auto_signal.py`, 80k M5, 2 Halbjahre): H1 durchgehend NEGATIV
|
||||||
|
# (ØR −0,05…−0,18, PF 0,78–0,93), H2 positiv (+0,06…+0,15) = Regime-Kippen;
|
||||||
|
# deckt sich mit dem Live-Dry-Run von 2026-07-23 (WR 29 %/PF 0,64). Default AUS.
|
||||||
|
# Der User hat das nach vollständiger Information bewusst gewollt → Schalter
|
||||||
|
# existiert, aber mit den gemessen BESTEN Parametern und Live-Monitor (B4/B5:
|
||||||
|
# driftet es weg → zurückbauen).
|
||||||
|
"auto_signal": "false",
|
||||||
|
# Mindest-Konfidenz für den Auto-Signal-Entry. 75 = gemessen beste Kombi
|
||||||
|
# (mit Nacht-Sperre: H1 −0,053 = mildester Verlust, H2 +0,141). ⚠ conf_pct ist
|
||||||
|
# NICHT kalibriert (`analyze_verdict_calibration.py`) — höher ist nicht monoton
|
||||||
|
# besser, 75 ist empirisch, nicht „mehr ist mehr".
|
||||||
|
"auto_signal_min_conf": "75",
|
||||||
|
# Nacht-Sperre 0–7 Uhr Berlin für den Auto-Signal-Entry (gemessene Kostenfalle,
|
||||||
|
# verbessert JEDE Variante: H1 −0,157→−0,111 bzw. −0,127→−0,053). true = an.
|
||||||
|
"auto_signal_skip_night": "true",
|
||||||
|
# 15-Minuten-Regel (User-Wunsch 2026-07-30 „wenn der Trade nach 15 Minuten
|
||||||
|
# gegen die Richtung läuft schließe ihn"): steht ein BOT-eröffneter Trade
|
||||||
|
# 15 min nach Entry mehr als X×ATR im Minus → schließen (`closed_by=adverse15`).
|
||||||
|
# ⚠ Wert GEMESSEN kalibriert (`backtest_auto_signal.py`): „sobald im Minus"
|
||||||
|
# (=0,0) ist die SCHLECHTESTE Variante (WR bricht 39→31 %, ØR-Netto −0,067);
|
||||||
|
# **0,5 ist die beste Regel-Variante** (H1 −0,111→−0,079). Netto über beide
|
||||||
|
# Hälften bringt die Regel ~nichts (+0,001 vs +0,026 ohne) — sie ist ein
|
||||||
|
# REGIME-Schutz (hilft im schlechten, kostet im guten). 2. Bestätigung von
|
||||||
|
# `backtest_adverse_nosignal.py`. 0 = aus.
|
||||||
|
"adverse_15min_atr": "0.5",
|
||||||
},
|
},
|
||||||
# (Der frühere `[setups]`-Block — Per-Setup-Toggles für den Dry-Run-Auto-Trader —
|
# (Der frühere `[setups]`-Block — Per-Setup-Toggles für den Dry-Run-Auto-Trader —
|
||||||
# ist entfernt, 2026-07-23: verwaist seit dem Entfernen des Dry-Run-Traders, wurde
|
# ist entfernt, 2026-07-23: verwaist seit dem Entfernen des Dry-Run-Traders, wurde
|
||||||
|
|||||||
+185
@@ -222,6 +222,26 @@ class TradingEngine:
|
|||||||
self._squeeze_last_sig = None # Dedup Entry (flat)
|
self._squeeze_last_sig = None # Dedup Entry (flat)
|
||||||
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) ───────────
|
||||||
|
# ⚠ GEMESSEN NICHT tragfähig (H1 negativ, s. backtest_auto_signal.py) — auf
|
||||||
|
# ausdrücklichen User-Wunsch gebaut, Default AUS, mit den besten Parametern.
|
||||||
|
_t = self.cfg["trading"]
|
||||||
|
self._auto_signal = _t.get("auto_signal", "false").lower() == "true"
|
||||||
|
try:
|
||||||
|
self._auto_signal_min_conf = int(float(_t.get("auto_signal_min_conf", "75")))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
self._auto_signal_min_conf = 75
|
||||||
|
self._auto_signal_skip_night = _t.get("auto_signal_skip_night", "true").lower() == "true"
|
||||||
|
self._signal_last_sig = None # Dedup: (dir, Signal-Episode) 1× handeln
|
||||||
|
self._signal_entry_count = 0
|
||||||
|
# 15-Minuten-Regel für BOT-eröffnete Trades (gemessen kalibriert: 0,5×ATR)
|
||||||
|
try:
|
||||||
|
self._adverse15_atr = max(0.0, float(_t.get("adverse_15min_atr", "0.5")))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
self._adverse15_atr = 0.5
|
||||||
|
self._bot_open_ticket = None # Ticket, das der Bot selbst eröffnet hat
|
||||||
|
self._adverse15_done_ticket = None # 15-Min-Prüfung je Ticket nur 1×
|
||||||
|
self._adverse15_count = 0
|
||||||
self._sr_close_ts = 0.0 # Zeit + Richtung des letzten S/R-Closes
|
self._sr_close_ts = 0.0 # Zeit + Richtung des letzten S/R-Closes
|
||||||
self._sr_close_dir = None # → Cooldown gegen Gleichrichtungs-Roundtrip
|
self._sr_close_dir = None # → Cooldown gegen Gleichrichtungs-Roundtrip
|
||||||
self._open_pos_dir = None # Richtung der aktuell offenen Position (LONG/SHORT)
|
self._open_pos_dir = None # Richtung der aktuell offenen Position (LONG/SHORT)
|
||||||
@@ -505,8 +525,10 @@ class TradingEngine:
|
|||||||
self.trader.refresh(self.data.symbol)
|
self.trader.refresh(self.data.symbol)
|
||||||
self._check_circuit_breaker() # Tagesverlust-Stopp (Überleben zuerst)
|
self._check_circuit_breaker() # Tagesverlust-Stopp (Überleben zuerst)
|
||||||
self._check_auto_close()
|
self._check_auto_close()
|
||||||
|
self._check_adverse15() # 15-Min-Regel (nur Bot-Trades)
|
||||||
self._check_sr_close()
|
self._check_sr_close()
|
||||||
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)
|
||||||
@@ -994,6 +1016,150 @@ 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 (gemessene Kostenfalle 0–7 Uhr Berlin) — kein Dedup-Marker,
|
||||||
|
# damit das Signal nach 08:00 noch feuern kann, falls es dann noch steht.
|
||||||
|
if self._auto_signal_skip_night and datetime.now(_BERLIN).hour in _SQUEEZE_NIGHT:
|
||||||
|
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
|
||||||
|
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):
|
||||||
|
"""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
|
||||||
|
(der Einstieg war falsch; gemessen: Gewinner nehmen Ø 0,19 $ Hitze, Verlierer
|
||||||
|
0,41 $). ⚠ Schwelle GEMESSEN kalibriert = **0,5×ATR**; „sobald im Minus" wäre
|
||||||
|
die schlechteste Variante (WR 39→31 %). Netto bringt die Regel über beide
|
||||||
|
Halbjahre ~nichts (Regime-Schutz: hilft im schlechten, kostet im guten) —
|
||||||
|
bewusst als Sicherheit eingebaut. Greift NICHT bei manuellen Trades (dort
|
||||||
|
bleibt die Diskretion beim User) und nicht in der Startup-Schonfrist."""
|
||||||
|
if self._adverse15_atr <= 0:
|
||||||
|
return
|
||||||
|
if self._in_startup_grace():
|
||||||
|
return
|
||||||
|
ps = self.trader.snapshot()
|
||||||
|
ticket = ps.get("ticket")
|
||||||
|
if not ticket:
|
||||||
|
return
|
||||||
|
if ticket != self._bot_open_ticket: # nur Bot-eröffnete Trades
|
||||||
|
return
|
||||||
|
if ticket == self._adverse15_done_ticket: # je Ticket nur 1× prüfen
|
||||||
|
return
|
||||||
|
ot = ps.get("open_time") or 0
|
||||||
|
if not ot:
|
||||||
|
return
|
||||||
|
age = time.time() - float(ot)
|
||||||
|
if age < 15 * 60:
|
||||||
|
return # noch keine 15 min alt
|
||||||
|
self._adverse15_done_ticket = ticket # Prüfzeitpunkt erreicht → 1× bewerten
|
||||||
|
# Verlust in ATR messen (M5-ATR wie im Backtest, Floor wie live)
|
||||||
|
try:
|
||||||
|
atr = float(((self.wave.snapshot() or {}).get("pb_feats") or {}).get("atr") or 0.0)
|
||||||
|
except Exception:
|
||||||
|
atr = 0.0
|
||||||
|
atr = max(atr, 0.06)
|
||||||
|
entry = float(ps.get("entry_price") or 0.0)
|
||||||
|
cur = float(ps.get("cur_price") or 0.0)
|
||||||
|
if not entry or not cur:
|
||||||
|
return
|
||||||
|
d = 1 if ps.get("order_type") == 0 else -1
|
||||||
|
adverse_atr = (entry - cur) * d / atr # >0 = läuft gegen die Position
|
||||||
|
if adverse_atr < self._adverse15_atr:
|
||||||
|
log.info(f"15-Min-Check (Pos {ticket}): {adverse_atr:+.2f}×ATR gegen die "
|
||||||
|
f"Position — unter Schwelle {self._adverse15_atr:.2f}, läuft weiter")
|
||||||
|
return
|
||||||
|
pnl = ps.get("pnl")
|
||||||
|
log.warning(f"⏱ 15-MIN-ADVERSE-CLOSE: Pos {ticket} läuft {adverse_atr:.2f}×ATR "
|
||||||
|
f"gegen die Richtung (≥ {self._adverse15_atr:.2f}) — P&L "
|
||||||
|
f"{(pnl if pnl is not None else 0):+.2f} → Einstieg war falsch, schließen")
|
||||||
|
err = self.trader.close(reason="adverse15")
|
||||||
|
tg = self.cfg["telegram"]
|
||||||
|
tg_on = (tg.get("enabled", "false").lower() == "true" and tg.get("bot_token"))
|
||||||
|
if err:
|
||||||
|
log.error(f"15-Min-Adverse-Close fehlgeschlagen: {err}")
|
||||||
|
self._adverse15_done_ticket = None # Fehlschlag → nächster Tick erneut
|
||||||
|
return
|
||||||
|
self._adverse15_count += 1
|
||||||
|
if tg_on:
|
||||||
|
try: send_telegram(f"⏱ <b>15-Min-Regel</b>\n{self.data.symbol or ''} nach 15 min "
|
||||||
|
f"{adverse_atr:.2f}×ATR gegen die Richtung (P&L "
|
||||||
|
f"{(pnl if pnl is not None else 0):+.2f}) → geschlossen.",
|
||||||
|
tg["bot_token"], tg["chat_id"])
|
||||||
|
except Exception as e: log.warning(f"Adverse15-Telegram: {e}")
|
||||||
|
|
||||||
def set_sr_autoclose(self, on: bool) -> bool:
|
def set_sr_autoclose(self, on: bool) -> bool:
|
||||||
"""Automatischen S/R-Close an/aus (UI/Config). Rückgabe = neuer Zustand."""
|
"""Automatischen S/R-Close an/aus (UI/Config). Rückgabe = neuer Zustand."""
|
||||||
self._auto_sr_close = bool(on)
|
self._auto_sr_close = bool(on)
|
||||||
@@ -1306,6 +1472,8 @@ 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:
|
||||||
|
self._auto_signal = bool(st["auto_signal"])
|
||||||
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:
|
||||||
@@ -1324,6 +1492,7 @@ 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}, f)
|
"sr_close_min_gain": self._sr_close_min_gain}, f)
|
||||||
os.replace(tmp, self._runtime_state_path)
|
os.replace(tmp, self._runtime_state_path)
|
||||||
@@ -1562,6 +1731,9 @@ class TradingEngine:
|
|||||||
if source == "auto_squeeze":
|
if source == "auto_squeeze":
|
||||||
log.info(f"[ORDER] {want} — autonom vom Auto-Squeeze eröffnet "
|
log.info(f"[ORDER] {want} — autonom vom Auto-Squeeze eröffnet "
|
||||||
f"(Signal={sig})")
|
f"(Signal={sig})")
|
||||||
|
elif source == "auto_signal":
|
||||||
|
log.info(f"[ORDER] {want} — autonom auf die Empfehlung eröffnet "
|
||||||
|
f"(Signal={sig})")
|
||||||
elif sig != want:
|
elif sig != want:
|
||||||
log.info(f"[ORDER] {want} ohne passende Empfehlung (Signal={sig}) "
|
log.info(f"[ORDER] {want} ohne passende Empfehlung (Signal={sig}) "
|
||||||
f"— vom User bestätigt, wird eröffnet")
|
f"— vom User bestätigt, wird eröffnet")
|
||||||
@@ -1569,6 +1741,14 @@ class TradingEngine:
|
|||||||
err = fn(sym)
|
err = fn(sym)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
# Vom BOT eröffnete Trades markieren → nur für die greift die 15-Min-Regel
|
||||||
|
# (manuelle Trades bleiben in der Diskretion des Users, User-Vorgabe).
|
||||||
|
if source in ("auto_squeeze", "auto_signal"):
|
||||||
|
try:
|
||||||
|
self._bot_open_ticket = self.trader.snapshot().get("ticket")
|
||||||
|
self._adverse15_done_ticket = None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
# Trailing automatisch aktivieren (wie _after_trade im Widget)
|
# Trailing automatisch aktivieren (wie _after_trade im Widget)
|
||||||
if not self.trail.enabled:
|
if not self.trail.enabled:
|
||||||
self.trail.set_setup(self._last_rec.get("setup"))
|
self.trail.set_setup(self._last_rec.get("setup"))
|
||||||
@@ -2147,6 +2327,11 @@ class TradingEngine:
|
|||||||
"sr_close_count": self._sr_close_count,
|
"sr_close_count": self._sr_close_count,
|
||||||
"auto_squeeze": self._auto_squeeze,
|
"auto_squeeze": self._auto_squeeze,
|
||||||
"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_count": self._adverse15_count,
|
||||||
"last_trades": last_trades,
|
"last_trades": last_trades,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -337,6 +337,18 @@ async def toggle_autosqueeze(x_auth_token: str | None = Header(default=None)):
|
|||||||
return {"ok": True, "enabled": state}
|
return {"ok": True, "enabled": state}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/autosignal")
|
||||||
|
async def toggle_autosignal(x_auth_token: str | None = Header(default=None)):
|
||||||
|
"""Autonomen Entry auf die EMPFEHLUNG an/aus (User-Wunsch 2026-07-30).
|
||||||
|
⚠ Gemessen NICHT tragfähig (backtest_auto_signal.py: H1 negativ) — bewusst
|
||||||
|
vom User gewollt, Default aus, mit 15-Min-Adverse-Schutz."""
|
||||||
|
_require_auth(x_auth_token)
|
||||||
|
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")
|
||||||
async def srclose_min(payload: dict = Body(...),
|
async def srclose_min(payload: dict = Body(...),
|
||||||
x_auth_token: str | None = Header(default=None)):
|
x_auth_token: str | None = Header(default=None)):
|
||||||
|
|||||||
@@ -543,6 +543,14 @@ 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
|
||||||
|
// tragfähig (H1 negativ) und läuft nur auf ausdrücklichen Wunsch.
|
||||||
|
const asBtn = $("btn-autosig");
|
||||||
|
if (asBtn) {
|
||||||
|
asBtn.classList.toggle("warn-on", !!d.auto_signal);
|
||||||
|
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
|
||||||
@@ -961,6 +969,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!)");
|
||||||
$("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");
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -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=118">
|
<link rel="stylesheet" href="style.css?v=119">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Modul-Menü (ganz oben) -->
|
<!-- Modul-Menü (ganz oben) -->
|
||||||
@@ -209,6 +209,7 @@
|
|||||||
<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>
|
||||||
<button class="btn tog" id="btn-trail">🎯 TRAIL</button>
|
<button class="btn tog" id="btn-trail">🎯 TRAIL</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -228,6 +229,6 @@
|
|||||||
<div id="toast" class="toast hidden"></div>
|
<div id="toast" class="toast hidden"></div>
|
||||||
|
|
||||||
<script src="lightweight-charts.standalone.production.js?v=114"></script>
|
<script src="lightweight-charts.standalone.production.js?v=114"></script>
|
||||||
<script src="app.js?v=118"></script>
|
<script src="app.js?v=119"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -274,6 +274,9 @@ main{padding-bottom:74px} /* Platz für die fixe Leiste */
|
|||||||
.btn.close {background:#2d2000; color:var(--accent)}
|
.btn.close {background:#2d2000; color:var(--accent)}
|
||||||
.btn.tog {flex:.8; font-size:16px}
|
.btn.tog {flex:.8; font-size:16px}
|
||||||
.btn.tog.on{background:#1f6feb; color:#fff}
|
.btn.tog.on{background:#1f6feb; color:#fff}
|
||||||
|
/* Auto-Signal-Entry: bernstein statt blau — gemessen NICHT tragfähig (H1 negativ),
|
||||||
|
läuft nur auf ausdrücklichen User-Wunsch → Farbe signalisiert „Achtung, aktiv". */
|
||||||
|
.btn.tog.warn-on{background:#9e6a03; color:#fff}
|
||||||
.btn:disabled{opacity:.32; cursor:default}
|
.btn:disabled{opacity:.32; cursor:default}
|
||||||
|
|
||||||
/* Empfehlungs-Blinken der LONG/SHORT-Buttons */
|
/* Empfehlungs-Blinken der LONG/SHORT-Buttons */
|
||||||
|
|||||||
Reference in New Issue
Block a user