Metric evaluation & risk flags
From a filtered option chain the evaluation service computes a metric (score) for each contract and sorts the candidates by it. Each candidate is additionally annotated with risk flags. The sorting is a neutral aid for overview – not a buy or sell signal and not a selection recommendation.
Annualized premium
annualized_premium_pct = (mid / strike) * (365 / dte) * 100Example: AAPL put 180, mid 2.10, DTE 30 → (2.10 / 180) * (365 / 30) * 100 = 14.2 % p.a.
Sorting by metric
Candidates are sorted by their metric in descending order and shown as a list. Sorting happens after DTE / delta / OI / volume filters are applied. You may optionally exclude strikes carrying certain risk flags (exclude_flags). Which contracts appear and in which order is controlled solely by your filter and sorting settings.
Risk flags
| Flag | Meaning |
|---|---|
earnings | Earnings date falls inside the configured window |
ex_div | Ex-dividend date is before expiry – early-assignment risk |
iv_low | IV low → small premium |
iv_extreme | IV extremely high → mean-reversion risk |
low_liquidity | Low open-interest or volume |
Default earnings window: 7 days before expiry.
Configuration
POST /api/options/medals/{SYMBOL}
{
"expiry": "2026-06-19",
"filters": { "option_type": "PUT", "dte_min": 7, "dte_max": 60 },
"top_n": 3,
"earnings_window_days": 7,
"exclude_flags": ["earnings", "iv_extreme"]
}The response carries per candidate: the contract, annualized_premium_pct and risk_flags: [].
Quick view (candidates across all expiries)
The company detail page (/research/companies/{SYMBOL}) uses an endpoint that scans every expiry inside your strategy DTE range (UserSettings.min_dte / max_dte) in a single round-trip and returns one bucket per allowed Wheel strategy (PUT-L1 if you hold no shares, PUT-L2 if you do, plus CC when shares are free for assignment).
POST /api/options/medals-best/{SYMBOL}
{
"top_n": 3,
"earnings_window_days": 7,
"min_open_interest": 0,
"dte_min": null, // optional override, otherwise from UserSettings
"dte_max": null
}The filter pipeline is identical to the cross-symbol Wheel screener (moneyness_pct=0.15, iv_extreme excluded, min ROC + strike-above-cost from your filter settings, sanity cap 200 % p.a.). Candidates can come from different expiries; each card shows its own expiry.
Reading the list
In the chain page the candidates are shown as cards on top, each with the metric, premium p.a., strike, DTE, mid, delta, IV, OI – plus the colored flag chips below.
The ATR value on a card is the ATR distance (strike distance to spot in ATR52 units), shown as e.g. 1.38× ATR – not the raw ATR52, which is a USD amount shown in the header as "ATR(52) … USD". The mid is displayed with three decimals; returns always use the unrounded mid.
Score model (detail)
The full description of the pipeline (filters → score → sorting), all sub-score weights, the target_return table, the cost_basis logic and the thresholds lives in its own topic "Scoring model (PUT L1/L2, CC)".
Short version below:
The metric uses the same scoring as the desktop app (file services/option_scoring.py, ported 1:1 from app/utils/option_utils.py). A strike is evaluated by several sub-scores; the sum is the metric used for sorting.
PUT (max 110)
| Component | Points | Idea | ||
|---|---|---|---|---|
| Premium % | 0–40 | min(40, premium_pct / target_return * 40) | ||
| ATR distance | 0–25 | `25 - | 2.5 - atr_distance | * 5` – peak at 2.5 ATR below spot |
| Open interest | 0–20 | min(20, OI / 100 * 20) – liquidity | ||
| IV | 0–15 | min(15, IV / 0.5 * 15) – higher IV = higher premium | ||
| Rule-of-16 bonus | 0–10 | Bonus if ATR daily move < VIX/16 (stock calmer than VIX implies) |
`target_return` depends on the PUT strategy:
| Strategy | When? | target_return |
|---|---|---|
| L1 | You hold no shares of the symbol | 1.5 % |
| L2 | You already hold shares (wheel phase 2) | 0.8 % |
| fallback | edge case | 0.6 % |
L2 demands a lower yield because the additional put primarily serves cost-basis reduction – not building a new position. Identical strikes therefore get a higher metric when has_stock=True.
ATR distance for puts is always measured against the current price:
atr_distance_put = (current_price - strike) / atr52Why: when selling a put you do not yet own the shares – the strike is the future assignment price. The distance measures the safety cushion relative to the current price.
Covered call (max 100)
| Component | Points | Idea | ||
|---|---|---|---|---|
| Premium % | 0–30 | min(30, premium_pct / 0.6 * 30) | ||
| Capital gain % | 0–30 | min(30, capital_gain_pct / 5.0 * 30) | ||
| ATR distance | 0–20 | `20 - | 2.5 - atr_distance | * 4` |
| Open interest | 0–10 | min(10, OI / 100 * 10) | ||
| IV | 0–10 | min(10, IV / 0.5 * 10) |
Important: for the CC the reference is your cost basis, not the current price. You already own shares and want the call to be exercised above your entry. Both ATR distance and capital gain are computed against cost_basis:
reference = cost_basis if (has_stock and cost_basis > 0) else current_price
atr_distance = (strike - reference) / atr52
capital_gain_pct = (strike - reference) / reference * 100Example CMG, cost_basis 34.00, current price 36.20, ATR52 1.31:
| Strike | distance vs. spot | distance vs. cost_basis | capital gain |
|---|---|---|---|
| 36 | -0.15 ATR (ITM) | +1.53 ATR | +5.9 % |
| 38 | +1.37 ATR | +3.05 ATR | +11.8 % |
This is why CC strikes that would have a lower metric vs. spot can still surface when you hold the underlying.
Where has_stock and cost_basis come from
The load_symbol_context service inspects your open long-stock positions for the symbol. If found, has_stock=True and the average entry price are passed to the evaluator as purchase_price.
Related
- "Options screening" (which filters exist)
- "Smart-limit & mid-drift" (how a candidate becomes a proposal with a limit)
- "Safety gates" (what is checked before sending)
Legal note
The metric is an algorithmic sorting aid. Not a buy signal, not a sell signal, not investment advice, not a recommendation. It does not replace your own review of market data and news on the underlying company.