Raxx · internal docs

internal · gated

Layered Covered Call Income Cycle — Strategy Spec

Strategy ID: lcc-income-cycle Status: Ready for feature-dev handoff Date: 2026-05-05 Last Updated: 2026-06-05 (OQ-1 through OQ-5 locked) Analyst: Data-scientist agent (Raxx) Source Brief: Kristerpher's "Covered Call Income Cycle" brief, 2026-05-04 Tracking Issue: #3282


Delta from pre-OQ state — 2026-06-05

The five open questions that blocked engineering handoff are now operator-locked. The most material change is OQ-2: early assignment is treated as an educational opportunity, not a system problem to auto-resolve. Kristerpher's framing — "early assignment is sometimes favorable — buy back lower" — explicitly rejects any autonomous buyback and shapes the entire early-assignment UX as a 4-option operator decision card, not a notification. That is operator pushback that materially reshapes the feature, not just a parameter tweak.

The other locked decisions: - OQ-1 introduces per-strategy-instance config for max roll debit (no longer a single global cap), with a system-level refusal + operator-decision path above the threshold. - OQ-3 resolves the "uncovered" ambiguity: absolutely no call sold against those shares. The far-OTM hedge-write option is explicitly not the intent. - OQ-4 + follow-up establishes that sentiment commits at market-open and expires at the next market-open (one market day). Staleness fallback is High-Uncertainty (ATM-only single-strike), not a strategy pause — strategy continues with the conservative default. - OQ-5 changes the default from LIFO to FIFO (IRS default when lot method is not specified), with per-trade override available at the order ticket.

Tax-lot default change (LIFO → FIFO) has a schema and backtest validity implication: existing LIFO-run backtest artifacts remain valid as-is because they used an explicit method. Any new backtest runs should use FIFO as the default unless LIFO is explicitly specified. Feature-developer must update the SimulationConfig default and the DB column default in the schema migration.


Decisions locked 2026-06-05

All five OQs from the original spec (Section 15) and failure-modes.md are now resolved. The locked answers are operator-authoritative — not defaults, not proposals.

OQ-1 — Maximum debit on roll

Locked: Per-strategy-config. Default = 50% of original credit collected. Operator can override per LCC instance in settings (for example, AAPL LCC 75%, TSLA LCC 30%, KO LCC 50%). Above the configured threshold, the system refuses the roll and notifies the operator; the operator then decides: take assignment, close-and-reopen, or issue a one-shot override for this specific roll.

Prior state: global max_roll_debit = $0.25/share hardcoded in SimulationConfig. New state: max_roll_debit_pct_of_original_credit: float per strategy instance; default 0.50. Dollar value is derived at roll-decision time as original_credit * max_roll_debit_pct. The $0.25/share figure in the old config is retired.

OQ-2 — Early assignment workflow

Locked: Educational walkthrough + 4 explicit options; no autonomous action.

On early assignment: 1. Full-screen card opens. 2. "Why this happened" — explainer surfaced by the system: deep-ITM call exercised, ex-dividend date tomorrow, etc. The system shows which trigger applied. 3. "Why no panic needed" — operator collected the credit; shares were delivered at the strike; P/L summary is shown (premium collected + assignment P/L). 4. Operator picks one of 4 options: - Buy back at market (operator sets limit price) - Buy back lower (set alert price; system notifies when price is reached) - Switch underlying - Close LCC, take the win

Per feedback_deterministic_execution_ai_augments — no auto-buyback under any condition. The framing is explicit: "early assignment is sometimes favorable — buy back lower." This is operator pushback on the original proposal that early assignment = bad outcome. The UX must not frame it as an error or an emergency.

P/L recording and lot selection follow Section 9 (standard assignment). The early_assignment_flag = True is set and the reason is logged. State machine does not advance to re-entry evaluation automatically; it waits for the operator's 4-option selection before any follow-on action.

OQ-3 — Uncovered shares behavior (strategy position drops below configured N)

Locked: Auto-disable LCC and alert when shares drop below configured N. Push + email alert. Operator decides: buy shares back and re-enable, reconfigure for smaller N, or close strategy. No autonomous re-sizing.

This also resolves the original ambiguity about what "uncovered" means in the call structure: absolutely no call is sold against those shares for that cycle. A far-OTM hedge-write is not the intent.

When shares < N_configured: - All pending call sells for the current cycle are blocked. - LCC strategy instance is set to DISABLED state. - Push notification + email alert sent. - Operator dashboard shows the 3-option card. - System does not re-enable automatically; requires explicit operator action.

OQ-4 — Sentiment staleness threshold

Locked: 1 market day. Sentiment label commits at market-open and is valid until the next market-open. A label set Friday evening is valid for Monday trading (covers the weekend gap by design).

Staleness fallback: Default to High-Uncertainty (most conservative — ATM-only single-strike, widest safety margin) + notify operator. The strategy continues with the conservative default rather than pausing. Operator can override the fallback by providing a fresh sentiment label before the cycle executes.

The staleness check runs at the start of each cycle, after the opening bell window (T+10). If the label is stale, the system logs STALE_SENTIMENT_LABEL, falls back to High-Uncertainty, and fires the notification. The fallback is deterministic — no AI inference about what the label "should" be.

OQ-5 — Default tax-lot selection

Locked: FIFO (IRS default when lot method is not specified). Per-trade override available in the order ticket. The global default changes from LIFO (original research default) to FIFO.

Configurable per LCC instance and overridable per trade. LIFO, HighCost, LowCost, and SpecificID remain supported as override values. SpecificID requires the operator to pre-select the lot at the order ticket before confirming.

Schema implication: default_lot_method column default changes from LIFO to FIFO. Feature-developer must include a migration that alters the default; existing rows that explicitly stored LIFO are not changed (they were operator choices). The models.py SimulationConfig default also changes to LotMethod.FIFO.


1. Plain-English Summary

The Layered Covered Call Income Cycle (LCC) sells short-dated (typically weekly) covered calls against an owned share position using a multi-strike distribution rather than a single ITM strike. The distribution varies by the trader's pre-committed sentiment label (Neutral / Bullish / Bearish / High-Uncertainty). The system tracks premium income, realized P/L from assignment, tax-lot accounting, and re-entry decisions as explicit state transitions — not discretionary judgments made under pressure.

This is a paper-trading simulation first. No live broker calls are part of this specification.


2. Hypothesis

Primary: A layered strike distribution (1-2 ITM + 1-2 ATM/OTM + optional uncovered shares) produces higher net cycle P/L per week than a single full-ITM covered call structure, primarily by preserving some upside participation and reducing the frequency of below-cost assignment.

Secondary: Pre-committed sentiment labeling (applied before market open) outperforms reactive strike selection at open, because it removes intra-day anchoring bias from the execution path.

What "better" means in this context: Net cycle P/L = premium collected + assignment proceeds + unrealized equity change, compared against a baseline of (a) buy-and-hold, (b) single-strike full-ITM covered call, and (c) selling no calls at all.

Neither hypothesis constitutes a prediction. Both are empirically testable on historical paper-trade data after simulation is instrumented.


3. Universe and Scope

Parameter Value
Initial ticker AMZN
Expansion universe (Phase 2+) High-liquidity large-cap: AAPL, MSFT, NVDA, SPY, QQQ
Liquidity screen Avg daily option volume > 50K contracts; bid/ask spread on ATM weekly ≤ $0.05
Expiration focus Weekly (Fri exp); monthly allowed for rolls
Share cap (paper) 800 shares per ticker (configurable)
Call constraint Short calls ≤ floor(shares / 100); no naked calls unless explicitly enabled
Excluded periods Within 5 calendar days of confirmed earnings date
Tax-lot method Default FIFO (IRS default); supports LIFO, HighCost, LowCost, SpecificID per-instance or per-trade

4. State Machine — Position Lifecycle

States and legal transitions:

NO_POSITION
  → [buy shares] → LONG_SHARES

LONG_SHARES
  → [sell calls] → CC_ACTIVE
  → [no calls sold] → LONG_SHARES (hold)
  → [shares < configured N] → DISABLED (auto-disable; alert fired)

CC_ACTIVE
  → [DTE > 2] → CC_ACTIVE (monitor/roll)
  → [DTE ≤ 2] → NEAR_EXPIRATION
  → [early assignment detected] → EARLY_ASSIGNMENT_PENDING

NEAR_EXPIRATION
  → [all calls OTM at exp] → LONG_SHARES (calls expire worthless, keep premium)
  → [≥1 call ITM at exp] → ASSIGNED (partial or full)
  → [manual buy-back before exp] → LONG_SHARES or CC_ACTIVE

EARLY_ASSIGNMENT_PENDING
  → [operator selects option 1: buy back at market] → LONG_SHARES or NO_POSITION
  → [operator selects option 2: buy back lower + alert set] → CC_ACTIVE (waiting)
  → [operator selects option 3: switch underlying] → NO_POSITION
  → [operator selects option 4: close LCC, take the win] → NO_POSITION

ASSIGNED
  → [re-entry criteria met] → LONG_SHARES (re-buy shares, restart cycle)
  → [re-entry criteria not met] → NO_POSITION (wait)

DISABLED
  → [operator re-enables after resolving share count] → LONG_SHARES
  → [operator closes strategy] → NO_POSITION

NO_POSITION
  → [re-entry criteria met] → LONG_SHARES

Each transition records: timestamp, price, lot affected, P/L contribution, reason code.


5. Sentiment Label — Pre-Committed Before Open

Sentiment is assigned the night before or pre-market. Once committed, it governs the day's call structure. It cannot be changed intra-day except by a manual override with a logged reason.

Staleness rule (locked 2026-06-05): A sentiment label is valid for exactly one market day — from the market-open at which it was committed until the next market-open. A label set Friday evening is valid for Monday's cycle. If no fresh label has been committed before a cycle starts and the existing label is stale, the system falls back to High-Uncertainty (ATM-only single-strike) and notifies the operator. The strategy continues under the fallback; it does not pause.

Label Condition Examples (pre-committed) Call Structure (per 400 shares)
Neutral No strong directional view 2 ITM + 2 ATM/OTM
Bullish Price near support, positive sector flow 1 ITM + 2 OTM + 100 uncovered (no call sold against uncovered shares)
Bearish Price near resistance, negative macro 3 ITM + 1 ATM
High-Uncertainty Macro event, FOMC, sector catalyst; or staleness fallback 1 ITM + 1 ATM (ATM-only single-strike, widest safety margin)

"Uncovered" means no call is sold against those shares for that cycle. A far-OTM hedge-write is not permitted under the uncovered allocation.

Sentiment label is the trader's pre-committed input — not a model output. The system enforces whatever structure corresponds to the label. The AI layer may surface historical context (prior weeks at similar price/IV, prior assignment outcomes) but does not override the label.


6. Strike Selection Rules

Zone Delta Range Use Case
ITM 0.60 – 0.75 Neutral-bearish; high premium; assignment more likely
ATM 0.45 – 0.55 Balanced premium vs. upside
OTM 0.25 – 0.40 Bullish tilt; lower assignment probability

Delta is measured at time of entry (post open, after price-action check per Section 7). Targets are approximations; nearest tradeable strike within ±$2.50 is acceptable if delta constraint is met.


7. Opening Bell Execution Rule

The system does not fire sells at market open. Price-action check runs 10 minutes after open:

Market Condition at T+10 Action
Flat (±0.3% vs prior close) Sell per sentiment structure immediately
Gap up >0.5% Wait 10–30 min; target strikes at ATM/OTM from new price
Gap down >0.5% Sell fewer calls (reduce by 1 contract) or raise strike by one step; log reason
Gap down >1.5% Hold all sells; re-evaluate at T+30; log "gap-down hold"

"Gap" is defined as (open – prior close) / prior close. Thresholds are configurable. The system logs the condition at T+10 and the resulting action for every cycle.


8. Exit / Roll Decision Tree

This tree is pre-committed. The system flags when a condition is met; the trader confirms (Phase 1–2) or the rule executes automatically (Phase 3+, paper only).

Premium remaining > 85% of original credit?
  No → hold
  Yes → buy back (lock profit)

Stock dropped significantly after sell?
  Extrinsic value < $0.10?
    Yes → buy back (cheap); recapture upside
    No → hold

Stock surged; short call now deep ITM?
  Roll debit <= max_roll_debit_pct × original_credit?
    Yes → roll up (same expiration) or roll out (next week, same/higher strike)
    No → system refuses roll; notify operator:
           options: take assignment / close-and-reopen / one-shot override

Assignment acceptable at this strike?
  Strike >= cost basis of lot being assigned?
    Yes → let assignment happen; log as "intentional exit"
    No → evaluate roll or close; log as "below-cost assignment risk"

DTE = 0 (expiration day) and call is ITM?
  → Assignment expected; prepare lot selection and re-entry criteria check

9. Assignment Handling

9a. Standard assignment (at expiration)

  1. Remove 100 shares per assigned contract from position, applying the configured tax-lot method (FIFO default; configurable per instance or per-trade override).
  2. Record: assignment price, lot cost basis, assignment P/L = assignment price − lot cost basis.
  3. Add premium already collected to premium_collected ledger (premium was booked at sale, this step finalizes the cycle entry).
  4. Evaluate re-entry criteria (Section 10).
  5. Rebuild call layer on remaining shares if any remain.

Assignment is a designed outcome, not a failure. The system never marks assignment as a loss event automatically — the P/L calculation determines outcome.

9b. Early assignment (American-style options)

Early assignment occurs when extrinsic value approaches zero — most commonly for deep ITM calls. The trigger is detected when extrinsic ≤ $0.05 on any open short call.

On early assignment detection:

  1. State transitions to EARLY_ASSIGNMENT_PENDING.
  2. A full-screen card opens in the operator dashboard.
  3. The card shows: - "Why this happened": the system identifies and shows the applicable trigger (deep ITM with near-zero extrinsic, ex-dividend date tomorrow, etc.). - "Why no panic needed": credit collected is shown; shares-delivered-at-strike framing; full P/L summary (premium collected + assignment P/L on the lot).
  4. Operator selects one of 4 options: - Buy back at market — operator sets a limit price; system queues the buyback. - Buy back lower — operator sets a target price; system sets an alert and waits. - Switch underlying — closes this LCC instance; operator navigates to a new setup. - Close LCC, take the win — closes the strategy, logs final P/L, state goes to NO_POSITION.

No autonomous action is taken pending the operator's selection. The state machine does not advance to re-entry evaluation until the operator has made a choice.

P/L recording and lot selection use the same logic as standard assignment (9a). early_assignment_flag = True and the trigger reason are written to the AssignmentEvent.

The operator framing must not present early assignment as an error or emergency. The copy should reflect that early assignment is sometimes the favorable outcome.


10. Re-entry Criteria (All Must Pass)

Criterion Check
Ticker still in approved universe Y/N lookup
Price not extended Price ≤ 1.5× 20-day avg range above prior close
New premium justifies cycle ATM weekly credit ≥ 0.3% of current stock price
No concentration breach Post-re-entry share count ≤ configured max (800 default)
No binary risk event within 5 days Earnings calendar check
Not flagged for "chasing" ≤2 re-entries in same week at higher price than assignment

If any criterion fails, state remains NO_POSITION and the system logs which criterion blocked re-entry.


11. Risk Controls (Formalized)

Control Rule Action on Breach
Covered call constraint short_calls ≤ floor(shares / 100) Block sell order; log
Max share exposure shares ≤ 800 per ticker (paper default) Block re-entry; alert
Shares below configured N shares < N_configured Auto-disable LCC; push + email alert
High assignment risk flag ITM call, DTE ≤ 2, extrinsic < $0.15, delta > 0.75 Alert: "high assignment risk"
Ex-dividend flag Ex-div date within 3 calendar days Alert: early assignment risk elevated
Below-cost assignment flag Strike < lot cost basis Alert: "below-cost assignment"
Consecutive below-cost assignments ≥ 3 in rolling 30 days Alert: "pattern flag — review strategy sizing"
Consecutive all-ITM sells ≥ 3 weeks in a row Alert: "overuse of ITM — review sentiment labels"
Re-entry recency flag Re-entered within 1 trading day of assignment Alert: "immediate re-entry — chasing pattern?"
Opening-bell sell flag Sell executed within 5 min of open Alert: "opening bell sell — price action check bypassed"
Roll debit exceeded Debit > max_roll_debit_pct × original_credit Refuse roll; notify operator with 3 options
Stale sentiment label Label older than 1 market day at cycle start Log STALE_SENTIMENT_LABEL; fallback to High-Uncertainty; notify operator

12. Edge Cases

EC-1: Early assignment (American-style) — Resolved by OQ-2. See Section 9b. Early assignment triggers the 4-option full-screen card. State transitions to EARLY_ASSIGNMENT_PENDING. No autonomous action. The system identifies the trigger (deep ITM with near-zero extrinsic, ex-dividend proximity, etc.) and shows it in the "why this happened" explainer.

EC-2: Dividend-driven early assignment If AMZN reinstates a dividend (currently none), holders of short calls with strike below ex-div price may be assigned the night before ex-div. The system should block new ITM call sells within 5 days of any future ex-div date for dividend-paying tickers in the universe. No current risk for AMZN but architecture must support it. When this fires as the early-assignment trigger, it is shown in the "why this happened" explainer.

EC-3: Trading halt during expiration If the underlying halts on expiration Friday, OCC standard procedure is to extend the expiration window. The system should not auto-act on expiration day until after 4:00 PM ET confirmation. Paper-trading state machine holds at NEAR_EXPIRATION until confirmed.

EC-4: IV crush post-event If IV collapses after a catalyst (e.g., broad market spike, FOMC), extrinsic on open calls evaporates. The system should detect when a held call's extrinsic drops >50% intra-day and alert: "IV crush — consider closing for residual credit."

EC-5: Assignment on fractional cycle Brief assumes 100-share lot multiples. If re-entry results in a share count not divisible by 100 (e.g., 470 shares), the system must track the "uncovered" remainder and not flag it as a naked call.

EC-6: Roll creates net debit above threshold — Resolved by OQ-1. The max_roll_debit_pct_of_original_credit per-instance config (default 0.50) is the gate. Above the threshold: system refuses the roll and notifies the operator with three options: take assignment, close-and-reopen, or one-shot override. The $0.25/share global default from the original spec is retired.


13. Data Requirements

See data-schema.md in this strategy directory for the full schema. Summary of required inputs:

Data Type Source Cadence License Note
Equity OHLCV (daily) Alpaca Market Data (or broker equivalent) Daily Included in Alpaca subscription
Options chain (strikes, bid/ask, delta, expiration) Alpaca Options API / ORATS / Tradier Per-cycle (weekly) ORATS historical requires subscription; verify before backtest
Earnings calendar Alpaca / Refinitiv / EarningsWhispers Weekly scan Free tier available; verify staleness
Ex-dividend dates Alpaca corporate actions feed As-announced Included
IV rank / IV percentile Derived from options chain or ORATS Per-cycle Derived = no license issue
Tax-lot records Internal (paper sim generates these) Per-trade Internal only

14. Assumptions Requiring Explicit Acknowledgment

These assumptions are embedded in the reference implementation. They are not predictions — they are modeling choices that affect backtest validity:

  1. Fill at mid: Paper fills modeled at bid/ask midpoint. Real slippage may be $0.02–$0.05 worse per contract depending on spread and time of day.
  2. No partial fills: The simulation assumes full-lot fills. Real execution on illiquid strikes may see partial fills.
  3. Assignment at 4:00 PM ET strike price: OCC exercise is based on intrinsic value at 4:00 PM ET close. After-hours moves can create unexpected assignment/non-assignment; paper sim uses 4:00 PM price.
  4. Commission: $0.65/contract (Alpaca default as of 2026). Configurable in backtest config.
  5. Tax treatment: P/L tracking is gross of tax. The tax-lot method simulation tells you which lots are assigned but does not calculate capital gains tax owed. A CPA, not this system, handles tax.
  6. FIFO paper default (updated 2026-06-05): FIFO is the default lot method in the simulation, matching the IRS default. Actual broker FIFO eligibility may differ; paper sim is illustrative. Historical backtest runs that used LIFO explicitly remain valid under their original config.
  7. Delta at entry: Delta is snapshot at the moment of sell execution. Delta drifts continuously; the system does not delta-hedge.

15. Open Questions (Resolved)

All OQs from the original spec are now operator-locked. See "Decisions locked 2026-06-05" section at the top of this document and the updated failure-modes.md for full context.


16. Cited References


Engineering handoff

Status: Ready for feature-developer. All 5 OQs resolved. PM should file the following atomic engineering cards. Each item is a shippable unit; they can be worked in parallel after the schema migration lands.

Tracking issue: #3282

Card Description Effort estimate
LCC-ENG-1: Schema migration Add per-strategy max_roll_debit_pct_of_original_credit config column (float, default 0.50); change default_lot_method column default from LIFO to FIFO; add EARLY_ASSIGNMENT_PENDING and DISABLED states to position state enum; add early_assignment_flag + early_assignment_reason to AssignmentEvent table. 0.5 day
LCC-ENG-2: Roll engine update Replace the global max_roll_debit dollar cap in evaluate_exit_or_roll with the per-instance max_roll_debit_pct_of_original_credit logic. On threshold breach: return "refuse_roll" action + message; fire operator notification with 3-option card (take assignment / close-and-reopen / one-shot override). 1 day
LCC-ENG-3: Early assignment educational UI Full-screen card in Antlers triggered by EARLY_ASSIGNMENT_PENDING state. Three sections: "why this happened" (system-identified trigger), "why no panic needed" (credit + P/L summary), and the 4-option selector. Operators must be able to reach this card from the push notification. No autonomous action at any point; all paths require explicit selection. 2 days
LCC-ENG-4: Uncovered-shares auto-disable In state machine: when position.total_shares < strategy.n_configured_shares, transition to DISABLED, block all pending call sells, fire push + email alert. Operator dashboard shows 3-option card: buy shares back + re-enable / reconfigure N / close strategy. 1 day
LCC-ENG-5: Sentiment expiry fallback Update sentiment staleness check to use 1-market-day window (not clock-24h). On staleness: log STALE_SENTIMENT_LABEL, fall back to High-Uncertainty call structure (ATM-only single-strike), fire notification. Strategy continues; does not pause. Note: High-Uncertainty fallback structure under OQ-4 is ATM-only single-strike — more conservative than the Bearish-scenario High-Uncertainty structure in the original sentiment table. Verify final copy with Kristerpher before shipping. 0.5 day
LCC-ENG-6: Tax-lot FIFO default + per-trade override Update SimulationConfig.default_lot_method to LotMethod.FIFO. Surface lot-method selector in the order ticket UI (Antlers) with FIFO pre-selected; allow override to LIFO / HighCost / LowCost / SpecificID. SpecificID flow requires a lot-picker modal. Ship FIFO + LIFO + HighCost + LowCost in v1; SpecificID lot-picker can be Phase 2. 1 day
LCC-ENG-7: Integration test — full lifecycle Automated test covering: strategy setup → sentiment label commit → opening bell rule → weekly call sell → roll evaluation (including roll-refuse path) → early assignment card flow (mock operator selection) → standard expiration → re-entry evaluation → strategy disable on share drop → operator re-enable → close. Run in paper-trading sim mode with synthetic prices. 1.5 days

Total estimated feature-dev effort: 7.5 days

This is a point estimate, not a commitment. LCC-ENG-3 (early assignment UI) has the most UX unknowns and should be spec-reviewed with Kristerpher before implementation starts to confirm the 4-option card layout and copy.


This document is a research specification. It does not constitute investment advice. Performance in any tested period depends on conditions that will not be replicated. Paper-trading results do not guarantee live-trading outcomes.