Strategy ID: lcc-income-cycle
Status: Research — not yet cleared for productization. Walk-forward validation required.
Intended Consumer: feature-developer (Raptor/Antlers implementation), software-architect (service design)
Date: 2026-05-05
The Layered Covered Call Income Cycle is a paper-trading strategy module that manages a share position paired with weekly covered calls distributed across multiple strike zones. The strategy tracks premium income, assignment events, tax-lot selection, and re-entry decisions as explicit, rule-based state transitions.
It is NOT a prediction engine. It does NOT recommend trades. It enforces a pre-committed rule set against a paper-trading position and records what happened.
{
"sentiment_label": "string (Neutral | Bullish | Bearish | HighUncertainty)",
"ticker": "string",
"options_chain": {
"expiration_date": "string (ISO 8601 date)",
"strikes": [
{
"strike": "number",
"bid": "number",
"ask": "number",
"delta": "number",
"extrinsic": "number"
}
]
},
"underlying_open_price": "number",
"underlying_prior_close": "number"
}
{
"cycle_id": "string (uuid4)",
"proposed_call_structure": [
{"zone": "string (ITM|ATM|OTM)", "contracts": "integer", "target_delta_range": "string"}
],
"opening_sell_timing": {
"market_condition": "string",
"recommended_delay_minutes": "integer",
"reason": "string"
},
"alerts": ["array of Alert objects"],
"re_entry_decision": "ReentryDecision object or null"
}
EquityLot — on every share acquisitionShortCall — on every call sell, update at closeAssignmentEvent — on every assignmentCycle — one per income cycle, closed at expirationReentryDecision — one per re-entry evaluationAlert — every alert firedSee metrics.py for implementations of all 14 metrics.
| Failure | Impact | Detection | Resolution |
|---|---|---|---|
| Options chain data missing for target expiration | Cycle cannot start — no strikes to select | Null check on options_chain input | Surface "chain unavailable" alert; skip cycle |
| Sentiment label stale (>24h) | Wrong call structure applied | STALE_SENTIMENT_LABEL alert |
Block cycle start until label recalibrated |
| State machine consistency violation | Naked call recorded, data corruption | NAKED_CALL_DETECTED + STATE_CONSISTENCY_ERROR alerts |
Block all actions; require manual reset |
| Early assignment (undetected) | Shares removed without system tracking | Daily extrinsic check; EARLY_ASSIGNMENT_RISK alert |
Process as standard assignment; log early_assignment_flag=true |
| Options data vendor outage | Can't price open calls for exit evaluation | Circuit breaker: mark data as stale; surface warning | Hold state; do not auto-act on stale data |
| LIFO lot selection diverges from broker | P/L attribution wrong | Documented known limitation | CPA disclaimer; verify per broker before live |
| Metric | Alert Threshold | Frequency |
|---|---|---|
| Consecutive below-cost assignments | ≥3 in 30 days | Per-cycle |
| Consecutive all-ITM weeks | ≥3 | Per-cycle |
| NAKED_CALL_DETECTED alert | Any firing | Real-time |
| Assignment surprise rate | >20% of assignments were not flagged as "high assignment risk" 24h before | Weekly |
| Premium below minimum rate | >40% of cycles unable to meet minimum premium criterion | Weekly |
| State machine error rate | Any | Real-time |
| Scale | Compute per Cycle | Storage per Year |
|---|---|---|
| 1 user, 1 ticker | < 10ms (pure Python, no ML) | ~500KB (JSON ledger) |
| 10 users, 1-3 tickers each | < 100ms aggregate | ~5MB |
| 100 users, 1-5 tickers each | < 1s aggregate | ~50MB |
| 1,000 users | Recommend async Celery task per user cycle | ~500MB |
Storage is append-only ledger records. Historical backtest artifacts are stored separately
under docs/data-science/backtests/lcc-income-cycle/ and not part of the per-user runtime storage.
This module is broker-agnostic. It operates on: - Share counts and lot records (internal state) - Options chain data (passed in from whatever data source the platform uses) - Fill confirmation (paper fills provided by the MBT paper-trading engine)
No brokerage vendor name appears in user-facing copy. The broker is plumbing. Raxx is the strategy layer.
For live trading (Phase 4, after paper validation): the paper-trading engine's fill confirmations are replaced by actual broker fill events. The strategy module itself does not change.
feedback_deterministic_execution_ai_augments.md.feedback_no_forward_looking_framing.md.Phase 1 — Manual Paper Sim (minimal Raptor work):
- Data schema (Postgres tables matching data-schema.md)
- State machine service (Python, deterministic, no ML)
- Alert log table + basic alert surfacing in Antlers dashboard
- AMZN single-ticker support only
Phase 2 — Chain Ingestion + Delta Filters: - Options chain ingestion from Alpaca options API - Delta-based strike selection (pull nearest strike in each zone's delta range) - Earnings calendar check (Alpaca corporate events) - Dashboard metrics (all 14, computed from ledger)
Phase 3 — Recommendations + Alerts + Backtest: - Automated opening bell rule (condition classification, delay logic) - Full alert routing (push notifications for WARNING+) - Backtest runner (Python script, not real-time) - Walk-forward validation report
Phase 4 — Live Broker Integration: - After Phase 3 walk-forward passes - Alpaca live options order submission (or BYOB equivalent) - This phase is out of scope for the current research handoff
| Dependency | Required By | Notes |
|---|---|---|
| Alpaca Market Data API | Phase 1+ | Daily OHLCV, corporate events |
| Alpaca Options API | Phase 2+ | Live options chain; verify options data tier |
| ORATS historical options | Backtest only | License required; do not use without confirmed subscription |
| MBT paper-trading engine | Phase 2+ | Fill simulation; see docs/architecture/mbt-paper-trading-engine.md |
| Postgres + TimescaleDB | Phase 1+ | Ledger storage; workflow UUID tracing per project_workflow_uuid_tracing_decisions.md |
This model card is a research handoff document. It does not constitute investment advice. The strategy described is a paper-trading simulation. Real-world outcomes will differ.