ib-trailing-stop · git:20260615.cfe2890 · 2026-06-15 · sha256 6b7d4c9d2e8107f0
ib-trailing-stop git:20260615.cfe2890A
Immutable. This exact content is served forever at /api/v1/blob/6b7d4c9d2e8107f0.
---
name: ib-trailing-stop
description: Server-side trailing stop management for stocks and naked LEAPS in IB. Places native TRAIL orders that auto-ratchet the stop as price climbs. Dry-run by default. Requires TWS or IB Gateway running locally.
dependencies: ["trading-skills"]
---
# IB Trailing Stop Manager
Places IB native **TRAIL** orders against stocks and naked LEAPS in the portfolio.
IB auto-adjusts the stop trigger as the price climbs and locks it as price falls.
PMCC positions are intentionally excluded — use `ib-stop-loss` for those (a standalone
trailing stop on the PMCC long leg would break the hedge at trigger).
**Default mode is dry-run** — no orders are placed unless `--execute` is in the request.
## IB Connection
TWS or IB Gateway must be running locally with API enabled:
- **Paper trading** — port 7497
- **Live trading** — port 7496
**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.
## Instructions
### Step 1: Run the script
Dry-run (default — no orders placed):
```bash
uv run python .claude/skills/ib-trailing-stop/scripts/trailing_stop.py --symbols JOBY --trail-pct 20
```
Execute (cancel orphan TS_ orders + place new TS_ TRAIL orders):
```bash
uv run python .claude/skills/ib-trailing-stop/scripts/trailing_stop.py --symbols JOBY --trail-pct 20 --execute
```
Execute forced (cancel + replace existing TS_ orders with current parameters):
```bash
uv run python .claude/skills/ib-trailing-stop/scripts/trailing_stop.py --execute --forced
```
### Step 2: Format the report
Format JSON output as a markdown report with three sections:
#### Section 1: Existing TRAIL Orders
Show `all_trail_orders.module` (TS_ orders) and `all_trail_orders.manual` (manually placed).
If `orphan_orders` is non-empty, warn that these were cancelled (execute mode) or need manual cancellation (dry-run).
#### Section 2: Positions
For each entry in `positions`, show a table:
| Field | Value |
|---|---|
| Symbol | JOBY — stock (1000 shares) |
| Spot | $7.50 |
| Reference | $7.50 (max of current $7.50, avg cost $5.00) |
| **Trail params** | 20% (initial stop $6.00) → action: place_new |
| Existing trail | none |
For LEAPS rows, also show the option leg (strike/expiry) and current option mark.
`action` values:
- `place_new` — no existing TS_ order; one will be created in execute mode
- `preserve_existing` — TS_ already in place; left alone (IB has been tracking the high)
- `overwrite` — `--forced` is on; existing TS_ will be cancelled and replaced
#### Section 3: Order Results (execute mode only)
Per-position result with `order_id` and `order_ref`.
### Step 3: Report to user
- State dry-run vs execute mode prominently.
- Lead with positions that have `action: place_new` (these are about to get a new TRAIL).
- Call out `preserve_existing` separately so user knows nothing changed.
- Show existing trail details (current `trail_stop_price`) so user knows where IB has trailed to.
## Arguments
| Flag | Default | Description |
|------|---------|-------------|
| `--port` | 7497 | IB Gateway/TWS port |
| `--account` | all | Specific account ID |
| `--symbols` | all | Analyze only these symbols |
| `--trail-pct` | 20 | Trail amount as percentage of reference |
| `--trail-amt` | — | Trail amount in dollars (mutually exclusive with --trail-pct) |
| `--price-mode` | mid | Option pricing: `mid` or `last` (LEAPS only) |
| `--execute` | off | Cancel orphans + place TS_ TRAIL orders |
| `--forced` | off | Cancel and replace existing TS_ orders (requires `--execute`) |
## JSON Output Structure
```json
{
"generated_at": "2026-05-29 10:00 ET",
"data_delay": "real-time",
"dry_run": true,
"forced": false,
"trail_pct": 20.0,
"trail_amt": null,
"accounts": ["U1234567"],
"symbols_filter": ["JOBY"],
"all_trail_orders": {"module": [], "manual": []},
"orphan_orders": [],
"positions": [
{
"symbol": "JOBY",
"type": "stock",
"account": "U1234567",
"qty": 1000,
"underlying_price": 7.50,
"stock": {
"avg_cost": 5.00,
"current_price": 7.50
},
"trail_stop": {
"trail_pct": 20.0,
"trail_amt": null,
"reference": 7.50,
"initial_stop_price": 6.00,
"action": "place_new",
"existing_trail": null
}
}
]
}
```
## Key Behaviors
- **Reference price** = `max(current_price, avg_cost)` — locks in profit when above cost, never starts below entry.
- **Forced mode** uses `current_price` as the reference (can place an initial stop below entry, useful when re-arming after a drawdown).
- **Existing TS_ orders are preserved by default** because IB has been ratcheting the trail since placement — replacing would reset that tracked high. Use `--forced` to deliberately reset.
- **Scope**: stocks + naked LEAPS only. PMCC positions are excluded; use `ib-stop-loss` for those.
## Order Identification
- `TS_{SYM}_{STRIKE}_{EXPIRY}_{RIGHT}` — naked LEAPS TRAIL orders (right is `C` or `P` so calls and puts on the same strike/expiry don't collide)
- `TS_{SYM}_STK` — stock TRAIL orders
## Architecture
All analytics live in `src/trading_skills/broker/trailing_stop.py`:
**Analytics (no IBKR — testable in isolation):**
- `calc_trail_reference` — `max(current_price, avg_cost)` normally; `current_price` if forced
- `calc_initial_trail_stop_price` — reference × (1 − trail_pct/100) or reference − trail_amt
- `identify_trailable_positions` — stocks + naked LEAPS; PMCC excluded
- `build_trail_analysis` — full per-position output dict
- `detect_orphan_trail_orders` — TS_ TRAIL orders for gone positions
- `summarize_all_trail_orders` — splits IB TRAIL orders into module vs manual
**Data layer (IBKR):**
- `get_trailing_stop_data` — main entry point
- `_cancel_orphan_orders` — cancel stale TS_ orders
- `_place_simple_trail_order` — native TRAIL order on stock or option
- `_execute_position_trail` — dispatch per position type