DEV Community

Agent Risk Desk
Agent Risk Desk

Posted on

Check an options agent for early assignment and pin risk

If your agent sells covered calls and put spreads, the order check at entry is the easy part. The two problems I care about show up later, while the position sits there: early assignment before an ex-dividend date, and pin risk on expiration day.

Both are about the calendar. A covered call that looked fine when it was sold can get assigned the night before the stock goes ex-dividend. A short put that closes a few cents from its strike on Friday can turn into stock you only find out about on Saturday.

Over the weekend I posted a short rundown of these risks. This one has the code and the actual responses.

I run every open options position through options_risk_check each morning, and again on expiration day.

The function

import os, requests

AFA = "https://agent-financial-advisor.agent-financial-advisor.workers.dev/v1/tools/options_risk_check"
ACT_ON = {"early_assignment_dividend", "pin_risk"}

def options_check(account, underlying, legs, as_of=None):
    body = {"account": account, "underlying": underlying, "legs": legs}
    if as_of:
        body["as_of"] = as_of
    key = os.environ.get("AFA_API_KEY")
    headers = {"authorization": f"Bearer {key}"} if key else {}
    r = requests.post(AFA, json=body, headers=headers, timeout=10)
    r.raise_for_status()
    result = r.json()
    flags = [c for c in result["checks"] if c["status"] != "pass" and c["check"] in ACT_ON]
    return result, flags
Enter fullscreen mode Exit fullscreen mode

as_of defaults to today in New York. I pass it here so the examples below are reproducible.

One thing to watch if you used my pre-trade check post: this tool names its fields check and detail. pre_trade_check uses id and message.

Early assignment before a dividend

Test account: margin, $40,000 equity, 200 shares of KO, short 2 KO Nov 20 70 calls. KO is at $72.40 and the call trades at $2.55. For the test I set the ex-dividend date to Nov 13 with a $0.53 dividend. Use the real date from your data feed.

result, flags = options_check(
    account={"type": "margin", "equity": 40000, "cash": 8000, "options_level": 2, "shares_held": 200},
    underlying={"symbol": "KO", "price": 72.40, "ex_dividend_date": "2026-11-13", "dividend": 0.53},
    legs=[{"type": "call", "side": "short", "strike": 70, "expiry": "2026-11-20", "quantity": 2, "premium": 2.55}],
    as_of="2026-11-02",
)
Enter fullscreen mode Exit fullscreen mode

Here's what came back (trimmed):

{
  "decision": "approve_with_warnings",
  "summary": "APPROVE_WITH_WARNINGS: 0 failed, 1 warning(s), 4 passed.",
  "margin_requirement_usd": 0,
  "max_loss_usd": 0,
  "required_options_level": 1,
  "structures": [
    {"kind": "covered_call", "quantity": 2, "legs": ["short 2 KO 2026-11-20 70 calls"], "requirement_usd": 0}
  ],
  "checks": [
    {
      "check": "early_assignment_dividend",
      "status": "warn",
      "detail": "Short 70 call is in the money with $0.15 time value, less than the $0.53 dividend going ex on 2026-11-13. Expect assignment the day before; close or roll it if you don't want to deliver 200 shares and owe the dividend.",
      "source": "OCC Rule 805(d); OCC Characteristics and Risks of Standardized Options"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The math is simple. The call is $2.40 in the money, so only $0.15 of the $2.55 is time value. Whoever owns that call gets more from exercising and collecting the $0.53 dividend than from holding it. The position is still covered and the margin requirement is $0. Nothing is wrong with the trade itself. The agent just has to decide by the close on Nov 12 whether it wants to give up the shares.

Pin risk on expiration day

Second test account: margin, $25,000 equity, level 3. The position is a put spread on SPY, short 3 Nov 6 580 puts and long 3 Nov 6 575 puts. SPY is at $580.20.

I ran the same legs twice. With as_of set to Wednesday, Nov 4, it came back approve with 4 checks passed. Max loss $1,155, requirement $1,500. There was no pin warning.

With as_of set to Friday, Nov 6, the expiration day, it added this:

{
  "check": "pin_risk",
  "status": "warn",
  "detail": "Short 580 put expires 2026-11-06 with the stock within 1% of the strike. Whether you're assigned won't be known until after the close; close it to avoid an unplanned position over the weekend or overnight.",
  "source": "OCC Rule 805(d); OCC Characteristics and Risks of Standardized Options"
}
Enter fullscreen mode Exit fullscreen mode

This is the one that worries me with agents. The spread caps the loss at $1,155 only if both legs behave. If SPY closes at $579.90, the 575 long expires worthless and the 580 short can be assigned. That's 300 shares of SPY, about $174,000, in a $25,000 account over the weekend. An agent that only looks at max loss isnt going to see it coming.

So on expiration morning the loop is short:

for pos in open_positions:
    result, flags = options_check(pos["account"], pos["underlying"], pos["legs"])
    for c in flags:
        print(f"{pos['underlying']['symbol']} {c['check']}: {c['detail']}")
        close_or_roll(pos)
Enter fullscreen mode Exit fullscreen mode

open_positions and close_or_roll are yours. I'd hand flagged positions to the agent with the detail text and let it pick between closing and rolling. A stricter setup would close them outright.

Limits and setup

  • No signup for the first 5 calls a day. POST /v1/keys gives a free key with 25 calls a day. Put it in AFA_API_KEY.
  • It only knows what you send. The ex-dividend date and dividend come from your data, and so do the prices.
  • The 1% pin band is a default. Pass pin_band_pct in the policy object to change it.
  • Greeks come back as null unless you send delta, gamma, theta and vega on every leg.
  • It's rule-based analysis for software, not investment advice.

The same call also does margin under FINRA Rule 4210, cash-account limits, required options level and auto-exercise of expiring in-the-money longs. The docs and examples are in the repo: agent-financial-advisor. Over MCP the server is at https://agent-financial-advisor.agent-financial-advisor.workers.dev/mcp and the tool is options_risk_check.

Disclosure: I built this. If your agent trades options and keeps getting surprised by something else, tell me in the comments.

Top comments (0)