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)
StatusBedeutung
draftErstellt, aber noch nicht zur Ausfuehrung freigegeben
pendingSubmitted, wartet auf Send-to-Lynx
sent_to_lynxAn Adapter uebergeben, Order aktiv
partially_executedTeil-Fill
executedVoll ausgefuehrt (offene Position, Praemie eingenommen)
rejectedVom Broker abgelehnt
discardedVom User verworfen ODER nach 3 Tagen ohne Fill am Broker storniert
expiredPosition-Endzustand: Short-Option ist wertlos verfallen (Win)
assignedPosition-Endzustand: Short-Option wurde ausgeuebt (Aktien getauscht)
Hinweis: expired und assigned betreffen nur ausgefuehrte Proposals deren Position am Verfallstag aufgeloest wurde. Ein nicht-ausgefuehrtes Proposal, das nach 3 Tagen am Broker storniert wird, geht auf discarded (nicht auf expired). Damit bleibt die Wheel-Statistik sauber: executed -> expired = vereinnahmte Praemie, executed -> assigned = Stock-Wechsel via Wheel.

Aktionen

  • Submit (draft → pending) – berechnet expires_at nach 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_CLOSE auf 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:

  1. 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.

  1. 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.

  1. 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:

  1. 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.

  1. Verfall-Reconcile: Fuer executed Proposals, 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}/coverage

Antwort:

{
  "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.