Toll Booth Trading
Covered Call

Covered Call Closing

High-level summary of the Toll Booth covered call position closing process.

Entry, pre-checks, and how positions are chosen

  • The top-level close action delegates immediately to the “close option positions” workflow.
  • If the account is a retirement account and option buying power is below the minimum threshold, the workflow stops and reports that closing is blocked due to insufficient buying power on a retirement account.
  • If the user is not allowed to place closing orders, the workflow stops and reports that closing orders are disabled for the user.
  • If a symbol is provided, positions for that symbol are selected. Otherwise, it selects option positions already marked as “close-ready,” orders them to prioritize ones tried fewer times, then reduces to one position per underlying and randomizes the order. If none are found, it reports that no ready positions were found.

First loop iteration (only the first position summarized)

  • It increments a “close attempts” counter on the position (and the related trade) and persists it, ensuring the same symbol won’t be starved in the next pass.
  • It calls the single-position close function:
    • Ensures the underlying option data is current (refreshes pricing when needed).
    • Obtains a “closing trade resource” for the position. If this resource is missing or returns a simple message instead of an object, that message is returned as the reason and the attempt for this position ends.
    • The resource runs its “close-order readiness” checks. If any check fails, it returns a plain-language reason indicating what blocked the close, and the attempt ends with that reason.
    • If the resource is ready:
      • The flow builds a “close order” from the resource. For non-spread single-leg options, it may automatically switch to a STOP_LIMIT order type if the pricing model indicates a stop-limit should be used.
      • Before sending the order, it proactively sets both the trade and the position to not close-ready to avoid loops and duplicate attempts.
      • It runs an “order-level readiness” pipeline on the built order that validates price sanity, checks for conflicts with other active orders, and validates the leg collection. If anything is off, a descriptive reason is returned and the attempt stops.
      • If the order passes that order-level readiness, it places the order with the broker and treats the attempt as successful.
  • After that first attempt result:
    • If successful, it increments a “closed” count, and records on the position that a closing order was placed today.
    • If unsuccessful, it records the reason (for example: a “not-ready” reason, or a quantitative threshold not met) and may suppress noisy notifications for common threshold messages.

How the position gets marked close-ready (“closeReady”)

Close-ready is set by the option close-ready pipelines that run during position reconciliation. The top-level “option close-ready” pipeline delegates to either a spread-specific close-ready pipeline or to a non-spread close-ready pipeline. The non-spread pipeline is the one that directly flips the position flags:

  • If the non-spread pipeline says “no” (returns a reason), it sets “non-spread close-ready” to false and stores the reason (and, if not already set, also stores it as the general close reason).
  • If the non-spread pipeline allows closing (returns no reason), it sets “non-spread close-ready” to true and also sets the position to close-ready true.
  • For short CALL/PUT legs, an internal flag closeStopLimitReady tracks when STOP_LIMIT-style exits are appropriate. In most cases this flag is advisory only, but when STOP_LIMIT is not yet appropriate and the implied closing LIMIT would be far from the current mark, it can keep the position from being treated as close-ready so we avoid slow, unlikely-to-fill LIMIT closes.

Top-level option close-ready checks (how the gate works)

  • If the user is not allowed to place closing orders, do not mark the position close-ready.
  • If there is ex-dividend risk on the trade, cancel any active closing orders tied to this risk and then skip setting the position close-ready (ex-dividend risk is handled outside normal closes).
  • If there are already any active orders on the trade, skip marking close-ready (prevents duplicates).
  • If there are already active closing orders, skip marking close-ready (prevents duplicates).
  • If the position’s available quantity is zero or below, skip marking close-ready.
  • If active closing-instruction order quantity is already greater than or equal to the position quantity, skip marking close-ready.
  • If the position is a spread:
    • Try the spread close-ready pipeline first (vertical spread readiness checks, then diagonal fallback). If it says “ready as a spread,” allow closing as a spread. If it says “not ready,” fall back to the non-spread pipeline.
  • Then run the non-spread close-ready pipeline (summarized below).
  • Precedence: diagonal close-ready takes priority over non-spread roll-ready.
  • Guard: diagonal close-ready does not apply when a same-expiration vertical close is available, except when ex‑dividend risk is present or either leg has DTE 0.

Non-spread option close-ready pipeline (single-leg close readiness)

This pipeline returns a string only when it wants to block closing; otherwise, it returns nothing to indicate “go ahead.” Key checks include:

  • Do not try to close a leg if, across currently active positions, the net position for that same option type (e.g., long puts, short puts, long calls, short calls) is already less than or equal to zero. This prevents closing a leg when it’s effectively already netted out by other legs.
  • Risk/buying-power checks that block closing in situations where closing would be inconsistent with risk policy. Examples include:
    • For long puts: if the “downside move” loss estimate is above certain thresholds, block the close (they’re protected hedges).
    • For long puts when buying power is too low: block to preserve hedges until buying power improves.
    • For short puts: if “upside move” risk isn’t sufficiently favorable (or configured as too high), block, unless other liquidation rules say to close.
    • For short puts with very low extrinsic value or where buying power rules force liquidation: allow closing earlier.
    • For very high mark prices on certain puts (e.g., deep in the money), allow closing with stop-limit logic.
  • Relationship to spreads and nearby partners:
    • If there are nearby vertical partners (e.g., you’re in part of a vertical), avoid setting close-ready in a way that would teardown spread structures unintentionally. Also prefer not to close one leg when it would over-close versus its related partner.
  • Earnings proximity and time-to-expiration:
    • If earnings are tomorrow or the next day, block some close actions to avoid poor executions from event volatility (with exceptions for low-value calls).
    • For near-expiration long or short legs, a series of “same-expiration” net-quantity rules ensure you don’t close the wrong side or leave a spread badly imbalanced.
  • Retirement account safeguards:
    • For retirement accounts, short positions with excessive mark values or over certain allowance thresholds will be evaluated to determine if closing should be forced or blocked per the policy.
  • If none of the above returns a blocking reason, the pipeline allows non-spread close-ready to be set true, and thus overall close-ready becomes true.

Single-position close attempt readiness checks

When the position is being closed, the workflow builds a “closing resource” and validates three categories of readiness before placing the order:

1) Instrument-level close-order readiness (base instrument)

  • Do not proceed if:
    • The trade or quote says “do not close.”
    • The trade has too many recent broker rejections.
    • The position doesn’t exist or its quantity is below 1.
    • The position is flagged “do not close.”
    • The position record is stale and needs refresh.
    • The trade record is missing.
    • The position is not actually marked close-ready anymore.
    • There are enough active closing-instruction orders to cover the entire position quantity already.
    • The close price basis is missing and cannot be populated.
    • The close price basis equals an active opening order on the same trade (to avoid crossing/matched orders).
    • The close price basis is zero or negative.
  • If all of the above checks pass, the instrument declares itself “close-order ready.”

2) Option-specific close-order readiness (base option)

  • Do not proceed if:
    • The option contract is in a “failed” state.
    • The position is already expired.
    • There are active rolling orders (those take precedence over simple close).
    • The resource’s put/call type does not match the trade type (safety check on leg type).
    • For near-expiration “very far out-of-the-money” contracts, skip some closes (to avoid noise and poor fills).
    • For non-spread short calls or short puts, if existing active closing-order quantity already exceeds your net short position for that symbol/type, block the close to prevent over-closing.
  • If none of the above triggers, the option-specific layer declares itself “close-order ready.”

3) Order-level readiness (the order object about to be sent)

  • Do not proceed if:
    • The order has already been marked “ready” or not; this avoids rechecking repeatedly within the same attempt.
    • The price type or numeric values are invalid for the broker (e.g., decimals in prohibited increments, bad limit/stop combos).
    • The close limit vs. mark price spread is excessive according to policy (too far off mid/mark).
    • The order’s computed mark/mark-calc check fails threshold sanity.
    • Another active order exists that would conflict (same or opposite side, either for this user or across users where applicable).
    • There is no leg collection, or the leg collection itself fails its own readiness pipeline.
  • If all pass, the order is “ready” to place.

4) Non-rolling order leg readiness (each leg of a non-rolling order)

  • Do not proceed on a leg if:
    • The leg object is missing or malformed.
    • The trade context isn’t loaded.
    • The leg quantity is not numeric or is zero/negative.
    • For closing instructions, the related position quantity is missing (indicates data problem).
    • You already have active closing-instruction orders with total quantity that meets or exceeds the position quantity (the system will cancel or block duplicates per policy to avoid over-closing).
    • The asset type for a leg is not an option where required (safety guard).
  • If all legs are fine, the leg collection is considered ready.

What “success” vs “failure” looks like (first loop)

  • Success path: The position’s trade resource and order pass all readiness checks, the order is placed, and the system marks the position and trade as not close-ready (to avoid duplicates), logging that a close order was placed.
  • Failure path: Any of the readiness checks (position, instrument, option, order-level, or leg-level) can return a message explaining why the close was not attempted. The position stores that reason. Certain “threshold-not-met” messages are intentionally not noisy to keep logs readable.

Notes about STOP_LIMIT behavior for single-leg closes

When closing a single-leg option and the pricing model signals a stop-limit exit should be used (for risk-managed exits), the workflow switches the close order to a STOP_LIMIT type automatically and sets the stop and limit prices according to that model, then validates and places the order as usual. For short CALL/PUT legs, this same STOP_LIMIT signal also feeds a position-level closeStopLimitReady flag that helps avoid marking positions close-ready when STOP_LIMIT is not yet appropriate and the candidate LIMIT would sit far from the current mark.

Overall flow shape (first pass only)

  • Gather candidate positions (close-ready or symbol-filtered).
  • For the first position:
    • Increment attempts, refresh pricing if needed, build the close resource.
    • Pass instrument-level readiness, then option-level readiness.
    • Construct the close order, mark the trade and position not close-ready to prevent duplicates.
    • Validate order-level readiness and leg-level readiness.
    • Place order if all checks pass; otherwise record the first reason why it’s blocked.

Multi-iteration, batching, and concurrency

  • The narrative above shows only the first item. After handling the first, the workflow continues over the remaining selected positions in priority order (fewer prior attempts first, one per underlying, then randomized).
  • Per-run caps and attempt counters reduce churn and rate-limit repeats on the same symbols. Trade/position flags (close-ready cleared on attempt) prevent immediate reprocessing and duplicate sends.
  • Active-order checks gate duplicates at trade/symbol/type scope; conflict detection also considers opposite-side/duplicate orders to avoid crossing or matched orders.
  • Execution is designed to be queue/worker safe; guards prevent concurrent runners from duplicating work for the same trade/position.

Market/session/time-of-day gates

  • Closes are evaluated under normal market-hours context. Pre/after-market placement is not assumed and may be gated off by policy.
  • Weekend/holiday and stale-quote guards stop order placement when market context is unsafe.
  • Near end-of-day behavior is policy-driven; readiness can block noisy/low-value closes or prefer structured roll paths instead of last-minute exits.

Close price basis and spreads (how price is derived)

  • For single-leg short CALL closes (covered-call short leg), the price basis is derived from the contract mark with strategy-specific close spread factors and sanity clamps. Randomization/recency bumps are small and applied last where configured.
  • Spread closes recompute width and expected close marks; winners honor minimum net-debit floors to avoid overpaying to exit structured positions.
  • Payload validity checks ensure increments, min/max bounds, and mark-vs-limit divergence remain within configured tolerances.

STOP_LIMIT arming and pricing model (single-leg)

  • STOP_LIMIT is only enabled above minimum mark thresholds and when PercentNetGain and other risk gates allow arming.
  • Stop is computed from current quotes (e.g., anchored to ask/last for calls); limit is set relative to stop with configurable bump/separation. Severity can widen the limit where policy allows.
  • Final safety checks validate stop vs bid/ask/last, increments, and max divergence from mark before allowing placement.

Spread-specific close-ready details

  • Two-legged readiness blocks closing when any leg PnL is negative (avoid premature loss-taking by default).
  • Winners enforce a minimum net-debit floor by width to avoid overpaying; this floor is applied when constructing spread close prices.
  • Close-ready avoids tearing down pairs unintentionally and prevents over-closing versus a related partner leg for same expiration.

Arbitration with rolling, OCO, and active orders

  • Rolling orders take precedence over simple closes; if a rolling instruction is active, the simple close path is blocked.
  • If active closing orders already cover available quantity, new simple closes are blocked or cancelled to prevent over-closing.
  • Covered-call OCO (optional): a parent OCO may manage both take-profit (LIMIT) and stop-loss (STOP_LIMIT) children under config control; it is guarded to avoid conflicts with existing close/roll logic.

Earnings and ex-dividend handling

  • When ex-dividend risk is present, related closing orders are cancelled and the position is excluded from normal simple close handling.
  • When earnings are imminent (e.g., T+1/T+2), close-ready gates can block some closes except low-value scenarios; thresholds are policy/config driven.
  • When a short call has ex‑dividend risk and annualized theoretical return is below 10 percent, the system treats it as a winning close and places a closing order to reduce assignment risk.

Post-placement lifecycle and monitoring

  • Broker events update trades/positions on fills and partial fills; OCO children auto-cancel on counterpart fill when applicable.
  • Cancel/replace and rejection handling integrate with readiness gates to prevent thrash; attempt counters and cool-downs reduce repeated rejections.
  • Position “closed today” markers and counts are updated after successful placement; reasons are recorded on failures for auditability.

Assignment risk and near-expiration rules

  • ITM short calls near expiry increase assignment risk; close-ready and order selection guard against poor exits or unintended naked exposure.
  • Same-expiration net-quantity rules help prevent closing the wrong side and leaving an imbalanced or over-closed structure.

Permissions, notifications, and noise control

  • User/account-level flags can disable closing entirely; readiness honors these gates before marking or placing orders.
  • Notifications use throttled channels; common threshold-not-met messages are suppressed or summarized to reduce noise.
  • Retirement accounts apply stricter buying-power and exposure checks; some short positions may be forced or blocked per policy thresholds.

Retry/backoff and rejection policy

  • Recent broker rejection counts can block immediate retries; cool-downs and per-run caps prevent rapid-fire re-attempts.
  • Attempt counters and recorded reasons provide traceability and help prioritize future passes across symbols.

Config surfaces (non-exhaustive)

Representative keys that influence closing behavior (naming varies by environment):

  • Close spreads/targets: trading.coveredCallCloseSpread, trading.shortOptionCloseSpread, strategy-specific close targets and floors.
  • STOP_LIMIT rules: pricing.stopLimit.minMarksByType, pricing.stopLimit.minPercentNetGainByInstrument, stop/limit bump factors, max mark/limit divergence.
  • Ordering sanity: ordering.priceSanity thresholds, active-order de-duplication gates, leg-collection readiness rules.
  • OCO feature flags (covered-call): ordering.ccOco.enable, ordering.ccOco.takeProfitNetGainPercent, ordering.ccOco.stopBelowMarkPercent, ordering.ccOco.stopLimitBelowStopPercent.
  • Earnings/ex-dividend gates and time-of-day/session guards (policy-driven).

Process flow diagram

Visual summary of the covered call closing workflow described on this page.

%%{init: {"theme":"base","flowchart":{"curve":"basis"},"themeVariables":{
  "fontFamily":"Inter, Nunito, system-ui",
  "primaryTextColor":"#e5e7eb",
  "primaryColor":"#111827",
  "primaryBorderColor":"#94a3b8",
  "lineColor":"#94a3b8",
  "tertiaryColor":"#0e1729",
  "tertiaryBorderColor":"#22d3ee",
  "edgeLabelBackground":"#00000000"
}}}%%
flowchart TB
  A([Start]) --> E1[Entry and pre-checks]
  E1 -->|Fail| OUT[Stop: retirement buying power or closing disabled]
  E1 -->|Pass| SEL{Positions selected}
  SEL -->|None| OUT
  SEL -->|Has items| L1[First position tasks]
  L1 --> R1[Increment attempts; refresh pricing; build closing resource]
  R1 --> R1V{Resource valid}
  R1V -->|No| OUT
  R1V -->|Yes| C1[1 Instrument close-order readiness]
  C1 -->|Fail| OUT
  C1 --> C2[2 Option close-order readiness]
  C2 -->|Fail| OUT
  C2 --> C3[3 Order-level readiness]
  C3 -->|Fail| OUT
  C3 --> C4[4 Non-rolling leg readiness]
  C4 -->|Fail| OUT
  C4 --> ORD[Build close order; may switch to STOP_LIMIT; set trade and position not close-ready]
  ORD --> PLACE[Place order]
  PLACE --> SUC{Placed successfully}
  SUC -->|Yes| DONE([Outcome: close order placed; counts updated])
  SUC -->|No| OUT

  classDef step fill:#111827,stroke:#94a3b8,color:#e5e7eb,stroke-width:1px;
  classDef gate fill:#0e1729,stroke:#22d3ee,color:#e5e7eb,stroke-width:1px;
  classDef out fill:transparent,stroke:#94a3b8,color:#cbd5e1,stroke-dasharray:5 3;

  class A,E1,SEL,L1,R1,R1V,C1,C2,C3,C4,ORD,PLACE,SUC,DONE step;
  class SEL,R1V gate;
  class OUT out;