Design Exchange and Instrument Abstractions for Quote Arbitrage
Jane StreetSoftware Engineer · Technical ScreenLast updated
Design an in-memory cross-exchange quote aggregator and arbitrage detector that identifies profitable, actionable price discrepancies while handling invalid spreads, quote staleness, and order sizing.
Problem Description
In electronic trading, identical financial instruments trade simultaneously across multiple exchanges (venues). When the highest price an exchange is willing to buy at (best bid) exceeds the lowest price another exchange is willing to sell at (best ask), a cross-exchange arbitrage opportunity exists.
Design and implement a quote processing and arbitrage detection engine that:
- Ingests top-of-book market data updates from multiple venues for various instruments.
- Validates incoming quotes, rejecting corrupted or crossed single-venue books (where single-venue
bid > ask). - Discards quotes that have expired based on a configurable time-to-live (
quote_ttl_ms). - Detects cross-venue arbitrage opportunities and returns trade execution instructions maximizing immediate profit.
Interface
Implement the following classes and methods in Python:
from dataclasses import dataclass
from typing import Optional, List
@dataclass
class Quote:
venue: str
instrument: str
bid_price: float
bid_size: int
ask_price: float
ask_size: int
timestamp_ms: int
@dataclass
class ArbitrageOpportunity:
instrument: str
buy_venue: str
sell_venue: str
buy_price: float
sell_price: float
quantity: int
profit: float
class ArbitrageEngine:
def __init__(self, quote_ttl_ms: int):
"""
Initialize the engine with a maximum quote age in milliseconds.
"""
pass
def process_quote(self, quote: Quote) -> Optional[ArbitrageOpportunity]:
"""
Process a new top-of-book quote. Updates internal state.
Returns an ArbitrageOpportunity if an immediate cross-venue arb exists,
otherwise returns None.
"""
pass
Examples
Example 1
engine = ArbitrageEngine(quote_ttl_ms=500)
# Venue A publishes quote for AAPL
q1 = Quote(venue="EXCH_A", instrument="AAPL", bid_price=150.10, bid_size=100, ask_price=150.20, ask_size=50, timestamp_ms=1000)
engine.process_quote(q1) # Returns None (only 1 venue known)
# Venue B publishes quote for AAPL
q2 = Quote(venue="EXCH_B", instrument="AAPL", bid_price=149.90, bid_size=80, ask_price=150.05, ask_size=40, timestamp_ms=1050)
engine.process_quote(q2)
# Output: ArbitrageOpportunity(
# instrument="AAPL",
# buy_venue="EXCH_B",
# sell_venue="EXCH_A",
# buy_price=150.05,
# sell_price=150.10,
# quantity=40,
# profit=2.00
# )
# Explanation: Sell on EXCH_A at 150.10, Buy on EXCH_B at 150.05. Volume = min(100, 40) = 40. Profit = (150.10 - 150.05) * 40 = 2.00.
Example 2: Corrupted Spread & Staleness
# Single venue crossed book (corrupted feed)
q3 = Quote(venue="EXCH_C", instrument="AAPL", bid_price=151.00, bid_size=50, ask_price=149.00, ask_size=50, timestamp_ms=1100)
engine.process_quote(q3) # Returns None (invalid quote: bid > ask on same venue)
# Stale quote check
q4 = Quote(venue="EXCH_B", instrument="AAPL", bid_price=149.00, bid_size=10, ask_price=150.00, ask_size=10, timestamp_ms=1600)
# EXCH_A's quote at timestamp 1000 is now 600ms old (> 500ms TTL), so it is stale and ignored.
engine.process_quote(q4) # Returns None
Constraints & Edge Cases
- Prices are positive floating point numbers rounded to 2 decimal places.
- Sizes are non-negative integers. If
bid_size == 0, bid is inactive. Ifask_size == 0, ask is inactive. - Input timestamps are monotonically non-decreasing per venue, but can arrive slightly interleaved across venues. Use the latest incoming timestamp as current time.
- An arbitrage opportunity cannot buy and sell on the same venue.
- Handle ties by prioritizing the pair with the greatest total profit, or greatest volume if profits are identical.
Hints
Hint 1
First validate incoming quotes by ensuring positive sizes and non-negative spreads (bid_price <= ask_price) on the same venue before updating venue state.
Hint 2
Maintain state partitioned by instrument, tracking the latest valid quote for each venue.
Hint 3
When evaluating potential arbitrage, filter out venues whose quotes are older than the engine's current reference time minus quote_ttl_ms.
Follow-up questions
- How would you handle legs that partially fill or execute asynchronously, leaving one side of the arbitrage position unhedged (legging risk)?
- How can you optimize the lookup of best bid and best ask across dozens of venues from O(V) to O(log V) without stale data lingering in priority queues?
- If fees and rebates differ per exchange (e.g., maker-taker models), how would you adjust the net profit calculation and routing decisions?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.