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:
co-authored by
Claude Opus 5
parent
b8705e297f
commit
5474c2be61
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -224,25 +224,43 @@ function renderM15(s) {
|
|||||||
+ "Quote ist also eher noch etwas optimistisch. 45–55 % bleibt ohne Richtung.";
|
+ "Quote ist also eher noch etwas optimistisch. 45–55 % 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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user