v2.0.0 to v3.0.0

106 added, 42 removed. Audit A to A.

---
name: broker-agnostic-adapter-interface
description: Use when designing quantitative trading systems to decouple strategy
logic from broker APIs using a unified abstract adapter interface, standardized
- order models, and pluggable broker factories. This institutional-grade implementation
- utilizes precise Decimal arithmetic for all currency calculations, robust typed
- exceptions, and normalized cross-venue status mapping.
+ order models, and pluggable broker factories. Enforces Decimal arithmetic at the
+ boundary, typed exceptions, and cross-venue status normalization that reports an
+ unrecognized broker status as UNKNOWN rather than guessing the order is live.
domain: algorithmic-trading
subdomain: broker-integration
tags:
- broker-integration
- adapter-pattern
- broker-agnostic
- trading-architecture
- order-routing
- - institutional-grade
+ - status-normalization
brokers_frameworks:
- - Zerodha Kite
- - Alpaca
- - Interactive Brokers
- - Upstox
- version: "2.0.0"
+ - Zerodha Kite Connect
+ - Alpaca Trading API
+ - Interactive Brokers TWS API
+ - Upstox API
+ version: "3.0.0"
author: algo-trading-skills-contributors
license: Apache-2.0
---
## When to Use
- Invoke this whenever building a trading platform or strategy engine intended to run across multiple brokers or exchange venues. Coupling strategy code directly to specific broker SDKs (e.g. Zerodha `kiteconnect`, Alpaca `alpaca-py`, or IBKR `ibapi`) creates codebase fragmentation, risks floating point inaccuracies, and prevents seamless venue migration. Designing an abstract broker adapter interface (`BaseBrokerAdapter`) with standardized data models (`OrderRequest`, `OrderResult`, `Position`, `AccountBalance`) using `Decimal` isolates strategy code from broker API drift and preserves financial precision.
+ Invoke this whenever building a trading platform or strategy engine intended to run
+ across multiple brokers or venues. Coupling strategy code directly to broker SDKs
+ (Zerodha `kiteconnect`, Alpaca `alpaca-py`, IBKR `ibapi`) fragments the codebase, leaks
+ float imprecision into prices, and makes venue migration a rewrite. An abstract
+ `BaseBrokerAdapter` with standardized models (`OrderRequest`, `OrderResult`, `Position`,
+ `AccountBalance`) in `Decimal` isolates strategy code from broker API drift.
+ The two things this layer must get right, because everything downstream trusts it: the
+ **status normalization** (does the strategy believe this order is live?) and the
+ **request validation** (does a malformed order reach the venue?).
+
+ ## When NOT to Use
+
+ - **As a working broker client.** `scripts/broker_adapter.py` defines the contract and
+ ships *simulated* adapters that fabricate fills. It performs no network I/O, no auth,
+ no rate limiting. Real adapters wrap real SDKs behind this interface.
+ - **As a single-broker abstraction.** If you will only ever trade one venue, the
+ indirection costs more than it saves; use the SDK directly and keep the enum
+ normalization.
+ - **For anything the interface does not model.** Bracket/OCO orders, order modification,
+ multi-leg and options strategies, streaming order updates, and per-venue product types
+ (Zerodha's MIS/CNC/NRML) are outside `OrderRequest`. Extend the model deliberately
+ rather than smuggling them through a broker-specific side channel — that reintroduces
+ the coupling this skill exists to remove.
+ - **As a substitute for idempotency or auth handling** — see the Related Skills.
+
## Prerequisites
- - Python `abc` (Abstract Base Classes) module for strict interface definition.
- - Python `decimal.Decimal` for exact arithmetic on quantities and prices to prevent float truncation.
- - Unified enum definitions for order sides (`BUY`, `SELL`), order types (`MARKET`, `LIMIT`, `STOP`), and order statuses (`PENDING`, `FILLED`, `REJECTED`, etc.).
- - Robust custom exception hierarchy (`BrokerAdapterError`, `OrderExecutionError`, etc.).
- - Factory pattern registry for dynamic broker adapter instantiation.
+ - Python `abc` for the interface and `decimal.Decimal` for all monetary values.
+ - Each broker's **documented status enumeration**, not the four statuses you happened to
+ see in testing. `references/standards.md` lists them with sources.
+ - A typed exception hierarchy (`BrokerAdapterError` and subclasses) that every adapter
+ maps its SDK errors into.
+ - A registry key per broker, supplied by configuration.
## Workflow
- 1. **Define Standardized Domain Data Models**:
- - Create unified dataclasses for `OrderRequest`, `OrderResult`, `Position`, and `AccountBalance`.
- - Ensure all price, quantity, and balance fields strictly utilize `Decimal`.
- - Normalize broker-specific field names into standard attributes (e.g., standardizing `commissions` and `timestamps`).
+ 1. **Model the domain in `Decimal`, and enforce it at the boundary.** `_to_decimal`
+ accepts `Decimal` and `int` (exact) and **rejects `float`** with an explanatory error.
+ A float that slips through survives every comparison and only fails much later, as a
+ `TypeError` the first time it meets a `Decimal` in arithmetic — far from the code that
+ introduced it.
- 2. **Define Abstract Base Adapter Class (`BaseBrokerAdapter`)**:
- - Declare mandatory abstract methods `@abstractmethod`:
- - `place_order(request: OrderRequest) -> OrderResult`
- - `cancel_order(order_id: str) -> bool`
- - `get_order_status(order_id: str) -> OrderResult`
- - `get_positions() -> List[Position]`
- - `get_account_balance() -> AccountBalance`
- - `normalize_status(broker_status: str) -> OrderStatus`
+ 2. **Validate the request on the base class, not in each adapter.** `_validate_request`
+ enforces symbol, enum types, finite positive quantity, and order-type/price
+ consistency: LIMIT and STOP_LIMIT require a positive price, STOP and STOP_LIMIT
+ require a positive stop price, and MARKET must **not** carry one. Putting it on the
+ base class means a newly written adapter cannot forget it.
- 3. **Implement Concrete Broker Adapters**:
- - Subclass `BaseBrokerAdapter` for each broker (e.g., `MockZerodhaAdapter`, `MockAlpacaAdapter`, `MockIBKRAdapter`).
- - Translate standard `OrderRequest` into broker-specific payloads.
- - Implement the `normalize_status` mapping dictionary to translate string-based broker responses into standardized `OrderStatus` Enum values.
+ 3. **Normalize status conservatively.** `normalize_status` upper-cases and looks up
+ `_STATUS_MAP`. **An unmapped status returns `OrderStatus.UNKNOWN` and logs at ERROR —
+ never `PENDING`.** Handle `UNKNOWN` by re-querying or reconciling; it is neither live
+ nor terminal, and `OrderResult.is_terminal` returns False for it. Treat its appearance
+ as a defect report: the broker has a status your map does not know.
- 4. **Register with Broker Factory**:
- - Implement `BrokerAdapterFactory.register("broker_name", AdapterClass)` to enable runtime instantiation of broker adapters via configuration strings.
+ 4. **Echo `client_order_id` on every `OrderResult`.** Without it the caller cannot
+ correlate a response to the request that produced it, and retry-safe submission is
+ impossible.
+ 5. **Register adapters explicitly; the factory registry starts empty.** `create()` raises
+ until you register a real adapter. The simulated adapters are **not** bound to
+ production broker names by default — call `register_simulated_adapters()` to opt in
+ for offline work. `register()` rejects any class that is not a `BaseBrokerAdapter`, so
+ a bad wiring fails at startup rather than on the first order.
+
+ 6. **Treat `cancel_order` as a request, not a cancellation.** A `True` return means the
+ broker accepted the cancellation *request*. The order can still fill in the race
+ window. Confirm with `get_order_status` before releasing risk budget or reusing the ID.
+
> Full step-by-step procedure with broker-specific detail: see `references/workflows.md`.
- > Broker/framework coverage table for this skill: see `references/standards.md`.
+ > Documented status sets per broker, with sources: see `references/standards.md`.
> Printable pre-flight checklist: see `assets/checklist.md`.
## Common Pitfalls
- - **Floating Point Truncation**: Using standard Python `float` instead of `Decimal` for currency pairs or crypto quantities. Always use exact precision.
- - **Leaky Abstractions**: Allowing broker-specific exception types (like `kiteconnect.exceptions.NetworkException`) or raw JSON dictionary responses to leak past the adapter boundary into strategy logic.
- - **Inconsistent Enum Normalization**: Leaving status strings like `"COMPLETE"` (Zerodha) vs `"filled"` (Alpaca) vs `"Submitted"` (IBKR) un-normalized.
- - **Tight Coupling to Specific SDKs**: Hardcoding SDK dependencies inside strategy modules instead of injecting abstract `BaseBrokerAdapter` instances.
+ - **Defaulting an unrecognized status to PENDING.** This is the worst default available:
+ it asserts the order is live and working. Real terminal statuses get missed by
+ hand-written maps — Zerodha's `LAPSED`, IBKR's `ApiCancelled`, Alpaca's `done_for_day`
+ and `replaced` are all finished, and calling any of them PENDING leaves the strategy
+ waiting on a dead order or re-sending one that already resolved.
+ - **Case-sensitive status lookup.** IBKR returns mixed-case strings (`PreSubmitted`,
+ `ApiCancelled`). A lookup that matches `"Filled"` but not `"FILLED"` sends the variant
+ to the default branch — a filled order reported as still working.
+ - **Mapping only the statuses you saw in testing.** Kite documents roughly a dozen order
+ states and Alpaca a dozen more; the four obvious ones are not the contract.
+ - **Binding simulated adapters to production broker names.** A factory that resolves
+ `config["broker"]` to a mock reports every order FILLED at an invented price, with no
+ error anywhere.
+ - **Falsy-checking a price.** `if request.price` treats `Decimal("0")` as absent, so a
+ zero limit price gets silently replaced by a default instead of rejected.
+ - **Floating point at the boundary.** `float` cannot represent ordinary decimal prices
+ and tick sizes exactly; construct `Decimal(str(value))` where the value enters.
+ - **Leaky abstractions.** Broker SDK exceptions, raw JSON, and — easy to miss —
+ `decimal.InvalidOperation` from a `NaN` comparison must all be wrapped into
+ `BrokerAdapterError` subclasses before crossing the adapter boundary.
+ - **Treating a cancel acknowledgement as a cancellation.**
+ - **Mutating the shared registry from library code.** It is process-wide class state; use
+ `reset()` for test isolation.
## Verification
- - Instantiate multiple concrete adapters (`MockZerodhaAdapter`, `MockAlpacaAdapter`, `MockIBKRAdapter`) via `BrokerAdapterFactory`.
- - Execute `place_order()` across different adapters and verify responses return uniform `OrderResult` objects with `Decimal` metrics.
- - Verify status normalization accurately maps diverse broker-specific status codes to standard `OrderStatus`.
- - Run unit test suite `python -m unittest test_broker_adapter.py` and confirm 100% pass rate.
+ - Run the unit suite and confirm every test passes:
+ `python -m unittest discover -s skills/broker-agnostic-adapter-interface/scripts`
+ - For each adapter, assert an invented status string returns `UNKNOWN`, not `PENDING`.
+ This is the highest-value single assertion in the suite.
+ - Assert every status in the broker's **documented** enumeration maps to something, and
+ that terminal broker states map to terminal `OrderStatus` values.
+ - Assert case variants (`"Filled"`, `"FILLED"`, `"filled"`) normalize identically.
+ - Assert a `float` quantity or price is rejected, and that an `int` widens to `Decimal`
+ losslessly.
+ - Assert `create()` raises on an empty registry, and that `register()` refuses a class
+ that does not implement `BaseBrokerAdapter`.
+ - Place orders through every adapter and confirm `filled_quantity`, `average_price` and
+ `commission` are all `Decimal` instances.
## Related Skills
- `order-placement-idempotency`
+ - `broker-api-idempotent-cancel-requests`
- `headless-broker-auth-patterns`
- `multi-broker-rate-limit-handling`