Sizing auf der echten Historie gerechnet (analyze_sizing_live.py)
Folge der S/R-Widerlegung: der Hebel zeigt auf die GROESSE. Pfad-Simulation ueber 1.278 geschlossene Trades (08.05.-13.08.), pnl/lots als groessenunabhaengiges Ergebnis, Lots aus der simulierten Equity abgeleitet. Zwei dokumentierte Zahlen stimmten nicht mehr und wurden aus den Daten neu geschaetzt: EUR je $ und Lot = 86,90 (notiert 87,6), Margin je Lot ~405 EUR (notiert 670). DER IST-PFAD IST DIE BOTSCHAFT: Start 1.115,70 · Hoch 2.724,16 (05.08.) · Tief 261,36 (30.07.) · Ende 1.279,04 Max-Drawdown 82,8 % · schlechtester Einzeltrade -381,16 EUR 42 von 71 Tagen negativ; getragen von wenigen Tagen (04.08. +1.297). Das Konto hat zweimal den Grossteil seines Werts verloren. Fuer unbeaufsichtigten Betrieb ist das die entscheidende Zahl, nicht der Endstand. VARIANTEN (Ende / MaxDD / schlechtester Trade): IST 1.279 / 82,8 % / -381 Lot-Deckel 1,5 1.362 / 82,8 % / -345 -> bringt fast nichts Risiko 1,5 %/Trade 680 / 54,4 % / -77 Risiko 0,5 %/Trade 960 / 22,2 % / -27 IST + Breaker 8 %/Tag 2.018 / 59,2 % / -381 -> verbessert BEIDES ABER: ein Tag traegt 70 % des Breaker-Vorteils (07.08. +556,82 von +800,19), und an drei Tagen kostet er (-106,85 / -96,00 / -50,77). Ohne den 07.08. bleiben +243 EUR ueber 10 Tage. Das ist kein Makel der Messung, sondern das Wesen einer Versicherung -- ihr Wert sitzt im Tail. Die +739 sind eine Realisierung, keine Erwartung. GRENZEN: die Sim sperrt nach dem Ausloesen ALLE Trades des Tages; live sperrt der Breaker nur die autonomen. Bei einem zu 94 % manuell gehandelten Konto misst sie damit eher eine Disziplin-Regel -- fuer das Ziel Autotrading ist sie dagegen das richtige Modell. Confounder bei Risiko-Sizing: laeuft auf 1.118 von 1.278 Trades (nur die mit sl_at_entry). Nichts am Live-System geaendert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1e8d700f46
commit
02bec8fbb2
@@ -5617,6 +5617,64 @@ Berührungen schliesst. Live prüft den **laufenden** Kurs im Sekundentakt; das
|
|||||||
Analogon ist die **Bar-Spanne**. Danach feuert er in 21–23 % der Trades (live
|
Analogon ist die **Bar-Spanne**. Danach feuert er in 21–23 % der Trades (live
|
||||||
4 von 11 = 36 %, bei n=11 kein Widerspruch).
|
4 von 11 = 36 %, bei n=11 kein Widerspruch).
|
||||||
|
|
||||||
|
### ⚠⚠ SIZING AUF DER ECHTEN HISTORIE GERECHNET (`analyze_sizing_live.py`, 2026-08-13)
|
||||||
|
|
||||||
|
Folge der S/R-Widerlegung: der Hebel zeigt auf die GRÖSSE. Pfad-Simulation über
|
||||||
|
**1.278 geschlossene Trades (08.05.–13.08.)**, `pnl/lots` als größenunabhängiges
|
||||||
|
Ergebnis, Lots aus der jeweils simulierten Equity abgeleitet.
|
||||||
|
⚠ Zwei dokumentierte Zahlen stimmen nicht mehr und wurden **aus den Daten neu
|
||||||
|
geschätzt**: EUR je $ und Lot = **86,90** (notiert 87,6) und Margin je Lot
|
||||||
|
**~405 €** (notiert 670 — bei 1.279 € Balance und ~3 Lots).
|
||||||
|
|
||||||
|
**(1) DER IST-PFAD IST DIE EIGENTLICHE BOTSCHAFT:**
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Start (rekonstruiert) | 1.115,70 € |
|
||||||
|
| **Hoch** | **2.724,16 €** (05.08.) |
|
||||||
|
| **Tief** | **261,36 €** (30.07.) |
|
||||||
|
| **Max-Drawdown** | **82,8 %** |
|
||||||
|
| schlechtester Einzeltrade | **−381,16 €** |
|
||||||
|
| Ende | 1.279,04 € |
|
||||||
|
⚠⚠ Das Konto hat **zweimal** den Großteil seines Werts verloren — −82,8 % bis
|
||||||
|
zum 30.07., danach vom Hoch am 05.08. nochmals **−53 %**. **42 von 71 Tagen sind
|
||||||
|
negativ**; getragen wird alles von wenigen Tagen (04.08. **+1.297 €**). Für
|
||||||
|
**unbeaufsichtigten** Betrieb ist das die entscheidende Zahl, nicht der Endstand.
|
||||||
|
|
||||||
|
**(2) VARIANTEN** (Ende / MaxDD / schlechtester Trade):
|
||||||
|
| Regel | Ende | MaxDD | schlecht. Trade |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **IST** | 1.279 € | **82,8 %** | **−381 €** |
|
||||||
|
| Lot-Deckel 1,5 | 1.362 € | 82,8 % | −345 € |
|
||||||
|
| Risiko 1,5 %/Trade | 680 € | **54,4 %** | **−77 €** |
|
||||||
|
| Risiko 0,5 %/Trade | 960 € | **22,2 %** | **−27 €** |
|
||||||
|
| **IST + Breaker 8 %/Tag** | **2.018 €** | **59,2 %** | −381 € |
|
||||||
|
| IST + Breaker 5 %/Tag | 1.976 € | 65,5 % | −381 € |
|
||||||
|
✅ **Lot-Deckel bringen fast nichts** — die realen Lots lagen meist darunter.
|
||||||
|
✅ **Risiko-Sizing drittelt den schlechtesten Trade und halbiert den Drawdown**,
|
||||||
|
kostet aber Endstand. ⚠ **Confounder: die Risiko-Varianten laufen auf 1.118 der
|
||||||
|
1.278 Trades** (nur die mit `sl_at_entry`) — nicht direkt vergleichbar.
|
||||||
|
⚠⚠ **Der Breaker verbessert BEIDES** (Ende +739 €, DD −23,6 Pp) — aber:
|
||||||
|
|
||||||
|
**(3) ⚠⚠ EIN TAG TRÄGT 70 % DES BREAKER-VORTEILS.** Vermiedene Beträge je
|
||||||
|
Auslösetag: **07.08. +556,82** · 30.07. +200,48 · 24.07. +106,61 · 28.07. +81,24
|
||||||
|
· 23.07. +63,23 · Rest klein — und an **drei Tagen kostet er**: 11.08. −106,85 ·
|
||||||
|
29.07. −96,00 · 27.07. −50,77. Summe +800,19 € über 11 Auslösetage.
|
||||||
|
⚠ Ohne den 07.08. bleiben **+243 €** über 10 Tage. **Das ist kein Makel der
|
||||||
|
Messung, sondern das Wesen einer Versicherung** — ihr Wert sitzt im Tail. Aber
|
||||||
|
die +739 € sind eine **Realisierung**, keine Erwartung.
|
||||||
|
⚠⚠ **UND EINE GRENZE, die für HEUTE entscheidend ist:** die Sim sperrt nach dem
|
||||||
|
Auslösen **alle** Trades des Tages. Live sperrt `_check_circuit_breaker` nur die
|
||||||
|
**autonomen** (`_check_auto_squeeze`), manuelle Orders bleiben frei. Bei einem zu
|
||||||
|
94 % manuell gehandelten Konto misst die Sim damit eher eine **Disziplin-Regel**
|
||||||
|
als das heutige Code-Verhalten. **Für das Ziel Autotrading ist sie dagegen das
|
||||||
|
richtige Modell** — dort greift die Sperre wirklich.
|
||||||
|
⚠ Weitere Grenzen: EIN Pfad ist keine Verteilung (für die Ruin-Frage bleibt
|
||||||
|
`backtest_sizing.py` das Werkzeug: Margin-Sizing 36 % P(DD>80 %) — was der
|
||||||
|
gemessene IST-Pfad mit 82,8 % **bestätigt**); die Sim unterstellt dieselben
|
||||||
|
Trades bei anderer Größe, was bei **manuellen** Trades eine starke Annahme ist;
|
||||||
|
und der Breaker wird auf **realisierte** Tagesverluste geprüft, während er live
|
||||||
|
auch die offene Position kappt → die Sim **unterschätzt** ihn.
|
||||||
|
|
||||||
### ✅ S/R-AUTO-CLOSE WIEDER AN (User-Entscheidung 2026-08-11)
|
### ✅ S/R-AUTO-CLOSE WIEDER AN (User-Entscheidung 2026-08-11)
|
||||||
|
|
||||||
Frage war: „sollten wir Trades bei einem S/R-Bounce automatisch schliessen?"
|
Frage war: „sollten wir Trades bei einem S/R-Bounce automatisch schliessen?"
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Was haetten Lot-Deckel, Risiko-Sizing und Circuit-Breaker WIRKLICH veraendert?
|
||||||
|
|
||||||
|
ANLASS (2026-08-13). Die Squeeze-Zerlegung hat gezeigt: der Verlust steckt nicht
|
||||||
|
im Exit (der S/R-Close trug +15,98 EUR bei), sondern in der GROESSE —
|
||||||
|
-175,21 EUR bei 2,91 Lots aus einer 0,695-$-Bewegung, also dem planmaessigen
|
||||||
|
2xATR-Stop. Ø-Verlust 94 EUR auf 1.279 EUR Konto = 7,4 % je Verlusttrade.
|
||||||
|
Diese Rechnung prueft das auf der ECHTEN Handelshistorie statt an einem Modell.
|
||||||
|
|
||||||
|
METHODE — Pfad-Simulation auf realisierten Ergebnissen:
|
||||||
|
· Je Trade ist `pnl / lots` das GROESSENUNABHAENGIGE Ergebnis (der Kursverlauf).
|
||||||
|
Bei einem Retail-CFD ist das sauber: die eigene Order bewegt den Markt nicht,
|
||||||
|
und der SL-Abstand kommt aus dem ATR, nicht aus der Lotzahl.
|
||||||
|
· Unter Margin-Sizing skaliert die Lotzahl mit dem Konto. Die Sim fuehrt
|
||||||
|
deshalb ein eigenes Equity-Konto und leitet die Groesse daraus ab —
|
||||||
|
`lots_sim = lots_ist x (equity_sim / equity_ist)`. Damit braucht es KEINE
|
||||||
|
Annahme ueber die Margin je Lot (die dokumentierten 670 EUR/Lot stimmen
|
||||||
|
ohnehin nicht mehr: bei 1.279 EUR und ~3 Lots sind es ~405).
|
||||||
|
· Risiko-Sizing nutzt `sl_at_entry`: Risiko je Lot = |entry - sl| x EUR/$/Lot.
|
||||||
|
Der EUR-Faktor wird aus den Daten geschaetzt, nicht geglaubt.
|
||||||
|
|
||||||
|
⚠⚠ WAS DIESE RECHNUNG NICHT KANN:
|
||||||
|
(1) Sie unterstellt, dass DIESELBEN Trades mit denselben Kursverlaeufen
|
||||||
|
zustande kommen. Bei den MANUELLEN Trades ist das eine starke Annahme —
|
||||||
|
ein Mensch handelt bei anderer Groesse womoeglich anders.
|
||||||
|
(2) Der Circuit-Breaker wird auf REALISIERTE Tagesverluste geprueft. Live
|
||||||
|
greift er auch auf die offene Position; er wuerde den ausloesenden Trade
|
||||||
|
also FRUEHER kappen. Die Sim laesst ihn auslaufen -> sie UNTERSCHAETZT
|
||||||
|
den Nutzen des Breakers. Das faellt zu Gunsten der Nullhypothese aus.
|
||||||
|
(3) Vergangene Pfade sind keine Verteilung. Max-Drawdown auf EINEM Pfad ist
|
||||||
|
eine Beobachtung, keine Wahrscheinlichkeit — `backtest_sizing.py` ist fuer
|
||||||
|
die Ruin-Frage das richtige Werkzeug (dort: Margin-Sizing 36 % P(DD>80 %),
|
||||||
|
1-2 % Risiko 0 %).
|
||||||
|
|
||||||
|
Aufruf: python analyze_sizing_live.py [--tage N]
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime as dt
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
DB = "oil_widget_history.db"
|
||||||
|
_BERLIN = ZoneInfo("Europe/Berlin")
|
||||||
|
|
||||||
|
|
||||||
|
def eur_pro_dollar_lot(rows) -> float:
|
||||||
|
"""EUR je 1,00 $ Kursbewegung und Lot — robust aus den Daten geschaetzt."""
|
||||||
|
f = []
|
||||||
|
for r in rows:
|
||||||
|
if not (r["entry_price"] and r["exit_price"] and r["lots"]):
|
||||||
|
continue
|
||||||
|
d = 1 if r["direction"] == "BUY" else -1
|
||||||
|
mv = (r["exit_price"] - r["entry_price"]) * d
|
||||||
|
if abs(mv) > 0.05: # kleine Bewegungen: Swap dominiert
|
||||||
|
f.append(r["pnl"] / (mv * r["lots"]))
|
||||||
|
f.sort()
|
||||||
|
return f[len(f) // 2] if f else 87.0
|
||||||
|
|
||||||
|
|
||||||
|
def lade(tage: int | None):
|
||||||
|
db = sqlite3.connect(DB); db.row_factory = sqlite3.Row
|
||||||
|
w = ""
|
||||||
|
if tage:
|
||||||
|
w = f" AND exit_time >= strftime('%s','now') - {tage*86400}"
|
||||||
|
rows = [dict(r) for r in db.execute(
|
||||||
|
"SELECT entry_time,exit_time,direction,lots,entry_price,exit_price,pnl,"
|
||||||
|
"closed_by,setup,sl_at_entry FROM trades WHERE exit_time IS NOT NULL "
|
||||||
|
f"AND pnl IS NOT NULL AND lots > 0{w} ORDER BY exit_time")]
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def simuliere(rows, start, regel, deckel=None, breaker=None, eurfak=87.0,
|
||||||
|
equity_ist=None):
|
||||||
|
"""Ein Equity-Pfad unter EINER Sizing-Regel.
|
||||||
|
|
||||||
|
regel: 'ist' | 'margin' | ('risiko', pct)
|
||||||
|
deckel: max. Lots oder None · breaker: Tagesverlust-Grenze in % oder None
|
||||||
|
"""
|
||||||
|
eq = start
|
||||||
|
minimum = start
|
||||||
|
peak = start
|
||||||
|
maxdd = 0.0
|
||||||
|
worst = 0.0
|
||||||
|
tag_akt, tag_pnl, tag_start, gesperrt = None, 0.0, start, False
|
||||||
|
n_block, n_trades = 0, 0
|
||||||
|
tage_getroffen = set()
|
||||||
|
|
||||||
|
for idx, r in enumerate(rows):
|
||||||
|
tag = dt.datetime.fromtimestamp(r["exit_time"], _BERLIN).date()
|
||||||
|
if tag != tag_akt:
|
||||||
|
tag_akt, tag_pnl, tag_start, gesperrt = tag, 0.0, eq, False
|
||||||
|
if gesperrt:
|
||||||
|
n_block += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
je_lot = r["pnl"] / r["lots"] # groessenunabhaengig
|
||||||
|
if regel == "ist":
|
||||||
|
lots = r["lots"]
|
||||||
|
elif regel == "margin":
|
||||||
|
# Margin-Sizing skaliert linear mit dem Konto
|
||||||
|
lots = r["lots"] * (eq / equity_ist[idx]) if equity_ist[idx] > 0 else 0.0
|
||||||
|
else:
|
||||||
|
pct = regel[1]
|
||||||
|
if not r["sl_at_entry"] or not r["entry_price"]:
|
||||||
|
continue # ohne SL kein Risiko-Sizing
|
||||||
|
dist = abs(r["entry_price"] - r["sl_at_entry"])
|
||||||
|
risiko_lot = dist * eurfak
|
||||||
|
if risiko_lot <= 0:
|
||||||
|
continue
|
||||||
|
lots = (eq * pct / 100.0) / risiko_lot
|
||||||
|
if deckel is not None:
|
||||||
|
lots = min(lots, deckel)
|
||||||
|
if lots <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
pnl = je_lot * lots
|
||||||
|
eq += pnl
|
||||||
|
tag_pnl += pnl
|
||||||
|
n_trades += 1
|
||||||
|
worst = min(worst, pnl)
|
||||||
|
peak = max(peak, eq)
|
||||||
|
minimum = min(minimum, eq)
|
||||||
|
if peak > 0:
|
||||||
|
maxdd = max(maxdd, 100 * (peak - eq) / peak)
|
||||||
|
if breaker is not None and tag_start > 0 and tag_pnl <= -breaker / 100 * tag_start:
|
||||||
|
gesperrt = True
|
||||||
|
tage_getroffen.add(tag)
|
||||||
|
if eq <= 0:
|
||||||
|
return {"eq": 0.0, "maxdd": 100.0, "worst": worst, "n": n_trades,
|
||||||
|
"block": n_block, "bt": len(tage_getroffen), "ruin": True}
|
||||||
|
return {"eq": eq, "maxdd": maxdd, "worst": worst, "n": n_trades,
|
||||||
|
"block": n_block, "bt": len(tage_getroffen), "ruin": False}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--tage", type=int, default=None)
|
||||||
|
a = ap.parse_args()
|
||||||
|
rows = lade(a.tage)
|
||||||
|
if not rows:
|
||||||
|
print(" keine Trades"); return 1
|
||||||
|
eurfak = eur_pro_dollar_lot(rows)
|
||||||
|
ende = 1279.04 # aktuelle Balance (Snapshot)
|
||||||
|
start = ende - sum(r["pnl"] for r in rows)
|
||||||
|
# Equity-Pfad, wie er TATSAECHLICH lief — Bezug fuer die Margin-Skalierung
|
||||||
|
eq_ist, e = [], start
|
||||||
|
for r in rows:
|
||||||
|
eq_ist.append(e); e += r["pnl"]
|
||||||
|
|
||||||
|
mit_sl = sum(1 for r in rows if r["sl_at_entry"])
|
||||||
|
f = lambda t: dt.datetime.fromtimestamp(t).strftime("%d.%m.%Y")
|
||||||
|
print(f"\n {len(rows)} geschlossene Trades · {f(rows[0]['exit_time'])} bis "
|
||||||
|
f"{f(rows[-1]['exit_time'])}")
|
||||||
|
print(f" Startkapital rekonstruiert {start:.2f} EUR -> IST-Ende {ende:.2f} EUR")
|
||||||
|
print(f" EUR je $ und Lot (aus den Daten): {eurfak:.2f} · mit SL: {mit_sl}/{len(rows)}")
|
||||||
|
print(f" ⚠ Pfad-Simulation auf realisierten Ergebnissen — Grenzen s. Docstring\n")
|
||||||
|
|
||||||
|
kopf = f" {'Regel':<34}{'Ende EUR':>11}{'MaxDD':>8}{'schlecht. Trade':>17}{'Trades':>8}{'geblockt':>10}"
|
||||||
|
def zeig(name, r):
|
||||||
|
ruin = " ⚠ RUIN" if r["ruin"] else ""
|
||||||
|
bt = f" ({r['bt']} Tage)" if r["bt"] else ""
|
||||||
|
print(f" {name:<34}{r['eq']:>11.2f}{r['maxdd']:>7.1f}%{r['worst']:>17.2f}"
|
||||||
|
f"{r['n']:>8}{r['block']:>10}{bt}{ruin}")
|
||||||
|
|
||||||
|
print(" ── BASIS ──────────────────────────────────────────────────────")
|
||||||
|
print(kopf)
|
||||||
|
ist = simuliere(rows, start, "ist", eurfak=eurfak, equity_ist=eq_ist)
|
||||||
|
zeig("IST (so gehandelt)", ist)
|
||||||
|
|
||||||
|
print("\n ── LOT-DECKEL (Margin-Sizing, aber gedeckelt) ─────────────────")
|
||||||
|
print(kopf)
|
||||||
|
zeig("Margin ohne Deckel (Kontrolle)",
|
||||||
|
simuliere(rows, start, "margin", eurfak=eurfak, equity_ist=eq_ist))
|
||||||
|
for c in (0.5, 1.0, 1.5, 2.0, 3.0):
|
||||||
|
zeig(f"Margin + Deckel {c:.1f} Lots",
|
||||||
|
simuliere(rows, start, "margin", deckel=c, eurfak=eurfak, equity_ist=eq_ist))
|
||||||
|
|
||||||
|
print("\n ── RISIKO-SIZING (calc_lots_risk, braucht sl_at_entry) ────────")
|
||||||
|
print(kopf)
|
||||||
|
for p in (0.5, 1.0, 1.5, 2.0, 3.0):
|
||||||
|
zeig(f"Risiko {p:.1f} % je Trade",
|
||||||
|
simuliere(rows, start, ("risiko", p), eurfak=eurfak, equity_ist=eq_ist))
|
||||||
|
|
||||||
|
print("\n ── CIRCUIT-BREAKER (Tagesverlust-Stopp, auf IST-Groesse) ──────")
|
||||||
|
print(kopf)
|
||||||
|
for b in (5, 8, 10, 15):
|
||||||
|
zeig(f"IST + Breaker {b} %/Tag",
|
||||||
|
simuliere(rows, start, "ist", breaker=b, eurfak=eurfak, equity_ist=eq_ist))
|
||||||
|
|
||||||
|
print("\n ── KOMBINATION ───────────────────────────────────────────────")
|
||||||
|
print(kopf)
|
||||||
|
for p, b in ((1.5, 8), (2.0, 8), (1.5, None), (2.0, None)):
|
||||||
|
nm = f"Risiko {p:.1f} %" + (f" + Breaker {b} %" if b else "")
|
||||||
|
zeig(nm, simuliere(rows, start, ("risiko", p), breaker=b, eurfak=eurfak,
|
||||||
|
equity_ist=eq_ist))
|
||||||
|
|
||||||
|
print("\n ⚠ Lesehilfe: 'Ende EUR' ist EIN Pfad, keine Erwartung. Entscheidend "
|
||||||
|
"sind\n MaxDD und der schlechteste Einzeltrade — die sagen, was das "
|
||||||
|
"Konto AUSHAELT.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user