C: Wahrscheinlichkeits-Kegel (kalibriert) + Config-Waechter im Reminder
C - core/cone.py + Kachel #card-cone + MT5 v1.31 (CN;<min>;<lo>;<hi>): "Erwartete Spanne" statt "Kursvorhersage". Der User wollte eingezeichnete Verlaufspfade; bewusst NICHT gebaut - sie suggerieren eine Praezision, die es nicht gibt (Lehre aus analyze_verdict_calibration.py). Der Kegel sagt wie WEIT der Kurs in 30/60/120 min plausibel laeuft, nicht wohin. analyze_cone.py: r(h)=(C[i+h]-C[i])/ATR[i], Fit auf H1, Abdeckung gemessen auf H2 (echtes out-of-sample): 80%-Band -> 76,7 / 76,6 / 75,3 % 90%-Band -> 87,5 / 87,2 / 86,5 % Konsistent ~3-5 Pp ZU ENG. Das wird NICHT weggefittet - Kachel und Tooltip nennen die REAL gemessene Abdeckung, nicht den Nennwert. Damit nach dem P(break)-Modell die zweite kalibriert geprueft Komponente im Projekt. Zentriert auf den aktuellen Kurs, KEINE Drift addiert (Median +-0,0..0,16 xATR = klein gegen die Bandbreite; eine Drift-Korrektur waere eine Richtungsaussage und die ist nicht belegt). Farbe neutral grau, kein gruen/rot. Kein Signal, kein Verdict-Gewicht. MQL5 v1.31: zwei sich oeffnende gepunktete Pfade, verkettet 30->60->120. Startpunkt aus der PX-Zeile - die setzt g_conePx jetzt auch, wenn InpShowPrice aus ist (sonst verliert der Faecher seinen Anker). Config-Waechter in measurement_reminder.py (Antwort auf "sollten wir alle verworfenen Backtests woechentlich neu rechnen?"): NEIN - eine Woche sind ~2000 Bars, und 18 Ideen x 52 Wochen = 936 Tests/Jahr erzeugen bei 5 % Fehlalarmquote ~47 falsche "funktioniert jetzt!" pro Jahr. Stattdessen EREIGNIS-gesteuert: CONFIG_DEPS verankert 8 config-abhaengige Messungen an den Wert, gegen den sie validiert wurden (entry_room_atr 0,6 / sr_close_pbreak 0,55 / breakout_k 0,3 / risk_pct 0 / margin_buffer_pct 95 / adverse_15min_atr 0,5 / auto_signal_min_conf 75 / dead_hours leer). Weicht einer ab, meldet der Timer welche Messung veraltet ist. Numerischer Vergleich, damit 0.60 == 0.6. Mit 5 synthetischen Szenarien getestet. Erledigt ausgetragen: Chartmuster-Kontrolltest (30.07., Muster schlagen die Kontrolle in beiden Haelften +0,022/+0,108). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
03be3b8f5f
commit
d9cb54a52c
@@ -1426,6 +1426,34 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
|||||||
Continuation = breakeven-negativ (−0,011/−0,005, Momentum-Klasse). Damit ist auch
|
Continuation = breakeven-negativ (−0,011/−0,005, Momentum-Klasse). Damit ist auch
|
||||||
die Struktur-Idee empirisch durch — die Kachel bleibt Ist-Zustand-Anzeige (wie
|
die Struktur-Idee empirisch durch — die Kachel bleibt Ist-Zustand-Anzeige (wie
|
||||||
TF-Ampel/Squeeze), steuert nichts.
|
TF-Ampel/Squeeze), steuert nichts.
|
||||||
|
- **Wahrscheinlichkeits-Kegel „Erwartete Spanne" (`core/cone.py` + Kachel `#card-cone`
|
||||||
|
+ MT5 `CN;…`, 2026-07-31): STREUUNG, NICHT RICHTUNG — und out-of-sample kalibriert.**
|
||||||
|
Der User wollte ein „Kursvorhersagemodul, das mögliche Verläufe in MT5 einzeichnet".
|
||||||
|
Eingezeichnete PFADE wurden bewusst NICHT gebaut (suggerieren eine Präzision, die es
|
||||||
|
nicht gibt — die Lehre aus `analyze_verdict_calibration.py`, wo conf_pct in H2
|
||||||
|
invertiert war). Stattdessen: wie WEIT der Kurs in 30/60/120 min plausibel gelaufen
|
||||||
|
sein kann. **Gemessen (`analyze_cone.py`, 80k M5, r(h)=(C[i+h]−C[i])/ATR[i], Fit auf
|
||||||
|
H1 → Abdeckung gemessen auf H2):**
|
||||||
|
| Band | 30 min | 60 min | 120 min |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 80 % nominal | 76,7 % | 76,6 % | 75,3 % |
|
||||||
|
| 90 % nominal | 87,5 % | 87,2 % | 86,5 % |
|
||||||
|
⚠ Die Bänder sind konsistent **~3–5 Pp ZU ENG** (H2 war relativ zum ATR volatiler).
|
||||||
|
Das wird **NICHT weggefittet** — Kachel und Tooltip nennen die REAL gemessene
|
||||||
|
Abdeckung („trifft 77 %"), nicht den Nennwert. Damit ist der Kegel nach dem
|
||||||
|
P(break)-Modell die **zweite kalibriert geprüfte Komponente** im Projekt. Quantile
|
||||||
|
final auf allen 80k Bars gefittet (`_CONE` in `core/cone.py`; gleiches Vorgehen wie
|
||||||
|
beim P(break)-Modell: erst out-of-sample validieren, dann voll fitten), ATR-normiert
|
||||||
|
→ regime-übergreifend gültig. **Zentriert auf den AKTUELLEN Kurs, KEINE Drift
|
||||||
|
addiert** (Median lag bei ±0,0…0,16×ATR = klein gegen die Bandbreite; eine
|
||||||
|
Drift-Korrektur wäre eine Richtungsaussage und genau die ist nicht belegt).
|
||||||
|
Snapshot `cone {atr, price, levels[{minutes,bars,band,lo,hi,coverage}]}`; MT5-Export
|
||||||
|
`CN;<minuten>;<lo>;<hi>` (nur 80 %-Band) → Indikator **v1.31** zeichnet zwei sich
|
||||||
|
öffnende, gepunktete Pfade, verkettet über 30→60→120 (`g_coneT/g_coneLo/g_coneHi`,
|
||||||
|
je Redraw zurückgesetzt; Startpunkt aus der `PX`-Zeile, die deshalb `g_conePx` auch
|
||||||
|
dann setzt, wenn `InpShowPrice` aus ist). Farbe **neutral grau** (`InpConeColor`) —
|
||||||
|
bewusst keine Richtungsfarbe. `InpShowCone=false` schaltet ab. **Kein Signal, kein
|
||||||
|
Verdict-Gewicht.**
|
||||||
- **Kerzen-Anatomie (`core/candles.py` + Kachel `#card-candles`, 2026-07-31, User-Vorgabe
|
- **Kerzen-Anatomie (`core/candles.py` + Kachel `#card-candles`, 2026-07-31, User-Vorgabe
|
||||||
„Kerzengröße und Dochtlänge … im Kontext"): REINE ANZEIGE — und die Kachel widerspricht
|
„Kerzengröße und Dochtlänge … im Kontext"): REINE ANZEIGE — und die Kachel widerspricht
|
||||||
bewusst der Lehrbuch-Deutung.** Erkennt mechanisch aus M5-OHLCV: Marubozu · Langer
|
bewusst der Lehrbuch-Deutung.** Erkennt mechanisch aus M5-OHLCV: Marubozu · Langer
|
||||||
@@ -1679,7 +1707,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=123** (in `web/index.html` hochzählen, siehe Workflows).
|
- Asset-Version aktuell **v=124** (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` —
|
||||||
|
|||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Wahrscheinlichkeits-Kegel: empirische Quantile fitten UND out-of-sample prüfen.
|
||||||
|
|
||||||
|
User-Wunsch 2026-07-31 war ein „Kursvorhersagemodul, das mögliche Verläufe in MT5
|
||||||
|
einzeichnet". Eingezeichnete PFADE würden eine Präzision suggerieren, die es nicht
|
||||||
|
gibt — das Projekt hat mit `analyze_verdict_calibration.py` teuer gelernt, dass eine
|
||||||
|
Zahl ohne Kalibrierungsprüfung in die Irre führt (conf_pct war in H2 invertiert).
|
||||||
|
|
||||||
|
STATTDESSEN: ein **Streuungs-Kegel**. Er sagt NICHT, wohin der Kurs geht, sondern
|
||||||
|
**wie weit** er in 30/60/120 min plausibel gelaufen sein kann. Der entscheidende
|
||||||
|
Unterschied: das ist ÜBERPRÜFBAR — landen wirklich ~80 % der Ausgänge im 80 %-Band?
|
||||||
|
|
||||||
|
METHODE
|
||||||
|
r(h) = (C[i+h] − C[i]) / ATR[i] (ATR-normiert → über Vola-Regime vergleichbar)
|
||||||
|
Quantile auf H1 fitten, Abdeckung auf H2 messen (echtes out-of-sample).
|
||||||
|
|
||||||
|
Ein Kegel ist nur dann brauchbar, wenn die gemessene Abdeckung nah am Nominalwert
|
||||||
|
liegt. Weicht sie stark ab, wird er NICHT gebaut — dann ist die Streuung nicht
|
||||||
|
stabil ATR-skalierbar.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
|
||||||
|
_H = (6, 12, 24) # 30 / 60 / 120 min auf M5
|
||||||
|
_ATRMIN = 0.06
|
||||||
|
# Nominale Bänder: (unteres Quantil, oberes Quantil, Nennabdeckung)
|
||||||
|
_BANDS = ((0.25, 0.75, 50), (0.10, 0.90, 80), (0.05, 0.95, 90))
|
||||||
|
|
||||||
|
|
||||||
|
def _atr_series(H, L, C, p=14):
|
||||||
|
t = [0.0]
|
||||||
|
for i in range(1, len(C)):
|
||||||
|
t.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
|
||||||
|
out = []
|
||||||
|
for i in range(len(C)):
|
||||||
|
if not i:
|
||||||
|
out.append(None); continue
|
||||||
|
w = t[max(1, i - p + 1):i + 1]
|
||||||
|
out.append(sum(w) / max(1, len(w)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def quantile(sorted_xs, q):
|
||||||
|
if not sorted_xs:
|
||||||
|
return 0.0
|
||||||
|
k = q * (len(sorted_xs) - 1)
|
||||||
|
lo = int(k); hi = min(lo + 1, len(sorted_xs) - 1)
|
||||||
|
return sorted_xs[lo] + (sorted_xs[hi] - sorted_xs[lo]) * (k - lo)
|
||||||
|
|
||||||
|
|
||||||
|
def collect(C, AT, a, b, h):
|
||||||
|
out = []
|
||||||
|
for i in range(a, min(b, len(C) - h)):
|
||||||
|
atr = AT[i]
|
||||||
|
if not atr:
|
||||||
|
continue
|
||||||
|
out.append((C[i + h] - C[i]) / max(atr, _ATRMIN))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
||||||
|
mt5.initialize(); sym = None
|
||||||
|
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
||||||
|
if mt5.symbol_info(c):
|
||||||
|
sym = c; break
|
||||||
|
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
||||||
|
mt5.shutdown()
|
||||||
|
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars]
|
||||||
|
C = [float(b["close"]) for b in bars]
|
||||||
|
AT = _atr_series(H, L, C)
|
||||||
|
N = len(C); mid = N // 2
|
||||||
|
|
||||||
|
print("=" * 92)
|
||||||
|
print(f" WAHRSCHEINLICHKEITS-KEGEL — {sym} M5 ({N} Bars)")
|
||||||
|
print(" Fit auf H1, Abdeckung gemessen auf H2 (out-of-sample). Werte in ×ATR.")
|
||||||
|
print("=" * 92)
|
||||||
|
|
||||||
|
fitted = {}
|
||||||
|
for h in _H:
|
||||||
|
tr = sorted(collect(C, AT, 100, mid, h))
|
||||||
|
te = collect(C, AT, mid, N, h)
|
||||||
|
print(f"\n Horizont {h} Bars = {h*5} min (fit n={len(tr)}, test n={len(te)})")
|
||||||
|
print(f" Median H1 {quantile(tr,0.5):+.3f} · Median H2 "
|
||||||
|
f"{sorted(te)[len(te)//2]:+.3f} (Drift-Kontrolle)")
|
||||||
|
row = {}
|
||||||
|
for qlo, qhi, nominal in _BANDS:
|
||||||
|
lo = quantile(tr, qlo); hi = quantile(tr, qhi)
|
||||||
|
cov = 100.0 * sum(1 for x in te if lo <= x <= hi) / len(te)
|
||||||
|
flag = "OK" if abs(cov - nominal) <= 5 else ("eng" if cov < nominal else "weit")
|
||||||
|
print(f" {nominal}%-Band [{lo:+.2f} … {hi:+.2f}]×ATR "
|
||||||
|
f"-> Abdeckung H2 {cov:>5.1f}% [{flag}]")
|
||||||
|
row[nominal] = (round(lo, 3), round(hi, 3))
|
||||||
|
fitted[h] = row
|
||||||
|
|
||||||
|
# ── Final auf ALLEN Bars fitten (wie beim P(break)-Modell: erst out-of-sample
|
||||||
|
# validieren, dann für den Live-Einsatz auf der vollen History fitten)
|
||||||
|
print("\n" + "=" * 92)
|
||||||
|
print(" FINAL (auf allen Bars gefittet) — zum Einbetten in core/cone.py")
|
||||||
|
print("=" * 92)
|
||||||
|
final = {}
|
||||||
|
for h in _H:
|
||||||
|
allx = sorted(collect(C, AT, 100, N, h))
|
||||||
|
row = {}
|
||||||
|
for qlo, qhi, nominal in _BANDS:
|
||||||
|
row[nominal] = (round(quantile(allx, qlo), 3), round(quantile(allx, qhi), 3))
|
||||||
|
final[h] = row
|
||||||
|
print("_CONE = {")
|
||||||
|
for h in _H:
|
||||||
|
parts = ", ".join(f"{k}: ({v[0]:+.3f}, {v[1]:+.3f})" for k, v in final[h].items())
|
||||||
|
print(f" {h}: {{{parts}}},")
|
||||||
|
print("}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
"""core/cone.py — Wahrscheinlichkeits-Kegel (Streuung, NICHT Richtung).
|
||||||
|
|
||||||
|
User-Wunsch 2026-07-31: „ein Kursvorhersagemodul, das mögliche Verläufe in MT5
|
||||||
|
einzeichnet". Eingezeichnete PFADE würden eine Präzision suggerieren, die es nicht
|
||||||
|
gibt — das Projekt hat mit `analyze_verdict_calibration.py` teuer gelernt, dass eine
|
||||||
|
Zahl ohne Kalibrierungsprüfung in die Irre führt (conf_pct war in H2 invertiert).
|
||||||
|
|
||||||
|
Deshalb bewusst WENIGER, dafür belegt: der Kegel sagt nicht, WOHIN der Kurs geht,
|
||||||
|
sondern WIE WEIT er in 30/60/120 min plausibel gelaufen sein kann.
|
||||||
|
|
||||||
|
GEMESSEN & OUT-OF-SAMPLE VALIDIERT (`analyze_cone.py`, 80k M5-Bars, Fit auf H1,
|
||||||
|
Abdeckung gemessen auf H2):
|
||||||
|
|
||||||
|
Band 30 min 60 min 120 min
|
||||||
|
80 % nom. 76,7 % 76,6 % 75,3 %
|
||||||
|
90 % nom. 87,5 % 87,2 % 86,5 %
|
||||||
|
|
||||||
|
⚠ Die Bänder sind konsistent ~3–5 Prozentpunkte ZU ENG (H2 war relativ zum ATR
|
||||||
|
etwas volatiler als H1). Das wird NICHT weggefittet — die Anzeige nennt die real
|
||||||
|
gemessene Abdeckung, nicht den Nennwert. Damit ist der Kegel nach dem P(break)-
|
||||||
|
Modell die zweite kalibriert geprüfte Komponente im Projekt.
|
||||||
|
|
||||||
|
Quantile final auf allen 80k Bars gefittet (dasselbe Vorgehen wie beim P(break)-
|
||||||
|
Modell: erst out-of-sample validieren, dann für den Live-Einsatz auf der vollen
|
||||||
|
History fitten). ATR-normiert → über Vola-Regime hinweg gültig.
|
||||||
|
|
||||||
|
⚠ KEIN Signal, kein Verdict-Gewicht, kein Trade-Trigger. Der Kegel ist um den
|
||||||
|
AKTUELLEN Kurs zentriert (keine Drift addiert — der Median lag bei ±0,0…0,16×ATR,
|
||||||
|
also klein gegen die Bandbreite; eine Drift-Korrektur wäre eine Richtungsaussage
|
||||||
|
und genau die ist hier nicht belegt).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# Horizont (M5-Bars) → {Nennband: (unteres, oberes Quantil in ×ATR)}
|
||||||
|
_CONE = {
|
||||||
|
6: {50: (-0.915, +0.940), 80: (-2.000, +1.962), 90: (-2.800, +2.713)},
|
||||||
|
12: {50: (-1.333, +1.346), 80: (-2.854, +2.833), 90: (-4.064, +4.000)},
|
||||||
|
24: {50: (-1.949, +2.000), 80: (-4.183, +4.177), 90: (-5.955, +5.938)},
|
||||||
|
}
|
||||||
|
# real gemessene Abdeckung (H2, out-of-sample) — das steht in der UI, nicht der Nennwert
|
||||||
|
_COVERAGE = {6: {50: 44.4, 80: 76.7, 90: 87.5},
|
||||||
|
12: {50: 45.1, 80: 76.6, 90: 87.2},
|
||||||
|
24: {50: 45.5, 80: 75.3, 90: 86.5}}
|
||||||
|
_ATRMIN = 0.06
|
||||||
|
MINUTES = {6: 30, 12: 60, 24: 120}
|
||||||
|
|
||||||
|
|
||||||
|
def build(price: float, atr: float, bands=(50, 80)) -> dict | None:
|
||||||
|
"""→ {'atr':…, 'levels':[{minutes, bars, band, lo, hi, coverage}, …]}
|
||||||
|
|
||||||
|
`price` = aktueller Kurs (Bid), `atr` = ATR der M5-Basis.
|
||||||
|
"""
|
||||||
|
if not price or not atr or atr <= 0:
|
||||||
|
return None
|
||||||
|
a = max(float(atr), _ATRMIN)
|
||||||
|
out = []
|
||||||
|
for bars in sorted(_CONE):
|
||||||
|
for band in bands:
|
||||||
|
q = _CONE[bars].get(band)
|
||||||
|
if not q:
|
||||||
|
continue
|
||||||
|
out.append({
|
||||||
|
"minutes": MINUTES[bars], "bars": bars, "band": band,
|
||||||
|
"lo": round(price + q[0] * a, 3),
|
||||||
|
"hi": round(price + q[1] * a, 3),
|
||||||
|
"coverage": _COVERAGE.get(bars, {}).get(band),
|
||||||
|
})
|
||||||
|
return {"atr": round(a, 4), "price": round(float(price), 3), "levels": out}
|
||||||
@@ -43,6 +43,7 @@ from core.gaps import GapAnalyzer
|
|||||||
from core.structure import MarketStructure
|
from core.structure import MarketStructure
|
||||||
from core.patterns import PatternDetector
|
from core.patterns import PatternDetector
|
||||||
from core.candles import CandleAnatomy
|
from core.candles import CandleAnatomy
|
||||||
|
from core.cone import build as _cone_build
|
||||||
from core.candle_logger import CandleLogger
|
from core.candle_logger import CandleLogger
|
||||||
from core.wave_rec import (_atr as _wr_atr, _ema_last,
|
from core.wave_rec import (_atr as _wr_atr, _ema_last,
|
||||||
_EMA_FAST, _EMA_SLOW, _TREND_DEADBAND, _STRETCH_MAX)
|
_EMA_FAST, _EMA_SLOW, _TREND_DEADBAND, _STRETCH_MAX)
|
||||||
@@ -2093,6 +2094,19 @@ class TradingEngine:
|
|||||||
lines.append(f"PX;{_bid}")
|
lines.append(f"PX;{_bid}")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# WAHRSCHEINLICHKEITS-KEGEL: CN;<minuten>;<lo>;<hi> je Horizont (30/60/120),
|
||||||
|
# aufsteigend — der Indikator verbindet sie vom aktuellen Bar aus zu zwei
|
||||||
|
# sich öffnenden Pfaden. ⚠ Streuung, NICHT Richtung: der Kegel ist um den
|
||||||
|
# aktuellen Kurs zentriert und sagt nur, wie weit der Kurs plausibel läuft.
|
||||||
|
# Out-of-sample kalibriert (`analyze_cone.py`), 80 %-Band trifft real ~76 %.
|
||||||
|
try:
|
||||||
|
_atr5 = float(((self.wave.snapshot() or {}).get("pb_feats") or {})
|
||||||
|
.get("atr") or 0.0)
|
||||||
|
_cn = _cone_build(_bid, _atr5, bands=(80,))
|
||||||
|
for lv in (_cn or {}).get("levels", []):
|
||||||
|
lines.append(f"CN;{lv['minutes']};{lv['lo']};{lv['hi']}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
# G/V der offenen Position, direkt unter dem Kurs (User-Wunsch 2026-07-30):
|
# G/V der offenen Position, direkt unter dem Kurs (User-Wunsch 2026-07-30):
|
||||||
# PL;wert;waehrung. **NETTO** wie im Dashboard — bei Gewinn abzüglich der
|
# PL;wert;waehrung. **NETTO** wie im Dashboard — bei Gewinn abzüglich der
|
||||||
# WHT-Quellensteuer, die der Broker einbehält (`[trading] wht_pct`); bei
|
# WHT-Quellensteuer, die der Broker einbehält (`[trading] wht_pct`); bei
|
||||||
@@ -2714,6 +2728,11 @@ class TradingEngine:
|
|||||||
# Kerzen-Anatomie (M5) — reine Anzeige, KEIN Verdict-Gewicht (gemessen:
|
# Kerzen-Anatomie (M5) — reine Anzeige, KEIN Verdict-Gewicht (gemessen:
|
||||||
# Lehrbuch-Deutung invertiert, Handelssignal durchgefallen).
|
# Lehrbuch-Deutung invertiert, Handelssignal durchgefallen).
|
||||||
"candle_anat": self.candle_anat.snapshot(),
|
"candle_anat": self.candle_anat.snapshot(),
|
||||||
|
# Wahrscheinlichkeits-Kegel (Streuung, NICHT Richtung) — out-of-sample
|
||||||
|
# kalibriert (`analyze_cone.py`). Reine Anzeige.
|
||||||
|
"cone": _cone_build(
|
||||||
|
(market or {}).get("bid"),
|
||||||
|
float(((wave_snap or {}).get("pb_feats") or {}).get("atr") or 0.0)),
|
||||||
"stats_compact": {"week": self._compact_stats("week"),
|
"stats_compact": {"week": self._compact_stats("week"),
|
||||||
"all": self._compact_stats("all")},
|
"all": self._compact_stats("all")},
|
||||||
"close_alert_count": self._close_alert_count,
|
"close_alert_count": self._close_alert_count,
|
||||||
|
|||||||
+151
-16
@@ -94,16 +94,8 @@ CHECKS = [
|
|||||||
"need": 30, "unit": "S/R-Closes seit der Umstellung",
|
"need": 30, "unit": "S/R-Closes seit der Umstellung",
|
||||||
"cmd": "sr_close-Trades vor/nach 2026-07-30 vergleichen (ØEUR, Trefferquote)",
|
"cmd": "sr_close-Trades vor/nach 2026-07-30 vergleichen (ØEUR, Trefferquote)",
|
||||||
},
|
},
|
||||||
{
|
# (erledigt 2026-07-30: Chartmuster-Kontrolltest gegen generischen Swing-Bruch —
|
||||||
"key": "patterns_control",
|
# Muster schlagen die Kontrolle in beiden Hälften (+0,022/+0,108) → Gewicht 1,0)
|
||||||
"title": "Chartmuster: Kontrolltest gegen generischen Breakout",
|
|
||||||
"why": "backtest_patterns.py zeigte positiven ØR — aber die Ziel-Trefferquote "
|
|
||||||
"ist nur 13–38 %, der Ertrag kommt vom Trailing-Exit. Es fehlt die "
|
|
||||||
"Kontrolle: gleicher Exit auf einem BELIEBIGEN Breakout. Erst danach "
|
|
||||||
"ist 'Muster wirkt' belegt oder widerlegt.",
|
|
||||||
"have": lambda: 1, "need": 1, "unit": "jederzeit machbar (nur Rechenzeit)",
|
|
||||||
"cmd": "backtest_patterns.py um eine Kontrollgruppe erweitern",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "m1_squeeze",
|
"key": "m1_squeeze",
|
||||||
"title": "M1-Squeeze (Variante B) backtesten",
|
"title": "M1-Squeeze (Variante B) backtesten",
|
||||||
@@ -128,6 +120,107 @@ CHECKS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ── Config-abhängige Backtests ───────────────────────────────────────────────
|
||||||
|
# User-Frage 2026-07-31: „sollten wir alle verworfenen Backtests wöchentlich neu
|
||||||
|
# laufen lassen, da wir laufend die Config anpassen?"
|
||||||
|
#
|
||||||
|
# ⚠ NEIN — wöchentlich wäre eine Fehlalarm-Maschine: eine Woche M5 sind ~2000 Bars,
|
||||||
|
# und das Projekt hat dreimal erlebt, was daraus wird (Auto-Signal-Vorlauf auf 6k
|
||||||
|
# Bars zeigte „trägt", der 80k-Lauf drehte es; Std 13; ORB mit ØR +1,3/PF 10 als
|
||||||
|
# Selektions-Artefakt). Dazu der Mehrfachvergleich: 18 verworfene Ideen × 52 Wochen
|
||||||
|
# = 936 Tests/Jahr → bei 5 % Fehlalarmquote ~47 falsche „funktioniert jetzt!"/Jahr.
|
||||||
|
#
|
||||||
|
# ABER das Anliegen dahinter stimmt: einige Messungen sind gegen einen KONKRETEN
|
||||||
|
# Config-Wert kalibriert und werden ungültig, wenn der sich ändert. Das ist
|
||||||
|
# EREIGNIS-gesteuert, nicht kalendergesteuert — und genau das prüft dieser Block.
|
||||||
|
# (Die meisten verworfenen Tests sind config-UNABHÄNGIG: PDH/PDL, Volume Profile,
|
||||||
|
# Chartmuster, Liquidity Sweeps testen Signalklassen auf rohen Preisdaten.)
|
||||||
|
CONFIG_DEPS = [
|
||||||
|
{
|
||||||
|
"key": "entry_room_atr", "test": "backtest_entryroom.py",
|
||||||
|
"validated": "0.6",
|
||||||
|
"why": "Das Raum-Gate ist auf diesen Schwellwert kalibriert (gemessen war 1,0 "
|
||||||
|
"netto besser, 0,6 ist die bewusste Frequenz-Entscheidung). Es nutzt "
|
||||||
|
"weiterhin die M5-`pb_levels` — eine Umstellung auf M30 wäre ungemessen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "sr_close_pbreak", "test": "backtest_srclose_prob.py",
|
||||||
|
"validated": "0.55",
|
||||||
|
"why": "Optimum-Plateau 0,50–0,60. Außerhalb ist der S/R-Auto-Close nicht mehr "
|
||||||
|
"durch die Messung gedeckt (die Hysterese ±5 Pp hängt ebenfalls daran).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "breakout_k", "test": "backtest_breakout.py",
|
||||||
|
"validated": "0.3",
|
||||||
|
"why": "Split-validiert: 0,3 ist in BEIDEN Hälften das Beste; 0,5 und 1,0 waren "
|
||||||
|
"in H1 netto NEGATIV. Ein anderer Wert ist nicht belegt.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "risk_pct", "test": "backtest_sizing.py",
|
||||||
|
"validated": "0",
|
||||||
|
"why": "0 = margin-basiert. Wechsel auf >0 aktiviert einen anderen Sizing-Pfad "
|
||||||
|
"(calc_lots_risk) — und koppelt den %-Notfall-Stop anders.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "margin_buffer_pct", "test": "backtest_sizing.py",
|
||||||
|
"validated": "95",
|
||||||
|
"why": "Die Ruin-Simulation ist gegen den Margin-Anteil gerechnet (90 % = 36 % "
|
||||||
|
"P(DD>80 %)). Ändert sich er, ändert sich das Ruin-Risiko.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "adverse_15min_atr", "test": "backtest_auto_signal.py (15-Min-Achse)",
|
||||||
|
"validated": "0.5",
|
||||||
|
"why": "0,5 war die beste Regel-Variante; 0,0 (sofort im Minus schliessen) war "
|
||||||
|
"die SCHLECHTESTE (WR bricht 39→31 %). Andere Werte sind ungemessen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "auto_signal_min_conf", "test": "backtest_auto_signal.py",
|
||||||
|
"validated": "75",
|
||||||
|
"why": "Empirisch bester Punkt (NICHT weil höhere Konfidenz besser wäre — "
|
||||||
|
"conf_pct ist unkalibriert). Nachbarwerte sind nicht besser.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "dead_hours", "test": "backtest_hourly_split.py / backtest_realcosts.py",
|
||||||
|
"validated": "",
|
||||||
|
"why": "Leer = Gate AUS (User-Vorgabe, bewusst GEGEN die Messung). Wird es "
|
||||||
|
"wieder gefüllt, gelten die gemessenen Stunden 0–7 + 12 + 16.",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _cfg_now() -> dict:
|
||||||
|
"""Liest NUR die überwachten [trading]-Keys. ⚠ Die ini enthält Live-Secrets —
|
||||||
|
hier wird bewusst nichts anderes gelesen oder ausgegeben."""
|
||||||
|
import configparser
|
||||||
|
out: dict = {}
|
||||||
|
try:
|
||||||
|
c = configparser.ConfigParser(inline_comment_prefixes=("#", ";"))
|
||||||
|
c.read(os.path.join(HERE, "oil_widget_config.ini"), encoding="utf-8")
|
||||||
|
t = c["trading"] if c.has_section("trading") else {}
|
||||||
|
for d in CONFIG_DEPS:
|
||||||
|
out[d["key"]] = (t.get(d["key"], "") or "").strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def config_drift() -> list:
|
||||||
|
"""→ [(dep, ist, soll)] für alle Keys, deren Wert von der Validierung abweicht."""
|
||||||
|
now = _cfg_now()
|
||||||
|
drift = []
|
||||||
|
for d in CONFIG_DEPS:
|
||||||
|
cur = now.get(d["key"])
|
||||||
|
if cur is None:
|
||||||
|
continue
|
||||||
|
try: # numerisch vergleichen, damit 0.60 == 0.6
|
||||||
|
same = abs(float(cur) - float(d["validated"])) < 1e-9
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
same = cur.lower() == d["validated"].lower()
|
||||||
|
if not same:
|
||||||
|
drift.append((d, cur, d["validated"]))
|
||||||
|
return drift
|
||||||
|
|
||||||
|
|
||||||
def _popup(title: str, text: str) -> None:
|
def _popup(title: str, text: str) -> None:
|
||||||
if DRY or STATUS:
|
if DRY or STATUS:
|
||||||
print(f"[POPUP] {title}\n{text}")
|
print(f"[POPUP] {title}\n{text}")
|
||||||
@@ -156,6 +249,21 @@ def main() -> None:
|
|||||||
else:
|
else:
|
||||||
pending.append((c, have, "sammelt"))
|
pending.append((c, have, "sammelt"))
|
||||||
|
|
||||||
|
# ── Config-Drift: welche Messung ist durch eine Config-Änderung veraltet?
|
||||||
|
# Marker enthält den Wert → ändert der User später erneut, wird neu gemeldet.
|
||||||
|
drift = config_drift()
|
||||||
|
drift_new = []
|
||||||
|
for d, cur, val in drift:
|
||||||
|
marker = os.path.join(MARKER_DIR, f"cfg_{d['key']}")
|
||||||
|
seen = ""
|
||||||
|
try:
|
||||||
|
with open(marker, encoding="utf-8") as f:
|
||||||
|
seen = f.read().strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if STATUS or DRY or seen != cur:
|
||||||
|
drift_new.append((d, cur, val, marker))
|
||||||
|
|
||||||
if STATUS or DRY:
|
if STATUS or DRY:
|
||||||
print("=" * 78)
|
print("=" * 78)
|
||||||
print(" AUSSTEHENDE MESSUNGEN — Status")
|
print(" AUSSTEHENDE MESSUNGEN — Status")
|
||||||
@@ -170,24 +278,51 @@ def main() -> None:
|
|||||||
print(f" {have:.0f}/{c['need']} {c['unit']} ({pct:.0f} %)")
|
print(f" {have:.0f}/{c['need']} {c['unit']} ({pct:.0f} %)")
|
||||||
if not due:
|
if not due:
|
||||||
print("\n (nichts fällig)")
|
print("\n (nichts fällig)")
|
||||||
|
print("\n" + "=" * 78)
|
||||||
|
print(" CONFIG-VERANKERUNG — sind die Messungen noch gültig?")
|
||||||
|
print("=" * 78)
|
||||||
|
if not drift:
|
||||||
|
print(f"\n ✅ alle {len(CONFIG_DEPS)} überwachten Werte stehen auf dem "
|
||||||
|
f"Stand, gegen den gemessen wurde")
|
||||||
|
for d, cur, val, _m in drift_new:
|
||||||
|
print(f"\n ⚠ {d['key']}: ist '{cur}' — gemessen/validiert gegen '{val}'")
|
||||||
|
print(f" betroffen: {d['test']}")
|
||||||
|
print(f" {d['why']}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not due:
|
if not due and not drift_new:
|
||||||
return
|
return
|
||||||
lines = []
|
lines = []
|
||||||
for c, have in due:
|
for c, have in due:
|
||||||
lines.append(f"• {c['title']}\n {have:.0f}/{c['need']} {c['unit']}\n"
|
lines.append(f"• {c['title']}\n {have:.0f}/{c['need']} {c['unit']}\n"
|
||||||
f" {c['why']}\n -> {c['cmd']}")
|
f" {c['why']}\n -> {c['cmd']}")
|
||||||
text = ("Diese Messungen haben jetzt genug Daten:\n\n"
|
for d, cur, val, _m in drift_new:
|
||||||
+ "\n\n".join(lines)
|
lines.append(f"• CONFIG geaendert: {d['key']} = '{cur}' "
|
||||||
+ "\n\nStatus aller Punkte:\n"
|
f"(validiert gegen '{val}')\n"
|
||||||
" python measurement_reminder.py --status")
|
f" {d['why']}\n -> neu rechnen: {d['test']}")
|
||||||
_popup(f"Oil-Bot: {len(due)} Messung(en) faellig", text)
|
head = []
|
||||||
|
if due:
|
||||||
|
head.append(f"{len(due)} Messung(en) faellig")
|
||||||
|
if drift_new:
|
||||||
|
head.append(f"{len(drift_new)} Config-Abweichung(en)")
|
||||||
|
text = ("Offene Punkte:\n\n" + "\n\n".join(lines)
|
||||||
|
+ "\n\n⚠ Verworfene Backtests NICHT woechentlich neu rechnen — eine Woche "
|
||||||
|
"ist Rauschen (~2000 Bars) und 936 Tests/Jahr erzeugen Fehlalarme.\n"
|
||||||
|
"Neu rechnen nur bei Config-Aenderung (oben) oder wenn genug WIRKLICH "
|
||||||
|
"neue Daten fuer eine dritte Stichprobe da sind (~quartalsweise).\n\n"
|
||||||
|
+ "Status: python measurement_reminder.py --status")
|
||||||
|
_popup("Oil-Bot: " + " + ".join(head), text)
|
||||||
for c, _ in due:
|
for c, _ in due:
|
||||||
try:
|
try:
|
||||||
open(os.path.join(MARKER_DIR, c["key"]), "w").close()
|
open(os.path.join(MARKER_DIR, c["key"]), "w").close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
for _d, cur, _val, marker in drift_new:
|
||||||
|
try:
|
||||||
|
with open(marker, "w", encoding="utf-8") as f:
|
||||||
|
f.write(cur)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+75
-2
@@ -12,7 +12,13 @@
|
|||||||
//| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). |
|
//| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). |
|
||||||
//+------------------------------------------------------------------+
|
//+------------------------------------------------------------------+
|
||||||
#property copyright "Oil Trading Bot"
|
#property copyright "Oil Trading Bot"
|
||||||
#property version "1.30"
|
#property version "1.31"
|
||||||
|
// v1.31 (2026-07-31): WAHRSCHEINLICHKEITS-KEGEL (CN;<minuten>;<lo>;<hi>) — zwei sich
|
||||||
|
// oeffnende, gepunktete Pfade vom aktuellen Bar ueber 30/60/120 min.
|
||||||
|
// ⚠ KEINE Richtungsprognose, sondern STREUUNG: wie weit der Kurs plausibel
|
||||||
|
// gelaufen sein kann. Out-of-sample kalibriert (analyze_cone.py): das
|
||||||
|
// 80%-Band trifft real ~76%. Bewusst NEUTRAL grau (keine Richtungsfarbe).
|
||||||
|
// InpShowCone / InpConeColor.
|
||||||
// v1.30 (2026-07-30): Konsolidierungs-Box BESCHRIFTET (User-Frage „was bedeutet die
|
// v1.30 (2026-07-30): Konsolidierungs-Box BESCHRIFTET (User-Frage „was bedeutet die
|
||||||
// graue Box") — sichtbares Label an der Oberkante, rechtsbuendig wie die
|
// graue Box") — sichtbares Label an der Oberkante, rechtsbuendig wie die
|
||||||
// uebrigen: „Konsolidierung (10-Bar-Spanne)" bzw. bei Kompression
|
// uebrigen: „Konsolidierung (10-Bar-Spanne)" bzw. bei Kompression
|
||||||
@@ -149,6 +155,8 @@ input bool InpShowConsol = true; // Konsolidierungs-Box (10 M5-Bar
|
|||||||
input color InpConsolColor = C'88,166,255'; // Box-Farbe (blau)
|
input color InpConsolColor = C'88,166,255'; // Box-Farbe (blau)
|
||||||
input int InpConsolAlpha = 8; // Deckkraft in % (komprimiert: x2)
|
input int InpConsolAlpha = 8; // Deckkraft in % (komprimiert: x2)
|
||||||
input int InpConsolBars = 10; // Box-Laenge in Bars (= _SQ_N im Bot)
|
input int InpConsolBars = 10; // Box-Laenge in Bars (= _SQ_N im Bot)
|
||||||
|
input bool InpShowCone = true; // Wahrscheinlichkeits-Kegel (Streuung, NICHT Richtung)
|
||||||
|
input color InpConeColor = C'139,148,158'; // Kegel-Farbe (neutral grau - bewusst KEINE Richtungsfarbe)
|
||||||
input bool InpShowPnL = true; // G/V der offenen Position unter dem Kurs
|
input bool InpShowPnL = true; // G/V der offenen Position unter dem Kurs
|
||||||
input color InpPnLUpColor = C'0,255,127'; // G/V im Plus (neongruen)
|
input color InpPnLUpColor = C'0,255,127'; // G/V im Plus (neongruen)
|
||||||
input color InpPnLDownColor = C'255,69,0'; // G/V im Minus (rot)
|
input color InpPnLDownColor = C'255,69,0'; // G/V im Minus (rot)
|
||||||
@@ -172,6 +180,12 @@ input bool InpShowTradeLevels = true; // MT5-Positionslinien+Labels (st
|
|||||||
const string PFX = "SRB_"; // Präfix S/R-Linien (aus Datei)
|
const string PFX = "SRB_"; // Präfix S/R-Linien (aus Datei)
|
||||||
const string RPFX = "SRR_"; // Präfix dynamische Range (aus Chart)
|
const string RPFX = "SRR_"; // Präfix dynamische Range (aus Chart)
|
||||||
string g_last = "\x01"; // letzter Datei-Inhalt (gegen Flackern)
|
string g_last = "\x01"; // letzter Datei-Inhalt (gegen Flackern)
|
||||||
|
// Kegel: Verkettung der Horizonte (30->60->120). Die CSV liefert sie aufsteigend;
|
||||||
|
// jeder Abschnitt startet dort, wo der vorherige endete -> ein sich oeffnender
|
||||||
|
// Faecher statt drei unverbundener Geraden. Wird je Redraw zurueckgesetzt.
|
||||||
|
datetime g_coneT = 0;
|
||||||
|
double g_coneLo = 0.0, g_coneHi = 0.0;
|
||||||
|
double g_conePx = 0.0; // aktueller Kurs (aus der PX-Zeile)
|
||||||
bool g_range_drawn = false; // hat DIESE Instanz die Range gezeichnet?
|
bool g_range_drawn = false; // hat DIESE Instanz die Range gezeichnet?
|
||||||
|
|
||||||
//+------------------------------------------------------------------+
|
//+------------------------------------------------------------------+
|
||||||
@@ -394,6 +408,9 @@ void Redraw()
|
|||||||
string rows[];
|
string rows[];
|
||||||
int nr = StringSplit(content, '\n', rows);
|
int nr = StringSplit(content, '\n', rows);
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
// Kegel-Verkettung je Redraw zuruecksetzen (sonst haengt der Faecher am Stand
|
||||||
|
// des vorherigen Durchlaufs)
|
||||||
|
g_coneT = 0; g_coneLo = 0.0; g_coneHi = 0.0; g_conePx = 0.0;
|
||||||
|
|
||||||
for(int r = 0; r < nr; r++)
|
for(int r = 0; r < nr; r++)
|
||||||
{
|
{
|
||||||
@@ -650,13 +667,69 @@ void Redraw()
|
|||||||
ObjectSetString(0, ctn, OBJPROP_TOOLTIP, ctip);
|
ObjectSetString(0, ctn, OBJPROP_TOOLTIP, ctip);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// WAHRSCHEINLICHKEITS-KEGEL: CN;<minuten>;<lo>;<hi>
|
||||||
|
// Zwei sich oeffnende Pfade vom aktuellen Bar aus. ⚠ Das ist KEINE Prognose
|
||||||
|
// der Richtung, sondern der STREUUNG: wie weit der Kurs in 30/60/120 min
|
||||||
|
// plausibel gelaufen sein kann. Out-of-sample kalibriert (analyze_cone.py):
|
||||||
|
// das 80%-Band trifft real ~76%. Bewusst NEUTRAL grau, kein gruen/rot.
|
||||||
|
if(typ == "CN")
|
||||||
|
{
|
||||||
|
if(k < 4) continue;
|
||||||
|
if(!InpShowCone) continue;
|
||||||
|
int mins = (int)StringToInteger(p[1]);
|
||||||
|
double clo = StringToDouble(p[2]);
|
||||||
|
double chi = StringToDouble(p[3]);
|
||||||
|
if(clo <= 0 || chi <= 0 || mins <= 0) continue;
|
||||||
|
// Anker: vom aktuellen Bar bis +mins Minuten (in Broker-Zeit)
|
||||||
|
datetime t0 = iTime(_Symbol, PERIOD_CURRENT, 0);
|
||||||
|
datetime t1 = t0 + (datetime)(mins * 60);
|
||||||
|
// Vorgaenger-Punkt: der naechstkleinere Horizont, sonst der aktuelle Kurs.
|
||||||
|
// g_coneT/g_coneLo/g_coneHi halten ihn zwischen den CSV-Zeilen fest; die
|
||||||
|
// Zeilen kommen aufsteigend sortiert vom Bot.
|
||||||
|
datetime pt = (g_coneT > 0 ? g_coneT : t0);
|
||||||
|
double plo = (g_coneT > 0 ? g_coneLo : g_conePx);
|
||||||
|
double phi = (g_coneT > 0 ? g_coneHi : g_conePx);
|
||||||
|
if(plo <= 0 || phi <= 0) { plo = clo; phi = chi; pt = t0; }
|
||||||
|
string ln = PFX + "CONE_L" + IntegerToString(mins);
|
||||||
|
string un = PFX + "CONE_U" + IntegerToString(mins);
|
||||||
|
string tip = "Streuung (keine Richtung): in " + IntegerToString(mins)
|
||||||
|
+ " min liegt der Kurs mit ~76% zwischen "
|
||||||
|
+ DoubleToString(clo, (int)_Digits) + " und "
|
||||||
|
+ DoubleToString(chi, (int)_Digits);
|
||||||
|
for(int s = 0; s < 2; s++)
|
||||||
|
{
|
||||||
|
string nm2 = (s == 0 ? ln : un);
|
||||||
|
double a2 = (s == 0 ? plo : phi);
|
||||||
|
double b2 = (s == 0 ? clo : chi);
|
||||||
|
if(ObjectCreate(0, nm2, OBJ_TREND, 0, pt, a2, t1, b2))
|
||||||
|
{
|
||||||
|
ObjectSetInteger(0, nm2, OBJPROP_COLOR, InpConeColor);
|
||||||
|
ObjectSetInteger(0, nm2, OBJPROP_STYLE, STYLE_DOT);
|
||||||
|
ObjectSetInteger(0, nm2, OBJPROP_WIDTH, 1);
|
||||||
|
ObjectSetInteger(0, nm2, OBJPROP_RAY_RIGHT, false);
|
||||||
|
ObjectSetInteger(0, nm2, OBJPROP_BACK, true);
|
||||||
|
ObjectSetInteger(0, nm2, OBJPROP_SELECTABLE, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ObjectMove(0, nm2, 0, pt, a2);
|
||||||
|
ObjectMove(0, nm2, 1, t1, b2);
|
||||||
|
}
|
||||||
|
ObjectSetString(0, nm2, OBJPROP_TOOLTIP, tip);
|
||||||
|
}
|
||||||
|
g_coneT = t1; g_coneLo = clo; g_coneHi = chi;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// AKTUELLER KURS oben rechts, fett (User-Wunsch): PX;bid
|
// AKTUELLER KURS oben rechts, fett (User-Wunsch): PX;bid
|
||||||
if(typ == "PX")
|
if(typ == "PX")
|
||||||
{
|
{
|
||||||
if(k < 2) continue;
|
if(k < 2) continue;
|
||||||
if(!InpShowPrice) continue;
|
|
||||||
double pxv = StringToDouble(p[1]);
|
double pxv = StringToDouble(p[1]);
|
||||||
if(pxv <= 0) continue;
|
if(pxv <= 0) continue;
|
||||||
|
// Kegel-Startpunkt IMMER merken - auch wenn das Kurs-Label aus ist
|
||||||
|
// (sonst verliert der Faecher seinen Anker).
|
||||||
|
g_conePx = pxv;
|
||||||
|
if(!InpShowPrice) continue;
|
||||||
string pn = PFX + "PRICE";
|
string pn = PFX + "PRICE";
|
||||||
if(ObjectFind(0, pn) < 0)
|
if(ObjectFind(0, pn) < 0)
|
||||||
{
|
{
|
||||||
|
|||||||
+32
@@ -308,6 +308,37 @@ function renderCandles(ca) {
|
|||||||
(<code>backtest_candle_fade.py</code>).</div>`;
|
(<code>backtest_candle_fade.py</code>).</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wahrscheinlichkeits-Kegel. ⚠ Zeigt STREUUNG, nicht Richtung — und beschriftet
|
||||||
|
// mit der REAL gemessenen Abdeckung (~76 %), nicht mit dem Nennwert 80 %. Der Kegel
|
||||||
|
// ist out-of-sample geprüft (analyze_cone.py, Fit H1 → Abdeckung H2) und damit nach
|
||||||
|
// dem P(break)-Modell die zweite kalibrierte Komponente im Projekt.
|
||||||
|
function renderCone(cn) {
|
||||||
|
const el = $("cone-list");
|
||||||
|
if (!el) return;
|
||||||
|
const lv = (cn && cn.levels) || [];
|
||||||
|
if (!lv.length) {
|
||||||
|
el.innerHTML = `<span class="dim">—</span>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const byMin = {};
|
||||||
|
lv.forEach(x => { (byMin[x.minutes] = byMin[x.minutes] || {})[x.band] = x; });
|
||||||
|
el.innerHTML = Object.keys(byMin).sort((a, b) => a - b).map(mn => {
|
||||||
|
const b80 = byMin[mn][80], b50 = byMin[mn][50];
|
||||||
|
const cov = b80 && b80.coverage != null ? `${fmt(b80.coverage, 0)}%` : "~";
|
||||||
|
return `<div class="cone-row">
|
||||||
|
<span class="cone-h">${mn} min</span>
|
||||||
|
<span class="cone-band">${b80 ? `${fmt(b80.lo, 2)} … ${fmt(b80.hi, 2)}` : "—"}
|
||||||
|
<i>trifft ${cov}</i></span>
|
||||||
|
<span class="cone-inner">${b50 ? `enger: ${fmt(b50.lo, 2)} … ${fmt(b50.hi, 2)}` : ""}</span>
|
||||||
|
</div>`;
|
||||||
|
}).join("") +
|
||||||
|
`<div class="pt-disclaimer">📐 <b>Streuung, keine Richtung.</b> Die Spanne sagt, wie
|
||||||
|
WEIT der Kurs plausibel läuft — nicht wohin. Out-of-sample geprüft
|
||||||
|
(<code>analyze_cone.py</code>, Fit auf einem Halbjahr, Abdeckung im anderen
|
||||||
|
gemessen): das nominale 80 %-Band trifft real ~76 %, ist also leicht zu eng.
|
||||||
|
Kein Signal, kein Einfluss auf die Empfehlung.</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderPatterns(pt) {
|
function renderPatterns(pt) {
|
||||||
const el = $("pt-list");
|
const el = $("pt-list");
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -432,6 +463,7 @@ function render(d) {
|
|||||||
renderCircuitBreaker(d.circuit_breaker);
|
renderCircuitBreaker(d.circuit_breaker);
|
||||||
renderPatterns(d.patterns);
|
renderPatterns(d.patterns);
|
||||||
renderCandles(d.candle_anat);
|
renderCandles(d.candle_anat);
|
||||||
|
renderCone(d.cone);
|
||||||
renderStructure(d.structure);
|
renderStructure(d.structure);
|
||||||
renderStatsCompact(d.stats_compact, d.market);
|
renderStatsCompact(d.stats_compact, d.market);
|
||||||
renderGaps(d.gaps, d.market);
|
renderGaps(d.gaps, d.market);
|
||||||
|
|||||||
+9
-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=123">
|
<link rel="stylesheet" href="style.css?v=124">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Modul-Menü (ganz oben) -->
|
<!-- Modul-Menü (ganz oben) -->
|
||||||
@@ -132,6 +132,13 @@
|
|||||||
<div class="ca-list" id="ca-list"><span class="dim">—</span></div>
|
<div class="ca-list" id="ca-list"><span class="dim">—</span></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Wahrscheinlichkeits-Kegel — Streuung, NICHT Richtung. Out-of-sample
|
||||||
|
kalibriert (analyze_cone.py); die UI nennt die REAL gemessene Abdeckung -->
|
||||||
|
<section class="card wide" id="card-cone">
|
||||||
|
<h2>Erwartete Spanne <span class="ca-tf">kalibriert · keine Richtung</span></h2>
|
||||||
|
<div class="cone-list" id="cone-list"><span class="dim">—</span></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- KI-Agent -->
|
<!-- KI-Agent -->
|
||||||
<section class="card wide" id="card-agent">
|
<section class="card wide" id="card-agent">
|
||||||
<h2>KI-Copilot</h2>
|
<h2>KI-Copilot</h2>
|
||||||
@@ -237,6 +244,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=123"></script>
|
<script src="app.js?v=124"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -180,6 +180,14 @@ main{display:grid;grid-template-columns:1fr 1fr;gap:11px;padding:11px}
|
|||||||
.ca-ago{margin-left:auto;font-size:12px;color:var(--dim);font-family:var(--mono)}
|
.ca-ago{margin-left:auto;font-size:12px;color:var(--dim);font-family:var(--mono)}
|
||||||
.ca-bars{font-size:12px;margin-top:3px;font-family:var(--mono);color:var(--dim)}
|
.ca-bars{font-size:12px;margin-top:3px;font-family:var(--mono);color:var(--dim)}
|
||||||
.ca-note{font-size:12px;color:var(--dim);margin-top:3px;line-height:1.4}
|
.ca-note{font-size:12px;color:var(--dim);margin-top:3px;line-height:1.4}
|
||||||
|
/* Erwartete Spanne — neutral, bewusst ohne Richtungsfarbe */
|
||||||
|
.cone-row{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
|
||||||
|
padding:6px 0;border-bottom:1px solid var(--border)}
|
||||||
|
.cone-row:last-of-type{border-bottom:none}
|
||||||
|
.cone-h{font-weight:700;font-size:14px;color:var(--bright);min-width:64px}
|
||||||
|
.cone-band{font-family:var(--mono);font-size:14px;color:var(--bright)}
|
||||||
|
.cone-band i{font-style:normal;font-size:11px;color:var(--dim);margin-left:6px}
|
||||||
|
.cone-inner{margin-left:auto;font-family:var(--mono);font-size:12px;color:var(--dim)}
|
||||||
#card-verdict{border-color:var(--accent)}
|
#card-verdict{border-color:var(--accent)}
|
||||||
.vd-head{display:flex;align-items:center;gap:14px;margin-bottom:10px}
|
.vd-head{display:flex;align-items:center;gap:14px;margin-bottom:10px}
|
||||||
.vd-ring{width:60px;height:60px;border-radius:50%;flex:0 0 auto;
|
.vd-ring{width:60px;height:60px;border-radius:50%;flex:0 0 auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user