Quantitative Finance Insights

Quantitative Finance Insights

Building a Crypto Trading Bot from Scratch - 11: Building a Trade Executor

From Signal to Order

QPY's avatar
QPY
Jan 21, 2026
∙ Paid

In the last article, we built a Risk Manager that validates signals and calculates trade parameters. But validation doesn’t execute trades—it just tells you if and how to trade.

Now comes the moment of truth: actually placing orders on the exchange. This is where signals become real trades, and where things can go wrong in fascinating ways.

The Trade Executor is the bridge between your bot’s decisions and the market. It handles order placement, retries, failures, partial fills, and all the messy reality of actually interacting with exchange APIs.

What Can Go Wrong?

Before diving into the implementation, let me share what I learned the hard way about order execution:

Network failures: API request times out mid-flight. Was the order placed or not?

Insufficient balance: You think you have $1,000 available, but fees or another order consumed it.

Order rejected: Price moved while you were calculating. Your limit order is now too far from the market.

Partial fills: You wanted 0.1 BTC but only got 0.06. Now what?

Exchange errors: “System overloaded, try again later.” How long do you wait?

Rate limiting: Make too many requests, get temporarily banned.

The Trade Executor’s job is to handle all of this gracefully, with retries, logging, and clear error states.

The Trade Executor Architecture

Here’s the structure:

class TradeExecutor:
    """
    Trade Executor handles order placement and management.

    Responsibilities:
    - Execute validated trading signals
    - Place orders via exchange adapter
    - Handle order lifecycle (pending → filled/cancelled)
    - Support market and limit orders
    - Retry logic with exponential backoff
    - Order reconciliation
    - Track pending orders
    """

    def __init__(
        self,
        exchange: ExchangeBase,
        data_store: DataStore,
        execution_config: Dict[str, Any],
        logger=None
    ):
        self.exchange = exchange
        self.data_store = data_store
        self.logger = logger or logging.getLogger(self.__class__.__name__)

        # Load config
        self.order_type = OrderType(execution_config.get('order_type', 'LIMIT'))
        self.slippage_tolerance_pct = execution_config.get('slippage_tolerance_pct', 0.1)
        self.order_timeout_seconds = execution_config.get('order_timeout_seconds', 30)
        self.max_retries = execution_config.get('max_retries', 3)
        self.retry_delay_seconds = execution_config.get('retry_delay_seconds', 1)

        # Track pending orders
        self.pending_orders: Dict[str, Trade] = {}

Key design decisions:

Market vs Limit Orders: Configurable. Market orders fill immediately but can have slippage. Limit orders control price but might not fill.

Retry Logic: If order placement fails, retry with exponential backoff. Don’t give up after one failure.

Order Tracking: Keep track of pending orders so we can reconcile them later.

Executing a Signal

The main entry point is execute_signal():

User's avatar

Continue reading this post for free, courtesy of QPY.

Or purchase a paid subscription.
© 2026 QPY · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture