# Konzept: Modul „OptionsWheel" (Covered Call / Cash-Secured Put) > Stand: 2026-08-03 > Ziel: Auf einer kleinen Watchlist von Tickern, an deren langfristigen Erfolg wir glauben, > **systematisch Optionsprämien vereinnahmen** – Cash-Secured Put → (Zuteilung) → Aktienbestand → > Covered Call → (Abruf) → wieder Cash. Vollautomatisch, delta-basierte Strike-Wahl, aktives Rollen. > > **Festlegungen (2026-08-03, mit dem Betreiber abgestimmt):** > 1. Put-Seite ist **Cash-Secured Put**, nicht Covered Put (kein Leerverkauf der Aktie). > 2. **Vollautomatisch** von Anfang an – Sicherungen sind Schalter und Limits, keine Klick-Freigabe. > 3. Strike-Wahl **delta-basiert** (Zielband 0,15–0,30). > 4. Bei drohender Zuteilung wird **gerollt**, solange das per Netto-Kredit möglich ist. ## 0. Leitprinzipien 1. **Niemals nackt.** Ein Short Call ist nur zulässig mit 100 freien Aktien je Kontrakt, ein Short Put nur mit reserviertem Cash über Strike × 100 je Kontrakt. Diese Deckungsprüfung gehört in den **Core-`RiskService`**, nicht ins Modul – ein Modulfehler darf keine ungedeckte Option schreiben können. 2. **Der Broker ist die Wahrheit.** Zuteilung und Verfall ändern Positionen **ohne** eine Order von uns. Ohne regelmäßigen Positionsabgleich gegen IBKR läuft die eigene Buchführung zwangsläufig auseinander. 3. **Nur Watchlist.** Das Modul handelt ausschließlich explizit eingetragene Ticker. Keine Entdeckung, kein Screening, keine Ausweitung zur Laufzeit. 4. **Jede Entscheidung ist nachlesbar.** Kandidatenbewertung, Zustandsübergang und Order gehen über `core_decision_journal` / `core_order_events` mit gemeinsamer `SignalId` – dieselbe Forensik-Grundlage, die der Supervisor bereits nutzt. --- ## 1. Warum das nicht additiv geht: der Core ist heute aktienbasiert | Stelle | Heutiger Stand | Konsequenz | |---|---|---| | `IbkrMapping.Stock()` | „der einzige Instrumententyp, den die Module handeln" | Kein Options-Kontrakt (Expiry/Strike/Right/Multiplier) baubar | | `TradeSignal` / `OrderRequest` | nur `Symbol` + `Side` + `Quantity` | Ein Signal kann keine Option benennen | | `Position.Notional` | `Quantity × AvgPrice` | Bei Optionen um Faktor 100 falsch → Risikolimits wirkungslos | | `RiskService.EvaluateSell` | „Verkauf schließt Position", lehnt ohne Bestand ab | **Sell-to-open wird grundsätzlich abgelehnt** – die Kernoperation des Moduls | | `IBrokerClient.PlaceOrderAsync` | Fill-oder-Fehlschlag innerhalb `OrderTimeoutSeconds` | Limitorder am Mid liegt im Buch → gilt als Fehlschlag, ist aber aktiv (bekannte Grenze, [IBKR-Integration.md](../IBKR-Integration.md)) | | `IBrokerClient` | kein `reqPositions`, keine Optionskette, keine Greeks | Zuteilung/Verfall unsichtbar, Strike-Wahl unmöglich | Das Modul setzt also auf einem **Core-Options-Fundament** auf, das zuerst gebaut wird. Der Aktienpfad bleibt dabei unverändert (neue Felder sind optional, `Kind = Stock` ist der Default) – CongressTrading darf nicht regressieren. --- ## 2. Core-Erweiterungen ### 2.1 Instrumententyp ```csharp public enum InstrumentKind { Stock, Option } public enum OptionRight { Call, Put } public sealed record OptionSpec( string Underlying, DateOnly Expiry, decimal Strike, OptionRight Right, int Multiplier = 100); ``` `TradeSignal` und `OrderRequest` bekommen je ein **optionales** `OptionSpec? Option`. Ist es null, verhält sich alles exakt wie heute. ### 2.2 Positionen mit Instrument-Identität `core_position` erhält `Kind`, `Expiry`, `Strike`, `Right`, `Multiplier`; der fachliche Schlüssel wird `(Module, Symbol, Kind, Expiry, Strike, Right)`. Bestandszeilen sind `Kind = Stock` mit leeren Optionsfeldern – Migration ohne Datenumzug. `Notional` rechnet ab jetzt mit dem Multiplikator. `IPortfolioService` bekommt entsprechend überladene Abfragen; die bestehenden Aktien-Signaturen bleiben. ### 2.3 Optionskette + Greeks Neuer Seam am `IBrokerClient`: ```csharp Task GetOptionChainAsync(string underlying, DateOnly from, DateOnly to, CancellationToken ct); Task GetOptionQuoteAsync(OptionSpec spec, CancellationToken ct); // + Delta, IV, OpenInterest Task> GetPositionsAsync(CancellationToken ct); ``` Umsetzung in `IbkrConnection`: - **Kette** über `reqSecDefOptParams` – liefert Expiries und Strikes je Underlying **ohne** Marktdaten und ohne Abo. Das ist der einzige gangbare Einstieg: eine volle Kette hat hunderte Kontrakte, TWS erlaubt aber nur ~100 gleichzeitige Marktdatenzeilen. - **Vorfilter, dann Quotes:** Expiry-Fenster aus dem DTE-Ziel, Strike-Fenster um den Spot (± x %) – erst für diese Handvoll Kontrakte `reqMktData` mit Greeks (`tickOptionComputation`). - `IbkrMapping.Option(spec)` → `SecType="OPT"`, `Exchange="SMART"`, `LastTradeDateOrContractMonth` als `yyyyMMdd`, `Strike`, `Right="C"/"P"`, `Multiplier="100"`, plus `TradingClass` (nötig, sobald ein Underlying mehrere Klassen führt, z. B. nach Splits oder bei Mini-Optionen). Rein, unit-getestet – wie das bestehende `IbkrMapping`. ### 2.4 Asynchrone Order- und Fill-Verfolgung Das ist der **teuerste, aber unumgängliche** Teil: eine Optionsorder wird limitiert am Mid platziert und füllt oft erst Minuten später oder gar nicht. - Neue Tabelle `core_order_state`: Order-ID, `SignalId`, Kontrakt, Menge, Limit, Status, gefüllte Menge, Zeitstempel – überlebt einen App-Neustart. - `IBrokerClient.SubmitOrderAsync(...)` gibt die Order-ID **ohne Warten** zurück; `PlaceOrderAsync` bleibt für den Aktienpfad erhalten (intern: submit + warten). - Dauerabo auf `orderStatus` / `openOrder` / `execDetails` statt Slot-je-Order; ein `OrderTrackingService` (`IHostedService`) trägt Endstatus nach, ruft dann `RecordFillAsync` und schreibt `core_order_events`. Nicht gefüllte Orders werden nach einer Frist neu bepreist oder storniert. - **Nebennutzen:** löst die in [IBKR-Integration.md](../IBKR-Integration.md) dokumentierte Grenze auch für CongressTrading. ### 2.5 Positionsabgleich (Zuteilung & Verfall) `ReconcilePositionsWorker` (`IHostedService`, z. B. stündlich und nach Marktschluss) vergleicht `reqPositions` mit `core_position`: - Aktien tauchen auf / Short Put verschwindet → **Zuteilung**. - Short Call verschwindet, Aktien weg → **Abruf (called away)**. - Option verschwindet am Verfallstag ohne Gegenbuchung → **wertlos verfallen**. Jede Abweichung wird gebucht **und** ins Entscheidungsjournal geschrieben. Unerklärbare Differenzen (manueller Eingriff in TWS) setzen den betroffenen Ticker auf `Halted`. ### 2.6 Risiko: sell-to-open + Deckung `RiskContext` bekommt `FreeShares` (nicht bereits durch andere Short Calls gebunden) und `AvailableCashForReservation`. Neue Regeln im `RiskService`: | Fall | Regel | |---|---| | Sell-to-open Call | `FreeShares ≥ 100 × Kontrakte`, sonst Ablehnung „ungedeckt" | | Sell-to-open Put | `AvailableCashForReservation ≥ Strike × 100 × Kontrakte`, sonst Ablehnung | | Buy-to-close | zulässig bis zur offenen Short-Menge | | Limits | `MaxTradePercent` / `MaxPositionPercentPerModule` rechnen mit dem **Multiplikator** | --- ## 3. Modul `IBKRTrader.Modules.OptionsWheel` `IModule` mit `Name = "OptionsWheel"`, `DbPrefix = "ow_"`, referenziert nur den Core, eigener DbContext, eigenes Fenster, eigene Settings-Sektion – analog Accounting/CongressTrading. ### 3.1 Zustandsautomat je Ticker ``` Idle ──sell put──► ShortPut ──ITM/DTE──► Rolling ──► ShortPut ▲ │ │ │ │ Zuteilung └─ kein Netto-Kredit ─► (Zuteilung zulassen) │ ▼ └── called away ── Stock ──sell call──► ShortCall ──ITM/DTE──► Rolling ──► ShortCall ▲ │ └─ verfallen ─────────┘ ``` Zusätzlich `PendingOrder` (Order im Buch) und `Halted` (Sperre nach Fehler/Abweichung, nur manuell lösbar). Der Automat ist **rein und unit-getestet** – er kennt weder Broker noch Datenbank. ### 3.2 Persistenz (`ow_`) | Tabelle | Inhalt | |---|---| | `ow_underlyings` | Watchlist: Ticker, aktiv, Parameter-Überschreibungen, aktueller Zustand | | `ow_cycles` | Ein Wheel-Durchlauf (Start, Ende, vereinnahmte Prämie brutto/netto, Ergebnis, Aktien-Einstand) | | `ow_legs` | Jede verkaufte/geschlossene Option: Kontrakt, Prämie, Delta+IV bei Eröffnung, DTE, Status, Roll-Kette | | `ow_candidates` | Bewertete Strike-Kandidaten je Scan (auch die verworfenen, mit Grund) – Nachvollziehbarkeit | | `ow_events` | Zustandsübergänge mit Auslöser | `ow_candidates` ist bewusst dabei: bei Vollautomatik ist die **nicht** getroffene Wahl die wichtigste Information für die spätere Fehlersuche und für den Supervisor. ### 3.3 Reine Logik (`Logic/`, vollständig unit-getestet) - **`StrikeSelector`** – filtert die Kette: DTE-Fenster, Delta-Zielband, Mindestprämie (absolut und als annualisierte Rendite), maximaler Bid/Ask-Spread, Mindest-Open-Interest. Bewertet die Verbleibenden und begründet jede Verwerfung. - **`RollDecider`** – rollen ja/nein: Trigger, Zielkontrakt, Netto-Kredit-Bedingung. - **`CoverageCalculator`** – welche Aktien/welches Cash sind frei, welche durch offene Shorts gebunden. - **`PremiumMath`** – annualisierte Rendite, Break-even, effektiver Einstand nach Prämien. - **`WheelStateMachine`** – zulässige Übergänge, Ableitung des Zustands aus Positionen. ### 3.4 Worker - `WheelScanWorker` – im Handelszeitfenster: Zustand je Ticker prüfen, ggf. neues Leg eröffnen. - `WheelManageWorker` – offene Legs überwachen: rollen, schließen, Orders nachbepreisen. ### 3.5 UI (ein Fenster, Tabs) Übersicht (je Ticker: Zustand, offenes Leg, DTE, aktuelles Delta, Prämie vereinnahmt/annualisiert), Watchlist (Ticker + Parameter, Not-Aus je Ticker), Kandidaten (letzter Scan inkl. Verwerfungsgründe), Zyklen/Historie, Log. DB-Zugriff nur auf Interaktion (Smoke-UI-sicher). --- ## 4. Regelwerk (Defaults, je Ticker überschreibbar) | Parameter | Default | Begründung | |---|---|---| | DTE beim Öffnen | 30–45 | Bestes Verhältnis Zeitwertverfall/Handelskosten | | Ziel-Delta | 0,20 (Band 0,15–0,30) | ≈ 20 % Zuteilungswahrscheinlichkeit | | Roll-Trigger | DTE ≤ 21 **und** (Delta > 0,50 oder ITM) | Gamma-Risiko steigt in der letzten Woche stark | | Roll-Bedingung | nur bei **Netto-Kredit** | Ein Debit-Roll kauft nur Zeit und kostet Geld | | Covered-Call-Strike | **≥ effektiver Einstand** der Aktien | Verhindert, dass der Wheel systematisch Verluste realisiert | | Max. Kontrakte je Ticker | 1 | Bewusst klein starten | | Max. gebundenes Cash gesamt | Anteil des Kontowerts | Zweite Grenze über die Core-Limits hinaus | | Vorzeitiges Schließen bei Gewinnziel | **aus** | Nicht gewählt – siehe Hinweis unten | > **Hinweis zum Gewinnziel:** Der übliche Begleiter der Roll-Politik ist, ein Leg bei ~50 % vereinnahmter > Prämie zurückzukaufen; das nimmt Gamma-Risiko aus der letzten Woche und ist der Grund, warum viele > Roll-Trigger nie greifen. Der Parameter ist vorgesehen (`ProfitTargetPercent` existiert bereits in > `TradingSettings`), steht per Vorgabe auf „aus" und lässt sich ohne Codeänderung zuschalten. > **Zuteilung ist nicht abwählbar.** Auch bei konsequentem Rollen teilt IBKR zu – amerikanische Optionen > können jederzeit ausgeübt werden, beim Call besonders vor dem Ex-Dividenden-Tag, und ein Roll ist nicht > immer per Netto-Kredit möglich. Zuteilung ist deshalb ein **regulärer Pfad** des Automaten, kein Fehler. > Genau dafür ist der Positionsabgleich (2.5) Pflicht. --- ## 5. Sicherungen Zu den bestehenden zwei Schaltern (`IBKR.UseTwsApi`, `Trading.TradingEnabled`) kommen: 1. `OptionsWheel.Enabled` – Modulschalter. 2. **Watchlist als Whitelist** – kein Ticker außerhalb. 3. **Naked-Sperre im Core-`RiskService`** (2.6) – wirkt auch, wenn die Modullogik falsch liegt. 4. `Halted` je Ticker bei unerklärter Positionsabweichung; Neueröffnungen stoppen, bestehende Legs werden weiter verwaltet. 5. Kill-Switch „keine Neueröffnungen" – laufende Positionen bleiben handhabbar. --- ## 6. Phasen | Phase | Inhalt | Abschlusskriterium | |---|---|---| | **W-0** | Core: `InstrumentKind`/`OptionSpec`, `IbkrMapping.Option`, Positionen mit Multiplikator | Build + Tests grün, Aktienpfad unverändert | | **W-1** | Core: Optionskette + Greeks (`reqSecDefOptParams`, `tickOptionComputation`) | Kette eines Watchlist-Tickers gegen das Paper-Gateway abrufbar | | **W-2** | Core: asynchrone Order-/Fill-Verfolgung (`core_order_state`, `OrderTrackingService`) | Limitorder überlebt Timeout und App-Neustart, Fill wird nachgebucht | | **W-3** | Core: Positionsabgleich + `RiskService` sell-to-open/Deckung | Zuteilung im Paper erkannt und gebucht; ungedeckte Order wird abgelehnt | | **W-4** | Modul-Gerüst: `IModule`, `ow_`-DbContext + Migration, UI-Tabs, Watchlist | App startet, `--smoke-ui` grün, kein Handel | | **W-5** | Reine Strategie-Logik + Tests (StrikeSelector, StateMachine, RollDecider, PremiumMath) | Hohe Testabdeckung ohne Broker | | **W-6** | Verdrahtung + vollautomatischer Paper-Betrieb, Beobachtung über mehrere Verfallszyklen | Mindestens ein vollständiger Wheel-Durchlauf im Paper | | **W-7** | Live-Freigabe | Eigene Entscheidung nach W-6 | W-0 bis W-3 sind Core-Arbeit und nützen auch den anderen Modulen; erst ab W-4 entsteht das Modul selbst. --- ## 7. Bewusst offen / zu klären 1. ~~**Optionsberechtigung im Paper-Konto DUR371528 prüfen**~~ → **erledigt am 2026-08-04, vorhanden.** Verifiziert gegen das laufende Gateway (Details siehe [IBKR-Integration.md](../IBKR-Integration.md), Abschnitt „Optionen"): `reqSecDefOptParams` für AAPL liefert 24 Verfallstermine, 127 Strikes, Multiplier 100 über SMART; eine **What-If-Order** auf `AAPL 20260812 C302.5` wurde von IBKR angenommen (Init-Margin 589,52) statt mit einem Berechtigungsfehler abgelehnt. Damit ist die Grundvoraussetzung für dieses Modul gegeben. 2. ~~**Greeks bei verzögerten Daten.**~~ → **erledigt am 2026-08-04, funktioniert.** Gemessen an `AAPL 20260821 C305`: IV 0,2730 / Delta 0,5443 / Gamma 0,0220 / Vega 0,2626 / Theta −0,2304. Die Werte kommen über die verzögerten Tick-Felder 80–83; **Feld 83 (Modell)** ist die für die Strike-Wahl maßgebliche Variante. Die delta-basierte Wahl im Band 0,15–0,30 ist damit umsetzbar, die Ersatzlösung über prozentualen Abstand wird nicht gebraucht (der `StrikeSelector` behält sie trotzdem als Rückfalllinie). Details: [KONZEPT-Datenlage-und-Strategien.md](KONZEPT-Datenlage-und-Strategien.md). 3. **Marktdatenabo (OPRA)** für Realtime-Optionskurse – Kosten/Notwendigkeit später entscheiden. 4. **Earnings-Sperre**: keine neuen Legs über Quartalszahlen hinweg. **Geprüft am 2026-08-04: über die TWS API nicht lösbar** – `CalendarReport` und alle übrigen Fundamentaldaten sind auf dem Konto gesperrt (Fehler 10358, Refinitiv-Abo nötig). Das ist die einzige Stelle, an der uns eine externe Quelle ernsthaft fehlt. **Behelf ohne Fremddaten:** ein IV-Filter – keine neuen Legs, wenn die implizite Volatilität des Basiswerts deutlich über ihrem 30-Tage-Mittel liegt. Fängt den Earnings-Anstieg indirekt mit ab, unschärfer, aber ohne Abhängigkeit. 5. **Accounting-Anschluss**: Optionsprämien, Zuteilungen und Abrufe müssen im `AccountingClassifier` eigene Buchungskategorien bekommen; der `RealizedPnlEngine` (FIFO) kennt weder Multiplikator noch die Einstandsverschiebung durch Zuteilung. Eigene Arbeit im Accounting-Modul. 6. **Steuer** bleibt wie im Accounting-Konzept unberührt und offen. Keine Steuerberatung.