技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
equity-broker-execution【数据与研究】Use when placing, managing, or reconciling US equity orders through real broker APIs (Alpaca, Tradier, IBKR stocks, Schwab) — equity order types (market/limit/marketable-limit, bracket/OCO, trailing stop, time-in-force and extended-hours flags), order lifecycle state machine with partial-fill reconciliation, per-broker idempotency and cancel/replace …
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
This skill places and reconciles US equity orders through real broker APIs. It authors only the equity-specific layer; it references, never duplicates, the generic machinery already owned elsewhere (see Routing).
increase (new/added risk), neutral (replace at same size), or reduce
(closes/reduces, reduce-only exits, cancels).trading-risk-management KillSwitch and
equity-trading-compliance.day_trade_permitted(...) — and FAIL CLOSED
on any unknown/stale input. Exposure-reducing orders and cancels are
NEVER blocked by an unknown regime or a tripped kill switch: halt /
EMERGENCY_EXIT states authorize cancel-and-reduce only. Fail-closed means
"no new risk", never "trapped in a position".def classify_exposure(current_position_qty: int, side: str, quantity: int,
reduce_only: bool = False) -> str:
"""Return 'increase' | 'neutral' | 'reduce' from broker-authoritative state."""
signed = quantity if side.lower() in ("buy", "buy_to_cover") else -quantity
if reduce_only:
return "reduce"
if current_position_qty == 0:
return "increase"
same_direction = (current_position_qty > 0) == (signed > 0)
if same_direction:
return "increase"
# Opposite direction: reduces (or flips) — reduces until it crosses zero.
return "reduce" if abs(signed) <= abs(current_position_qty) else "increase"
crypto-exchange-integration (do NOT re-derive it).market-data-engineering.trading-risk-management.equity-trading-compliance.equity-scanning-and-watchlists.crypto-exchange-integration.One row per adapter; capability flags are not universal — read them, do not
assume. (See research/equity-day-trading-skills/broker-api-research.md for the
authoring citations; verify against live vendor docs before shipping.)
| Adapter | Auth | Idempotency | Cancel/replace | Bracket/OCO | Paper fidelity |
|---|---|---|---|---|---|
| Alpaca v2 | key/secret or OAuth; strict paper vs live host | client_order_id gives ACTIVE-order duplicate protection (reuse → 422); NOT permanent exactly-once — reconcile by id before retrying | replace returns NEW id; can race a fill | order_class bracket/oco/oto; child exits activate after entry fill | fills at NBBO, no queue/impact; free data = IEX, SIP paid |
| Tradier | OAuth2 bearer; sandbox vs prod host | NO hard dedup; tag is reference-only | per-order cancel/replace | class otoco/oco/oto | sandbox market data delayed 15m; delayed-market-data streaming NOT available in sandbox, but sandbox account/order-event streaming exists |
| IBKR Web API | Client Portal gateway/OAuth session (times out) | cOID supported (≤64 chars, unique 24h) | REST cancel/replace | contingent order types | paper via paper account |
IBKR TWS via ib_async | attach to running TWS/Gateway | integer order-id sequencing per clientId; resync nextValidId on reconnect | socket cancel/modify | parent/child + transmit flag | paper by gateway/port |
| Schwab Trader API | 3-legged OAuth2 (verify token lifetime, preview endpoints, order-strategy enums + streamer in the authenticated Schwab developer specification) | preview endpoints | orderStrategyType | SINGLE/OCO/TRIGGER | streamer for activity |
ib_asyncis the ACTIVE third-party successor to the archivedib_insync— a community wrapper over the socket TWS API, not an official IBKR SDK. The IBKR Web API and the TWS socket API are TWO distinct adapters with different auth, sessions, reconnect, and order-id behavior.
def price_protected_limit(quote, side: str, max_spread_bps: float,
max_quote_age_ms: int, luld_band, min_increment):
"""Validate freshness/spread/tick/LULD, then price a marketable limit.
Raises if protection fails — caller must NOT fall back to a market order.
min_increment is the security's CURRENT permitted minimum price increment,
obtained from broker/venue reference data — do NOT assume $0.01. Sub-dollar
NMS stocks may quote in $0.0001, and the Rule 612 amendments (SEC compliance
date extended to the first business day of November 2027, review ongoing)
add symbol-dependent increments.
"""
if quote.age_ms > max_quote_age_ms:
raise ValueError("stale quote — refuse to price")
spread_bps = (quote.ask - quote.bid) / quote.mid * 10_000
if spread_bps > max_spread_bps:
raise ValueError("spread too wide — refuse to price")
if luld_band is not None and not luld_band.contains(quote.mid):
raise ValueError("price outside LULD band")
aggress = quote.ask if side.lower() == "buy" else quote.bid
# Quantize to the permitted increment — never a blanket 2-decimal round.
return round(aggress / min_increment) * min_increment
States: new → accepted → partial → filled plus canceled / rejected / replaced and the pending_cancel / pending_replace invariants. Reuse the
FSM skeleton in crypto-exchange-integration; author here only the
equity-specific reconciliation rules:
trading-automation-runtime defines ONE asset-neutral ReconciliationRequest → ReconciliationResult protocol (open orders, executions/fills by immutable/composite
execution key, positions, balances; account/venue scope; snapshot watermark; pagination
completeness; discrepancy list; and an explicit complete | incomplete | unknown status).
The four §4 invariants above ARE the equity implementation of that protocol —
query-before-retry, reconcile by immutable execution id + cumulative filled quantity,
cancel/replace fill-race reconciliation, and reconcile-on-reconnect. The runtime calls this
adapter at TWO sites: intraday on reconnect (above) and at boot, before the first
strategy callback. An incomplete result (a paginated fetch that did not fully drain, or a
broker endpoint that timed out) MUST be surfaced as incomplete — it blocks new
exposure (the runtime admits to QUARANTINED), never silently treated as "no open state".
Equity-specific reject causes authored here (buying power, locate, halt,
PDT/regime block); the generic reject taxonomy + disconnect/duplicate
protection are referenced from crypto-exchange-integration.
Flatten-all is a BOUNDED STATE MACHINE, not a one-shot:
def flatten_all(broker, tolerance: int = 0, max_rounds: int = 5):
"""Liquidation-only mode. Cancels new-risk orders, then reduces to tolerance.
Halted symbols and residuals are SURFACED, never reported as success.
"""
broker.enter_liquidation_only_mode()
broker.cancel_exposure_increasing_and_child_orders()
residuals = []
for _ in range(max_rounds):
broker.reconcile_executions()
positions = broker.authoritative_positions()
open_qty = {p.symbol: p.qty for p in positions if abs(p.qty) > tolerance}
if not open_qty:
return {"status": "flat", "residuals": []}
for symbol, qty in open_qty.items():
if broker.is_halted(symbol):
residuals.append({"symbol": symbol, "qty": qty, "reason": "halted"})
continue
exit_side = "sell" if qty > 0 else "buy_to_cover"
broker.submit_reduce_only(symbol, exit_side, abs(qty))
return {"status": "incomplete", "residuals": residuals} # SURFACED, not success
Note the exits use a computed exit_side — never a hardcoded side.
The D1 side of the compliance split (the rule model lives in
equity-trading-compliance §7 — reference, never restate):
Acceptance criteria the equity feed must meet (transport plumbing is referenced,
not duplicated — WS reconnect → crypto-exchange-integration; tick/bar cleaning
→ market-data-engineering):
UNATTENDED EXTENDED-HOURS PROHIBITION (binding, first-class). In an UNATTENDED/automated context, extended-hours (pre / after-hours / overnight) orders are PROHIBITED, or hard-collared, whenever authoritative halt/band state is unavailable — because LULD does not protect those sessions and no human is watching. Attended mode may permit extended-hours with an explicit, per-order human confirmation. Fail closed to "no extended-hours entry" when band/halt state is stale, degraded, or absent.
*(TW-1 note: this section is deliberately acceptance-criteria only. If NBBO/SIP
equity-realtime-market-data skill.)*| Anti-Pattern | Why It Fails | Correct Approach |
|---|---|---|
| Hardcoding order side | Cannot place a SELL; the historical IBKR stub raised on limit orders | Parametrize side and validate required params on every order |
| Limit order without price validation | Pays through a wide spread / stale quote / outside LULD | Run the §3 price-protection procedure before pricing; refuse if it fails |
| Blind retry after a submit timeout | Doubles exposure when the first order actually landed | AMBIGUOUS-SUBMISSION: query broker state before retrying; use idempotency guard |
| Replace without fill-race handling | A replace response does not prove the old order was replaced pre-fill | Reconcile executions by immutable id + cumulative qty after any replace |
| Polling status without reconcile | Status alone hides partial fills and races | Reconcile by execution ids and cumulative filled quantity |
| Assuming atomic fills | Partial fills leave bracket children unprotected | Treat partials explicitly; verify protective legs cover filled qty |
| Client-synthesized OCO without reconciliation | Overfill / double-exit when both legs touch | Prohibited unless atomic qty reconciliation + overfill recovery documented |
| Live trading without paper validation | A logic bug drains the account | Paper is the asserted default; live needs the one-shot order-digest confirmation |
| Bypassing the exposure-intent gate chain | Unpermitted new risk reaches the market | Classify exposure; increasing orders pass KillSwitch AND day_trade_permitted |
| Fail-closed applied to exits | Traps the account in a losing position | Reducing orders/cancels are never blocked; halts authorize cancel-and-reduce |
| Unattended extended-hours order without band/halt state | LULD does not protect those sessions; no human is watching | Prohibit or hard-collar unattended extended-hours when halt/band state is unavailable |