Scoring-Modell (PUT L1, PUT L2, Covered Call)
Das Score-Modell ist das Herz des Options-Screeners. Aus jedem Strike einer gefilterten Optionskette berechnet der Backend-Service einen numerischen Score; die drei besten Strikes pro Symbol werden zu Gold, Silber und Bronze.
Identische Formel wie in der Desktop-App – Web ist 1:1 portiert. Bei Differenzen gewinnt Desktop. Quellen:
- Web:
wheeltrading-backend/services/option_scoring.py,
services/medal_ranking_service.py, services/option_recommendation.py
- Desktop:
app/utils/option_utils.py
(calculate_put_score, calculate_call_score, calculate_atr_distance_put, calculate_atr_distance_call)
Pipeline auf einen Blick
Optionskette (Provider)
|
v
[1] Filter: DTE, |Δ|, OI, Volume, Moneyness, ITM-Sperre
|
v
[2] Risk-Flags: earnings, ex_div, iv_low, iv_extreme, low_liquidity
|
v
[3] SCORE → calculate_put_score / calculate_call_score
|
v
[4] KLASSIFIKATION → recommend_put / recommend_call
(L1 / L2 / CC / 🚫 / ⚠️ + Warn-Icons)
|
v
[5] Sortierung: Score DESC → Praemie p.a. DESC → DTE ASC → Strike
|
v
Top-3 Medals (🥇🥈🥉)Die fuenf Stufen sind klar getrennt:
| Stufe | Frage | Ergebnis |
|---|---|---|
| 1 | Ist der Strike ueberhaupt einen Blick wert? | gefilterte Liste |
| 2 | Welche Risiken haengen daran? | Flag-Set |
| 3 | Wie attraktiv ist der Strike numerisch? | Score (0–110 / 0–100) |
| 4 | Erfuellt der Strike die L1/L2/CC-Schwellen? | Label L1/L2/CC/🚫 |
| 5 | Welche drei sind die Top-Picks? | Gold/Silber/Bronze |
Score und Klassifikation sind getrennte Welten: ein Strike kann hoch scoren und trotzdem 🚫 bekommen (z. B. Strike unter Kaufpreis bei CC), oder umgekehrt eine schwache Klassifikation haben aber durch Liquiditaet trotzdem in die Top-3 rutschen, wenn nichts Besseres da ist.
Eingaben aus dem SymbolContext
Vor der Bewertung laedt load_symbol_context pro Symbol:
| Feld | Quelle | Verwendet fuer |
|---|---|---|
has_stock | offene Long-Stock-Position | PUT L1 vs L2, CC erlaubt? |
purchase_price | Avg. Einstand der Long-Position | CC ATR-Distanz + Capital-Gain |
atr_52 | EOD-Daten / DB | ATR-Distanz |
last_close | EOD | Fallback-Underlying |
vix_value | tagesaktuell | Regel-16-Bonus, VIX-Band |
next_earnings_date | Earnings-Calendar | Risk-Flag + Warn-Icon |
next_ex_div_date | Dividenden-Calendar | Risk-Flag + Warn-Icon |
Der Underlying-Preis wird aus dem Snapshot des Provider-Quotes genommen und nur dann durch last_close ersetzt, wenn der Live-Preis fehlt.
PUT-Score (max 110 Punkte)
score = premium_score # 0..40
+ atr_score # 0..25
+ oi_score # 0..20
+ iv_score # 0..15
+ regel_16_bonus # 0..10 (nur L1/L2)1. Premium-Score (0–40, 40 % Gewicht)
premium_score = min(40, premium_pct / target_return * 40)premium_pct = mid / strike * 100 (Einperioden-Praemie in %, NICHT annualisiert).
target_return haengt von der Strategie ab:
| Strategie | has_stock | target_return | Bedeutung |
|---|---|---|---|
| L1 | nein | 1.5 % | Initial-Eintritt, hoehere Renditeforderung |
| L2 | ja | 0.8 % | Wheel-Phase 2: Cost-Basis-Reduktion |
fallback | n/a | 0.6 % | Sonderfall (sehr schwacher Markt) |
Auswirkung an einem Beispiel (mid 1.10, strike 100 → premium_pct 1.1 %):
| Strategie | Score | Maximum |
|---|---|---|
| L1 | 1.1 / 1.5 * 40 = 29.3 | 40 |
| L2 | 1.1 / 0.8 * 40 = 55 → 40 | 40 |
Genau dadurch werden moderate Praemien fuer Bestandshalter (L2) attraktiver bewertet als fuer Neueinsteiger (L1).
2. ATR-Score (0–25, 23 % Gewicht)
atr_score = max(0, min(25, 25 - |2.5 - atr_distance| * 5))
atr_distance_put = (current_price - strike) / atr52- Optimum bei 2.5 ATR unter dem aktuellen Kurs (full points).
- Pro 1 ATR Abweichung: −5 Punkte. Bei 0 oder 5 ATR: 0 Punkte.
Warum `current_price` und nicht cost_basis? Beim Put-Verkauf besitzen Sie die Aktien noch nicht – der Strike ist der zukuenftige Andienungspreis. Die Distanz misst die Sicherheits-Cushion gegen den heutigen Kurs.
| atr_distance | Score |
|---|---|
| 0.0 | 12.5 |
| 1.5 | 20.0 |
| 2.5 | 25.0 |
| 3.5 | 20.0 |
| 5.0 | 12.5 |
| 7.5 | 0.0 |
3. OI-Score (0–20, 18 % Gewicht)
oi_score = min(20, open_interest / 100 * 20)Lineare Skalierung bis 100 Kontrakte. Reine Liquiditaets-Komponente – kein Volumen-Gewicht (Volume nur als Filter, nicht als Score).
4. IV-Score (0–15, 14 % Gewicht)
iv_score = min(15, iv * 100 / 50 * 15) # iv als Dezimal (0..1)Hoehere IV → groessere Praemie → wir wollen sie. Saturation bei IV ≥ 50 %.
5. Regel-16-Bonus (0–10, max 9 % Gewicht)
Bonus wenn die Aktie ruhiger laeuft als der VIX impliziert:
expected_daily_move = vix_value / 16 # %
actual_daily_move = atr52 / 52 / stock_price * 100
if actual < expected:
bonus = min(10, (expected / actual - 1) * 20)Greift nur fuer L1 und L2, nicht fuer Fallback. Macht den Unterschied zwischen einem 70-Punkte- und einem 80-Punkte-Strike, wenn alles andere gleich ist.
Einordnung der PUT-Sub-Scores
| Komponente | max Punkte | Anteil am Maximum |
|---|---|---|
| Premium % | 40 | 36.4 % |
| ATR-Distanz | 25 | 22.7 % |
| Open-Interest | 20 | 18.2 % |
| IV | 15 | 13.6 % |
| Regel-16-Bonus | 10 | 9.1 % |
| Summe | 110 | 100 % |
Covered-Call-Score (max 100 Punkte)
score = premium_score # 0..30
+ capital_gain_score # 0..30
+ atr_score # 0..20
+ oi_score # 0..10
+ iv_score # 0..101. Premium-Score (0–30)
premium_score = min(30, premium_pct / 0.6 * 30)Basis 0.6 % Mid/Strike – ueber 0.6 % volle Punktzahl.
2. Capital-Gain-Score (0–30) [neu mit cost_basis]
reference = cost_basis if (has_stock and cost_basis > 0) else current_price
capital_gain_pct = max(0, (strike - reference) / reference * 100)
capital_gain_score = min(30, capital_gain_pct / 5 * 30)Saturation bei 5 % Aufschlag ueber dem Einstand. Genau hier sitzt der Kern der CC-Logik:
Sie sind Aktien-Owner und willst im Falle der Andienung mit Gewinn verkaufen. Reference ist daher Ihr Einstand, nicht der Tagespreis.
3. ATR-Score (0–20) [neu mit cost_basis]
atr_distance_call = (strike - reference) / atr52
atr_score = max(0, min(20, 20 - |2.5 - atr_distance| * 4))Selbe Glocke wie beim Put, aber mit −4 statt −5 pro ATR und Cap auf 20.
4. OI-Score (0–10) und 5. IV-Score (0–10)
oi_score = min(10, open_interest / 100 * 10)
iv_score = min(10, iv * 100 / 50 * 10)Einordnung der CC-Sub-Scores
| Komponente | max Punkte | Anteil am Maximum |
|---|---|---|
| Premium % | 30 | 30 % |
| Capital-Gain | 30 | 30 % |
| ATR-Distanz | 20 | 20 % |
| Open-Interest | 10 | 10 % |
| IV | 10 | 10 % |
| Summe | 100 | 100 % |
CMG-Beispiel
cost_basis 34.00, Spot 36.20, ATR52 1.31:
| Strike | atr vs Spot | atr vs cost_basis | capital_gain | Score-Bewegung |
|---|---|---|---|---|
| 36 | −0.15 ITM | +1.53 | 5.9 % | klar besser |
| 38 | +1.37 | +3.05 | 11.8 % | sehr stark |
Mit Spot-Reference waere Strike 36 gar nicht erst aufgetaucht (ITM). Mit cost_basis-Reference ist er ein attraktiver CC.
Klassifikation (Stufe 4): von Score zu Label
Der Score sortiert. Die Klassifikation labelt den Strike algorithmisch fuer den User (keine Anlageberatung, keine persoenliche Empfehlung). Beide Funktionen sind in services/option_recommendation.py.
VIX-Band (Mindest-ATR)
VIX < 15 → min_atr 1.0 👌 niedrig - stabiler Markt
VIX < 20 → min_atr 1.5 🟢 normal
VIX < 30 → min_atr 2.0 🟡 erhoeht - strikteres Algorithmus-Profil
VIX ≥ 30 → min_atr 2.5 🔴 EXTREM - nur sichere StrikesPUT-Klassifikation
annual_return = premium_pct * 365 / dte| Bedingung | Label |
|---|---|
not has_stock und annual ≥ 12 % und atr ≥ min_atr | L1 ✅ |
not has_stock und annual ≥ 12 % und atr ≥ min_atr−0.5 | L1 ⚠️ |
has_stock und annual ≥ 6 % und atr ≥ min_atr | L2 ✅ |
has_stock und annual ≥ 6 % und atr ≥ min_atr−0.5 | L2 ⚠️ |
| sonst (Praemie/ATR zu niedrig) | - |
Risk-Icon kommt zusaetzlich aus _risk_band:
risk_score = (vix / 20) * (1 / max(atr_distance, 0.1))
< 0.6 → Niedrig ✅
< 1.2 → Mittel ⚠️
≥ 1.2 → Hoch 🔴Covered-Call-Klassifikation
Vorab-Hard-Stops (geben sofort 🚫):
- Strike ≤ aktueller Kurs (sofort ITM)
- Strike ≤ Kaufpreis (Verlust bei Assignment)
- Kein Aktien-Bestand (
has_stock=False)
Danach Min-Renditeschwelle:
sc_min_roc = 1 % monatlich → annualisiert ~12 % p.a.
min_strike = purchase_price * (1 + sc_min_roc)
sc_atr_min = 1.5 ATR| Bedingung | Label |
|---|---|
strike < min_strike | ⚠️ |
atr_distance ≥ sc_atr_min und annual ≥ 12 % und atr ≥ min_atr | CC ✅ |
| Wie oben, aber atr ≥ min_atr − 0.5 | CC ⚠️ |
| Ex-Date < Expiry + 7 d | ⚠️ |
| sonst | - |
Warn-Icons (an L1 / L2 / CC angehaengt)
| Icon | Bedeutung |
|---|---|
📅 | Earnings ≤ 7 Tage vor Expiry |
💰 | Ex-Dividend zwischen heute und Expiry |
Sortierung der Top-3
ORDER BY score DESC,
premium_pct DESC,
dte ASC,
strike ASCTie-Breaker also: Score > Praemie p.a. > kurze Laufzeit > kleiner Strike. Alle Kandidaten muessen die Filter UND die Risk-Flag-Excludes ueberlebt haben.
Beispielrechnung (CMG, Bestandshalter, VIX 18)
Annahmen: cost_basis 34.00, Spot 36.20, ATR52 1.31, IV 0.32, OI 800, VIX 18, DTE 40.
CC Strike 36, mid 0.55:
premium_pct = 0.55 / 36 * 100 = 1.53
capital_gain_pct = (36 - 34) / 34 * 100 = 5.88 → satur. → 30
atr_distance_call = (36 - 34) / 1.31 = 1.53
→ atr_score = 20 - |2.5 - 1.53| * 4 = 16.1
premium_score = min(30, 1.53 / 0.6 * 30) = 30
oi_score = min(10, 800 / 100 * 10) = 10
iv_score = min(10, 0.32 * 100 / 50 * 10) = 6.4
score = 30 + 30 + 16.1 + 10 + 6.4 = 92.5Klassifikations-Check:
annual = 1.53 * 365 / 40 = 13.96 % p.a.
min_strike = 34 * 1.01 = 34.34 → 36 ≥ 34.34 ✅
atr_distance 1.53 ≥ 1.5 (sc_atr_min) ✅
atr_distance 1.53 ≥ 1.5 (VIX-Band, normal) → grenzwertig
risk_score = 18/20 * 1/1.53 = 0.59 → Niedrig ✅
→ Label: "CC ✅"Erweiterungspunkte (fuer Devs)
- Zusaetzliche Sub-Scores ueber
option_scoring.calculate_*_score
ergaenzen – das maximale Score-Total muss in den Tests dokumentiert werden, damit Sortierung und Tooltips konsistent bleiben.
- VIX-Baender und Schwellen (
12 % p.a.L1,6 % p.a.L2,
sc_min_roc 1 %) in option_recommendation.py zentralisieren – aenderbar pro Plan/User-Override.
- Saturationspunkte pro Sub-Score sind bewusst absolut, nicht
prozentual: 0.6 %/30, 1.5 %/40, 5 %/30 etc. Bei Anpassungen Desktop- Pendant in app/utils/option_utils.py zuerst aendern.
Verwandte Themen
- "Medals & Risk-Flags" – wie aus dem Score Gold/Silber/Bronze wird
- "Volatilitaets-Regime (VIX)" – die VIX-Stufen im Detail
- "Options-Screening" – die Filter-Stufe vor dem Scoring
- "Sicherheits-Gates" – was zwischen Klassifikation und Order-Send liegt