What Are Dark Pools and Why Do They Matter for AI Agents?
A dark pool is a private trading venue where orders are matched without exposing them to the public market before execution. The term originated in equities markets, where institutional investors needed to move large blocks of stock without telegraphing their intent to high-frequency traders and other participants who would front-run the order — moving the price against them before they could complete the trade.
In crypto, the same economics apply with even more urgency. Public order books on most exchanges have shallow depth. A 500 BTC market order hitting Binance's order book will walk through multiple price levels, suffering significant slippage. Every basis point of slippage on a $20M position is $2,000 in frictional cost. Over hundreds of trades, this becomes a substantial drag on strategy returns.
Market impact is the price movement caused by your own order. Dark pools reduce market impact by allowing large orders to execute without informing the market. The trade happens in the dark; the public order book sees nothing until the transaction is settled.
For AI agents specifically, dark pool access matters for four reasons:
- Block execution efficiency: Agents running momentum or arbitrage strategies often need to enter or exit large positions quickly. Routing through dark liquidity minimizes the price concession required.
- Information leakage prevention: A visible large order is a signal to other market participants. Competing agents watching the order book can infer your strategy and trade against you.
- Better average fills: Dark pools frequently execute at the midpoint of the bid-ask spread, meaning both buyer and seller get a better price than either the best bid or the best ask.
- Operational privacy: An agent's trading patterns can reveal its strategy. Dark pool execution limits the public footprint of trading activity.
Key concept: Market impact is asymmetric. The impact of a large buy order is not reversed when you exit — you pay twice, once on entry and once on exit. Minimizing impact on both legs compounds over a strategy's lifetime.
Types of Dark Pools and Private Liquidity Venues
Not all dark liquidity is the same. The four main structures differ in how counterparties are found, who bears custody risk, and what information leaks before or after the trade.
Exchange-Affiliated Dark Pools
Major exchanges operate their own dark pool tiers alongside their public books. Orders placed in the dark tier are matched internally before being routed to the lit order book. Binance's dark liquidity matching, Coinbase's institutional block desk, and Kraken's OTC product all fall into this category. The exchange acts as the central counterparty, so custody risk is the same as normal exchange trading. The downside: you are trusting the exchange's matching engine and its fairness — and the exchange itself can see your order flow.
OTC Desks
Over-the-counter desks (Cumberland, Galaxy, Wintermute, Amber Group, and dozens of others) provide principal liquidity — they quote a price and take the opposite side of your trade from their own inventory. You negotiate a price for the entire block, typically via a chat interface or API. Settlement happens bilaterally, with no public book involvement. OTC desks are well-suited for very large blocks ($5M+) where even a lit exchange's OTC tier would struggle.
The tradeoff: OTC desks need to know who you are. KYC/AML requirements apply. For AI agents without a legal entity attached, this is a barrier. Crypto-native desks are more flexible than traditional prime brokers, but counterparty negotiation still requires an established relationship.
RFQ Systems (Request for Quote)
RFQ systems broadcast your order parameters (asset, size, direction) to a curated set of market makers who respond with firm quotes. You accept the best quote and the trade executes at that price. The key advantage over a standard OTC desk is competition — multiple market makers bid against each other, so you get a tighter spread. Paradigm.co, FalconX, and several exchange-native RFQ systems operate this way.
From an agent perspective, RFQ workflows are particularly well-suited to programmatic access: POST your order parameters, receive a list of quotes as JSON, compare prices, POST an acceptance. The entire flow can be automated with a simple state machine and completed in under two seconds.
Internal Crossing Networks
A crossing network matches orders between participants without routing to any external venue. If your buy order can be matched against another participant's sell order at the midpoint, neither party pays exchange fees, spread, or market impact. The operator of the crossing network acts as the intermediary. Purple Flea's internal order matching crosses agent orders against each other before routing to external liquidity, effectively acting as a crossing network for the agent ecosystem.
| Venue Type | Minimum Size | Anonymity | Speed | Price Quality | KYC Required |
|---|---|---|---|---|---|
| Exchange Dark Pool | $50K+ | Partial | Instant | Midpoint | Exchange KYC |
| OTC Desk | $500K+ | Low | Minutes | Negotiated | Full KYC |
| RFQ System | $25K+ | Partial | <2 seconds | Competitive | Varies |
| Crossing Network | Any size | High | Instant | Midpoint | API key only |
| Purple Flea Block API | $1K+ | High | Instant | Best of book | Agent key only |
How to Access Dark Liquidity as an AI Agent
The practical mechanics of accessing dark liquidity vary by venue type, but the common thread is API-first access. AI agents don't walk up to a trading desk — they POST JSON and receive fills.
The RFQ Workflow in Detail
An RFQ workflow has four steps that map cleanly to HTTP calls:
- Initiate: POST your order parameters to the RFQ system. Specify the asset pair, notional size (or quantity), side (buy/sell), and your acceptable quote window (e.g., 5 seconds).
- Receive quotes: Market makers respond with firm quotes — a price at which they will execute your entire order. The response is a JSON array of quote objects, each with a price, the responding market maker's ID, and an expiry timestamp.
- Accept: POST an acceptance with the quote ID of the best price. The trade is locked in immediately.
- Settle: Funds move between accounts. Settlement can be instant (if both parties are on the same platform) or T+1 for cross-platform trades.
Timing sensitivity: Quotes in RFQ systems expire in 2-10 seconds. Your agent's acceptance logic must run synchronously — do not yield to async coroutines or make external calls between receiving quotes and selecting the best one. Latency in acceptance leads to expired quotes and failed fills.
Block Trade Mechanisms
For very large orders that cannot be filled by a single counterparty, block trade mechanisms split the order across multiple dark venues and internal crossing pools. The routing logic typically works as follows:
- Check the internal crossing pool first — if there is a matching order on the other side, cross at midpoint and capture the full spread for both parties.
- If no internal cross is available, route to the RFQ network to gather competitive quotes.
- If no single quote covers the full size, split the order into tranches and fill each tranche from the best available quote, subject to a maximum acceptable price range.
- Any residual that cannot be filled in the dark is either held as a resting limit order or, if urgency is high, routed to the lit book with randomized timing to reduce information leakage.
Purple Flea Block API Integration
Purple Flea's trading API supports block orders with internal crossing logic. When you submit an order flagged as a block trade, the matching engine first checks for eligible counterparties among registered agents before routing externally. This is particularly effective in the agent ecosystem, where multiple agents often hold complementary positions:
import requests import time from dataclasses import dataclass, field from typing import Optional, List from enum import Enum PF_API_KEY = "your_pf_live_api_key" BASE_URL = "https://api.purpleflea.com" HEADERS = {"X-API-Key": PF_API_KEY, "Content-Type": "application/json"} class OrderRouting(Enum): DARK_ONLY = "dark_only" # Fail if no dark fill available DARK_FIRST = "dark_first" # Try dark, fall back to lit CROSSING = "crossing" # Internal crossing only VWAP = "vwap" # VWAP slicing across venues @dataclass class BlockOrderParams: market: str # e.g. "BTC-USD-PERP" side: str # "buy" | "sell" size_usd: float # Notional in USD routing: OrderRouting = OrderRouting.DARK_FIRST max_slippage_bps: int = 20 # Reject fills worse than 20bps time_limit_sec: int = 30 # How long to try before failing rfq_enabled: bool = True # Include RFQ quotes in routing @dataclass class BlockOrderResult: order_id: str filled_size_usd: float avg_fill_price: float venue: str # "crossing" | "rfq" | "lit" market_impact_bps: float elapsed_ms: int tranches: List[dict] = field(default_factory=list) def get_midpoint(market: str) -> float: """Fetch current midpoint price for the market.""" r = requests.get(f"{BASE_URL}/trading/markets/{market}/ticker", headers=HEADERS) data = r.json() return (data["best_bid"] + data["best_ask"]) / 2 def submit_block_order(params: BlockOrderParams) -> BlockOrderResult: """Submit a block order through Purple Flea's dark routing layer.""" ref_price = get_midpoint(params.market) t_start = time.time() payload = { "market": params.market, "side": params.side, "type": "block", "size_usd": params.size_usd, "routing": params.routing.value, "max_slippage_bps": params.max_slippage_bps, "rfq_enabled": params.rfq_enabled, "ref_price": ref_price, } r = requests.post(f"{BASE_URL}/trading/orders/block", json=payload, headers=HEADERS) r.raise_for_status() result = r.json() elapsed_ms = int((time.time() - t_start) * 1000) fill_price = result["avg_fill_price"] # Compute realised market impact in basis points if params.side == "buy": impact_bps = (fill_price - ref_price) / ref_price * 10_000 else: impact_bps = (ref_price - fill_price) / ref_price * 10_000 return BlockOrderResult( order_id=result["order_id"], filled_size_usd=result["filled_notional_usd"], avg_fill_price=fill_price, venue=result["fill_venue"], market_impact_bps=impact_bps, elapsed_ms=elapsed_ms, tranches=result.get("tranches", []), ) # Example usage if __name__ == "__main__": order = submit_block_order(BlockOrderParams( market="BTC-USD-PERP", side="buy", size_usd=250_000, routing=OrderRouting.DARK_FIRST, max_slippage_bps=15, )) print(f"Filled ${order.filled_size_usd:,.0f} via {order.venue}") print(f"Avg fill: {order.avg_fill_price:.2f} | Impact: {order.market_impact_bps:.1f}bps | {order.elapsed_ms}ms")
Dark Pool Strategies for AI Agents
Knowing that dark pools exist is table stakes. The competitive advantage comes from implementing strategies that intelligently decide when to use dark liquidity, how much to reveal in each tranche, and how to handle the residual order that cannot be filled in the dark.
Block Order Routing Logic
A well-implemented block router operates as a decision tree that evaluates dark liquidity at each step before exposing anything to the public book. The routing decision depends on three variables: available dark liquidity depth, urgency (is this a time-sensitive directional trade or a patient accumulation?), and acceptable price range.
For patient accumulation strategies — building a large position over hours rather than minutes — the agent has the luxury of setting tight price constraints and waiting for natural crossing opportunities. For directional trades reacting to a market signal, urgency is higher and the agent may need to accept a wider price range to ensure execution before the signal decays.
VWAP Execution to Minimize Information Leakage
VWAP (Volume-Weighted Average Price) execution slices a large order into smaller tranches timed to coincide with periods of high market volume. By trading in proportion to market volume, the agent's orders blend into normal market activity and appear less conspicuous to observers monitoring the order flow.
Dark VWAP enhances this further by routing each tranche first to the dark pool before allowing any residual to hit the lit book. The combination of time-slicing (VWAP) and venue selection (dark-first) produces the lowest achievable market impact for a patient trade.
Key parameters for a dark VWAP implementation:
- Participation rate: What percentage of market volume should your order represent? 5-10% is typical for stealth; above 20% becomes visible.
- Dark fill threshold: Minimum size that must be filled in the dark before routing residual to lit. Set too high and you may not complete the order; too low and you lose the benefit.
- Completion deadline: The time by which the full order must be filled. Tightening the deadline increases participation rate and market impact.
- Price limit: The maximum price (for buys) or minimum price (for sells) at which you will accept fills. Orders outside this range are cancelled rather than filled at bad prices.
VWAP timing risk: VWAP algorithms assume that volume patterns follow historical norms. In fast-moving markets driven by news or liquidation cascades, volume patterns become unpredictable and VWAP execution can lag significantly. Always set a maximum price limit independent of VWAP logic to protect against runaway fills during volatility spikes.
| Strategy | Urgency | Market Impact | Info Leakage | Best For |
|---|---|---|---|---|
| Direct market order | High | Very High | High | Emergency exits only |
| TWAP (lit book) | Medium | Moderate | Moderate | Small-mid positions |
| Dark pool direct | Low | Very Low | Very Low | Large patient blocks |
| RFQ auction | Medium | Low | Low | $25K–$5M blocks |
| Dark VWAP | Low | Minimal | Minimal | Large accumulation |
| Internal crossing | Any | Zero | Zero | Agent-to-agent trades |
Integration with Purple Flea Trading
Purple Flea's trading API is built for agent-native access: API key authentication, JSON request/response, and order routing that treats dark liquidity as the first stop rather than an afterthought. The platform's 275 perpetual markets and 6-chain wallet infrastructure make it well-suited for agents that need to execute large positions across multiple assets.
Large Position Execution Workflow
For block trades, the recommended workflow through Purple Flea is:
- Check the internal crossing pool via
GET /trading/dark-pool/depth?market=BTC-USD-PERP&side=buyto see if there is opposing interest. - If depth exists, submit an internal crossing order. No spread is paid; execution is instant.
- If no crossing depth, initiate an RFQ via
POST /trading/rfqwith your size and acceptable price range. - Accept the best RFQ quote if within parameters.
- Route any residual to dark VWAP for patient execution over the next N minutes.
Order Routing Decision Parameters
The routing decision requires up-to-date information about market conditions. The agent should sample the following before deciding on routing strategy:
- Bid-ask spread: Wide spread signals thin liquidity; dark pool execution is even more valuable in thin markets where slippage on the lit book would be extreme.
- Order book depth: How many USD of depth exists within 10bps, 25bps, 50bps of mid? This tells you what slippage a market order would suffer.
- Recent trade volume: Is the market active? Higher volume means VWAP slicing blends more easily into background noise.
- Funding rate: For perpetual markets, a high funding rate changes the economics — urgency to close a short position that is paying high funding might override the preference for patient dark execution.
Privacy Routing via XMR for Financial Operations
Dark pool execution reduces your footprint on the trading venue, but the flow of funds to and from exchanges is itself a source of information leakage. An agent that moves large sums directly between its exchange account and a known wallet address creates an on-chain trail that can be analyzed by blockchain surveillance tools.
Monero (XMR) is the primary privacy layer available to AI agents through Purple Flea's wallet API. XMR transactions are confidential by default: sender address, recipient address, and transaction amount are all obscured using ring signatures, stealth addresses, and RingCT. Unlike Bitcoin or Ethereum, where every transaction is publicly visible on the blockchain, XMR transactions leave no exploitable trail.
When to Use XMR in the Trading Flow
XMR is appropriate as an intermediate step when moving funds between venues or withdrawing profits. A typical privacy-preserving treasury cycle for an agent:
- Trade on the exchange, accumulate USD-denominated profits.
- Withdraw profits to an XMR wallet via Purple Flea's wallet API — the exchange sees a withdrawal to an XMR address, not to any identifiable downstream destination.
- From the XMR wallet, convert back to the settlement currency needed for the next operation (USDC for DeFi, BTC for on-chain settlements).
This pattern breaks the on-chain link between the exchange account and the agent's operational wallets, preventing portfolio reconstruction by external observers.
Purple Flea wallet API: XMR wallet operations are available at POST /wallet/xmr/transfer. The API handles Monero's complexity — subaddress generation, ring size selection, fee estimation — so the agent only needs to specify the destination and amount. Wallet creation is free with any registered agent API key.
XMR Technical Characteristics Relevant to Agents
A few Monero-specific properties that affect how an agent should handle XMR in its treasury logic:
- 10-block confirmation requirement: XMR funds are spendable after 10 confirmations (~20 minutes). Build this delay into treasury workflows that depend on XMR as an intermediate step.
- Balance visibility lag: Because Monero uses stealth addresses, checking your balance requires scanning the blockchain with your private view key. Purple Flea's API handles this but may have a ~30 second cache on balance updates.
- Variable fees: XMR transaction fees vary with ring size and transaction complexity. Use the API's
GET /wallet/xmr/fee-estimateendpoint before submitting large transfers to avoid surprises.
Full Dark Pool Order Router Implementation
The following Python class implements a complete dark pool order router that checks internal crossing liquidity first, then falls back to RFQ, then to dark VWAP slicing for any residual. It is designed to be dropped into an existing agent codebase with minimal dependencies.
import requests, time, math, logging from dataclasses import dataclass, field from typing import List, Optional, Tuple from enum import Enum log = logging.getLogger("dark_pool_router") PF_KEY = "your_pf_live_api_key" BASE = "https://api.purpleflea.com" HDR = {"X-API-Key": PF_KEY, "Content-Type": "application/json"} # ── Configuration ───────────────────────────────────────────────────────────── @dataclass class RouterConfig: # Crossing pool: if dark depth covers this fraction, use crossing only crossing_fill_threshold: float = 0.8 # RFQ: accept quotes within this many bps of midpoint rfq_max_spread_bps: float = 12.0 # RFQ: how long to wait for quote responses rfq_timeout_sec: float = 3.0 # VWAP: target participation rate (% of market volume per interval) vwap_participation_rate: float = 0.08 # VWAP: interval length in seconds vwap_interval_sec: int = 60 # VWAP: maximum order completion time vwap_max_duration_sec: int = 1800 # 30 minutes # Absolute price limit: bps from midpoint hard_price_limit_bps: float = 50.0 # ── Helpers ─────────────────────────────────────────────────────────────────── def api_get(path: str, params: dict = None) -> dict: r = requests.get(f"{BASE}{path}", headers=HDR, params=params, timeout=10) r.raise_for_status() return r.json() def api_post(path: str, payload: dict) -> dict: r = requests.post(f"{BASE}{path}", json=payload, headers=HDR, timeout=10) r.raise_for_status() return r.json() def get_market_state(market: str) -> Tuple[float, float, float]: """Returns (midpoint, spread_bps, volume_1min_usd).""" d = api_get(f"/trading/markets/{market}/ticker") mid = (d["best_bid"] + d["best_ask"]) / 2 spread_bps = (d["best_ask"] - d["best_bid"]) / mid * 10_000 vol_1m = d.get("volume_1min_usd", 0) return mid, spread_bps, vol_1m # ── Stage 1: Internal crossing pool ────────────────────────────────────────── def check_crossing_depth(market: str, side: str, size_usd: float, cfg: RouterConfig) -> float: """Returns the fraction of order that can be filled via internal crossing.""" d = api_get("/trading/dark-pool/depth", {"market": market, "side": side}) available_usd = d.get("available_notional_usd", 0) return min(available_usd / size_usd, 1.0) def execute_crossing_order(market: str, side: str, size_usd: float, mid: float) -> Optional[dict]: try: result = api_post("/trading/orders/crossing", { "market": market, "side": side, "size_usd": size_usd, "ref_price": mid, }) log.info("Crossing fill: $%.0f @ %.4f via %s", result["filled_notional_usd"], result["avg_fill_price"], result["fill_venue"]) return result except Exception as e: log.warning("Crossing order failed: %s", e) return None # ── Stage 2: RFQ auction ────────────────────────────────────────────────────── def run_rfq(market: str, side: str, size_usd: float, mid: float, cfg: RouterConfig) -> Optional[dict]: """Initiate RFQ, collect quotes, accept best within spread limit.""" rfq = api_post("/trading/rfq", { "market": market, "side": side, "size_usd": size_usd, "ref_price": mid, "timeout_sec": cfg.rfq_timeout_sec, }) rfq_id = rfq["rfq_id"] time.sleep(cfg.rfq_timeout_sec) quotes = api_get(f"/trading/rfq/{rfq_id}/quotes").get("quotes", []) if not quotes: log.info("RFQ %s: no quotes received", rfq_id) return None # Select the best quote: lowest ask (for buys) or highest bid (for sells) if side == "buy": best = min(quotes, key=lambda q: q["price"]) deviation_bps = (best["price"] - mid) / mid * 10_000 else: best = max(quotes, key=lambda q: q["price"]) deviation_bps = (mid - best["price"]) / mid * 10_000 if deviation_bps > cfg.rfq_max_spread_bps: log.info("RFQ best quote %.1fbps outside limit %.1fbps, rejecting", deviation_bps, cfg.rfq_max_spread_bps) return None acceptance = api_post(f"/trading/rfq/{rfq_id}/accept", {"quote_id": best["quote_id"]}) log.info("RFQ fill: $%.0f @ %.4f (%.1fbps)", acceptance["filled_notional_usd"], acceptance["avg_fill_price"], deviation_bps) return acceptance # ── Stage 3: Dark VWAP for residual ─────────────────────────────────────────── def execute_dark_vwap(market: str, side: str, size_usd: float, mid: float, cfg: RouterConfig) -> dict: """Slice remaining order using dark-first VWAP until filled or deadline.""" filled_usd = 0.0 fills: List[dict] = [] deadline = time.time() + cfg.vwap_max_duration_sec price_limit = mid * (1 + cfg.hard_price_limit_bps / 10_000 * (1 if side == "buy" else -1)) while filled_usd < size_usd - 10 and time.time() < deadline: _, _, vol_1m = get_market_state(market) tranche_usd = min(vol_1m * cfg.vwap_participation_rate, size_usd - filled_usd) tranche_usd = max(tranche_usd, 100) # min tranche $100 try: fill = api_post("/trading/orders/block", { "market": market, "side": side, "size_usd": tranche_usd, "routing": "dark_first", "price_limit": price_limit, "ref_price": mid, }) filled_usd += fill["filled_notional_usd"] fills.append(fill) except Exception as e: log.warning("VWAP tranche failed: %s", e) time.sleep(cfg.vwap_interval_sec) vwap_price = sum(f["avg_fill_price"] * f["filled_notional_usd"] for f in fills) / (filled_usd or 1) log.info("VWAP complete: $%.0f filled @ avg %.4f over %d tranches", filled_usd, vwap_price, len(fills)) return {"filled_usd": filled_usd, "vwap_price": vwap_price, "tranches": fills} # ── Main router ─────────────────────────────────────────────────────────────── class DarkPoolRouter: def __init__(self, cfg: RouterConfig = None): self.cfg = cfg or RouterConfig() def route(self, market: str, side: str, size_usd: float) -> dict: mid, spread_bps, _ = get_market_state(market) log.info("Routing %s $%.0f on %s | mid=%.4f spread=%.1fbps", side, size_usd, market, mid, spread_bps) # Stage 1: Internal crossing crossing_fraction = check_crossing_depth(market, side, size_usd, self.cfg) if crossing_fraction >= self.cfg.crossing_fill_threshold: result = execute_crossing_order(market, side, size_usd, mid) if result and result["filled_notional_usd"] >= size_usd * 0.99: return {"venue": "crossing", "result": result} # Stage 2: RFQ rfq_result = run_rfq(market, side, size_usd, mid, self.cfg) if rfq_result: residual = size_usd - rfq_result["filled_notional_usd"] if residual > 100: log.info("RFQ partial fill, routing $%.0f residual to dark VWAP", residual) vwap_result = execute_dark_vwap(market, side, residual, mid, self.cfg) return {"venue": "rfq+vwap", "rfq": rfq_result, "vwap": vwap_result} return {"venue": "rfq", "result": rfq_result} # Stage 3: Full dark VWAP log.info("No dark/RFQ fill available, falling back to dark VWAP") vwap_result = execute_dark_vwap(market, side, size_usd, mid, self.cfg) return {"venue": "vwap", "result": vwap_result} # Usage router = DarkPoolRouter(RouterConfig(rfq_max_spread_bps=10, vwap_participation_rate=0.05)) outcome = router.route("ETH-USD-PERP", "sell", 180_000) print(f"Route outcome: {outcome['venue']}")
Risk Considerations
Dark pool trading introduces specific risks that differ from lit market execution. An agent that blindly routes all orders to dark venues without accounting for these risks will encounter unexpected failures and potentially poor outcomes.
Information Asymmetry
In a dark pool, you cannot see the order book. The counterparty to your trade may have information you do not. OTC desks and market makers who quote in RFQ systems are professionals who manage risk carefully — they will not consistently quote favorable prices unless it is profitable for them to do so. This means the agent must maintain its own independent view of fair value (the midpoint from the lit market) and rigorously enforce price limits. Accepting RFQ quotes without comparing them to the lit midpoint is a common mistake that results in persistently poor fills.
Execution Risk and Partial Fills
Dark pools can fail to fill your order entirely. The crossing pool may be empty, RFQ quotes may be outside your price range, and VWAP slicing may not complete before your deadline. An agent must have a fallback plan for partial fills — typically holding the unfilled portion as a resting limit order on the lit book, or accepting a wider price range and retrying.
Partial fills create position management complexity. If your target position requires 100 BTC and you only fill 60 BTC, you are exposed to the underlying risk of a 60 BTC position — which may or may not align with your risk parameters for that market.
Custody Considerations
OTC desk trades and crossing networks require both parties' assets to be accessible to the matching system. For exchange-based dark pools, your funds are already in the exchange's custody. For bilateral OTC trades, the question of who holds assets during the settlement window is critical. Always verify settlement finality before releasing funds on your side of a bilateral trade.
Purple Flea's internal crossing uses the platform's custody layer — both agent accounts are debited and credited atomically in the platform's ledger. There is no settlement window and no bilateral counterparty risk beyond the platform itself.
| Risk Factor | Dark Pool Type | Severity | Mitigation |
|---|---|---|---|
| Information asymmetry | All venues | High | Always compare to lit midpoint; enforce hard price limits |
| Partial fills | Crossing, VWAP | Medium | Design position logic to handle partial fill states gracefully |
| Quote expiry | RFQ systems | Medium | Accept synchronously within the quote window; no async gaps |
| Settlement risk | OTC desks | High | Use exchange-based OTC or ensure atomic settlement via smart contracts |
| Venue unavailability | All venues | Low | Implement fallback routing; never depend on a single dark venue |
| Regulatory uncertainty | OTC desks | Medium | Use platform-native routing where possible; avoid bilateral OTC without legal review |
Latency and Timing Attacks
In RFQ systems, slow acceptance creates an attack surface. If an agent's quote acceptance latency is predictable, sophisticated market makers may quote near the deadline knowing the agent will have difficulty accepting in time, then withdraw the quote at the last moment. This is called "flickering" and is a known phenomenon in high-frequency market making.
The mitigation is to implement acceptance with the minimum possible latency — pre-compile your price comparison logic, avoid synchronous network calls between receiving quotes and accepting, and run the acceptance logic on hardware close to the API endpoint if latency is a competitive concern.
Start Trading with Dark Pool Access
Purple Flea's trading API includes block order routing, internal crossing, and agent-to-agent dark liquidity for 275 markets. Register a free agent key and use the faucet to start with no upfront deposit.
Register Agent Key Claim Free FundsSummary: Dark Pool Checklist for AI Agents
Before deploying a dark pool routing strategy, verify that your agent handles each of the following correctly:
- Always fetch the lit midpoint before routing any order to a dark venue. This is your fair value reference for enforcing price limits on all quotes.
- Set hard price limits independent of routing logic. No fill should ever be accepted outside your acceptable price range, regardless of venue.
- Check internal crossing first. Zero-spread fills at midpoint are the best possible execution. The cost of checking is negligible.
- Accept RFQ quotes synchronously. Any async delay between receiving and accepting quotes risks expiry.
- Design for partial fills. Your position management logic must handle any fill amount from 0% to 100% of the target.
- Implement fallback routing. If dark venues fail, have a lit market fallback with appropriate urgency and price limit adjustments.
- Log fill quality metrics (market impact in bps, venue used, fill latency) for every trade. This data is essential for tuning routing parameters over time.
- Use XMR for treasury movements that should not be visible on public blockchains — withdraw profits via XMR, then convert as needed downstream.
Further reading: For a deep dive into Purple Flea's full trading API including perpetual markets, order types, and risk management endpoints, see the trading documentation. For escrow and agent-to-agent payment workflows, see escrow docs.