Gate 4 der M15-Karte: Ziel nur noch in der ERLAUBTEN Richtung

User-Fund: Karte zeigte "verboten SHORT" (= LONG erlaubt) UND ein Ziel
0,15 $ UNTER dem Kurs. Die Arithmetik war korrekt - aber fuer die
verbotene Richtung.

Ursache: das Ziel war schlicht das entferntere der beiden Level
(max nach gap), ohne Richtungspruefung. Strukturell steckte dahinter
ein Fade-Trade (Gegenlevel als Ziel = Mean Reversion) in einer Kette,
deren Gate 2 ein Trendfilter ist - zwei unvereinbare Konzepte. Damit
derselbe Fehler, wegen dem die Gesamtempfehlung entfernt wurde: aus
einem VERBOT wird ueber die Hintertuer wieder eine RICHTUNG.

Fix nach der gemessenen Population (backtest_m15_gates.py): Einstieg
am naechstgelegenen Level, Richtung = die vom HTF erlaubte. Ein Ziel
nur, wenn es jenseits dieses Einstiegs in der erlaubten Richtung
liegt; sonst "kein Ziel" mit Grund statt einer erfundenen Zahl.
Richtung steht jetzt im Anzeigetext.

- tests/test_m15_ziel.py (6 Tests, Invariante ueber 32 Konstellationen)
- Mutationsprobe: alte Zeile zurueck -> 4 von 6 Tests fallen
- deploy.py --feld unterstuetzt jetzt Punktpfade (m15_setup.ziel_grund)
- v=159

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-10 06:36:02 +02:00
co-authored by Claude Opus 5
parent b8705e297f
commit 5474c2be61
6 changed files with 293 additions and 30 deletions
+66
View File
@@ -5154,6 +5154,72 @@ Mit **5 Szenarien getestet** (alles passt · Bias uneinig · Kosten teuer · Lev
zu weit · Kaltstart ohne ATR_M15 → kein Absturz), live verifiziert über zu weit · Kaltstart ohne ATR_M15 → kein Absturz), live verifiziert über
`deploy.py --feld m15_setup`. `deploy.py --feld m15_setup`.
### ⚠⚠ GATE ④ ZEIGTE DAS ZIEL IN DER VERBOTENEN RICHTUNG — behoben 2026-08-10
**User-Fund:** „Kurs ist ca. 79 und Ziel ist 78,7 bei Empfehlung LONG — ein
Widerspruch?" **Ja, und zwar ein echter.** Live nachgestellt:
| | |
|---|---|
| Kurs (bid) | **78,918** |
| ② Bias | `verboten: SHORT` → erlaubt ist **LONG** |
| ④ Ziel | **78,768** — 0,15 $ **unter** dem Kurs, `von: 79,033` |
Gelesen heisst das „am Widerstand einsteigen, Support anlaufen" = ein **SHORT**.
**Die Arithmetik war korrekt — nur für die verbotene Richtung.** Ursache war
eine Zeile:
```python
fern = max(zonen, key=lambda x: x["gap_atr15"]) # rein geometrisch
```
Das Ziel war schlicht das **entferntere** der beiden Level, ohne jede
Richtungsprüfung.
⚠⚠ **Der strukturelle Grund ist der wichtigere Teil: zwei unvereinbare
Handelskonzepte in einer Kette.** Gate ④ unterstellte stillschweigend einen
**Fade-Trade** (Level anlaufen, Gegenlevel als Ziel = Mean Reversion), Gate ② ist
aber ein **Trendfilter**. Damit ist es **derselbe Fehler, wegen dem die
Gesamtempfehlung entfernt wurde**: aus einem **VERBOT** wird über die Hintertür
wieder eine **RICHTUNG** — hier nicht über verrechnete Stimmen, sondern über die
Geometrie. Und es ist der zweite Fall in dieser Karte (der erste war der
Richtungs-Bias, korrigiert 08.08. nach 13 LONG-Episoden an einem fallenden Tag).
**Ein Verbot lässt sich offenbar auf mehr Wegen in eine Richtung verwandeln, als
man beim Bauen sieht.**
**Fix — die gemessene Population ist der Maßstab.** `backtest_m15_gates.py`
misst: Einstieg am **nächstgelegenen** Level, Richtung = die vom HTF **erlaubte**;
der Exit ist der kanonische Trailing-Exit — **ein Level-Ziel war NIE gemessen**
(④ ist ausdrücklich „reine Arithmetik"). Jetzt gilt:
| Lage (LONG erlaubt) | Anzeige |
|---|---|
| nächstes Level = **Support** → Abpraller | Ziel = Widerstand darüber ▲ |
| nächstes Level = **Widerstand** → Ausbruch | **kein Ziel** + Grund |
| **kein Veto** (M30 flach) | **kein Ziel** — ein einzelnes wäre wieder Richtung |
**„Kein Ziel" ist ein eigener Zustand, keine Lücke** — die Karte nennt den
Grund, statt eine Zahl zu erfinden. Beim Ausbruchs-Einstieg ist das nächste Ziel
schlicht **nicht berechenbar**: `_draw_levels` liefert bewusst nur die nächste
Linie je Seite (Entscheidung 14.07., sonst tauchen beim Kurslauf laufend Linien
auf/ab).
⚠ Die **Richtung steht jetzt IM Text** (`④ Ziel ▲ 79,033 … Einstieg 78,768`).
Ein Ziel ohne genannte Richtung lässt sich still wieder verdrehen — mit genannter
nicht. Neue Felder: `ziel.richtung`, `ziel_grund`.
**Mutationsprobe bestanden** (ein grüner Test belegt sonst nichts): mit der
alten Zeile zurückgebaut fallen **4 von 6** Tests, und die Fehlermeldung
reproduziert den Fund wörtlich (`{'level': 78.768, … 'von': 79.033}`).
⚠ Die zwei übrigen bleiben **korrekterweise** grün — dort liegt das entferntere
Level tatsächlich in der erlaubten Richtung, alt und neu stimmen überein. Ein
Test, der auch die richtigen Fälle rot färbt, wäre zu eng gefasst.
`tests/test_m15_ziel.py` (6 Tests) prüft die Invariante **„ein ausgewiesenes Ziel
liegt IMMER in der erlaubten Richtung"** über 32 Konstellationen × 2 Richtungen.
**Live beide Zweige belegt**, weil der Kurs während der Prüfung lief: bei
bid 78,918 (nächstes = Widerstand) → kein Ziel + „Ausbruch"; bei bid 78,871
(nächstes = Support) → Ziel 79,033 ▲, Einstieg 78,768.
⚠ **Nebenbei behoben: `deploy.py --feld` konnte nur die oberste Snapshot-Ebene**
(`a.feld in snap`). Für `m15_setup.ziel_grund` war der Neuheits-Beweis damit gar
nicht führbar — und Schritt 4 ist der EINZIGE belastbare Nachweis, dass neuer
Python-Code läuft. Jetzt mit **Punktpfaden**; ein Feld mit Wert `None` zählt als
vorhanden (geprüft wird der Schlüssel, nicht der Inhalt).
## ⚠⚠ EXIT-ABSTAND AUS M15-ATR = FÄLLT DURCH (2026-08-08) ## ⚠⚠ EXIT-ABSTAND AUS M15-ATR = FÄLLT DURCH (2026-08-08)
Zweiter Teil der M15-Idee: M5-Einstieg behalten, nur den EXIT gröber führen. Zweiter Teil der M15-Idee: M5-Einstieg behalten, nur den EXIT gröber führen.
+50 -12
View File
@@ -4368,27 +4368,65 @@ class TradingEngine:
for x in b80] or None for x in b80] or None
# ── 4b) ZIEL-ERREICHBARKEIT — reine GEOMETRIE auf dem Kegel. # ── 4b) ZIEL-ERREICHBARKEIT — reine GEOMETRIE auf dem Kegel.
# Ziel eines Trades am nahen Level ist das GEGENÜBERLIEGENDE. # Gefragt wird nur: passt der Abstand noch in das 80-%-Band des
# Gefragt wird nur: passt dieser Abstand noch in das 80-%-Band # jeweiligen Horizonts? ⚠ KEINE Bruch-Annahme — die im Vorschlag
# des jeweiligen Horizonts? ⚠ KEINE Bruch-Annahme — die im # mitgedachte „je länger, desto eher bricht es"-Regel ist
# Vorschlag mitgedachte „je länger, desto eher bricht es"-Regel # gemessen NICHT robust (Touch-Zahl: umgekehrtes U auf 4
# ist gemessen NICHT robust (Touch-Zahl: umgekehrtes U auf 4
# Live-Tagen, im Backtest +0,010 AUC und nach dem Nachtrainieren # Live-Tagen, im Backtest +0,010 AUC und nach dem Nachtrainieren
# gar nichts mehr). Hier steckt deshalb nur Arithmetik. # gar nichts mehr). Hier steckt deshalb nur Arithmetik.
if len(zonen) == 2 and bid and b80: #
# ⚠⚠ FEHLER BEHOBEN 2026-08-10 (User-Fund „Kurs 79, Ziel 78,7 — bei
# LONG?"). Der erste Entwurf nahm als Ziel schlicht das ENTFERNTERE
# der beiden Level (`max` nach gap) — also **rein geometrisch, ohne
# die erlaubte Richtung zu prüfen**. Real zeigte die Karte damit
# `verboten=SHORT` UND ein Ziel 0,15 $ UNTER dem Kurs: die Arithmetik
# war korrekt, aber für die VERBOTENE Richtung.
# Strukturell steckte dahinter die stillschweigende Annahme eines
# **Fade-Trades** (am Level einsteigen, Gegenlevel als Ziel = Mean
# Reversion), während Gate ② ein **Trendfilter** ist. Zwei
# unvereinbare Handelskonzepte in einer Kette — und damit derselbe
# Fehler, wegen dem die Gesamtempfehlung entfernt wurde: aus einem
# VERBOT wird über die Hintertür wieder eine RICHTUNG.
#
# Jetzt gilt die gemessene Population aus `backtest_m15_gates.py`:
# Einstieg am **nächstgelegenen** Level, Richtung = die vom HTF
# erlaubte. Ein Ziel wird nur ausgewiesen, wenn es JENSEITS dieses
# Einstiegs in der erlaubten Richtung liegt.
# ⚠ Ohne Veto (M30 flach) gibt es KEIN Ziel — ein einzelnes Ziel
# zu zeigen wäre wieder eine Richtungsaussage.
# ⚠ `_draw_levels` liefert bewusst nur die NÄCHSTE Linie je Seite.
# Beim Ausbruchs-Einstieg (nächstes Level == Ziel-Seite) ist das
# nächste Ziel damit schlicht NICHT berechenbar — dann sagt die
# Karte das, statt eine Zahl zu erfinden.
out["ziel"] = None
d_ok = (1 if verboten == "SHORT" else
-1 if verboten == "LONG" else 0)
if len(zonen) == 2 and bid and b80 and d_ok:
nah = min(zonen, key=lambda x: x["gap_atr15"]) nah = min(zonen, key=lambda x: x["gap_atr15"])
fern = max(zonen, key=lambda x: x["gap_atr15"]) kand = next((z for z in zonen
strecke = abs(fern["level"] - bid) if z["seite"] == ("res" if d_ok > 0 else "sup")), None)
if kand is None:
grund = "kein Level in der erlaubten Richtung"
elif (kand["level"] - nah["level"]) * d_ok > 0:
strecke = abs(kand["level"] - bid)
out["ziel"] = { out["ziel"] = {
"level": fern["level"], "strecke": round(strecke, 3), "level": kand["level"], "strecke": round(strecke, 3),
"horizonte": [{"minutes": x.get("minutes"), "horizonte": [{"minutes": x.get("minutes"),
"drin": bool(x.get("lo") is not None "drin": bool(x.get("lo") is not None
and x.get("hi") is not None and x.get("hi") is not None
and x["lo"] <= fern["level"] <= x["hi"])} and x["lo"] <= kand["level"] <= x["hi"])}
for x in b80], for x in b80],
"von": nah["level"]} "von": nah["level"],
"richtung": "LONG" if d_ok > 0 else "SHORT"}
grund = None
else: else:
out["ziel"] = None grund = ("Ausbruch — Einstieg am "
+ ("Widerstand" if nah["seite"] == "res" else "Support")
+ f" {nah['level']:.3f}, nächstes Ziel dahinter nicht berechenbar")
out["ziel_grund"] = grund
else:
out["ziel_grund"] = (None if d_ok else
"kein Veto — Richtung offen, kein Ziel bestimmbar")
# ── 4c) VOLA-REGIME als Slippage-HINWEIS (kein Gate, s. Docstring) # ── 4c) VOLA-REGIME als Slippage-HINWEIS (kein Gate, s. Docstring)
out["vola"] = self._vola_regime(atr15) out["vola"] = self._vola_regime(atr15)
+125
View File
@@ -0,0 +1,125 @@
"""Gate ④ der M15-Karte: das Ziel muss in der ERLAUBTEN Richtung liegen.
ANLASS (2026-08-10, User-Fund): die Karte zeigte `verboten=SHORT` (also LONG
erlaubt) UND gleichzeitig ein Ziel 0,15 $ UNTER dem Kurs. Die Arithmetik war
korrekt — aber fuer die verbotene Richtung. Ursache: das Ziel war schlicht das
ENTFERNTERE der beiden Level (`max` nach gap), ohne Richtungspruefung.
⚠ Die Tests pruefen BEIDE Richtungen. Ein Test, der nur zeigt „im Fehlerfall
kommt kein Ziel", belegt nicht, dass der korrekte Fall noch funktioniert —
genau daran ist am 07.08. die Neustart-Sperre fast gescheitert (Negativ-Test
gruen, Positiv-Test rot).
"""
from __future__ import annotations
import pytest
from core.engine import TradingEngine
class _Stub:
"""Minimal-Engine: nur die beiden Methoden, die `_m15_setup` wirklich ruft."""
def __init__(self, res, sup):
self._res, self._sup = res, sup
def _draw_levels(self):
return {"res": [self._res] if self._res else [],
"sup": [self._sup] if self._sup else []}
def _vola_regime(self, atr15):
return None
def _setup(bid, res, sup, htf):
"""`_m15_setup` mit Stub-Engine aufrufen (die Methode ist eine reine Funktion)."""
ws = {"tf_atr": {"M15": 0.25}, "htf_trend": htf, "h1_trend": htf,
"pb_feats": {"atr": 0.2, "ema_diff": 0.05, "mom6": 0.1, "mom3": 0.1}}
cone = {"levels": [{"band": 80, "minutes": m, "lo": bid - 1.0, "hi": bid + 1.0}
for m in (30, 60, 120)]}
return TradingEngine._m15_setup(
_Stub(res, sup), {"bid": bid, "ask": bid + 0.03}, ws, cone, None)
# ── Der reale Fehlerfall vom 10.08. ──────────────────────────────────────
def test_kein_ziel_gegen_das_veto():
"""LONG erlaubt, naechstes Level ist der Widerstand → Ausbruch, kein Ziel.
Exakt die Live-Zahlen: bid 78,918 · res 79,033 (0,47xATR) · sup 78,768
(0,61xATR). Vorher stand hier `ziel=78.768` — 0,15 $ UNTER dem Kurs, bei
verbotenem SHORT."""
s = _setup(78.918, 79.033, 78.768, htf=1)
assert s["bias"]["verboten"] == "SHORT" # LONG ist erlaubt
assert s["ziel"] is None, "Ziel zeigte in die verbotene Richtung"
assert "Ausbruch" in (s["ziel_grund"] or "")
def test_ziel_niemals_auf_der_verbotenen_seite():
"""Die eigentliche Invariante — ueber viele Konstellationen geprueft.
Wird ein Ziel ausgewiesen, MUSS es in der erlaubten Richtung vom Kurs
liegen. Das ist die Aussage, die der Fehler verletzt hat."""
for htf, verboten in ((1, "SHORT"), (-1, "LONG")):
for res_d in (0.05, 0.15, 0.30, 0.60):
for sup_d in (0.05, 0.15, 0.30, 0.60):
bid = 78.918
s = _setup(bid, bid + res_d, bid - sup_d, htf=htf)
assert s["bias"]["verboten"] == verboten
z = s["ziel"]
if z is None:
continue
if verboten == "SHORT": # LONG erlaubt → Ziel OBEN
assert z["level"] > bid, f"Ziel {z['level']} unter Kurs {bid}"
assert z["richtung"] == "LONG"
else: # SHORT erlaubt → Ziel UNTEN
assert z["level"] < bid, f"Ziel {z['level']} ueber Kurs {bid}"
assert z["richtung"] == "SHORT"
# ── Der korrekte Fall MUSS erhalten bleiben ──────────────────────────────
def test_fade_am_widerstand_bleibt_wenn_short_erlaubt():
"""M30 ab → SHORT erlaubt, naechstes Level ist der Widerstand → Ziel Support.
Das ist genau die Anzeige, die vorher zu sehen war — sie war nur unter dem
FALSCHEN Veto zu sehen. Sie darf nicht mit weggefixt werden."""
s = _setup(78.918, 79.033, 78.768, htf=-1)
assert s["bias"]["verboten"] == "LONG"
assert s["ziel"] is not None
assert s["ziel"]["level"] == 78.768
assert s["ziel"]["von"] == 79.033
assert s["ziel"]["richtung"] == "SHORT"
def test_bounce_am_support_mit_long():
"""Kurs dicht ueber dem Support, LONG erlaubt → Einstieg Support, Ziel Widerstand."""
s = _setup(78.80, 79.30, 78.77, htf=1) # sup 0,03 nah, res 0,50 fern
assert s["ziel"] is not None
assert s["ziel"]["von"] == 78.77 # Einstieg = naechstes Level
assert s["ziel"]["level"] == 79.30 # Ziel = oben
assert s["ziel"]["richtung"] == "LONG"
def test_ohne_veto_kein_ziel():
"""M30 flach → keine erlaubte Richtung → ein einzelnes Ziel waere wieder
eine Richtungsaussage."""
s = _setup(78.918, 79.033, 78.768, htf=0)
assert s["bias"]["verboten"] is None
assert s["ziel"] is None
assert "kein Veto" in (s["ziel_grund"] or "")
def test_kegel_pruefung_bleibt_wirksam():
"""Liegt das Ziel ausserhalb des 80-%-Bandes, muessen alle Horizonte
`drin=False` melden — die Erreichbarkeits-Aussage selbst bleibt intakt."""
ws = {"tf_atr": {"M15": 0.25}, "htf_trend": 1, "h1_trend": 1,
"pb_feats": {"atr": 0.2, "ema_diff": 0.05, "mom6": 0.1, "mom3": 0.1}}
cone = {"levels": [{"band": 80, "minutes": m, "lo": 78.79, "hi": 78.81}
for m in (30, 60, 120)]}
s = TradingEngine._m15_setup(
_Stub(79.30, 78.77), {"bid": 78.80, "ask": 78.83}, ws, cone, None)
assert s["ziel"] is not None
assert all(h["drin"] is False for h in s["ziel"]["horizonte"])
if __name__ == "__main__":
raise SystemExit(pytest.main([__file__, "-v"]))
+17 -1
View File
@@ -168,8 +168,24 @@ def main() -> int:
schritt(3, f"Port {p}: ⚠ {len(ids)} Instanzen {ids}") schritt(3, f"Port {p}: ⚠ {len(ids)} Instanzen {ids}")
# ── 4) beweist ein neues Feld, dass der PYTHON-Code neu ist? ───────── # ── 4) beweist ein neues Feld, dass der PYTHON-Code neu ist? ─────────
# ⚠ PUNKTPFADE seit 2026-08-10 (`m15_setup.ziel_grund`). Vorher ging nur die
# oberste Ebene (`a.feld in snap`) — die meisten neuen Felder liegen aber
# verschachtelt, und fuer die war der Neuheits-Beweis damit gar nicht
# fuehrbar. Genau das ist Schritt 4 aber: der EINZIGE belastbare Nachweis,
# dass wirklich neuer Python-Code laeuft (statische Dateien beweisen
# nichts — `restart_server.bat` hat zweimal still nicht neu gestartet).
# ⚠ Ein Feld mit Wert `None` zaehlt als VORHANDEN — geprueft wird die
# Existenz des Schluessels, nicht sein Inhalt. Ein neues Feld ist beim
# ersten Snapshot oft noch leer.
if a.feld: if a.feld:
if a.feld in snap: knoten, pfad_ok = snap, True
for teil in a.feld.split("."):
if isinstance(knoten, dict) and teil in knoten:
knoten = knoten[teil]
else:
pfad_ok = False
break
if pfad_ok:
schritt(4, f"Feld `{a.feld}` vorhanden → Python-Code ist neu ✅") schritt(4, f"Feld `{a.feld}` vorhanden → Python-Code ist neu ✅")
else: else:
fehler.append(f"Feld `{a.feld}` fehlt im Snapshot") fehler.append(f"Feld `{a.feld}` fehlt im Snapshot")
+28 -10
View File
@@ -224,25 +224,43 @@ function renderM15(s) {
+ "Quote ist also eher noch etwas optimistisch. 4555 % bleibt ohne Richtung."; + "Quote ist also eher noch etwas optimistisch. 4555 % bleibt ohne Richtung.";
// ── Reichweite (Kegel) — sagt WIE WEIT, nicht wohin ────────────────── // ── Reichweite (Kegel) — sagt WIE WEIT, nicht wohin ──────────────────
// ⚠ REINE GEOMETRIE: passt das Gegenlevel (= das Ziel) noch in das 80-%-Band // ⚠ REINE GEOMETRIE: passt das Ziel noch in das 80-%-Band des jeweiligen
// des jeweiligen Horizonts? Keine Bruch-Annahme — die wäre nicht belegt. // Horizonts? Keine Bruch-Annahme — die wäre nicht belegt.
// ⚠⚠ Die Richtung steht seit dem Fix vom 2026-08-10 MIT im Text. Vorher war
// das Ziel rein geometrisch das entferntere Level und stand real GEGEN das
// Veto aus ② („verboten SHORT" + Ziel 0,15 $ unter dem Kurs). Ein Ziel ohne
// genannte Richtung lässt sich still wieder verdrehen — mit genannter nicht.
const zi = s.ziel; const zi = s.ziel;
const r = s.reichweite || []; const r = s.reichweite || [];
const spanne = r.length
? " · Spanne " + r.map(x => `${x.minutes} ${f(x.lo, 2)}${f(x.hi, 2)}`).join(" · ")
: "";
const rEl = $("m15-reich");
if (zi && zi.horizonte && zi.horizonte.length) { if (zi && zi.horizonte && zi.horizonte.length) {
const drin = zi.horizonte.filter(x => x.drin).map(x => x.minutes); const drin = zi.horizonte.filter(x => x.drin).map(x => x.minutes);
const el = $("m15-reich"); const pfeil = zi.richtung === "LONG" ? "▲" : "▼";
el.className = "m15-item " + (drin.length ? "ok" : "warn"); rEl.className = "m15-item " + (drin.length ? "ok" : "warn");
el.textContent = drin.length rEl.textContent = drin.length
? `④ Ziel ${f(zi.level, 3)} erreichbar ab ${Math.min(...drin)} ` ? `④ Ziel ${pfeil} ${f(zi.level, 3)} erreichbar ab ${Math.min(...drin)} `
+ `(${zi.strecke} $ vom Kurs)` + `(${zi.strecke} $ vom Kurs, Einstieg ${f(zi.von, 3)})`
: `④ Ziel ${f(zi.level, 3)} liegt AUSSERHALB des 120-Kegels ` : `④ Ziel ${pfeil} ${f(zi.level, 3)} liegt AUSSERHALB des 120-Kegels `
+ `(${zi.strecke} $) — CRV zeitlich nicht gedeckt`; + `(${zi.strecke} $) — CRV zeitlich nicht gedeckt`;
} else if (s.ziel_grund) {
// Kein berechenbares Ziel ist ein EIGENER Zustand, keine Lücke —
// deshalb wird der Grund genannt statt eine Zahl erfunden.
rEl.className = "m15-item";
rEl.textContent = `④ kein Ziel — ${s.ziel_grund}${spanne}`;
} else { } else {
$("m15-reich").className = "m15-item"; rEl.className = "m15-item";
$("m15-reich").textContent = r.length rEl.textContent = r.length
? "④ erwartete Spanne · " + r.map(x => `${x.minutes} ${f(x.lo, 2)}${f(x.hi, 2)}`).join(" · ") ? "④ erwartete Spanne · " + r.map(x => `${x.minutes} ${f(x.lo, 2)}${f(x.hi, 2)}`).join(" · ")
: "④ erwartete Spanne —"; : "④ erwartete Spanne —";
} }
rEl.title = "Reine Geometrie auf dem Kegel: liegt das Ziel im 80-%-Band des "
+ "Horizonts? Das Ziel wird NUR in der von ② erlaubten Richtung gebildet — "
+ "ein rein geometrisches Ziel zeigte am 10.08. auf die verbotene Seite. "
+ "Beim Ausbruchs-Einstieg gibt es kein berechenbares Ziel: _draw_levels "
+ "liefert bewusst nur die nächste Linie je Seite.";
// ── Veto: die eigene Bilanz, JE LOT ────────────────────────────────── // ── Veto: die eigene Bilanz, JE LOT ──────────────────────────────────
const v = s.veto || {}; const v = s.veto || {};
+2 -2
View File
@@ -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=158"> <link rel="stylesheet" href="style.css?v=159">
</head> </head>
<body> <body>
<!-- Modul-Menü (ganz oben) --> <!-- Modul-Menü (ganz oben) -->
@@ -347,6 +347,6 @@
</div> </div>
<div id="toast" class="toast hidden"></div> <div id="toast" class="toast hidden"></div>
<script src="app.js?v=158"></script> <script src="app.js?v=159"></script>
</body> </body>
</html> </html>