Proposals & Lifecycle
Ein Proposal ist ein Order-Vorschlag: ausgewaehlter Strike + Limit, noch nicht an Lynx geschickt. Sie behalten die Kontrolle vom Entwurf bis zur Ausfuehrung.
Statuswerte
draft -> pending -> sent_to_lynx -> partially_executed -> executed
+-> expired (Option verfaellt wertlos)
+-> assigned (Option wird ausgeuebt)
+-> rejected
+-> discarded (3 Tage ohne Fill ODER manuell verworfen)| Status | Bedeutung |
|---|---|
| draft | Erstellt, aber noch nicht zur Ausfuehrung freigegeben |
| pending | Submitted, wartet auf Send-to-Lynx |
| sent_to_lynx | An Adapter uebergeben, Order aktiv |
| partially_executed | Teil-Fill |
| executed | Voll ausgefuehrt (offene Position, Praemie eingenommen) |
| rejected | Vom Broker abgelehnt |
| discarded | Vom User verworfen ODER nach 3 Tagen ohne Fill am Broker storniert |
| expired | Position-Endzustand: Short-Option ist wertlos verfallen (Win) |
| assigned | Position-Endzustand: Short-Option wurde ausgeuebt (Aktien getauscht) |
Hinweis:expiredundassignedbetreffen nur ausgefuehrte Proposals deren Position am Verfallstag aufgeloest wurde. Ein nicht-ausgefuehrtes Proposal, das nach 3 Tagen am Broker storniert wird, geht aufdiscarded(nicht aufexpired). Damit bleibt die Wheel-Statistik sauber:executed -> expired= vereinnahmte Praemie,executed -> assigned= Stock-Wechsel via Wheel.
Aktionen
- Submit (draft → pending) – berechnet
expires_atnach derselben
Regel wie die Desktop-App:
- Submit an einem Trading-Tag vor 14:00 ET -> Tag zaehlt als Tag 1.
- Submit nach 14:00 ET oder am Wochenende/Feiertag -> der naechste
Trading-Tag ist Tag 1.
- Loeschung am 3. Trading-Tag nach 16:00 ET (NYSE-Schluss).
- Der Auto-Cancel-Cleanup laeuft ausschliesslich **ausserhalb der
NYSE-Handelszeit** und nur wenn das User-Gateway authenticated ist; ist es offline, bleibt der Proposal offen und wird beim naechsten Tick erneut geprueft.
- Refresh-Mid – holt frischen Mid-Preis und prueft die Drift gegen den
ursprünglich vorgeschlagenen Mid (siehe Help-Topic "Smart-Limit & Mid-Drift").
- Send (pending → sent_to_lynx) – fuehrt einen finalen Drift-Check durch
und uebergibt die Order an den Lynx-Adapter.
- Discard – setzt den Proposal auf
discarded(jederzeit moeglich, solange
nicht executed).
Stage "Offen" (sent_to_lynx | partially_executed)
- Cancel – storniert die offene Order am Broker und setzt den Proposal auf
discarded. Bei Teil-Fills bleibt der bereits ausgefuehrte Anteil als Position bestehen; nur der Restbestand wird zurueckgezogen. Endpoint: POST /api/proposals/{id}/cancel.
- Replace – aendert das Limit der offenen Order am Broker (Replace-Order).
Status bleibt sent_to_lynx/partially_executed, nur limit_usd wird ueberschrieben. Endpoint: POST /api/proposals/{id}/replace.
Stage "Aktiv" (executed)
- Close – legt einen
BUY_TO_CLOSE-Draft auf dieselbe Option an. Der
Draft laeuft anschliessend ueber die normale Pipeline (Vorschlaege → Zu senden → Offen → ...). Endpoint: POST /api/proposals/{id}/close.
- Roll – legt zwei Drafts an: einen
BUY_TO_CLOSEauf die alte Option
und einen SELL_TO_OPEN mit neuem Strike/Verfall. Beide bleiben zunaechst Drafts und muessen einzeln bestaetigt und gesendet werden. Endpoint: POST /api/proposals/{id}/roll (Body: close_limit_usd, new_strike, new_expiry, open_limit_usd, optional new_quantity).
Plan-Gating: Cancel/Replace/Close/Roll erfordern wie alle Schreib-Aktionen Plan medium oder hoeher.
Auto-Sync mit IBKR
Ein Hintergrund-Scheduler synchronisiert alle 5 Minuten mit dem IBKR-Gateway:
- Order-Adoption: Live-Orders, die ausserhalb der WebApp aufgegeben
wurden (z.B. Desktop-App oder Lynx-Client), werden als Proposals im Status sent_to_lynx/partially_executed uebernommen. Damit erscheint jede offene Option-Order automatisch im Tab Offen.
- Position-Sync + Fill-Match: Positionen werden frisch geholt und
offene Proposals gegen sie gematcht (Symbol/Type/Strike/Expiry). Sobald IBKR die Position meldet, wechselt das Proposal auf executed und erscheint im Tab Aktiv.
- Positions-Adoption: Aktive SHORT-Optionen (
short_put/
short_call), zu denen es noch kein Proposal gibt - z.B. Trades, die in der Desktop-App oder direkt am Broker entstanden sind - werden automatisch als executed-Proposals angelegt und erscheinen ebenfalls im Tab Aktiv. Im status_reason steht adopted_position@<fill>.
Die Tabs Offen und Aktiv zeigen oben eine Statuszeile ("Letzte Aktualisierung: vor Xs - Naechste: in Ys") plus einen Jetzt-Button fuer einen manuellen Trigger.
Endpoints (intern):
GET /api/proposals/sync/status- Scheduler-Status fuer die UI.POST /api/proposals/sync/run-now- manueller Trigger (Adoption + Sync
+ Fill-Match) fuer den eingeloggten User.
Auto-Lifecycle (3-Tage-Cancel + Verfall-Reconcile)
Zwei Scheduler-Jobs halten den Status sauber:
- 3-Tage-Cancel ungefuellter Orders: Wurde ein Proposal binnen 3 NYSE-
Handelstagen (Stichzeit 19:00 UTC) nicht gefuellt, ruft das System die Lynx-Cancel-API auf und setzt den Status auf discarded (Reason auto_3day_no_fill+broker_cancel). So bleibt nichts am Broker haengen.
- Verfall-Reconcile: Fuer
executedProposals, deren Verfall erreicht
wurde, vergleicht der Position-Reconcile die Aktien-Bestaende vor/nach dem IBKR-Sync:
- Short-Put + Bestand stieg um
qty * 100->assigned - Short-Call + Bestand sank um
qty * 100->assigned - sonst Short-Option verschwunden ->
expired(worthless)
US-Feiertage 2026 sind hardcoded; das System ueberspringt sie korrekt.
CC-Lock-Schutz
Beim Anlegen eines Covered-Call-Proposals prueft das Backend, wieviele Aktien bereits durch andere offene CCs gebunden sind:
GET /api/proposals/locks/{symbol}Antwort z. B. {"symbol": "AAPL", "shares_locked": 200}. Die Engine verhindert, dass Sie mehr Calls schreibst, als Sie Aktien hast.
Naked-Call-Schutz beim Senden
Zusaetzlich prueft das Backend beim Klick auf Senden im Send-Dialog explizit die Aktien-Deckung:
GET /api/proposals/{id}/coverageAntwort:
{
"symbol": "AAPL",
"shares_owned": 300,
"shares_locked_other": 100,
"shares_required": 200,
"shares_free": 200,
"covered": true
}shares_owned= Stueckzahl im Depot (long_stock).shares_locked_other= durch andere offene CCs (pending/sent/partial)
bereits gebundene Aktien.
shares_required=quantity * 100.shares_free=shares_owned - shares_locked_other.covered=shares_free >= shares_required.
Ist covered=false, blockt sowohl die UI (Send-Button disabled) als auch das Backend (POST /send antwortet mit 409 CC_NOT_COVERED). Damit kann nie ein nackter Call rausgehen.
Plan-Gating
Schreib-Aktionen (create / submit / refresh-mid / send / discard) erfordern Plan medium oder hoeher. Lesen (Liste, Detail, Locks) ist frei.
Lynx-Adapter
Ein Proposal wird unter der client_order_id = proposal-{id} an den Adapter gesendet (in PROD: Lynx-Gateway, in Tests: In-Memory-Adapter, Default-Build: Dummy-Adapter). Die Order-ID wird zurueckgeschrieben in ibkr_order_id.