Wirtschaftskalender-Blackout + Konsolidierungs-Box beschriftet
core/econ_calendar.py (NEU): High-Impact-News-Fenster als Handels-Sperre. Quelle Forex Factory JSON (kostenlos, kein Key, mit impact-Feld), beide Wochen (thisweek endet Sa, Markt oeffnet So 23:00 -> sonst blinder Fleck), 1x/h im Daemon-Thread, dedupliziert. Filter: High+USD ODER Oil/OPEC-Titel. WICHTIG - nicht gemessen: Events sind zu selten fuer eine 2-Stichproben- Aussage. Das ist die Handbuch-Regel, kein belegter Edge. Deshalb bewusst asymmetrisch: sperrt NUR die autonomen Entries (Auto-Squeeze + Auto-Signal, ohne Dedup-Marker -> feuert nach dem Fenster nach), in der Entry-Checkliste nur "warn" statt "fail". Manuelle Orders bleiben frei. Der gemessene EIA-Blackout (Mi 15:30-16:30) bleibt unabhaengig aktiv und greift auch ohne Netz. Verifiziert: Live-Fetch (EIA korrekt Mi 16:30 Berlin, FOMC/GDP/PCE erkannt) + 4 synthetische Szenarien (5 min vor/nach Event, 50 min davor, disabled). MQL5 v1.30: graue Konsolidierungs-Box beschriftet (User-Frage "was bedeutet die graue Box") - sichtbares Label an der Oberkante, rechtsbuendig wie die uebrigen: "Konsolidierung (10-Bar-Spanne)" bzw. bei Kompression "Konsolidierung - komprimiert, Ausbruch steht bevor". 0 Fehler kompiliert. Frontend v=122: #cal-note in der Meldungen-Karte (laufendes Fenster bzw. Vorwarnung <=60 min). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7e0d2a4563
commit
a6f370ac02
@@ -320,6 +320,30 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
||||
dem alten „16 Uhr"-Befund. eia_post/api_pre/api_post NICHT robust → nicht geblockt.
|
||||
Nur im Live-Pfad (`hour≠None`), Backtests bleiben gate-frei. Best-Practice
|
||||
„Event-Blackout" statt Pauschal-Gate.
|
||||
- **Wirtschaftskalender-Blackout (`core/econ_calendar.py`, 2026-07-30, `[trading]
|
||||
news_blackout=true`, ±15 min):** Erweiterung der EIA-Idee auf ALLE High-Impact-
|
||||
Termine (FOMC/Zinsentscheid/NFP/CPI/GDP/PCE + alle Oil-/OPEC-Titel). Quelle
|
||||
**Forex Factory JSON** (`nfs.faireconomy.media`, kostenlos, kein Key, mit
|
||||
`impact`-Feld); geholt werden **beide Wochen** (`thisweek`+`nextweek` — „thisweek"
|
||||
endet samstags, der Markt öffnet aber Sonntag 23:00 → sonst blinder Fleck), 1×/h
|
||||
**im Daemon-Thread** (ein hängender HTTP-Call darf den Trend-Loop nicht blockieren),
|
||||
dedupliziert über (ts, title). Filter: `impact=High` + `country=USD`, ODER Titel
|
||||
enthält crude oil/opec/oil inventories/petroleum (der EIA-Report läuft bei FF teils
|
||||
als Low/Medium). ⚠ **NICHT gemessen** — Events sind zu selten für eine
|
||||
2-Stichproben-Aussage, das ist die Handbuch-Regel („keine Trades 15 Min. vor
|
||||
High-Impact-News"), kein belegter Edge. Deshalb bewusst asymmetrisch verdrahtet:
|
||||
**sperrt nur die AUTONOMEN Entries** (`_check_auto_squeeze` + `_check_auto_signal`,
|
||||
jeweils OHNE Dedup-Marker → nach dem Fenster feuert das Setup, falls noch aktiv) und
|
||||
erscheint in der Entry-Checkliste nur als **„warn"**, nicht „fail" (soll den Verdict
|
||||
nicht allein auf `stop` kippen); **manuelle Orders bleiben frei.** Der gemessene
|
||||
EIA-Blackout bleibt unabhängig davon aktiv — er greift auch, wenn der Kalender kein
|
||||
Netz hat (Fail-safe: ohne Daten → kein Blackout, Log 1×, dann still). Snapshot
|
||||
`calendar {enabled, n, blackout{title,impact,ccy,minutes}, next, age_min}`,
|
||||
Frontend `#cal-note` (Meldungen-Karte, bernstein: laufendes Fenster bzw. Vorwarnung
|
||||
≤60 min). Abschalten `news_blackout=false`; Fenster via `news_blackout_before_min`/
|
||||
`_after_min`. Mit 4 synthetischen Szenarien getestet (5 min vor/nach Event, 50 min
|
||||
davor = kein Blackout, disabled) + Live-Fetch verifiziert (EIA lag korrekt auf
|
||||
Mi 16:30 Berlin).
|
||||
- **Winkel-vs-EMA-Warnung (nur Konfidenz):** Regressions-Winkel der Basis-TF gegen
|
||||
die EMA-Richtung → −10 „⚠ Winkel gegen EMA (mögliche Wende)" (nachlaufende EMA!);
|
||||
dafür → +5. `backtest_angle.py`: dafür/neutral +0,055 vs dagegen +0,025; als Gate
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
"""core/econ_calendar.py — Wirtschaftskalender (High-Impact-News) als Handels-Sperre.
|
||||
|
||||
User-Vorgabe 2026-07-30 („berücksichtige auch die Quellen aus dem Trading-Handbuch",
|
||||
Kapitel 5.4 + 8.1 Regel 8: „Keine Trades vor High-Impact-News", „keine Trades 15 Min.
|
||||
vor EIA").
|
||||
|
||||
Quelle: **Forex Factory JSON** (`nfs.faireconomy.media`) — kostenlos, kein API-Key,
|
||||
strukturiert mit `impact`-Level. Enthält auch „Crude Oil Inventories" (= der
|
||||
EIA-Report) und OPEC-Termine.
|
||||
|
||||
⚠ ABGRENZUNG zum bestehenden EIA-Blackout: Der ist **gemessen** (`backtest_events.py`:
|
||||
Mi 15:30–16:30 in BEIDEN Hälften negativ) und bleibt als fester Fallback bestehen —
|
||||
er funktioniert auch, wenn dieses Modul keine Daten bekommt. Der Kalender-Blackout
|
||||
hier ist eine **plausible Erweiterung** (FOMC/NFP/CPI/OPEC statt nur Mittwoch), aber
|
||||
NICHT separat backtestbar: Event-Zeitreihen reichen nicht weit genug zurück, und die
|
||||
Ereignisse sind zu selten für eine 2-Stichproben-Aussage. Er ist damit ein
|
||||
**Risiko-Schutz auf Basis der Handbuch-Regel**, kein gemessener Edge — deshalb sperrt
|
||||
er nur die AUTONOMEN Entries und warnt in der Checkliste; er blockt keine manuelle
|
||||
Order.
|
||||
|
||||
Fail-safe: Ohne Netz/Daten liefert das Modul „kein Blackout" und der Bot arbeitet
|
||||
unverändert weiter (der gemessene EIA-Blackout greift trotzdem).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from core.logger import get_logger
|
||||
|
||||
log = get_logger("calendar")
|
||||
|
||||
# BEIDE Wochen holen: „thisweek" endet samstags — der Markt öffnet aber Sonntag
|
||||
# 23:00 (Berlin), und ein Montag-Event läge dann in einem toten Fenster.
|
||||
_URLS = ("https://nfs.faireconomy.media/ff_calendar_thisweek.json",
|
||||
"https://nfs.faireconomy.media/ff_calendar_nextweek.json")
|
||||
_REFRESH_S = 3600.0 # 1×/Stunde reicht — der Kalender ändert sich selten
|
||||
_TIMEOUT_S = 15.0
|
||||
# Währungen, die WTI real bewegen (USD dominiert; OPEC/Oil-Titel unabhängig davon)
|
||||
_CCY = {"USD"}
|
||||
# Titel-Stichworte, die IMMER als relevant gelten (auch bei niedrigerem Impact —
|
||||
# „Crude Oil Inventories" läuft bei Forex Factory teils als Low/Medium)
|
||||
_OIL_KEYS = ("crude oil", "opec", "oil inventories", "petroleum")
|
||||
|
||||
|
||||
class EconCalendar:
|
||||
def __init__(self, before_min: int = 15, after_min: int = 15,
|
||||
enabled: bool = True):
|
||||
self.enabled = bool(enabled)
|
||||
self.before_s = max(0, int(before_min)) * 60
|
||||
self.after_s = max(0, int(after_min)) * 60
|
||||
self._lock = threading.Lock()
|
||||
self._events: list[dict] = [] # [{ts, title, ccy, impact}]
|
||||
self._ts = 0.0
|
||||
self._busy = False
|
||||
self._fail_logged = False
|
||||
|
||||
# ── Daten holen ──────────────────────────────────────────────────────────
|
||||
def _fetch(self) -> None:
|
||||
try:
|
||||
raw: list = []
|
||||
errs = 0
|
||||
for url in _URLS:
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
url, headers={"User-Agent": "Mozilla/5.0"})
|
||||
with urllib.request.urlopen(req, timeout=_TIMEOUT_S) as r:
|
||||
part = json.loads(r.read().decode())
|
||||
if isinstance(part, list):
|
||||
raw.extend(part)
|
||||
except Exception:
|
||||
errs += 1 # eine Woche darf fehlen (nextweek ist oft leer)
|
||||
if errs == len(_URLS):
|
||||
raise RuntimeError("keine Woche abrufbar")
|
||||
out = []
|
||||
seen = set()
|
||||
for e in raw:
|
||||
title = (e.get("title") or "").strip()
|
||||
ccy = (e.get("country") or "").strip()
|
||||
imp = (e.get("impact") or "").strip()
|
||||
tl = title.lower()
|
||||
is_oil = any(k in tl for k in _OIL_KEYS)
|
||||
if not (is_oil or (imp == "High" and ccy in _CCY)):
|
||||
continue
|
||||
try:
|
||||
dt = datetime.fromisoformat(e.get("date"))
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
except Exception:
|
||||
continue
|
||||
key = (round(dt.timestamp()), title)
|
||||
if key in seen: # Überlappung thisweek/nextweek
|
||||
continue
|
||||
seen.add(key)
|
||||
out.append({"ts": dt.timestamp(), "title": title,
|
||||
"ccy": ccy, "impact": ("Oil" if is_oil else imp)})
|
||||
out.sort(key=lambda x: x["ts"])
|
||||
with self._lock:
|
||||
self._events = out
|
||||
self._ts = time.time()
|
||||
self._fail_logged = False
|
||||
log.info(f"Wirtschaftskalender geladen: {len(out)} relevante Events")
|
||||
except Exception as e:
|
||||
if not self._fail_logged:
|
||||
log.info(f"Kalender nicht abrufbar ({e}) — News-Sperre inaktiv "
|
||||
f"(der gemessene EIA-Blackout greift weiterhin)")
|
||||
self._fail_logged = True
|
||||
|
||||
def maybe_refresh(self) -> None:
|
||||
"""Im Trend-Loop aufrufen; self-throttled auf ~1 h.
|
||||
|
||||
⚠ Der Fetch läuft in einem **Daemon-Thread** — sonst würde ein hängender
|
||||
HTTP-Call den Trend-Loop bis zum Timeout (15 s) blockieren.
|
||||
"""
|
||||
if not self.enabled or self._busy:
|
||||
return
|
||||
if time.time() - self._ts < _REFRESH_S:
|
||||
return
|
||||
self._busy = True
|
||||
self._ts = time.time() # sofort setzen → kein Retry-Sturm bei Fehler
|
||||
|
||||
def _job():
|
||||
try:
|
||||
self._fetch()
|
||||
finally:
|
||||
self._busy = False
|
||||
|
||||
threading.Thread(target=_job, daemon=True,
|
||||
name="econ-calendar").start()
|
||||
|
||||
# ── Abfragen ─────────────────────────────────────────────────────────────
|
||||
def blackout(self) -> dict | None:
|
||||
"""Läuft gerade ein News-Fenster? → {title, impact, minutes} sonst None."""
|
||||
if not self.enabled:
|
||||
return None
|
||||
now = time.time()
|
||||
with self._lock:
|
||||
evs = list(self._events)
|
||||
for e in evs:
|
||||
if e["ts"] - self.before_s <= now <= e["ts"] + self.after_s:
|
||||
return {"title": e["title"], "impact": e["impact"], "ccy": e["ccy"],
|
||||
"minutes": round((e["ts"] - now) / 60.0, 1)}
|
||||
return None
|
||||
|
||||
def next_event(self) -> dict | None:
|
||||
"""Nächstes relevantes Event (für die Anzeige)."""
|
||||
now = time.time()
|
||||
with self._lock:
|
||||
evs = list(self._events)
|
||||
for e in evs:
|
||||
if e["ts"] >= now:
|
||||
return {"title": e["title"], "impact": e["impact"], "ccy": e["ccy"],
|
||||
"minutes": round((e["ts"] - now) / 60.0)}
|
||||
return None
|
||||
|
||||
def snapshot(self) -> dict:
|
||||
bl = self.blackout()
|
||||
return {"enabled": self.enabled, "n": len(self._events),
|
||||
"blackout": bl, "next": self.next_event(),
|
||||
"age_min": round((time.time() - self._ts) / 60.0) if self._ts else None}
|
||||
@@ -173,6 +173,15 @@ class TradingEngine:
|
||||
url=_t2.get("hl_walls_url", "http://localhost:8001/api/walls"),
|
||||
enabled=_t2.get("export_hl_walls", "true").lower() == "true")
|
||||
self.candles = CandleLogger(HISTORY_DB_FILE) # M1-Candle-Log (Daten-Sammlung)
|
||||
# Wirtschaftskalender (Handbuch-Regel „keine Trades vor High-Impact-News").
|
||||
# ⚠ NICHT gemessen (Events zu selten für 2-Stichproben) → sperrt nur die
|
||||
# AUTONOMEN Entries + warnt in der Checkliste, blockt keine manuelle Order.
|
||||
# Der gemessene EIA-Blackout (Mi 15:30–16:30) bleibt unabhängig davon aktiv.
|
||||
from core.econ_calendar import EconCalendar
|
||||
self.calendar = EconCalendar(
|
||||
before_min=int(_t2.get("news_blackout_before_min", "15")),
|
||||
after_min =int(_t2.get("news_blackout_after_min", "15")),
|
||||
enabled =_t2.get("news_blackout", "true").lower() == "true")
|
||||
# News: eigener Fetch-Loop (versorgt UI /api/news UND Agent-Kontext)
|
||||
tcfg = self.cfg["translation"]
|
||||
translator = NewsTranslator(
|
||||
@@ -265,6 +274,7 @@ class TradingEngine:
|
||||
self._sr_close_dir = None # → Cooldown gegen Gleichrichtungs-Roundtrip
|
||||
self._open_pos_dir = None # Richtung der aktuell offenen Position (LONG/SHORT)
|
||||
self._pos_close_ts = 0.0 # Zeit + Richtung des letzten Positions-Close (JEDE
|
||||
self._news_block_logged = None # Dedup für die News-Blackout-Logzeile
|
||||
self._pos_close_dir = None # Art) → Squeeze-Wiedereinstiegs-Sperre (gegen die
|
||||
# Open→Instant-Close-Kaskade bei zu engem Notfall-Stop)
|
||||
self._sr_close_min_gain_armed_ticket = None # Ticket, für das die %-Schwelle schon
|
||||
@@ -533,6 +543,10 @@ class TradingEngine:
|
||||
except Exception as ce: log.warning(f"candles.log: {ce}")
|
||||
try: self._evaluate_pbreak_predictions() # ~alle 5 min, self-throttled
|
||||
except Exception as pe: log.warning(f"pbreak_predictions: {pe}")
|
||||
# Wirtschaftskalender (self-throttled ~1 h, außerhalb des mt5_lock
|
||||
# unkritisch — reiner HTTP-Fetch mit Timeout)
|
||||
try: self.calendar.maybe_refresh()
|
||||
except Exception as ke: log.warning(f"calendar.refresh: {ke}")
|
||||
self._run_analysis()
|
||||
except Exception as e:
|
||||
log.error(f"_trend_loop: {e}", exc_info=True)
|
||||
@@ -944,6 +958,18 @@ class TradingEngine:
|
||||
# aktiv. `auto_squeeze_skip_night=false` schaltet aus.
|
||||
if self._squeeze_skip_night and datetime.now(_BERLIN).hour in _SQUEEZE_NIGHT:
|
||||
return
|
||||
# ── News-Blackout (Handbuch-Regel, NICHT gemessen): 15 min um ein
|
||||
# High-Impact-Event (FOMC/NFP/CPI/EIA/OPEC) kein autonomer Entry. Setzt
|
||||
# KEINEN Dedup-Marker → nach dem Fenster feuert der Ausbruch, falls noch
|
||||
# aktiv. Gilt nur für den Bot; manuelle Orders bleiben frei.
|
||||
_bl = self.calendar.blackout()
|
||||
if _bl:
|
||||
if self._news_block_logged != _bl["title"]:
|
||||
self._news_block_logged = _bl["title"]
|
||||
log.info(f"📅 News-Blackout — kein Auto-Entry: {_bl['title']} "
|
||||
f"({_bl['impact']}, in {_bl['minutes']:.0f} min)")
|
||||
return
|
||||
self._news_block_logged = None
|
||||
|
||||
ps = self.trader.snapshot()
|
||||
ticket = ps.get("ticket")
|
||||
@@ -1061,6 +1087,9 @@ class TradingEngine:
|
||||
# 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
|
||||
# News-Blackout (s. `_check_auto_squeeze`) — kein Dedup-Marker.
|
||||
if self.calendar.blackout():
|
||||
return
|
||||
ps = self.trader.snapshot()
|
||||
if ps.get("ticket"):
|
||||
# Position offen: läuft sie in Signalrichtung, gilt die Episode als
|
||||
@@ -2391,6 +2420,25 @@ class TradingEngine:
|
||||
"gemessen: 0–7 Uhr = Kostenfalle (Spread ÷ dünner ATR)")
|
||||
except Exception:
|
||||
pass
|
||||
# ── 8. Wirtschaftskalender (Handbuch: „keine Trades vor High-Impact-News")
|
||||
# ⚠ NICHT gemessen (zu wenige Events für eine 2-Stichproben-Aussage) —
|
||||
# deshalb hier nur „warn", nicht „fail": es soll den Verdict nicht allein
|
||||
# auf `stop` kippen. Der Bot selbst pausiert im Fenster (autonome Entries).
|
||||
try:
|
||||
bl = self.calendar.blackout()
|
||||
if bl:
|
||||
m = bl["minutes"]
|
||||
when = (f"in {m:.0f} min" if m > 0 else f"vor {abs(m):.0f} min")
|
||||
add("event", "warn", f"News-Fenster: {bl['title']} ({when})",
|
||||
"Handbuch-Regel: 15 min um High-Impact-Events nicht handeln "
|
||||
"(im Projekt nicht gegengemessen)")
|
||||
else:
|
||||
nx = self.calendar.next_event()
|
||||
if nx and nx["minutes"] <= 90:
|
||||
add("event", "ok",
|
||||
f"Nächstes Event: {nx['title']} in {nx['minutes']} min", "")
|
||||
except Exception:
|
||||
pass
|
||||
fails = sum(1 for x in items if x["state"] == "fail")
|
||||
warns = sum(1 for x in items if x["state"] == "warn")
|
||||
return {"side": want, "items": items, "fails": fails, "warns": warns,
|
||||
@@ -2689,6 +2737,9 @@ class TradingEngine:
|
||||
"adverse15_atr": self._adverse15_atr,
|
||||
"adverse15_count": self._adverse15_count,
|
||||
"last_trades": last_trades,
|
||||
# Wirtschaftskalender: {blackout, next, enabled, n} — Anzeige + Grund,
|
||||
# warum der Bot gerade keine autonome Order eröffnet.
|
||||
"calendar": self.calendar.snapshot(),
|
||||
}
|
||||
# ── Entry-Checkliste je Richtung (User-Vorgabe 2026-07-30: „blende einen
|
||||
# Hinweis ein, wenn die Checkliste gegen meine Eröffnung ist"). Wird im
|
||||
|
||||
+26
-4
@@ -12,7 +12,11 @@
|
||||
//| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Oil Trading Bot"
|
||||
#property version "1.29"
|
||||
#property version "1.30"
|
||||
// v1.30 (2026-07-30): Konsolidierungs-Box BESCHRIFTET (User-Frage „was bedeutet die
|
||||
// graue Box") — sichtbares Label an der Oberkante, rechtsbuendig wie die
|
||||
// uebrigen: „Konsolidierung (10-Bar-Spanne)" bzw. bei Kompression
|
||||
// „Konsolidierung - komprimiert, Ausbruch steht bevor".
|
||||
// v1.29 (2026-07-30): KONSOLIDIERUNGS-BOX (CO;lo;hi;tight) — die Spanne der letzten
|
||||
// 10 M5-Bars, aus der der validierte Squeeze-Ausbruch kommt. Komprimiert
|
||||
// (<=2,5xATR) wird sie deutlicher gezeichnet. ⚠ REINE ANZEIGE: alle
|
||||
@@ -621,11 +625,29 @@ void Redraw()
|
||||
ObjectSetInteger(0, cn, OBJPROP_COLOR,
|
||||
BlendBg(InpConsolColor, tight ? InpConsolAlpha * 2
|
||||
: InpConsolAlpha));
|
||||
ObjectSetString (0, cn, OBJPROP_TOOLTIP,
|
||||
(tight ? "Konsolidierung (komprimiert - Ausbruch steht bevor)"
|
||||
string ctip = (tight ? "Konsolidierung (komprimiert - Ausbruch steht bevor)"
|
||||
: "aktuelle 10-Bar-Spanne")
|
||||
+ " " + DoubleToString(clo, (int)_Digits) + " - "
|
||||
+ DoubleToString(chi, (int)_Digits));
|
||||
+ DoubleToString(chi, (int)_Digits);
|
||||
ObjectSetString (0, cn, OBJPROP_TOOLTIP, ctip);
|
||||
// SICHTBARE BESCHRIFTUNG (User-Frage 2026-07-30 „was bedeutet die graue Box"):
|
||||
// eigenes OBJ_TEXT an der OBERKANTE, rechtsbündig wie alle anderen Labels
|
||||
// (Name mit PFX+"T" → RepositionLabels() führt es beim Scrollen nach).
|
||||
string ctn = PFX + "TCONSOL";
|
||||
if(ObjectCreate(0, ctn, OBJ_TEXT, 0, RightTime(), chi))
|
||||
{
|
||||
ObjectSetInteger(0, ctn, OBJPROP_COLOR, InpLabelColor);
|
||||
ObjectSetInteger(0, ctn, OBJPROP_FONTSIZE, InpLabelSize);
|
||||
ObjectSetInteger(0, ctn, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER);
|
||||
ObjectSetInteger(0, ctn, OBJPROP_BACK, false);
|
||||
ObjectSetInteger(0, ctn, OBJPROP_SELECTABLE, false);
|
||||
}
|
||||
else
|
||||
ObjectMove(0, ctn, 0, RightTime(), chi);
|
||||
ObjectSetString(0, ctn, OBJPROP_TEXT,
|
||||
tight ? "Konsolidierung - komprimiert, Ausbruch steht bevor"
|
||||
: "Konsolidierung (10-Bar-Spanne)");
|
||||
ObjectSetString(0, ctn, OBJPROP_TOOLTIP, ctip);
|
||||
continue;
|
||||
}
|
||||
// AKTUELLER KURS oben rechts, fett (User-Wunsch): PX;bid
|
||||
|
||||
+16
@@ -666,6 +666,22 @@ function render(d) {
|
||||
if (txt) { ncEl.className = "news-conflict warn"; ncEl.textContent = txt; }
|
||||
else { ncEl.className = "news-conflict hidden"; ncEl.textContent = ""; }
|
||||
}
|
||||
// Wirtschaftskalender (2026-07-30): laufendes News-Fenster bzw. Vorwarnung ≤60 min.
|
||||
// ⚠ NICHT gemessen — Handbuch-Regel; der Bot pausiert im Fenster nur seine
|
||||
// AUTONOMEN Entries, manuelle Orders bleiben frei.
|
||||
const calEl = $("cal-note");
|
||||
if (calEl) {
|
||||
const c = d.calendar || {};
|
||||
let t = "";
|
||||
if (c.blackout) {
|
||||
const m = c.blackout.minutes;
|
||||
t = `📅 News-Fenster: ${c.blackout.title} ${m > 0 ? "in " + Math.round(m) + " min" : "vor " + Math.round(-m) + " min"} — Bot eröffnet gerade keine Auto-Trades`;
|
||||
} else if (c.next && c.next.minutes <= 60) {
|
||||
t = `📅 ${c.next.title} in ${c.next.minutes} min`;
|
||||
}
|
||||
if (t) { calEl.className = "news-conflict warn"; calEl.textContent = t; }
|
||||
else { calEl.className = "news-conflict hidden"; calEl.textContent = ""; }
|
||||
}
|
||||
// Schalter-Zustand: Auto-Close am S/R an/aus + Mindestgewinn
|
||||
const srBtn = $("srclose-btn");
|
||||
if (srBtn) {
|
||||
|
||||
+3
-2
@@ -6,7 +6,7 @@
|
||||
<meta name="theme-color" content="#0d1117">
|
||||
<title>Oil · MT5</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="style.css?v=121">
|
||||
<link rel="stylesheet" href="style.css?v=122">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modul-Menü (ganz oben) -->
|
||||
@@ -96,6 +96,7 @@
|
||||
<div class="pos-srhint hidden" id="pos-srhint"></div>
|
||||
<div class="pos-stophint hidden" id="pos-stophint"></div>
|
||||
<div class="news-conflict hidden" id="news-conflict"></div>
|
||||
<div class="news-conflict hidden" id="cal-note"></div>
|
||||
<div class="reasons" id="wave-reasons"></div>
|
||||
<div class="kv"><span>Setup</span><b id="wave-setup">—</b></div>
|
||||
<div class="kv"><span>Lauf</span><b id="wave-move">—</b></div>
|
||||
@@ -229,6 +230,6 @@
|
||||
<div id="toast" class="toast hidden"></div>
|
||||
|
||||
<script src="lightweight-charts.standalone.production.js?v=114"></script>
|
||||
<script src="app.js?v=121"></script>
|
||||
<script src="app.js?v=122"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user