staskh/ib-stop-loss
Downside stop-loss management for PMCC, naked LEAPS, and stock positions in IB. Computes stop prices, detects alerts, and places conditional combo orders. Dry-run by default. Requires TWS or IB Gateway running locally.
npx skills add https://github.com/staskh/trading_skills --skill ib-stop-loss
Analyzes PMCC (diagonal call spread), naked LEAPS, and stock positions in the IB portfolio and manages conditional stop-loss orders.
Default mode is dry-run — no orders are placed unless --execute is in the request.
TWS or IB Gateway must be running locally with API enabled:
IB_PORT env var — default port when --port is omitted (e.g. IB_PORT=4001 for a Gateway container). Precedence: --port flag > IB_PORT > built-in default. Set it in the shell or a .env file.Port fallback: If the configured port fails, automatically retry on the other port.
If the retry succeeds, save to memory which account type worked (live/paper) and reuse it for all IB skill calls in this and future sessions — until the user explicitly asks for the other account.
If both ports fail, ask the user to verify that TWS or IB Gateway is running with API access enabled.
Dry-run (default — no orders placed):
uv run python .claude/skills/ib-stop-loss/scripts/stop_loss.py
Execute (cancel orphan orders + place SL_ conditional orders):
uv run python .claude/skills/ib-stop-loss/scripts/stop_loss.py --execute
Execute forced (basis = current mid price, can lower existing stops):
uv run python .claude/skills/ib-stop-loss/scripts/stop_loss.py --execute --forced
Format JSON output as a markdown report with four sections:
List symbols in alert_soon prominently — these are past the early-warning threshold.
Show all_conditional_orders.module (SL_ orders) and all_conditional_orders.manual (manually placed).
If orphan_orders is non-empty, warn that these were cancelled (execute mode) or need manual cancellation (dry-run).
For each entry in positions, show a table:
| Field | Value |
|---|---|
| Symbol | NVDA — pmcc (3 contracts) |
| Spot | $219.05 |
| LEAPS | 200C 20270115 · avg cost $44.27 · current $44.23 · basis $44.27 |
| Stop price | $22.14 (40% stop) → action: place_new |
| LEAPS loss | 0.1% |
| Shorts | 235C 20260515 · received $0.61 · current $0.56 · 9.5% decayed |
Show preserve_existing when a more-protective stop already exists.
Show overwrite (red) when forced=true lowers an existing stop.
Group alerts by symbol. Types:
| Type | Meaning |
|---|---|
| leaps_early_warning | LEAPS down ≥ stop_pct/2% from basis |
| short_premium_decay | 90%+ of short premium captured — close or roll |
| short_near_strike | Spot at/above or within X% of short strike |
alert_soon symbols.| Flag | Default | Description |
|------|---------|-------------|
| --port | 7497 | IB Gateway/TWS port |
| --account | all | Specific account ID |
| --symbols | all | Analyze only these symbols |
| --legs | none | Specific option legs: SYMBOL:STRIKE[C\|P]:EXPIRY (e.g. IBKR:70C:20270115 IBKR:100C:20260918). Right defaults to C. Takes precedence over --symbols. Use when multiple PMCC/LEAPS coexist on the same symbol and only one pairing should get a stop. |
| --stop-pct | 40 | Loss % that triggers exit |
| --short-near-strike-pct | 5 | Near-strike alert threshold |
| --price-mode | mid | Option pricing: mid or last |
| --execute | off | Cancel orphans + place SL_ orders |
| --forced | off | Use current mid as basis (requires --execute) |
{
"generated_at": "2026-05-12 10:00 ET",
"dry_run": true,
"forced": false,
"stop_pct": 40.0,
"short_near_strike_pct": 5.0,
"accounts": ["U1234567"],
"symbols_filter": null,
"all_conditional_orders": {"module": [], "manual": []},
"orphan_orders": [],
"alert_soon": ["PFE"],
"positions": [
{
"symbol": "NVDA",
"type": "pmcc",
"account": "U1234567",
"qty": 3,
"underlying_price": 219.05,
"leaps": {
"strike": 200.0, "expiry": "20270115", "avg_cost": 44.27,
"current_price": 44.23, "stop_basis": 44.27,
"stop_price": 22.14, "loss_pct": 0.1
},
"shorts": [
{"strike": 235.0, "expiry": "20260515",
"premium_received": 0.61, "current_price": 0.56, "decay_pct": 9.5}
],
"stop_loss": {"stop_price": 22.14, "action": "place_new", "existing_stop": null},
"alert_soon": false,
"alerts": []
},
{
"symbol": "AAPL",
"type": "stock",
"account": "U1234567",
"qty": 100,
"underlying_price": 189.50,
"stock": {
"avg_cost": 175.00, "stop_basis": 189.50,
"stop_price": 94.75, "loss_pct": 0.0
},
"stop_loss": {"stop_price": 94.75, "action": "place_new", "existing_stop": null},
"alert_soon": false,
"alerts": []
}
]
}
alert_soon — top-level list of symbols where loss ≥ stop_pct/2%position.type — pmcc | leaps | stockstop_loss.action — place_new | preserve_existing | overwritestop_loss.existing_stop — price of the existing SL_FALL_ order if presentSL_FALL_{SYM}_{STRIKE}_{EXPIRY} — options (PMCC or naked LEAPS)SL_FALL_{SYM}_STK — stock positionsAll analytics live in src/trading_skills/broker/stop_loss.py:
Analytics (no IBKR — testable in isolation):
calc_stop_basis — max(mid, avg_cost) normally; current_mid if forcedcalc_stop_price — basis × (1 - stop_pct/100)calc_short_premium_decay_pct — % of short premium capturedidentify_positions — classify normalized positions into pmcc/leaps/stockbuild_position_analysis — full per-position output dictdetect_orphan_orders — SL_FALL_ orders for gone positionssummarize_all_conditional_orders — splits IB orders into module vs manualData layer (IBKR):
get_stop_loss_data — main entry point_cancel_orphan_orders — cancel stale SL_ orders_place_combo_stop_order — BAG order for PMCC (atomic LEAPS + shorts)_place_simple_stop_order — single order for naked LEAPS or stock_execute_position_stop — dispatch per position typeTake staskh/ib-stop-loss from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.