Trade-Leiste: Laufzeit-Uhr des offenen Trades (v=118)

- trader: open_time (echte Epoch, Broker-Offset korrigiert) aus position.time,
  im Snapshot; nur 1x je Position berechnet, beim Close auf 0.
- _broker_offset_s(sym=None): Symbol explizit uebergebbar -- beim Adoptieren ist
  self.symbol noch nicht gesetzt (war Offset 0 -> Zeit 3h in der Zukunft);
  zusaetzlich Selbstheilung bei Zukunftswerten.
- app.js/index.html/style.css: Feld #tb-dur + Titel-Uhr #tb-runtime, eigener
  1-s-Ticker (flüssig zwischen Snapshots), ab 2h bernstein (Time-Stop-Naehe).
- Verifiziert: Live-Trade 48898382 open_time == Log-Zeile 13:22:48.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-07-30 13:40:41 +02:00
co-authored by Claude Opus 4.8
parent e9653a842a
commit 919ed7c3ee
5 changed files with 71 additions and 7 deletions
+29
View File
@@ -50,6 +50,30 @@ function beepCloseAlert() {
// der Button blinkt (Empfehlung aktiv & flat) — einmaliger Piep war überhörbar.
const _REC_REBEEP_MS = 30000;
let _lastRecBeep = 0, _lastRecSignal = null, _audioHintShown = false, _lastSrCloseCount = null;
// ── Laufzeit-Uhr des offenen Trades ───────────────────────────────────────────
// Öffnungszeit kommt als echte Epoch (Broker-Offset serverseitig korrigiert) im
// Snapshot (`position.open_time`); der Ticker läuft eigenständig jede Sekunde,
// damit die Uhr auch zwischen den Snapshots (4 s Poll / 1 s WS) flüssig zählt.
let _tradeOpenTime = 0;
function _fmtDuration(sec) {
sec = Math.max(0, Math.floor(sec));
const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60;
const p2 = (n) => String(n).padStart(2, "0");
return h > 0 ? `${h}:${p2(m)}:${p2(s)}` : `${m}:${p2(s)}`;
}
function _tickTradeDuration() {
const el = document.getElementById("tb-dur"), hd = document.getElementById("tb-runtime");
if (!el) return;
if (!_tradeOpenTime) { el.textContent = "—"; if (hd) hd.textContent = ""; return; }
const sec = Date.now() / 1000 - _tradeOpenTime;
const txt = _fmtDuration(sec);
el.textContent = txt;
if (hd) hd.textContent = "⏱ " + txt;
// Ab 2 h dezent hervorheben (Time-Stop greift bei 120 min in Phase „Init")
el.className = "tb-val" + (sec >= 7200 ? " warn" : "");
}
setInterval(_tickTradeDuration, 1000);
function beepRecommendation(sig, isNew) {
if (_muted) return; // auch kein Sperr-Toast
if (!_audioCtx || _audioCtx.state !== "running") {
@@ -386,12 +410,17 @@ function render(d) {
dEl.textContent = long ? "▲ LONG" : "▼ SHORT";
$("tb-lots").textContent = fmt(p.lots, 2);
$("tb-margin").textContent = p.margin ? cs + fmt(p.margin, 2) : "—";
// Laufzeit: Öffnungszeit merken (echte Epoch, Broker-Offset korrigiert) und
// sekündlich per Ticker anzeigen — unabhängig vom Snapshot-Intervall.
_tradeOpenTime = p.open_time ? Number(p.open_time) : 0;
_tickTradeDuration();
// SL/TP-Felder nur befüllen, wenn nicht gerade bearbeitet (kein Tipp-Konflikt)
const slEl = $("tb-sl"), tpEl = $("tb-tp");
if (document.activeElement !== slEl) slEl.value = p.sl ? fmt(p.sl, 2) : "";
if (document.activeElement !== tpEl) tpEl.value = p.tp ? fmt(p.tp, 2) : "";
} else {
tb.classList.add("hidden");
_tradeOpenTime = 0;
}
}
+5 -3
View File
@@ -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=117">
<link rel="stylesheet" href="style.css?v=118">
</head>
<body>
<!-- Modul-Menü (ganz oben) -->
@@ -44,8 +44,10 @@
<div id="circuit-banner" class="circuit hidden"></div>
<!-- Aktueller Trade (nur bei offener Position) — Anzeige + SL/TP/Close editierbar -->
<section class="card wide trade-bar hidden" id="trade-bar">
<h2 class="tb-title">aktueller Trade läuft</h2>
<h2 class="tb-title">aktueller Trade läuft <span class="tb-runtime" id="tb-runtime"></span></h2>
<div class="tb-row">
<span class="tb-field"><label>Laufzeit</label>
<b class="tb-val" id="tb-dur"></b></span>
<span class="tb-field"><label>Richtung</label>
<b class="tb-val tb-dir" id="tb-dir"></b></span>
<span class="tb-field"><label>Lots</label>
@@ -226,6 +228,6 @@
<div id="toast" class="toast hidden"></div>
<script src="lightweight-charts.standalone.production.js?v=114"></script>
<script src="app.js?v=117"></script>
<script src="app.js?v=118"></script>
</body>
</html>
+4
View File
@@ -112,6 +112,10 @@ main{display:grid;grid-template-columns:1fr 1fr;gap:11px;padding:11px}
#card-trail.hidden{display:none} /* Trailing-Kachel ausgeblendet, 2026-07-22 */
.tb-title{margin:0 0 8px;font-size:14px;letter-spacing:1.5px;text-transform:uppercase;
color:var(--dim);font-weight:600}
/* Laufzeit-Uhr: im Titel mitlaufend (kompakt) + eigenes Feld in der Zeile */
.tb-runtime{margin-left:8px;color:var(--bright);font-family:var(--mono);
letter-spacing:.5px;text-transform:none}
.tb-field .tb-val.warn{color:var(--accent);border-color:#4a3600}
.tb-row{display:flex;flex-wrap:wrap;gap:10px}
/* EINHEITLICHES Feld-Design: Anzeige-Boxen (Richtung/Lots/Einsatz) und Eingabe-
Felder (SL/TP/Gewinn-Close/Notfall-Close) sehen identisch aus (Label über Box). */