"""Die Verhaltens-Behauptungen aus „Ein Fuchs namens Risiko“ auf den EIGENEN Trades gemessen (User-Auftrag 2026-08-19: "nutze alle Buecher als Ideenquelle"). WARUM AUSGERECHNET DIESES BUCH. Von den sechs Buechern im Verzeichnis ist es das einzige, dessen Kernaussagen auf der vorhandenen Datenlage **pruefbar** sind: Kap. 9 "Der Drang, Verluste zurueckzuholen" -> Revenge-Trading Kap. 11 "Overtrading fuehlt sich an wie Kontrolle" Kap. 12 "Revenge Trading und der rote Nebel" Kap. 14 "Warum Langeweile profitabel ist" Kap. 10 "Warum kleine Verluste gesund sind" Alle vier sind Aussagen ueber die REIHENFOLGE und den ABSTAND von Trades -- und genau das steht in `trades` (entry_time, exit_time, pnl, lots). ⚠⚠ WARUM DAS NICHT NUR EIN BUCH-TEST IST. Die Projekt-Zahlen zeigen seit Wochen in dieselbe Richtung: der MENSCH schlaegt den Bot (+3,20 gegen -5,85 EUR/Lot), 28 Signal-Eingriffe sind gescheitert, und die einzige beidhaelftig robuste Aussage ist ein VETO (gegen das Signal -6,60 EUR/Lot). Der Hebel liegt gemessen im Verhalten -- nur ist das Verhalten selbst hier nie gemessen worden. Die Entry-Checkliste nennt "nach 2 Losses Session beenden" als Regel, ohne sie je auf den eigenen Trades geprueft zu haben. ⚠ JE LOT NORMIERT. Absolute Euro sind bei schwankender Positionsgroesse ungueltig (Lehre 04.08.: der 04.08. sah nur wegen groesserer Lots wie ein Ausreisser aus). Gerechnet wird `pnl / lots`. ⚠ 95-%-BOOTSTRAP je Zelle. Ein Punktschaetzer ist auf so kleinen Gruppen keine Aussage. Zellen mit n < 15 werden als zu duenn markiert statt interpretiert. ⚠ GRENZE, vorab benannt: das sind BEOBACHTUNGSdaten, keine Zuweisung. Wer nach einem Verlust weiterhandelt, waehlt das selbst -- ein Unterschied kann also auch heissen "in solchen Marktlagen handelt er anders", nicht "der Zeitabstand ist die Ursache". Die Richtung ist trotzdem verwertbar, weil sie handlungsnah ist. Aufruf: python analyze_verhalten.py [tage] """ import sqlite3 import sys from backtest_cost_gate import ci95 DB = "oil_widget_history.db" MIN_N = 15 def lade(tage): c = sqlite3.connect(DB) c.row_factory = sqlite3.Row q = ("SELECT entry_time, exit_time, pnl, lots FROM trades " " WHERE exit_time IS NOT NULL AND pnl IS NOT NULL AND lots > 0 ") if tage: q += " AND entry_time > strftime('%s','now','-{} days') ".format(int(tage)) q += " ORDER BY entry_time" rows = [dict(r) for r in c.execute(q)] c.close() for r in rows: r["pl"] = r["pnl"] / r["lots"] return rows def zeig(lbl, werte): if not werte: print(" {:<34} --".format(lbl)); return n = len(werte); m = sum(werte) / n ci = ci95(werte) if n >= 20 else None s = "n={:>4} {:>5.0f}% Gew. {:+8.2f} EUR/Lot".format( n, 100.0 * sum(1 for x in werte if x > 0) / n, m) if ci: s += " [{:+7.2f} .. {:+7.2f}]".format(ci[0], ci[1]) s += "" if ci[0] * ci[1] > 0 else " ~0" if n < MIN_N: s += " ⚠ zu duenn" print(" {:<34} {}".format(lbl, s)) def main(): tage = int(sys.argv[1]) if len(sys.argv) > 1 else 0 tr = lade(tage) if len(tr) < 50: print("zu wenige Trades:", len(tr)); return print("=" * 100) print(" VERHALTENS-MESSUNG auf {} eigenen Trades{}".format( len(tr), " (letzte {} Tage)".format(tage) if tage else " (gesamt)")) print(" je Lot normiert · 95-%-Bootstrap · Zellen < {} als zu duenn markiert".format(MIN_N)) print("=" * 100) # ---- (1) REVENGE: Abstand zum vorigen EXIT, getrennt nach dessen Ausgang -- print("\n (1) REVENGE-TRADING — wie schnell nach dem letzten Ausstieg?") print(" " + "-" * 96) for nach_verlust in (True, False): gr = {"< 5 min": [], "5–30 min": [], "30–120 min": [], "> 2 h": []} for a, b in zip(tr, tr[1:]): if (a["pnl"] < 0) != nach_verlust: continue dt = (b["entry_time"] - a["exit_time"]) / 60.0 if dt < 0: continue k = ("< 5 min" if dt < 5 else "5–30 min" if dt < 30 else "30–120 min" if dt < 120 else "> 2 h") gr[k].append(b["pl"]) print(" nach einem {}:".format("VERLUST" if nach_verlust else "Gewinn")) for k, v in gr.items(): zeig(" " + k, v) # ---- (2) VERLUST-SERIE --------------------------------------------------- print("\n (2) VERLUST-SERIE — der wievielte Trade nach wie vielen Verlusten?") print(" (Projekt-Regel der Entry-Checkliste: 'nach 2 Losses Session beenden')") print(" " + "-" * 96) serie = 0 gr = {0: [], 1: [], 2: [], 3: []} for t in tr: gr[min(serie, 3)].append(t["pl"]) serie = serie + 1 if t["pnl"] < 0 else 0 for k in sorted(gr): zeig(" nach {} Verlust{} in Folge".format(k, "" if k == 1 else "en"), gr[k]) # ---- (3) OVERTRADING / "Langeweile" ------------------------------------- print("\n (3) OVERTRADING — der wievielte Trade des Tages?") print(" " + "-" * 96) tage_d = {} for t in tr: d = int(t["entry_time"]) // 86400 tage_d.setdefault(d, []).append(t) gr = {"1.–2.": [], "3.–5.": [], "6.–10.": [], "11.+": []} tagesbilanz = {"1–3 Trades": [], "4–8": [], "9–15": [], "16+": []} for d, ts in tage_d.items(): for i, t in enumerate(ts, 1): k = ("1.–2." if i <= 2 else "3.–5." if i <= 5 else "6.–10." if i <= 10 else "11.+") gr[k].append(t["pl"]) n = len(ts) kk = ("1–3 Trades" if n <= 3 else "4–8" if n <= 8 else "9–15" if n <= 15 else "16+") tagesbilanz[kk].append(sum(x["pl"] for x in ts)) for k, v in gr.items(): zeig(" " + k + " Trade des Tages", v) print("\n (4) „LANGEWEILE IST PROFITABEL“ — TAGESbilanz je Lot nach Handelsmenge") print(" (Einheit: Summe EUR/Lot des Tages, n = Anzahl Tage)") print(" " + "-" * 96) for k, v in tagesbilanz.items(): zeig(" " + k, v) print("=" * 100) if __name__ == "__main__": main()