Strategy

Optimal Order Execution for
AI Trading Agents

March 7, 2026 19 min read TWAP • VWAP • Implementation Shortfall

Smart agents don't just decide what to trade — they decide how to trade it. This guide covers the three dominant execution algorithms: TWAP, VWAP, and implementation shortfall minimization. Includes a full Python OrderExecutionEngine that integrates with Purple Flea's trading API.

1. The Execution Problem

Most AI trading agents invest enormous effort in signal generation — predicting price direction, sizing positions, and timing entries. But once the decision is made, many agents submit a single market order and call it done. That's leaving money on the table.

For small orders relative to market depth, the naive approach works fine. But as order size grows relative to available liquidity, three forces erode your edge before a single unit of profit is locked in.

1.1 Market Impact

Market impact is the price movement caused by your own order. When you buy, you consume the best ask offers in the order book. If your order is large enough, you exhaust the first price level and begin filling at progressively worse prices — temporary impact. Additionally, other market participants infer from your activity that a well-informed agent is buying, causing them to update their own quotes upward — permanent impact.

The square-root market impact model (Almgren-Chriss framework) quantifies this:

Almgren-Chriss Market Impact
I(x) = σ · T½ · f(x / (V · T))

Where x is total order size, V is average daily volume, T is execution horizon, and σ is volatility. The function f is roughly proportional to the square root of participation rate. A 10% participation rate typically costs 3x more than a 1% rate.

Participation Rate
1%
Typical TWAP target — minimal impact
Market Order Slippage
0.15%
Average for $10K order in liquid crypto
Impact Reduction
60-80%
VWAP vs naive market order (institutional)

1.2 Timing Risk

Every millisecond your order remains unexecuted is a millisecond of price exposure. If you're trying to buy and the market moves up before you finish, you've paid more than your decision price. This is timing risk — the variance of final execution price around the arrival midpoint.

Timing risk grows with the square root of execution time and with volatility. An execution stretched over 60 minutes in a 30% annualized volatility asset has roughly 30% / sqrt(252 * 24) * sqrt(60/60) ≈ 0.36% of one-sigma price uncertainty. For a $100K position, that's ±$360 of luck.

The tradeoff is fundamental: executing faster reduces timing risk but increases market impact. Executing slower reduces impact but increases timing risk. Optimal execution algorithms find the path that minimizes total cost — impact plus timing risk — given your urgency.

1.3 Adverse Selection

Adverse selection is the subtlest of the three forces. Limit orders get filled when someone on the other side decides to take liquidity — and that someone often has more information than you. When your buy limit order fills, it's frequently because a well-informed seller just decided to dump.

This means passive (limit order) execution isn't free. The bid-ask spread you "save" by posting a limit order is partially offset by the adverse selection cost of who fills against you. Sophisticated execution engines measure the post-fill price drift to quantify this cost and adapt their passive/aggressive split accordingly.

The Core Insight: Execution cost = market impact + timing risk + adverse selection cost - rebates. Algorithms that minimize one component often increase another. The art is finding the right balance for your specific order characteristics.

2. TWAP: Time-Weighted Average Price

TWAP is the simplest meaningful execution algorithm. It slices your total order into equal pieces and executes them at regular time intervals throughout a defined window. If you want to buy 1,000 units over 60 minutes with 6 slices, you submit one 167-unit order every 10 minutes.

The benchmark for TWAP is the arithmetic mean of all mid-prices sampled during the execution window. If your executed average price beats this benchmark, you "won" your TWAP execution.

2.1 When TWAP Works Best

2.2 TWAP Formula and Benchmark

TWAP Benchmark Price
TWAP = (1/N) ∑i=1N Pi

Where P_i is the mid-price at each interval i of N total sampling points. Your execution quality is measured as:

TWAP Slippage (Buy Orders)
Slippage = (Executed Price - TWAP Benchmark) / TWAP Benchmark

A negative slippage on a buy order means you beat the benchmark — you paid less than the time-weighted average. A positive slippage means you paid more.

2.3 TWAP Variations

Pure TWAP is rarely used in production without modifications:


3. VWAP: Volume-Weighted Average Price

VWAP improves on TWAP by aligning your order flow with market volume patterns. Instead of equal time slices, you execute larger child orders during high-volume periods (when your market impact is proportionally smaller) and smaller orders during low-volume periods.

The insight: if the market naturally processes 30% of its daily volume between 9am and 10am, your order should also be 30% executed during that window. This keeps your participation rate constant throughout the day, minimizing impact per unit traded.

3.1 Volume Profiles in Crypto Markets

Traditional equity VWAP relies on predictable intraday volume profiles — the classic U-shape with high volume at open and close. Crypto markets have different patterns:

Purple Flea's trading API provides historical volume data by minute, enabling agents to build dynamic volume profiles that adapt to the current market regime rather than relying on stale averages.

3.2 VWAP Formula

VWAP Benchmark Price
VWAP = ∑(Pi × Vi) / ∑Vi

Where P_i is the transaction price and V_i is the volume at each transaction. Your order slice sizes are:

Child Order Size for Interval i
Qi = Qtotal × (Viforecast / ∑Vforecast)

The quality of your VWAP execution depends entirely on the accuracy of your volume forecast. Agents that build better volume prediction models consistently outperform naive VWAP implementors.

3.3 Dynamic VWAP Adjustment

Static VWAP uses a fixed historical volume profile computed before execution begins. Dynamic VWAP continuously updates the remaining schedule based on actual realized volumes. If volume is higher than expected in early intervals, the remaining slices are reduced proportionally.

The adaptive formula for remaining quantity at interval k:

Dynamic Remaining Schedule
Qremaining = Qtotal - Qexecuted    distributed over    Vk..Nforecast

4. Implementation Shortfall & Arrival Price

Implementation shortfall (IS), also known as arrival price benchmarking, is the most theoretically rigorous of the three main execution frameworks. It measures the total cost of execution relative to the price at the moment you made the trading decision — your arrival price.

The concept was formalized by Robert Kissell and Robert Almgren and is now the standard benchmark for institutional execution quality measurement.

4.1 Components of Implementation Shortfall

Total IS has four additive components:

  1. Delay cost: Price drift between signal generation and order submission. If you decide to buy at $100 but take 30 seconds to submit (price is now $100.05), you've incurred $0.05 of delay cost.
  2. Market impact: The price movement caused by your own execution. This is the permanent component — prices that don't revert after your order completes.
  3. Timing risk: The realized variance of execution prices around the expected trajectory. This component has zero expectation but non-zero variance.
  4. Opportunity cost: The cost of not completing the order. If you planned to buy 1,000 units but only filled 850 because prices moved away, the missed 150 units carry an opportunity cost.
Implementation Shortfall (Buy Order)
IS = (Pexecuted - Parrival) / Parrival × 10,000 bps

4.2 IS vs TWAP/VWAP Benchmarks

The key philosophical difference: TWAP and VWAP benchmark against prices that occur during your execution window. Implementation shortfall benchmarks against the price that existed when you decided to trade. This matters enormously.

If you're executing a large buy order over 2 hours while prices drift steadily upward, TWAP will show near-zero slippage (you matched the time-weighted price). But IS will show large positive cost — you paid far more than the arrival price. IS captures the real cost of being a slow mover in a fast market.

IS is the honest benchmark. It credits you for being fast (arriving early when prices are low) and charges you for being slow (still executing while prices drift away). TWAP/VWAP can make slow execution look "good" by benchmarking against the same slow prices.

4.3 IS-Optimal Execution Schedule

Almgren and Chriss (2000) derived the IS-optimal execution schedule analytically. Under their model assumptions (linear market impact, quadratic utility), the optimal trajectory is an exponential decay:

Optimal Inventory Trajectory
x(t) = X · sinh(κ(T-t)) / sinh(κT)

Where X is initial order size, T is execution horizon, and κ is a risk-aversion parameter derived from impact and volatility. High risk aversion (urgency) leads to front-loaded execution; low risk aversion leads to more evenly spread execution.


5. Python OrderExecutionEngine

Below is a production-quality OrderExecutionEngine class that implements TWAP, VWAP, and an adaptive IS-minimizing algorithm. It integrates directly with Purple Flea's trading API for sliced order submission.

order_execution_engine.py
import asyncio
import time
import math
import statistics
from dataclasses import dataclass, field
from typing import List, Optional, Literal
import aiohttp

# Purple Flea Trading API base URL
PF_API = "https://purpleflea.com/api/trading"
API_KEY = "pf_live_YOUR_KEY_HERE"  # replace with your key


@dataclass
class ExecutionSlice:
    """Represents a single child order in an execution schedule."""
    index: int
    quantity: float
    target_time: float       # Unix timestamp for submission
    submitted: bool = False
    fill_price: Optional[float] = None
    fill_quantity: float = 0.0
    order_id: Optional[str] = None
    latency_ms: Optional[float] = None


@dataclass
class ExecutionReport:
    """Post-execution quality report."""
    algorithm: str
    symbol: str
    side: str
    total_quantity: float
    filled_quantity: float
    arrival_price: float
    executed_avg_price: float
    twap_benchmark: float
    slices: List[ExecutionSlice] = field(default_factory=list)

    @property
    def fill_rate(self) -> float:
        return self.filled_quantity / self.total_quantity

    @property
    def implementation_shortfall_bps(self) -> float:
        """IS vs arrival price, in basis points (buy order)."""
        direction = 1 if self.side == "buy" else -1
        return direction * (
            (self.executed_avg_price - self.arrival_price) / self.arrival_price
        ) * 10000

    @property
    def twap_slippage_bps(self) -> float:
        """Slippage vs TWAP benchmark, in basis points."""
        direction = 1 if self.side == "buy" else -1
        return direction * (
            (self.executed_avg_price - self.twap_benchmark) / self.twap_benchmark
        ) * 10000

    def summary(self) -> dict:
        return {
            "algorithm": self.algorithm,
            "symbol": self.symbol,
            "side": self.side,
            "fill_rate": f"{self.fill_rate:.1%}",
            "avg_price": f"{self.executed_avg_price:.6f}",
            "arrival_price": f"{self.arrival_price:.6f}",
            "IS_bps": f"{self.implementation_shortfall_bps:.2f}",
            "twap_slippage_bps": f"{self.twap_slippage_bps:.2f}",
            "n_slices": len(self.slices),
        }


class OrderExecutionEngine:
    """
    Algorithmic order execution engine for AI trading agents.
    Supports TWAP, VWAP, and adaptive IS-minimizing execution.

    Usage:
        engine = OrderExecutionEngine(api_key="pf_live_...")
        report = await engine.twap(
            symbol="BTC-USDT",
            side="buy",
            quantity=0.5,
            duration_minutes=30,
            n_slices=6
        )
        print(report.summary())
    """

    def __init__(self, api_key: str, base_url: str = PF_API):
        self.api_key = api_key
        self.base_url = base_url
        self.session: Optional[aiohttp.ClientSession] = None
        self._price_samples: List[float] = []

    async def __aenter__(self):
        self.session = aiohttp.ClientSession(headers={
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        })
        return self

    async def __aexit__(self, *args):
        if self.session:
            await self.session.close()

    # ─── Internal helpers ──────────────────────────────────────────

    async def _get_mid_price(self, symbol: str) -> float:
        """Fetch current mid price from Purple Flea orderbook."""
        url = f"{self.base_url}/orderbook/{symbol}"
        async with self.session.get(url) as resp:
            data = await resp.json()
            best_bid = float(data["bids"][0][0])
            best_ask = float(data["asks"][0][0])
            mid = (best_bid + best_ask) / 2
            self._price_samples.append(mid)
            return mid

    async def _submit_order(
        self,
        symbol: str,
        side: str,
        quantity: float,
        order_type: str = "market",
        limit_price: Optional[float] = None
    ) -> ExecutionSlice:
        """Submit a single child order to Purple Flea trading API."""
        payload = {
            "symbol": symbol,
            "side": side,
            "quantity": quantity,
            "type": order_type,
        }
        if limit_price:
            payload["price"] = limit_price

        t0 = time.time()
        async with self.session.post(
            f"{self.base_url}/orders", json=payload
        ) as resp:
            data = await resp.json()
            latency_ms = (time.time() - t0) * 1000

        return ExecutionSlice(
            index=0,
            quantity=quantity,
            target_time=t0,
            submitted=True,
            fill_price=float(data.get("fill_price", 0)),
            fill_quantity=float(data.get("filled_quantity", 0)),
            order_id=data.get("order_id"),
            latency_ms=latency_ms,
        )

    def _compute_vwap_benchmark(self) -> float:
        """Compute TWAP from sampled prices during execution."""
        if not self._price_samples:
            return 0.0
        return statistics.mean(self._price_samples)

    def _weighted_avg_fill(self, slices: List[ExecutionSlice]) -> float:
        """Compute quantity-weighted average fill price."""
        total_value = sum(s.fill_price * s.fill_quantity for s in slices if s.fill_price)
        total_qty = sum(s.fill_quantity for s in slices)
        return total_value / total_qty if total_qty > 0 else 0.0

    # ─── TWAP ──────────────────────────────────────────────────────

    async def twap(
        self,
        symbol: str,
        side: str,
        quantity: float,
        duration_minutes: float,
        n_slices: int = 10,
        randomize: bool = True,
        order_type: str = "market",
    ) -> ExecutionReport:
        """
        Execute using Time-Weighted Average Price algorithm.

        Args:
            symbol: Trading pair (e.g. "BTC-USDT")
            side: "buy" or "sell"
            quantity: Total quantity to execute
            duration_minutes: Execution window in minutes
            n_slices: Number of child orders (default 10)
            randomize: Add ±20% noise to slice size and timing
            order_type: "market" or "limit"
        """
        self._price_samples = []
        interval_seconds = (duration_minutes * 60) / n_slices
        base_slice_qty = quantity / n_slices

        # Record arrival price
        arrival_price = await self._get_mid_price(symbol)
        slices: List[ExecutionSlice] = []

        for i in range(n_slices):
            # Determine slice quantity with optional randomization
            if randomize:
                noise = 1.0 + ((__import__('random').random() - 0.5) * 0.4)
                # Keep running total in check
                remaining = quantity - sum(s.quantity for s in slices)
                slices_left = n_slices - i
                slice_qty = min(remaining, base_slice_qty * noise)
                if i == n_slices - 1:
                    slice_qty = remaining  # last slice fills remainder
            else:
                slice_qty = base_slice_qty

            # Sample price before submitting
            mid = await self._get_mid_price(symbol)

            # Submit child order
            lp = mid * (1.001 if side == "buy" else 0.999) if order_type == "limit" else None
            s = await self._submit_order(symbol, side, slice_qty, order_type, lp)
            s.index = i
            s.target_time = time.time()
            slices.append(s)

            print(f"TWAP slice {i+1}/{n_slices}: qty={slice_qty:.4f} fill={s.fill_price:.4f}")

            # Wait for next interval (skip wait after last slice)
            if i < n_slices - 1:
                wait = interval_seconds
                if randomize:
                    wait *= 1.0 + ((__import__('random').random() - 0.5) * 0.2)
                await asyncio.sleep(wait)

        avg_fill = self._weighted_avg_fill(slices)
        twap_benchmark = self._compute_vwap_benchmark()

        return ExecutionReport(
            algorithm="TWAP",
            symbol=symbol,
            side=side,
            total_quantity=quantity,
            filled_quantity=sum(s.fill_quantity for s in slices),
            arrival_price=arrival_price,
            executed_avg_price=avg_fill,
            twap_benchmark=twap_benchmark,
            slices=slices,
        )

    # ─── VWAP ──────────────────────────────────────────────────────

    async def vwap(
        self,
        symbol: str,
        side: str,
        quantity: float,
        duration_minutes: float,
        volume_profile: Optional[List[float]] = None,
        dynamic: bool = True,
    ) -> ExecutionReport:
        """
        Execute using Volume-Weighted Average Price algorithm.

        Args:
            symbol: Trading pair
            side: "buy" or "sell"
            quantity: Total quantity
            duration_minutes: Execution window in minutes
            volume_profile: List of relative volume weights per interval.
                           If None, fetches from Purple Flea historical data.
            dynamic: Continuously update schedule based on realized volumes
        """
        self._price_samples = []

        # Fetch historical volume profile if not provided
        if volume_profile is None:
            volume_profile = await self._fetch_volume_profile(symbol, n_intervals=12)

        n_slices = len(volume_profile)
        total_weight = sum(volume_profile)
        interval_seconds = (duration_minutes * 60) / n_slices

        arrival_price = await self._get_mid_price(symbol)
        slices: List[ExecutionSlice] = []
        remaining_qty = quantity
        remaining_weights = volume_profile.copy()

        for i, weight in enumerate(volume_profile):
            # Dynamic recalculation of remaining schedule
            if dynamic and i > 0:
                remaining_weights = volume_profile[i:]
                total_remaining_weight = sum(remaining_weights)
                slice_qty = remaining_qty * (weight / total_remaining_weight)
            else:
                slice_qty = quantity * (weight / total_weight)

            slice_qty = min(slice_qty, remaining_qty)

            mid = await self._get_mid_price(symbol)
            s = await self._submit_order(symbol, side, slice_qty)
            s.index = i
            s.target_time = time.time()
            slices.append(s)
            remaining_qty -= s.fill_quantity

            print(f"VWAP slice {i+1}/{n_slices}: weight={weight:.3f} qty={slice_qty:.4f}")

            if remaining_qty <= 0:
                break
            if i < n_slices - 1:
                await asyncio.sleep(interval_seconds)

        avg_fill = self._weighted_avg_fill(slices)
        twap_benchmark = self._compute_vwap_benchmark()

        return ExecutionReport(
            algorithm="VWAP",
            symbol=symbol,
            side=side,
            total_quantity=quantity,
            filled_quantity=sum(s.fill_quantity for s in slices),
            arrival_price=arrival_price,
            executed_avg_price=avg_fill,
            twap_benchmark=twap_benchmark,
            slices=slices,
        )

    async def _fetch_volume_profile(self, symbol: str, n_intervals: int) -> List[float]:
        """Fetch normalized intraday volume profile from Purple Flea API."""
        url = f"{self.base_url}/volume-profile/{symbol}?intervals={n_intervals}"
        async with self.session.get(url) as resp:
            data = await resp.json()
            # Returns list of relative volume weights
            return data.get("profile", [1.0] * n_intervals)

    # ─── Adaptive (IS-minimizing) ───────────────────────────────────

    async def adaptive(
        self,
        symbol: str,
        side: str,
        quantity: float,
        duration_minutes: float,
        risk_aversion: float = 0.5,
        n_slices: int = 12,
        volatility: Optional[float] = None,
    ) -> ExecutionReport:
        """
        IS-minimizing adaptive execution (Almgren-Chriss inspired).

        Uses exponential decay schedule with dynamic kappa adjustment
        based on realized volatility and spread width.

        Args:
            symbol: Trading pair
            side: "buy" or "sell"
            quantity: Total quantity
            duration_minutes: Max execution window
            risk_aversion: 0.0 = patient (minimize impact), 1.0 = urgent (minimize timing risk)
            n_slices: Number of child orders
            volatility: Override annualized volatility estimate
        """
        self._price_samples = []

        # Fetch vol if not provided
        if volatility is None:
            volatility = await self._estimate_volatility(symbol)

        arrival_price = await self._get_mid_price(symbol)

        # Compute kappa (urgency factor) from risk aversion and vol
        interval_seconds = (duration_minutes * 60) / n_slices
        sigma_per_interval = volatility / math.sqrt(365 * 24 * 3600 / interval_seconds)
        kappa = risk_aversion * sigma_per_interval * 10  # simplified kappa

        # Generate IS-optimal schedule (exponential decay)
        T = n_slices
        schedule = []
        for t in range(1, T + 1):
            if kappa > 0.001:
                weight = math.sinh(kappa * (T - t + 1)) - math.sinh(kappa * (T - t))
            else:
                weight = 1.0  # degenerate to TWAP for very low kappa
            schedule.append(weight)

        total_weight = sum(schedule)
        slices: List[ExecutionSlice] = []
        remaining_qty = quantity

        for i, weight in enumerate(schedule):
            slice_qty = min(remaining_qty, quantity * weight / total_weight)

            # Check spread — if too wide, delay this slice
            mid = await self._get_mid_price(symbol)
            spread_bps = await self._get_spread_bps(symbol)

            if spread_bps > 50 and remaining_qty / quantity > 0.2:
                print(f"Slice {i+1}: spread {spread_bps:.1f}bps too wide — pausing")
                await asyncio.sleep(interval_seconds / 2)
                continue

            s = await self._submit_order(symbol, side, slice_qty)
            s.index = i
            s.target_time = time.time()
            slices.append(s)
            remaining_qty -= s.fill_quantity

            print(
                f"Adaptive slice {i+1}/{n_slices}: "
                f"qty={slice_qty:.4f} spread={spread_bps:.1f}bps fill={s.fill_price:.4f}"
            )

            if remaining_qty <= 0:
                break
            if i < n_slices - 1:
                await asyncio.sleep(interval_seconds)

        avg_fill = self._weighted_avg_fill(slices)
        twap_benchmark = self._compute_vwap_benchmark()

        return ExecutionReport(
            algorithm="Adaptive-IS",
            symbol=symbol,
            side=side,
            total_quantity=quantity,
            filled_quantity=sum(s.fill_quantity for s in slices),
            arrival_price=arrival_price,
            executed_avg_price=avg_fill,
            twap_benchmark=twap_benchmark,
            slices=slices,
        )

    async def _estimate_volatility(self, symbol: str) -> float:
        """Estimate annualized volatility from recent 1-min candles."""
        url = f"{self.base_url}/candles/{symbol}?interval=1m&limit=60"
        async with self.session.get(url) as resp:
            candles = await resp.json()
        closes = [float(c["close"]) for c in candles]
        returns = [math.log(closes[i] / closes[i-1]) for i in range(1, len(closes))]
        vol_per_min = statistics.stdev(returns)
        return vol_per_min * math.sqrt(365 * 24 * 60)

    async def _get_spread_bps(self, symbol: str) -> float:
        """Return current bid-ask spread in basis points."""
        url = f"{self.base_url}/orderbook/{symbol}"
        async with self.session.get(url) as resp:
            data = await resp.json()
        best_bid = float(data["bids"][0][0])
        best_ask = float(data["asks"][0][0])
        mid = (best_bid + best_ask) / 2
        return ((best_ask - best_bid) / mid) * 10000

6. Integration with Purple Flea Trading API

Purple Flea's trading API provides the primitives needed for sophisticated order execution: order submission with configurable types, live orderbook depth, historical candles, and volume profile endpoints.

6.1 Authentication

All trading API calls require a bearer token obtained after agent registration. Tokens are prefixed pf_live_ and carry per-agent rate limits based on your volume tier.

agent_setup.py
import asyncio
from order_execution_engine import OrderExecutionEngine

async def main():
    async with OrderExecutionEngine(
        api_key="pf_live_YOUR_KEY_HERE"
    ) as engine:

        # TWAP: buy 0.1 BTC over 20 minutes, 10 slices
        report = await engine.twap(
            symbol="BTC-USDT",
            side="buy",
            quantity=0.1,
            duration_minutes=20,
            n_slices=10,
            randomize=True,
        )
        print(report.summary())
        # {algorithm: TWAP, fill_rate: 100.0%, IS_bps: 4.2, ...}

        # VWAP: sell 500 USDT of ETH aligned to volume profile
        report2 = await engine.vwap(
            symbol="ETH-USDT",
            side="sell",
            quantity=0.2,
            duration_minutes=60,
            dynamic=True,
        )
        print(report2.summary())

        # Adaptive: urgency=0.8 (60% front-loaded)
        report3 = await engine.adaptive(
            symbol="SOL-USDT",
            side="buy",
            quantity=10.0,
            duration_minutes=15,
            risk_aversion=0.8,
        )
        print(report3.summary())

asyncio.run(main())

6.2 Key API Endpoints

Free tier for new agents: Claim free funds via the Purple Flea Faucet to test your execution engine without risking real capital. New agents get a starting balance to experiment with all execution algorithms.

7. Measuring Execution Quality

Building execution algorithms is only half the work. Without rigorous quality measurement, you can't improve. Purple Flea's wallet API exposes transaction history that enables post-trade analysis.

7.1 Slippage Reporting via Wallet API

execution_analytics.py
import aiohttp
import statistics
from dataclasses import dataclass
from typing import List

@dataclass
class SlippageReport:
    symbol: str
    period_days: int
    n_executions: int
    avg_is_bps: float         # average implementation shortfall
    p50_is_bps: float         # median IS
    p95_is_bps: float         # 95th pctile IS (worst executions)
    avg_fill_rate: float      # what fraction of orders fully filled
    algo_breakdown: dict      # IS by algorithm type

class ExecutionAnalytics:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.headers = {"Authorization": f"Bearer {api_key}"}

    async def fetch_execution_history(self, days: int = 30) -> List[dict]:
        """Pull recent trade history from Purple Flea wallet API."""
        url = f"https://purpleflea.com/api/wallet/transactions?days={days}&type=trade"
        async with aiohttp.ClientSession(headers=self.headers) as s:
            async with s.get(url) as resp:
                data = await resp.json()
                return data.get("transactions", [])

    def compute_slippage_report(
        self, trades: List[dict], symbol: str = None
    ) -> SlippageReport:
        """
        Compute execution quality stats from trade history.

        Each trade dict should include:
          - symbol, side, fill_price, arrival_price, quantity, algorithm
        """
        if symbol:
            trades = [t for t in trades if t["symbol"] == symbol]

        is_list = []
        fill_rates = []
        algo_is: dict = {}

        for t in trades:
            direction = 1 if t["side"] == "buy" else -1
            is_bps = direction * (
                (t["fill_price"] - t["arrival_price"]) / t["arrival_price"]
            ) * 10000
            is_list.append(is_bps)

            fill_rates.append(t.get("fill_rate", 1.0))

            algo = t.get("algorithm", "UNKNOWN")
            algo_is.setdefault(algo, []).append(is_bps)

        is_list.sort()
        algo_breakdown = {
            algo: round(statistics.mean(vals), 2)
            for algo, vals in algo_is.items()
        }

        return SlippageReport(
            symbol=symbol or "ALL",
            period_days=30,
            n_executions=len(trades),
            avg_is_bps=round(statistics.mean(is_list), 2),
            p50_is_bps=round(statistics.median(is_list), 2),
            p95_is_bps=round(is_list[int(len(is_list) * 0.95)], 2),
            avg_fill_rate=round(statistics.mean(fill_rates), 4),
            algo_breakdown=algo_breakdown,
        )

7.2 Key Quality Metrics

Target IS
<10 bps
Liquid pairs, well-sized orders
Fill Rate Target
>97%
For time-sensitive strategies
Max Participation
5-10%
Before self-impact becomes significant

8. When to Use Each Algorithm

The choice of execution algorithm depends on four variables: order size relative to market volume, urgency, market conditions (trending vs mean-reverting), and available execution time. Here's a decision framework:

Scenario Best Algorithm Reason
Small order (<0.1% ADV), any urgency Market Order Impact negligible; no benefit to slicing
Medium order, low urgency, stable market TWAP Simple, predictable, easy to benchmark
Medium order, low urgency, predictable volume VWAP Minimizes participation rate cost vs TWAP
Large order, moderate urgency Adaptive IS Balances impact and timing risk dynamically
Strong directional signal, needs speed Front-loaded IS High risk aversion kappa; accept more impact to capture signal
Thin market, wide spreads TWAP + spread filter Pause execution during wide-spread periods
Known news event approaching Avoid VWAP Volume forecast breaks down around events
End-of-day rebalance with exact target Arrival price IS Benchmarks against decision point, not execution window

8.1 The Urgency Dial

Most real-world executions lie somewhere on a continuum between "pure impact minimization" (extremely patient, like central bank FX intervention) and "pure urgency" (immediate fill at any cost, like event-driven liquidation). Your optimal point on this dial depends on:

8.2 Dynamic Algorithm Switching

Advanced agents don't commit to a single algorithm for the duration of an order. They switch dynamically based on real-time market conditions:

  1. Start with TWAP as baseline during the first 10-15% of the execution window while assessing market microstructure.
  2. If volume profile matches historical expectations, switch to VWAP for the remaining execution.
  3. If the bid-ask spread widens more than 2x normal, pause and wait for liquidity to improve.
  4. If more than 70% of the window has elapsed with less than 60% fill rate, increase urgency — shift to more aggressive market orders for the remainder.
  5. If a strong contra-trend signal emerges mid-execution, abort and reverse — the opportunity cost of completing the original order has reversed sign.
Key Principle: Execution algorithms are not rigid schedules — they're adaptive policies that respond to market feedback. The best execution engines continuously recalculate their optimal path given the current state of the order book, recent volume, and remaining quantity.

Conclusion

Order execution is where AI trading agents lose or preserve the value of their signals. TWAP provides a clean, auditable baseline. VWAP captures liquidity patterns to reduce per-unit impact costs. Implementation shortfall benchmarking reveals the true cost of execution against the most honest price: the moment you made your decision.

The OrderExecutionEngine above gives your agent a production-ready framework that integrates with Purple Flea's trading API. Start with TWAP for small orders while building your execution track record, graduate to VWAP as you scale, and deploy adaptive IS-minimizing execution once your order sizes warrant the additional complexity.

Every basis point saved in execution is a basis point added to your edge — compound that over thousands of trades, and optimal execution becomes one of the highest-return improvements an AI trading agent can make.

Ready to Trade on Purple Flea?

Register your agent, claim free funds from the faucet, and start building your execution track record today.

Register Your Agent    Trading API Docs