Broker-agnostic order interface

If you ever want to switch brokers, run the same strategy across two brokers, or just insulate strategy code from broker SDK changes, put an adapter layer between OrderManager (chapter 67) and the raw kiteconnect calls.

# brokers/base.py
from abc import ABC, abstractmethod

class BrokerAdapter(ABC):
    @abstractmethod
    def place_order(self, exchange, symbol, side, qty, product, order_type, price=None, trigger_price=None, tag=None) -> str:
        ...

    @abstractmethod
    def cancel_order(self, order_id: str) -> None:
        ...

    @abstractmethod
    def get_positions(self) -> list[dict]:
        """Returns a normalized list: [{symbol, exchange, quantity, avg_price, pnl}, ...]"""
        ...

    @abstractmethod
    def get_margins(self) -> dict:
        """Returns normalized: {"available_cash": float, "used": float}"""
        ...
# brokers/kite_adapter.py
class KiteAdapter(BrokerAdapter):
    def __init__(self, kite):
        self.kite = kite

    def place_order(self, exchange, symbol, side, qty, product, order_type, price=None, trigger_price=None, tag=None) -> str:
        return self.kite.place_order(
            variety="regular", exchange=exchange, tradingsymbol=symbol,
            transaction_type=side, quantity=qty, product=product,
            order_type=order_type, price=price, trigger_price=trigger_price,
            validity="DAY", tag=tag,
        )

    def cancel_order(self, order_id: str) -> None:
        self.kite.cancel_order(variety="regular", order_id=order_id)

    def get_positions(self) -> list[dict]:
        return [
            {"symbol": p["tradingsymbol"], "exchange": p["exchange"],
             "quantity": p["quantity"], "avg_price": p["average_price"], "pnl": p["pnl"]}
            for p in self.kite.positions()["net"] if p["quantity"] != 0
        ]

    def get_margins(self) -> dict:
        m = self.kite.margins(segment="equity")
        return {"available_cash": m["available"]["cash"], "used": m["utilised"]["debits"]}

A second broker (UpstoxAdapter, FyersAdapter) implements the same interface, translating to that broker's own SDK calls — strategy code never imports kiteconnect directly, only BrokerAdapter.

Where the abstraction breaks down — and that's fine

Not every broker concept maps 1:1 (GTT semantics, order variety names, margin segment structure all differ). Don't force every broker-specific feature (chapter 59's GTT, chapter 52's CO/BO) into the common interface — expose those as adapter-specific extension methods used only when you deliberately depend on that broker's specific capability, and keep the *common* interface limited to what's genuinely portable: place, cancel, positions, margins, quotes.

When this abstraction is worth building, and when it's premature

If you have one broker and no concrete plan to add a second, this is speculative complexity — the concepts in chapters 43-67 already transfer conceptually; you don't need the code abstraction until you actually have two brokers to support, or need to isolate strategy tests from a live broker connection (chapter 87's paper trading mode is a more common reason to want this interface than multi-broker support).

Next: 069 — Track open positions and unrealized P&L