Verdict: Chartmuster + Orderbuch + Liquiditaets-Trend als Stimmen (v=121)
User-Vorgaben: "beruecksichtige die chartmuster mit in der gesamtempfehlung sobald eins erkannt wird" + "auch der liquiditaetstrend und das orderbuch". MUSTER (Gewicht 1,0 bestaetigt / 0,5 bildet sich) -- vorher den faelligen KONTROLLTEST nachgeholt (stand seit heute im measurement_reminder): Muster H1 +0.206 (Ziel 23%) H2 +0.328 (Ziel 26%) Kontrolle H1 +0.184 (Ziel 19%) H2 +0.220 (Ziel 18%) <- beliebiger Swing-Bruch Delta H1 +0.022 H2 +0.108 Die Muster schlagen die Kontrolle in BEIDEN Haelften, aber der Loewenanteil des Ertrags kommt vom Breakout+Trailing. Deshalb Gewicht 1,0 (wie Elliott/KI), nicht 2,0 (Squeeze). Ziel-Trefferquote bleibt niedrig -> die Stimme sagt Richtungs- tendenz, kein Kursziel. ORDERBUCH-Imbalance + LIQ-TREND (Gewicht je 0,5 = kleinstes im Verdict): ⚠ BEIDE UNGEMESSEN. Die Uebertragbarkeit HL->CFD wird gerade erst erhoben (analyze_bookflow.py, ~3% der Daten). Der Lead-Lag-Test zeigt sogar, dass PEPPERSTONE fuehrt (r=0.525 bei k=-1) und HL folgt -- HL ist Oracle-basierter HIP-3-Perp, strukturell Follower. Daher das kleinste Gewicht. Imbalance-Schwelle _OB_IMB_MIN=0.30, darunter keine Stimme. MITLOGGING: verdict_votes um pattern/pattern_w/orderbook/orderbook_w/liqtrend/ liqtrend_w erweitert (DB migriert, Backup angelegt). Nur so ist in ein paar Wochen datenbasiert entscheidbar, ob die Stimmen bleiben, mehr Gewicht bekommen oder rausfliegen (wie TU 2026-07-06). Frontend: Chips rendern dynamisch (keine Aenderung noetig); NEU wird das GEWICHT im Chip angezeigt und stumme Module (w=0) ausgegraut -- so ist sichtbar, dass Orderbuch/Liq-Trend schwaecher zaehlen als Welle (3,0) oder Squeeze (2,0). Verifiziert live: alle 9 Stimmen korrekt, Bias-Rechnung geprueft (1.5/5.5=0.273), Logging schreibt die neuen Spalten. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
99bed4bdce
commit
7e0d2a4563
+76
-5
@@ -66,6 +66,7 @@ _MIN_CONF_DISPLAY = 70 # Konfidenz-Schwelle für die "Order ohne Sign
|
|||||||
# bis 2026-07-23 auf dem entfernten Dry-Run-Auto-
|
# bis 2026-07-23 auf dem entfernten Dry-Run-Auto-
|
||||||
# Trader beheimatet, hier als eigener Wert erhalten)
|
# Trader beheimatet, hier als eigener Wert erhalten)
|
||||||
_BERLIN = ZoneInfo("Europe/Berlin")
|
_BERLIN = ZoneInfo("Europe/Berlin")
|
||||||
|
_OB_IMB_MIN = 0.30 # Orderbuch-Imbalance darunter = zu schwach fuer eine Stimme
|
||||||
_SQUEEZE_NIGHT = (0, 1, 2, 3, 4, 5, 6, 7) # Auto-Squeeze-Nacht-Sperre (Kostenfalle)
|
_SQUEEZE_NIGHT = (0, 1, 2, 3, 4, 5, 6, 7) # Auto-Squeeze-Nacht-Sperre (Kostenfalle)
|
||||||
_SQUEEZE_SR_COOLDOWN_S = 180.0 # nach S/R-Close kein Auto-Squeeze in GLEICHER Richtung
|
_SQUEEZE_SR_COOLDOWN_S = 180.0 # nach S/R-Close kein Auto-Squeeze in GLEICHER Richtung
|
||||||
# (sonst Close-„Level hält" vs. Entry-„Level bricht"-
|
# (sonst Close-„Level hält" vs. Entry-„Level bricht"-
|
||||||
@@ -1305,7 +1306,8 @@ class TradingEngine:
|
|||||||
# py, 2026-07-24): Copilot nicht robust prädiktiv, n aber klein → sammeln.
|
# py, 2026-07-24): Copilot nicht robust prädiktiv, n aber klein → sammeln.
|
||||||
try:
|
try:
|
||||||
vd = self._verdict(s, rec, self.wave.snapshot(), self.tu.snapshot(),
|
vd = self._verdict(s, rec, self.wave.snapshot(), self.tu.snapshot(),
|
||||||
self.agent.snapshot(), self.elliott.snapshot())
|
self.agent.snapshot(), self.elliott.snapshot(),
|
||||||
|
self.patterns.snapshot())
|
||||||
vv = {v["name"]: v for v in vd.get("votes") or []}
|
vv = {v["name"]: v for v in vd.get("votes") or []}
|
||||||
def _v(name, key="vote"):
|
def _v(name, key="vote"):
|
||||||
return (vv.get(name) or {}).get(key, 0)
|
return (vv.get(name) or {}).get(key, 0)
|
||||||
@@ -1315,7 +1317,16 @@ class TradingEngine:
|
|||||||
tf=vd.get("tf") or "", wave=_v("Welle"), m30=_v("M30"), h1=_v("H1"),
|
tf=vd.get("tf") or "", wave=_v("Welle"), m30=_v("M30"), h1=_v("H1"),
|
||||||
ki=_v("KI-Copilot"), ki_w=float(_v("KI-Copilot", "weight") or 0.0),
|
ki=_v("KI-Copilot"), ki_w=float(_v("KI-Copilot", "weight") or 0.0),
|
||||||
elliott=_v("Elliott"), elliott_w=float(_v("Elliott", "weight") or 0.0),
|
elliott=_v("Elliott"), elliott_w=float(_v("Elliott", "weight") or 0.0),
|
||||||
squeeze=_v("Squeeze"), news_score=_news)
|
squeeze=_v("Squeeze"), news_score=_news,
|
||||||
|
# Die drei ab 2026-07-30 ergänzten Stimmen MITLOGGEN — nur so ist in
|
||||||
|
# ein paar Wochen datenbasiert entscheidbar, ob sie bleiben (Muster:
|
||||||
|
# Kontrolltest zeigt kleinen Zusatz) bzw. ob die UNGEMESSENEN
|
||||||
|
# Orderbuch-/Liq-Trend-Stimmen überhaupt Prädiktivität haben.
|
||||||
|
pattern=_v("Muster"), pattern_w=float(_v("Muster", "weight") or 0.0),
|
||||||
|
orderbook=_v("Orderbuch"),
|
||||||
|
orderbook_w=float(_v("Orderbuch", "weight") or 0.0),
|
||||||
|
liqtrend=_v("Liq-Trend"),
|
||||||
|
liqtrend_w=float(_v("Liq-Trend", "weight") or 0.0))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(f"log_verdict_votes: {e}")
|
log.debug(f"log_verdict_votes: {e}")
|
||||||
|
|
||||||
@@ -2094,7 +2105,8 @@ class TradingEngine:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(f"write_levels: {e}")
|
log.debug(f"write_levels: {e}")
|
||||||
|
|
||||||
def _verdict(self, market, wave_signal, wave_snap, tu, agent, elliott) -> dict:
|
def _verdict(self, market, wave_signal, wave_snap, tu, agent, elliott,
|
||||||
|
snap_parts_patterns=None) -> dict:
|
||||||
"""Gesamtempfehlung aus den Modulen für die Widget-Grafik.
|
"""Gesamtempfehlung aus den Modulen für die Widget-Grafik.
|
||||||
|
|
||||||
Headline = die **validierte Welle** (einziges Modul mit belegtem Edge);
|
Headline = die **validierte Welle** (einziges Modul mit belegtem Edge);
|
||||||
@@ -2161,6 +2173,63 @@ class TradingEngine:
|
|||||||
add("Squeeze", sqv, 2.0 if sq_active else 0.0,
|
add("Squeeze", sqv, 2.0 if sq_active else 0.0,
|
||||||
f"{sq.get('state') or '—'}" + (f" {sq.get('dir')}" if sq.get("dir") else ""))
|
f"{sq.get('state') or '—'}" + (f" {sq.get('dir')}" if sq.get("dir") else ""))
|
||||||
|
|
||||||
|
# 8) Chartmuster — User-Vorgabe 2026-07-30 („berücksichtige die Chartmuster
|
||||||
|
# in der Gesamtempfehlung, sobald eins erkannt wird"). Gewicht bewusst KLEIN:
|
||||||
|
# der fällige KONTROLLTEST (2026-07-30) zeigt, dass die Muster einen zwar in
|
||||||
|
# BEIDEN Hälften positiven, aber kleinen Zusatz über einen GENERISCHEN
|
||||||
|
# Breakout hinaus liefern — Muster ØR +0,206/+0,328 vs. beliebiger Swing-Bruch
|
||||||
|
# +0,184/+0,220 (Delta nur +0,022 in H1, +0,108 in H2). Der Löwenanteil des
|
||||||
|
# Ertrags kommt also vom Breakout+Trailing, nicht vom Muster. Deshalb 1,0
|
||||||
|
# (wie Elliott/KI) statt 2,0 (Squeeze) — und `forming` zählt nur halb, weil
|
||||||
|
# das Muster dort noch nicht bestätigt ist. Ziel-Trefferquote bleibt niedrig
|
||||||
|
# (23–26 %), die Stimme sagt also „Richtungstendenz", nicht „Kursziel".
|
||||||
|
pv, pdet, pw = 0, "—", 0.0
|
||||||
|
try:
|
||||||
|
_pats = (snap_parts_patterns or {}).get("patterns") or []
|
||||||
|
_p0 = next((m for m in _pats if m.get("dir") in ("bull", "bear")), None)
|
||||||
|
if _p0:
|
||||||
|
pv = 1 if _p0["dir"] == "bull" else -1
|
||||||
|
_conf = _p0.get("status") == "confirmed"
|
||||||
|
pw = 1.0 if _conf else 0.5
|
||||||
|
pdet = f"{_p0.get('name', '—')} · {'bestätigt' if _conf else 'bildet sich'}"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
add("Muster", pv, pw, pdet)
|
||||||
|
|
||||||
|
# 9/10) ORDERBUCH-Imbalance + LIQUIDITÄTS-TREND (User-Vorgabe 2026-07-30).
|
||||||
|
# ⚠ BEIDE sind UNGEMESSEN — anders als alle anderen Stimmen. Die Übertragbar-
|
||||||
|
# keit von Hyperliquid-Orderbuchdaten auf den Pepperstone-CFD wird gerade
|
||||||
|
# erst erhoben (`analyze_bookflow.py`, ab ~50k Snapshots ≈ 3 Tage erster Blick).
|
||||||
|
# ⚠ Der Lead-Lag-Test (2026-07-30, 72 h) zeigt sogar, dass **Pepperstone
|
||||||
|
# FÜHRT** (Peak bei k=−1, r=0,525) und HL folgt — HL ist ein Oracle-basierter
|
||||||
|
# HIP-3-Perp, also strukturell Follower. Das dämpft die Erwartung erheblich.
|
||||||
|
# Deshalb bewusst das KLEINSTE Gewicht im ganzen Verdict (0,5 je Stimme;
|
||||||
|
# zum Vergleich: Welle 3,0 · Squeeze 2,0 · Muster/Elliott/KI 1,0).
|
||||||
|
# Beide werden in `verdict_votes` mitgeloggt → in ein paar Wochen ist
|
||||||
|
# datenbasiert entscheidbar, ob sie bleiben, mehr Gewicht bekommen oder
|
||||||
|
# rausfliegen (wie TU 2026-07-06).
|
||||||
|
ov, odet, ow = 0, "—", 0.0
|
||||||
|
lv, ldet, lw = 0, "—", 0.0
|
||||||
|
try:
|
||||||
|
w = self.hl_walls.walls()
|
||||||
|
if w:
|
||||||
|
imb = w.get("imb5")
|
||||||
|
if isinstance(imb, (int, float)) and abs(imb) >= _OB_IMB_MIN:
|
||||||
|
ov = 1 if imb > 0 else -1
|
||||||
|
ow = 0.5
|
||||||
|
odet = f"Imbalance {imb:+.2f}"
|
||||||
|
elif isinstance(imb, (int, float)):
|
||||||
|
odet = f"Imbalance {imb:+.2f} (zu schwach)"
|
||||||
|
tr = w.get("trend") or {}
|
||||||
|
if tr.get("dir") in ("up", "down"):
|
||||||
|
lv = 1 if tr["dir"] == "up" else -1
|
||||||
|
lw = 0.5
|
||||||
|
ldet = f"{tr['dir']} {tr.get('slope_per_h', 0):+.2f}$/h"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
add("Orderbuch", ov, ow, odet)
|
||||||
|
add("Liq-Trend", lv, lw, ldet)
|
||||||
|
|
||||||
# Bias nur über stimmberechtigte Module (weight>0); agree/total = wie viele
|
# Bias nur über stimmberechtigte Module (weight>0); agree/total = wie viele
|
||||||
# der ANDEREN stimmberechtigten Module die Wellen-Richtung teilen (die Welle
|
# der ANDEREN stimmberechtigten Module die Wellen-Richtung teilen (die Welle
|
||||||
# zählte sich vorher selbst mit — „3/6 für LONG" war geschönt).
|
# zählte sich vorher selbst mit — „3/6 für LONG" war geschönt).
|
||||||
@@ -2548,6 +2617,8 @@ class TradingEngine:
|
|||||||
}
|
}
|
||||||
sr_close_hint = self._sr_close_hint(market, position, wave_snap)
|
sr_close_hint = self._sr_close_hint(market, position, wave_snap)
|
||||||
stop_approach = self._stop_approach_hint(market, position, wave_snap)
|
stop_approach = self._stop_approach_hint(market, position, wave_snap)
|
||||||
|
# einmal holen — Verdict-Stimme UND Snapshot-Feld nutzen dasselbe Objekt
|
||||||
|
_pat_snap = self.patterns.snapshot()
|
||||||
snap = {
|
snap = {
|
||||||
"ts": time.time(),
|
"ts": time.time(),
|
||||||
"connected": self._connected,
|
"connected": self._connected,
|
||||||
@@ -2580,11 +2651,11 @@ class TradingEngine:
|
|||||||
"tu": tu,
|
"tu": tu,
|
||||||
"agent": agent,
|
"agent": agent,
|
||||||
"verdict": self._verdict(market, wave_signal, wave_snap,
|
"verdict": self._verdict(market, wave_signal, wave_snap,
|
||||||
tu, agent, elliott),
|
tu, agent, elliott, _pat_snap),
|
||||||
"daily_levels": self.daily.snapshot(),
|
"daily_levels": self.daily.snapshot(),
|
||||||
"gaps": self.gaps.snapshot(),
|
"gaps": self.gaps.snapshot(),
|
||||||
"structure": self.structure.snapshot(),
|
"structure": self.structure.snapshot(),
|
||||||
"patterns": self.patterns.snapshot(),
|
"patterns": _pat_snap,
|
||||||
"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,
|
||||||
|
|||||||
+16
-5
@@ -154,7 +154,14 @@ SCHEMA = [
|
|||||||
elliott INTEGER,
|
elliott INTEGER,
|
||||||
elliott_w REAL,
|
elliott_w REAL,
|
||||||
squeeze INTEGER,
|
squeeze INTEGER,
|
||||||
news_score REAL -- News-Sentiment zum Zeitpunkt (Kontext)
|
news_score REAL, -- News-Sentiment zum Zeitpunkt (Kontext)
|
||||||
|
-- ab 2026-07-30 (User-Vorgabe, Stimmen mit KLEINEM Gewicht):
|
||||||
|
pattern INTEGER, -- Chartmuster-Vote (Kontrolltest: kleiner Zusatz)
|
||||||
|
pattern_w REAL, -- 1,0 bestaetigt / 0,5 bildet sich / 0 keins
|
||||||
|
orderbook INTEGER, -- HL-Orderbuch-Imbalance (UNGEMESSEN)
|
||||||
|
orderbook_w REAL,
|
||||||
|
liqtrend INTEGER, -- Liquiditaets-Trend (UNGEMESSEN)
|
||||||
|
liqtrend_w REAL
|
||||||
)
|
)
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
@@ -417,7 +424,9 @@ class HistoryLogger:
|
|||||||
def log_verdict_votes(self, *, headline: str, conf: int, bias: float, tf: str,
|
def log_verdict_votes(self, *, headline: str, conf: int, bias: float, tf: str,
|
||||||
wave: int, m30: int, h1: int, ki: int, ki_w: float,
|
wave: int, m30: int, h1: int, ki: int, ki_w: float,
|
||||||
elliott: int, elliott_w: float, squeeze: int,
|
elliott: int, elliott_w: float, squeeze: int,
|
||||||
news_score=None) -> None:
|
news_score=None, pattern: int = 0, pattern_w: float = 0.0,
|
||||||
|
orderbook: int = 0, orderbook_w: float = 0.0,
|
||||||
|
liqtrend: int = 0, liqtrend_w: float = 0.0) -> None:
|
||||||
"""Verdict-Modul-Stimmen für die spätere Kalibrier-Auswertung loggen
|
"""Verdict-Modul-Stimmen für die spätere Kalibrier-Auswertung loggen
|
||||||
(Copilot/Elliott sind unbelegte Stimmen — nach ein paar Wochen kann
|
(Copilot/Elliott sind unbelegte Stimmen — nach ein paar Wochen kann
|
||||||
`analyze_verdict_calibration.py`-artig entschieden werden, ob sie
|
`analyze_verdict_calibration.py`-artig entschieden werden, ob sie
|
||||||
@@ -430,10 +439,12 @@ class HistoryLogger:
|
|||||||
conn.execute("""
|
conn.execute("""
|
||||||
INSERT INTO verdict_votes
|
INSERT INTO verdict_votes
|
||||||
(ts, headline, conf, bias, tf, wave, m30, h1, ki, ki_w,
|
(ts, headline, conf, bias, tf, wave, m30, h1, ki, ki_w,
|
||||||
elliott, elliott_w, squeeze, news_score)
|
elliott, elliott_w, squeeze, news_score,
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
pattern, pattern_w, orderbook, orderbook_w, liqtrend, liqtrend_w)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
""", (int(now), headline, conf, bias, tf, wave, m30, h1, ki, ki_w,
|
""", (int(now), headline, conf, bias, tf, wave, m30, h1, ki, ki_w,
|
||||||
elliott, elliott_w, squeeze, news_score))
|
elliott, elliott_w, squeeze, news_score,
|
||||||
|
pattern, pattern_w, orderbook, orderbook_w, liqtrend, liqtrend_w))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
def alignment_stats(self, last_n: int = 40) -> dict:
|
def alignment_stats(self, last_n: int = 40) -> dict:
|
||||||
|
|||||||
+8
-2
@@ -131,8 +131,14 @@ function renderVerdict(v, snap) {
|
|||||||
const vote = (x) => x > 0 ? ["long", "▲"] : x < 0 ? ["short", "▼"] : ["flat", "—"];
|
const vote = (x) => x > 0 ? ["long", "▲"] : x < 0 ? ["short", "▼"] : ["flat", "—"];
|
||||||
$("vd-modules").innerHTML = (v.votes || []).map(mod => {
|
$("vd-modules").innerHTML = (v.votes || []).map(mod => {
|
||||||
const [c, a] = vote(mod.vote);
|
const [c, a] = vote(mod.vote);
|
||||||
return `<div class="vd-mod ${c}"><div class="m-top">` +
|
// Gewicht sichtbar machen: Module OHNE Aussage (w=0) grau, sonst das Gewicht
|
||||||
`<span class="m-name">${mod.name}</span>` +
|
// anzeigen — so ist erkennbar, dass z. B. Orderbuch/Liq-Trend (0,5, ungemessen)
|
||||||
|
// schwächer zählen als die Welle (3,0) oder der Squeeze (2,0).
|
||||||
|
const w = Number(mod.weight || 0);
|
||||||
|
const cls = w > 0 ? c : "flat mute";
|
||||||
|
const wtag = w > 0 ? `<span class="m-w">${w.toFixed(1).replace(".0", "")}</span>` : "";
|
||||||
|
return `<div class="vd-mod ${cls}"><div class="m-top">` +
|
||||||
|
`<span class="m-name">${mod.name}</span>${wtag}` +
|
||||||
`<span class="m-vote">${a}</span></div>` +
|
`<span class="m-vote">${a}</span></div>` +
|
||||||
`<div class="m-det">${mod.detail || ""}</div></div>`;
|
`<div class="m-det">${mod.detail || ""}</div></div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
|||||||
+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=120">
|
<link rel="stylesheet" href="style.css?v=121">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Modul-Menü (ganz oben) -->
|
<!-- Modul-Menü (ganz oben) -->
|
||||||
@@ -229,6 +229,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=120"></script>
|
<script src="app.js?v=121"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-1
@@ -186,7 +186,9 @@ main{display:grid;grid-template-columns:1fr 1fr;gap:11px;padding:11px}
|
|||||||
.vd-mod{background:var(--bg);border:1px solid var(--border);border-radius:7px;
|
.vd-mod{background:var(--bg);border:1px solid var(--border);border-radius:7px;
|
||||||
padding:6px 7px}
|
padding:6px 7px}
|
||||||
.vd-mod .m-top{display:flex;justify-content:space-between;align-items:center;gap:4px}
|
.vd-mod .m-top{display:flex;justify-content:space-between;align-items:center;gap:4px}
|
||||||
.vd-mod .m-name{font-size:14px;color:var(--dim);font-weight:600}
|
.vd-mod .vd-mod.mute{opacity:.45} /* Modul ohne Aussage (Gewicht 0) */
|
||||||
|
.m-w{font-size:11px;color:var(--dim);font-family:var(--mono);margin-left:4px}
|
||||||
|
.m-name{font-size:14px;color:var(--dim);font-weight:600}
|
||||||
.vd-mod .m-vote{font-size:17px;font-weight:800;color:var(--dim)}
|
.vd-mod .m-vote{font-size:17px;font-weight:800;color:var(--dim)}
|
||||||
.vd-mod .m-det{font-size:13px;color:var(--dim);margin-top:2px;
|
.vd-mod .m-det{font-size:13px;color:var(--dim);margin-top:2px;
|
||||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
|||||||
Reference in New Issue
Block a user