DEV Community

tomasz dobrowolski
tomasz dobrowolski

Posted on Originally published at flashalpha.com

sell-window: An API That Flags When Not to Sell SPY, QQQ and IWM Options

Premium-selling bots usually encode a schedule: sell the 30-day put on the first of the month, sell 0DTE every morning. The hard part is not placing the order. It is knowing when to stand aside.

sell-window turns that decision into one GET request. For SPY, QQQ and IWM it returns four expiry windows, each with sell, skip or unavailable, and on a sell it lists candidate trades with strikes, bid/ask, delta, mid credit and an exit rule. Treat it as a gate in front of your own strategy, not as the strategy.

It came out of more than 5 million backtests on 2018 to 2026 minute-level option quotes. In those backtests, standing aside on skip days cut max drawdown on the same trades at the same size from $958,924 to $255,105 and lifted Sharpe from 0.61 to 0.76. Details and limitations are further down.

The request

curl -H "X-Api-Key: $FLASHALPHA_API_KEY" \
  "https://lab.flashalpha.com/v1/strategies/sell-window/SPY"
Enter fullscreen mode Exit fullscreen mode
Name In Required Notes
symbol path yes SPY, QQQ or IWM. Any other symbol returns every window as unavailable.

No query parameters. The decision is set from the previous session's close and fixed for the session. The session rolls at 16:00 ET (13:00 ET on early-close days): before the close you get the current session, after it you get the next one.

The response

Top-level fields:

Field Meaning
symbol Upper-cased symbol
session Session the answer applies to (yyyy-MM-dd)
spot Underlying mid used to pick strikes (present when a window sells)
quoted_at UTC time of the quotes (present when a window sells)
legs_indicative true when quotes predate that session's 09:35 ET entry, so re-check strikes after 09:35
windows[] Always four entries: same day, ~1 week, ~2 weeks, ~1 month
endpoint_version, data_as_of Standard response envelope

Each window:

Field Meaning
expiry, days_to_expiry Listed expiry closest to the window target; absent if none falls in range
action sell, skip or unavailable
candidates[] Only on sell: structure, exit, legs[], mid_credit
legs[] side, quantity, type, strike, delta, bid, ask (quantity is 2 on the sold body of put_bwb, else 1)
mid_credit Net credit per share at mid. Multiply by 100 for dollars per unit.

Window ranges:

Window Target Accepted
Same day 0 days 0
~1 week 7 days 4 to 10
~2 weeks 14 days 11 to 17
~1 month 30 days 25 to 35

Structures: short_put, short_call, short_strangle, short_straddle, tail_hedged_put, put_credit_spread, put_bwb.

Exits: expiry (hold to expiration) or tp50 (close once it can be bought back for half the credit or less, otherwise hold).

A skip day is short. This is the first window from SPY on 5 November 2026, where all four windows skipped:

{ "expiry": "2026-11-05", "days_to_expiry": 0, "action": "skip" }
Enter fullscreen mode Exit fullscreen mode

A minimal client

import os
import requests

API = "https://lab.flashalpha.com/v1/strategies/sell-window/{}"
HEADERS = {"X-Api-Key": os.environ["FLASHALPHA_API_KEY"]}


def sell_window(symbol: str) -> dict:
    r = requests.get(API.format(symbol), headers=HEADERS, timeout=10)
    r.raise_for_status()
    return r.json()


for sym in ("SPY", "QQQ", "IWM"):
    d = sell_window(sym)
    flag = " (indicative, re-check after 09:35 ET)" if d.get("legs_indicative") else ""
    print(f"{sym} session {d['session']}{flag}")

    for w in d["windows"]:
        print(f"  {w.get('expiry', '-'):>10}  {w['action']}")
        for c in w.get("candidates", []):
            legs = " / ".join(
                f"{l['side']} {l['quantity']}x {l['strike']} {l['type']}" for l in c["legs"]
            )
            print(f"      {c['structure']:<18} exit={c['exit']:<6} "
                  f"${c['mid_credit'] * 100:,.0f}  {legs}")
Enter fullscreen mode Exit fullscreen mode

Treat unavailable exactly like skip. Budget three calls per poll (one per symbol), and note that rejected requests, including 403s, count toward your daily quota.

Status When
401 Missing or invalid key (empty body)
403 tier_restricted Plan below the required tier
429 Quota exceeded Daily quota used; Retry-After gives seconds to the 00:00 UTC reset

Strike selection, so you can reproduce it

Every leg must be out of the money, have a bid above zero, a bid/ask spread no wider than 20% of mid, and open interest of at least 250. Among qualifying contracts the leg is the one whose delta is closest to the backtest target, within 0.05. Ties go to the tighter spread, then higher open interest. If any leg cannot be found, the candidate is dropped rather than approximated.

Backtesting it yourself

The same route is mirrored on the historical host. Pass at set to 09:35 ET to get what the endpoint would have said that morning (Alpha plan):

import os
import requests

r = requests.get(
    "https://historical.flashalpha.com/v1/strategies/sell-window/SPY",
    params={"at": "2024-03-01T09:35:00"},
    headers={"X-Api-Key": os.environ["FLASHALPHA_API_KEY"]},
    timeout=30,
)
print(r.json()["windows"])
Enter fullscreen mode Exit fullscreen mode

Loop that over sessions, price each candidate to its listed exit, and you can check the published backtest trade by trade.

What the backtest shows

January 2018 to October 2026, SPY, QQQ and IWM, one unit per listed candidate. Entries at 09:35 ET filled halfway between mid and the bid or ask, plus $0.01 per share and $1 per contract. Positions valued daily and closed at the 15:55 ET quote on expiry day.

Metric sell-window Same trades every session
Max drawdown $255,105 $958,924
Worst month -$170,829 -$307,241
Sharpe, 2018 to 2026 0.76 0.61
Sharpe, 2022 to 2026 0.89 0.66
Total P&L $945,541 $2,321,150
Trades 20,883 56,975

Total P&L is lower because it trades about a third as often. Scaled 3.76x to the same max drawdown, it earned $3,554,225 against $2,321,150.

Limitations

  • In-sample: candidates were chosen with 2018 to 2026 visible. Chosen on 2018 to 2021 only, the 2022 to 2026 Sharpe was 0.67.
  • Losing years: 2018 and 2022. 29% of months lost money.
  • In 2017, a calm year before the backtest window with no selloff to avoid, it lagged selling every day (Sharpe 1.55 vs 1.70).
  • A skip blocks new trades only. It does not manage open positions.
  • Overlapping candidates can stack: roughly eight one-month positions open at once is plausible.
  • Early assignment and margin interest are not modelled.

The forward test is a paper bot posting trades and P&L on Discord: https://discord.gg/UtH22J8df2

Access

Basic plan through November 2026, Growth from 1 December. Historical replay requires Alpha.

Hypothetical, pre-tax backtest results. Not investment advice.

Top comments (0)