G/V-Lag: 1-s-Fallback + ehrlicher Verbindungspunkt (v=151)
Zuerst gemessen, dann gedreht - und das Backend war NICHT das Problem: /api/snapshot 75 Abrufe in 20 s -> 21 verschiedene ts = ~1/s WebSocket-Push direkt am Socket gemessen: Median 1,04 s (min 1,00 max 1,06) G/V-Aenderungen Ø 1,44 s - das ist der Markt, keine Verzoegerung Die ganze Server-Kette liefert bereits 1 s. Der Lag entsteht erst am Handy: bricht der WebSocket STILL weg (WireGuard-Aussetzer, App-Wechsel), traegt nur noch der REST-Poll - und der stand auf 4 s. POLL_MS 4000 -> 1000, aber mit Sperre: solange der WS liefert (lastWsMs, Fenster WS_FRISCH_MS=2200 ms), ueberspringt der Poll den Abruf. Kein zusaetzlicher Verkehr im Normalfall. Denkfehler, den erst die Simulation zeigte: der erste Entwurf las die WS-Frische aus lastRecvMs (jede Quelle) - damit drosselte sich der Poll SELBST und kam bei totem WS nur auf 2,9 s statt 1 s. Jetzt zwei getrennte Uhren. Simuliert: WS gesund -> 0 Zusatz-Abrufe, max. Alter 0,9 s. WS tot -> 1/s, max. Alter 0,9 s. WS traege (5 s) -> max. 2,9 s. Vorher bei totem WS: 4,0 s. Der Verbindungspunkt log: er wurde nur bei sock.onclose auf "off" gesetzt, aber genau der Fall, der den Lag erzeugt, feuert KEIN onclose - der Socket haengt still, der Punkt blieb gruen. Neu .dot.stale (bernstein, pulsierend) ab 3,5 s ohne frischen Snapshot: gemessen wird, was ANKOMMT, nicht was der Socket ueber sich behauptet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c445c687f5
commit
59e9498237
@@ -260,6 +260,37 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
|||||||
⚠ Die 40 vorhandenen `if (!el) return;`-Wächter feuern jetzt nicht mehr. Sie
|
⚠ Die 40 vorhandenen `if (!el) return;`-Wächter feuern jetzt nicht mehr. Sie
|
||||||
bleiben stehen (kein Nutzen im Entfernen, und sie greifen weiter, wenn jemand
|
bleiben stehen (kein Nutzen im Entfernen, und sie greifen weiter, wenn jemand
|
||||||
`$streng` benutzt) — beim Lesen aber mitdenken.
|
`$streng` benutzt) — beim Lesen aber mitdenken.
|
||||||
|
- **✅ G/V-LAG: 1-s-FALLBACK + EHRLICHER VERBINDUNGSPUNKT (2026-08-07, v=151).**
|
||||||
|
User: „G/V wird extrem lagged — 1 Sekunde".
|
||||||
|
⚠⚠ **ZUERST GEMESSEN, DANN GEDREHT — und das Backend war NICHT das Problem:**
|
||||||
|
| Stufe | gemessen |
|
||||||
|
|---|---|
|
||||||
|
| `/api/snapshot` (75 Abrufe / 20 s) | **21 verschiedene `ts`** = ~1/s |
|
||||||
|
| **WebSocket-Push, direkt am Socket** | **Median 1,04 s** (min 1,00 · max 1,06) |
|
||||||
|
| G/V-Änderungen | Ø 1,44 s — das ist der **Markt**, keine Verzögerung |
|
||||||
|
**Die ganze Server-Kette liefert bereits 1 s.** Der Lag entsteht erst am Handy:
|
||||||
|
bricht der WebSocket **still** weg (WireGuard-Aussetzer, App-Wechsel), trägt nur
|
||||||
|
noch der REST-Poll — und der stand auf **4 s**.
|
||||||
|
✅ **`POLL_MS` 4000 → 1000, aber MIT Sperre:** solange der WS liefert
|
||||||
|
(`lastWsMs`, Fenster `WS_FRISCH_MS`=2200 ms), überspringt der Poll den Abruf.
|
||||||
|
**Kein zusätzlicher Verkehr im Normalfall** (~14 KB/Abruf über WireGuard).
|
||||||
|
⚠ **Denkfehler, den erst die Simulation zeigte:** der erste Entwurf las die
|
||||||
|
WS-Frische aus `lastRecvMs` (jede Quelle) — damit **drosselte sich der Poll
|
||||||
|
selbst** und kam bei totem WS nur auf **2,9 s** statt 1 s. Jetzt zwei getrennte
|
||||||
|
Uhren: `lastWsMs` (nur `sock.onmessage`) entscheidet über das Überspringen,
|
||||||
|
`lastRecvMs` (jede Quelle) über die Stale-Anzeige.
|
||||||
|
**Simuliert:** WS gesund → **0** Zusatz-Abrufe, max. Alter **0,9 s** · WS tot →
|
||||||
|
1/s, max. Alter **0,9 s** · WS träge (5 s) → max. 2,9 s. Vorher bei totem WS:
|
||||||
|
4,0 s.
|
||||||
|
✅ **Der Verbindungspunkt log** — behoben. Er wurde **nur** bei `sock.onclose`
|
||||||
|
auf „off" gesetzt; genau der Fall, der den Lag erzeugt, feuert aber **kein**
|
||||||
|
`onclose`: der Socket hängt still, der Punkt blieb **grün**. Neu `.dot.stale`
|
||||||
|
(bernstein, pulsierend) ab **3,5 s** ohne frischen Snapshot — gemessen wird,
|
||||||
|
was ANKOMMT, nicht was der Socket über sich behauptet. Tooltip nennt das Alter.
|
||||||
|
⚠ Kein Konflikt mit `render()`, das `className` komplett überschreibt: läuft
|
||||||
|
`render()`, ist gerade ein Snapshot angekommen und `stale` gehört ohnehin weg.
|
||||||
|
`.dot.stale` steht in `style.css` **nach** `.on`/`.off` — gleiche Spezifität,
|
||||||
|
die Reihenfolge entscheidet.
|
||||||
- **⚠ NEUE HTML-Elemente IMMER null-sicher ansprechen (Fix 2026-08-01).**
|
- **⚠ NEUE HTML-Elemente IMMER null-sicher ansprechen (Fix 2026-08-01).**
|
||||||
`index.html` und `app.js` werden vom Browser **unabhängig** gecacht. Trifft neue JS
|
`index.html` und `app.js` werden vom Browser **unabhängig** gecacht. Trifft neue JS
|
||||||
auf eine alte, gecachte HTML, wirft ein direkter Zugriff (`$("neu").textContent = …`)
|
auf eine alte, gecachte HTML, wirft ein direkter Zugriff (`$("neu").textContent = …`)
|
||||||
@@ -4151,7 +4182,7 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
|||||||
(„⚠ die Welle (steuert die Order) schweigt").
|
(„⚠ die Welle (steuert die Order) schweigt").
|
||||||
Live gegengerechnet, beide Zeilen erscheinen wie beabsichtigt.
|
Live gegengerechnet, beide Zeilen erscheinen wie beabsichtigt.
|
||||||
⚠ Reine Anzeige — an Gewichten, Gates und Order-Logik ist nichts geändert.
|
⚠ Reine Anzeige — an Gewichten, Gates und Order-Logik ist nichts geändert.
|
||||||
- Asset-Version aktuell **v=150** (in `web/index.html` hochzählen, siehe Workflows).
|
- Asset-Version aktuell **v=151** (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` —
|
||||||
|
|||||||
+46
-1
@@ -1124,15 +1124,27 @@ let sock = null, retry = null;
|
|||||||
// `engine.snapshot_cached().ts`) — nur die jeweils NEUERE Quelle rendert, kein
|
// `engine.snapshot_cached().ts`) — nur die jeweils NEUERE Quelle rendert, kein
|
||||||
// Flackern/Doppel-Render, wenn beide fast gleichzeitig eintreffen.
|
// Flackern/Doppel-Render, wenn beide fast gleichzeitig eintreffen.
|
||||||
let lastTs = 0;
|
let lastTs = 0;
|
||||||
|
// Wann kam zuletzt ein FRISCHER Snapshot an (Wanduhr, egal aus welcher Quelle)?
|
||||||
|
// Grundlage für den 1-s-Fallback und die Stale-Erkennung unten.
|
||||||
|
let lastRecvMs = 0;
|
||||||
|
// Getrennt davon: wann kam zuletzt eine WS-NACHRICHT? Nur diese Uhr entscheidet,
|
||||||
|
// ob der Poll uebersprungen wird (s. Kommentar in `sock.onmessage`).
|
||||||
|
let lastWsMs = 0;
|
||||||
function _applySnapshot(d) {
|
function _applySnapshot(d) {
|
||||||
if (typeof d.ts === "number" && d.ts <= lastTs) return; // schon Neueres gezeigt
|
if (typeof d.ts === "number" && d.ts <= lastTs) return; // schon Neueres gezeigt
|
||||||
if (typeof d.ts === "number") lastTs = d.ts;
|
if (typeof d.ts === "number") lastTs = d.ts;
|
||||||
|
lastRecvMs = Date.now();
|
||||||
render(d);
|
render(d);
|
||||||
}
|
}
|
||||||
function connect() {
|
function connect() {
|
||||||
const proto = location.protocol === "https:" ? "wss" : "ws";
|
const proto = location.protocol === "https:" ? "wss" : "ws";
|
||||||
sock = new WebSocket(`${proto}://${location.host}/ws`);
|
sock = new WebSocket(`${proto}://${location.host}/ws`);
|
||||||
sock.onmessage = (ev) => {
|
sock.onmessage = (ev) => {
|
||||||
|
// ⚠ NUR hier setzen, NICHT in `_applySnapshot`. Der Poll darf die
|
||||||
|
// WS-Frische nicht mitschreiben, sonst drosselt er sich selbst: er würde
|
||||||
|
// nach jedem eigenen Abruf wieder WS_FRISCH_MS pausieren und käme bei totem
|
||||||
|
// WS nur auf ~2,9 s statt 1 s. (In der Simulation aufgefallen, s. unten.)
|
||||||
|
lastWsMs = Date.now();
|
||||||
try { _applySnapshot(JSON.parse(ev.data)); } catch (e) { console.warn(e); }
|
try { _applySnapshot(JSON.parse(ev.data)); } catch (e) { console.warn(e); }
|
||||||
};
|
};
|
||||||
sock.onclose = () => {
|
sock.onclose = () => {
|
||||||
@@ -1149,9 +1161,21 @@ function connect() {
|
|||||||
// mehr — z. B. schwaches WireGuard-Signal, App-Wechsel ohne Visibility-Event),
|
// mehr — z. B. schwaches WireGuard-Signal, App-Wechsel ohne Visibility-Event),
|
||||||
// ohne auf einen Reconnect warten zu müssen. Der `lastTs`-Dedup verhindert
|
// ohne auf einen Reconnect warten zu müssen. Der `lastTs`-Dedup verhindert
|
||||||
// Doppel-Renders, solange der WS normal liefert — reine Redundanz, kein Konflikt.
|
// Doppel-Renders, solange der WS normal liefert — reine Redundanz, kein Konflikt.
|
||||||
const POLL_MS = 4000;
|
// ⚠⚠ 4000 → 1000 (2026-08-07, User: „G/V wird extrem lagged, 1 Sekunde").
|
||||||
|
// GEMESSEN, bevor gedreht wurde: die gesamte Server-Kette liefert bereits 1 s
|
||||||
|
// (`_pos_loop` 1 s · Snapshot-Cache 0,8 s · WS-Push **Median 1,04 s**, direkt am
|
||||||
|
// Socket nachgemessen). Der Lag entsteht also NICHT im Backend, sondern wenn der
|
||||||
|
// WebSocket am Handy still wegbricht (WireGuard-Aussetzer, App-Wechsel): dann
|
||||||
|
// trägt nur noch dieser Poll — und der stand auf 4 s.
|
||||||
|
// ⚠ Das VERDOPPELT den Verkehr NICHT: solange der WS liefert, überspringt der
|
||||||
|
// Poll den Abruf (s. `WS_FRISCH_MS`). Er feuert nur, wenn der WS schweigt.
|
||||||
|
const POLL_MS = 1000;
|
||||||
|
const WS_FRISCH_MS = 2200; // > WS-Takt (1,04 s) + Jitter, sonst pollt es doppelt
|
||||||
async function pollSnapshot() {
|
async function pollSnapshot() {
|
||||||
if (document.hidden) return; // seite unsichtbar → spart Akku/Daten, Wake-Handler holt beim Zurückkommen sofort nach
|
if (document.hidden) return; // seite unsichtbar → spart Akku/Daten, Wake-Handler holt beim Zurückkommen sofort nach
|
||||||
|
// Liefert der WS gerade? Dann NICHT zusätzlich abfragen — reine Redundanz
|
||||||
|
// kostet über WireGuard sonst ~14 KB/s ohne jeden Nutzen.
|
||||||
|
if (Date.now() - lastWsMs < WS_FRISCH_MS) return;
|
||||||
let d;
|
let d;
|
||||||
try {
|
try {
|
||||||
d = await (await fetch("api/snapshot")).json();
|
d = await (await fetch("api/snapshot")).json();
|
||||||
@@ -1166,6 +1190,27 @@ async function pollSnapshot() {
|
|||||||
}
|
}
|
||||||
setInterval(pollSnapshot, POLL_MS);
|
setInterval(pollSnapshot, POLL_MS);
|
||||||
|
|
||||||
|
// ── Ehrlicher Verbindungspunkt (2026-08-07) ────────────────────────────────
|
||||||
|
// ⚠ Bisher wurde er NUR bei `sock.onclose` auf „off" gesetzt. Genau der Fall,
|
||||||
|
// der den Lag verursacht, löst aber KEIN onclose aus: der Socket hängt still
|
||||||
|
// (WireGuard-Aussetzer, App-Wechsel) — der Punkt blieb grün und behauptete eine
|
||||||
|
// Verbindung, die nichts mehr liefert. Ein Zustandsanzeiger, der im
|
||||||
|
// Fehlerfall nicht anschlägt, ist schlimmer als keiner.
|
||||||
|
// Jetzt zählt, was WIRKLICH ankommt: liegt der letzte angewandte Snapshot mehr
|
||||||
|
// als STALE_MS zurück, wird der Punkt matt — unabhängig davon, was der Socket
|
||||||
|
// über sich selbst behauptet.
|
||||||
|
const STALE_MS = 3500;
|
||||||
|
setInterval(() => {
|
||||||
|
const el = $("conn");
|
||||||
|
if (!el) return;
|
||||||
|
const alt = Date.now() - lastRecvMs;
|
||||||
|
el.classList.toggle("stale", lastRecvMs > 0 && alt > STALE_MS);
|
||||||
|
el.title = lastRecvMs
|
||||||
|
? `letzte Aktualisierung vor ${(alt / 1000).toFixed(1)} s` +
|
||||||
|
(alt > STALE_MS ? " — Verbindung hängt, es wird nachgepollt" : "")
|
||||||
|
: "noch keine Daten empfangen";
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
// ── Trade-Steuerung ────────────────────────────────────────────────────────
|
// ── Trade-Steuerung ────────────────────────────────────────────────────────
|
||||||
const TOKEN_KEY = "oil_token";
|
const TOKEN_KEY = "oil_token";
|
||||||
|
|
||||||
|
|||||||
+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=150">
|
<link rel="stylesheet" href="style.css?v=151">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Modul-Menü (ganz oben) -->
|
<!-- Modul-Menü (ganz oben) -->
|
||||||
@@ -330,6 +330,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="toast" class="toast hidden"></div>
|
<div id="toast" class="toast hidden"></div>
|
||||||
|
|
||||||
<script src="app.js?v=150"></script>
|
<script src="app.js?v=151"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ header{
|
|||||||
.dot{width:11px;height:11px;border-radius:50%;display:inline-block}
|
.dot{width:11px;height:11px;border-radius:50%;display:inline-block}
|
||||||
.dot.on{background:var(--up);box-shadow:0 0 8px var(--up)}
|
.dot.on{background:var(--up);box-shadow:0 0 8px var(--up)}
|
||||||
.dot.off{background:var(--down);box-shadow:0 0 8px var(--down)}
|
.dot.off{background:var(--down);box-shadow:0 0 8px var(--down)}
|
||||||
|
/* stale = der Socket behauptet Verbindung, liefert aber nichts mehr (2026-08-07).
|
||||||
|
Bernstein + Pulsieren, damit der Unterschied zu „verbunden" auf einen Blick
|
||||||
|
sichtbar ist — genau dieser Zustand erzeugte den G/V-Lag. Schlägt `.on` und
|
||||||
|
`.off`, deshalb hier NACH beiden. */
|
||||||
|
.dot.stale{background:var(--warn,#f0a020);box-shadow:0 0 8px var(--warn,#f0a020);
|
||||||
|
animation:dotpuls 1.2s ease-in-out infinite}
|
||||||
|
@keyframes dotpuls{0%,100%{opacity:1}50%{opacity:.35}}
|
||||||
.price-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
|
.price-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
|
||||||
/* flex-basis 28% → 3 Boxen je Reihe, die 5. Box (KURS/G/V) bricht sauber um */
|
/* flex-basis 28% → 3 Boxen je Reihe, die 5. Box (KURS/G/V) bricht sauber um */
|
||||||
.px{flex:1 1 28%;background:var(--card);border:1px solid var(--border);
|
.px{flex:1 1 28%;background:var(--card);border:1px solid var(--border);
|
||||||
|
|||||||
Reference in New Issue
Block a user