Market Maker Service
The Market Maker Service is a production-grade Go microservice responsible for providing liquidity to the TradeX exchange through automated market making strategies.
Overview
Section titled “Overview”The Market Maker Service handles:
- Automated Quoting: Computing and placing bid/ask orders based on real-time market data
- Inventory Management: Tracking positions and adjusting quotes based on inventory skew
- Risk Management: Enforcing strict position and loss limits with an automatic kill switch
- Order Lifecycle: Placing, managing, and canceling orders via the Order Service
- Shadow Mode: Testing strategies without placing real orders
- Real-time Monitoring: Exposing metrics and status via REST API
Architecture
Section titled “Architecture”Actor Model Architecture
Section titled “Actor Model Architecture”The service uses an actor model where each trading symbol has its own dedicated worker:
- Single-threaded per symbol: No shared mutable state across symbols
- Event-driven: Workers process messages from a dedicated event queue
- Isolated failure domains: If one symbol crashes, others continue operating
- 1000-message queue per worker: Provides buffering for high-volume events
This architecture ensures thread safety without locks and enables independent lifecycle management per symbol.
Core Components
Section titled “Core Components”Services
Section titled “Services”- Actor Worker: Core market making logic, one per symbol (event loop, quote generation, order management)
- Quoting Engine: Calculates bid/ask prices with inventory-adjusted spread pricing
- Risk Guards: Pre-trade checks (position limits, loss limits) and kill switch management
- Inventory Tracker: Maintains position and P&L tracking from fill events
Repositories
Section titled “Repositories”- Config Repository: PostgreSQL storage for per-symbol MM configurations (uses SQLC)
- State Repository: PostgreSQL storage for current symbol states (ACTIVE/PAUSED/HALTED)
- Position Repository: PostgreSQL storage for positions and P&L (uses SQLC)
- Order Repository: PostgreSQL storage for open MM orders (uses SQLC)
- Fill Repository: PostgreSQL storage for fill history (uses SQLC)
- Outbox Repository: Transactional outbox pattern for Kafka events
Processors
Section titled “Processors”- Kafka Router: Routes incoming Kafka messages to appropriate symbol workers
- Order Book Processor: Processes order book snapshots and deltas
- Trade Processor: Processes trade events and updates P&L
- Instrument Processor: Handles instrument lifecycle events (create, update, halt, resume)
Data Flow
Section titled “Data Flow”Market Making Flow
Section titled “Market Making Flow”- Market data events (order book, trades, mark price) arrive via Kafka
- Kafka router dispatches events to appropriate symbol worker
- Worker updates internal order book and pricing state
- Quoting engine computes new bid/ask prices with inventory adjustment
- Pre-trade risk checks validate proposed orders
- Worker places orders via Order Service HTTP API
- Matching engine processes orders and emits fill events
- Worker consumes fill events, updates inventory, and adjusts quotes
- Post-trade risk checks monitor exposure and P&L
Kill Switch Flow
Section titled “Kill Switch Flow”- Risk guard detects violation (exposure, loss, lag, reject storm)
- Kill switch triggered, state set to HALTED
- All open orders canceled immediately
- Risk event published to Kafka (
mm.risk.triggered.v1) - Worker stops placing new orders
- Manual intervention required to reset via
/resetendpoint
Quoting Algorithm
Section titled “Quoting Algorithm”The service uses inventory-adjusted spread pricing to manage risk:
Formula
Section titled “Formula”1. inv_norm = clamp(position / max_position, -1.0, +1.0)2. bias = inv_norm × inventory_skew_factor × (spread / 2)3. spread = mid × (base_spread_bps / 10000)4. bid = mid - (spread / 2) - bias - (level × level_spacing)5. ask = mid + (spread / 2) - bias + (level × level_spacing)Behavior
Section titled “Behavior”-
When long inventory (positive position):
biasis positive- Bids are lowered (discourages buying)
- Asks are lowered (encourages selling)
-
When short inventory (negative position):
biasis negative- Bids are raised (encourages buying)
- Asks are raised (discourages selling)
-
Multi-level quoting:
- Each level adds
level_spacing_bpsto the spread - Provides depth at progressively worse prices
- Each level adds
Price Rounding
Section titled “Price Rounding”All quotes are automatically rounded to:
- Tick size: Minimum price increment (from instrument metadata)
- Lot size: Minimum quantity increment (from instrument metadata)
Risk Management
Section titled “Risk Management”Pre-Trade Checks
Section titled “Pre-Trade Checks”Before placing any order, the service validates:
- Position Limit:
post_fill_position ≤ max_position_usdt - Loss Limit:
total_pnl ≥ -max_loss_usdt - Symbol State: Must be in
ACTIVEstate - Instrument Status: Not halted by exchange
Kill Switch Triggers
Section titled “Kill Switch Triggers”The kill switch activates automatically on:
| Trigger | Condition | Default Threshold |
|---|---|---|
| Exposure Breach | Position exceeds max_position | Configurable per symbol |
| Loss Breach | Total P&L exceeds max_loss | Configurable per symbol |
| Market Halt | Exchange halts the symbol | Immediate |
| Kafka Lag | Consumer lag exceeds threshold | 10 seconds |
| Reject Storm | Too many rejections in window | 5 rejections in 1 minute |
| Response Timeout | Order Service timeout | 50ms per request |
Post-Kill Switch Behavior
Section titled “Post-Kill Switch Behavior”When the kill switch is triggered:
- All open MM orders are canceled immediately
- Symbol state set to
HALTED - Event emitted to Kafka:
mm.risk.triggered.v1 - Worker stops processing new market data
- Manual intervention required to reset via
/v1/mm/resetendpoint
Admin API
Section titled “Admin API”The service exposes a comprehensive REST API for configuration and control. See the Market Maker API Reference for complete documentation.
Configuration Management
Section titled “Configuration Management”GET /v1/mm-configs- List all configurationsPOST /v1/mm-configs- Create new configurationGET /v1/mm-configs/{symbol}- Get configurationPUT /v1/mm-configs/{symbol}- Update configurationDELETE /v1/mm-configs/{symbol}- Delete configuration (only if not active)
Symbol Control
Section titled “Symbol Control”POST /v1/mm-configs/{symbol}/enable- Start market making (PAUSED → ACTIVE)POST /v1/mm-configs/{symbol}/pause- Pause market making (ACTIVE → PAUSED)POST /v1/mm-configs/{symbol}/resume- Resume from pause (PAUSED → ACTIVE)
Status & Monitoring
Section titled “Status & Monitoring”GET /v1/mm/status- Overall system status (all symbols, totals, kill switch state)GET /v1/mm-configs/{symbol}/status- Detailed symbol status (config, position, orders, P&L)
Emergency Controls
Section titled “Emergency Controls”POST /v1/mm/emergency/kill- Manually trigger kill switchPOST /v1/mm/emergency/flatten- Cancel all orders across all symbolsPOST /v1/mm/reset- Reset kill switch (requires manual intervention)
Configuration
Section titled “Configuration”Key Environment Variables
Section titled “Key Environment Variables”POSTGRES_URL: PostgreSQL connection URL (required)REDIS_URL: Redis connection URL (required)KAFKA_BROKERS: Kafka broker addresses (required)SCHEMA_REGISTRY_URL: Confluent Schema Registry URL (required)ORDER_SERVICE_URL: Order service HTTP endpoint (default:http://localhost:3000)METADATA_SERVICE_GRPC_URL: Metadata service gRPC endpoint (default:localhost:50051)AUTH_SERVICE_URL: Authentication service URL (required unlessDISABLE_AUTH=true)HTTP_PORT: REST API port (default:8080)METRICS_PORT: Prometheus metrics port (default:9090)
Market Making Behavior
Section titled “Market Making Behavior”MM_SHADOW_MODE: Compute quotes without placing orders (default:false)QUOTE_UPDATE_INTERVAL: Quote refresh interval (default:100ms)MAX_ORDERS_PER_SECOND: Rate limiting (default:10)MARKET_DATA_STALENESS_THRESHOLD: Max data age (default:5s)
Kill Switch Settings
Section titled “Kill Switch Settings”KILL_SWITCH_KAFKA_LAG_THRESHOLD: Max consumer lag (default:10s)KILL_SWITCH_REJECT_STORM_COUNT: Reject count threshold (default:5)KILL_SWITCH_REJECT_STORM_WINDOW: Time window (default:1m)KILL_SWITCH_RESPONSE_TIMEOUT_MS: Response timeout (default:50)
System Identity
Section titled “System Identity”SYSTEM_ACCOUNT_ID: Market maker account UUID (default:00000000-0000-0000-0000-000000000001)SYSTEM_USER_ID: Market maker user UUID (default:00000000-0000-0000-0000-000000000001)
Per-Symbol Configuration (Database)
Section titled “Per-Symbol Configuration (Database)”Stored in the mm_configs table:
| Field | Type | Description | Default |
|---|---|---|---|
symbol | VARCHAR(32) | Trading symbol (primary key) | Required |
enabled | BOOLEAN | Enable/disable market making | false |
base_spread_bps | NUMERIC | Base spread in basis points | 10.0 |
order_size_usdt | NUMERIC | Order size in USDT | 100.0 |
inventory_skew_factor | NUMERIC | Inventory bias multiplier | 0.5 |
max_position_usdt | NUMERIC | Maximum position size | 10000.0 |
max_loss_usdt | NUMERIC | Maximum loss threshold | 500.0 |
volatility_multiplier | BOOLEAN | Apply volatility adjustment | true |
num_levels | INT | Number of price levels | 1 |
level_spacing_bps | NUMERIC | Spacing between levels | 5.0 |
check_staleness | BOOLEAN | Enable staleness checks | false |
staleness_threshold_ms | NUMERIC | Max data age | 1000.0 |
quote_interval_ms | NUMERIC | Quote refresh interval | 100.0 |
price_threshold_bps | NUMERIC | Requote threshold | 5.0 |
Shadow Mode
Section titled “Shadow Mode”When MM_SHADOW_MODE=true:
- Quotes are computed normally using real market data
- NO orders are placed to the Order Service
- Simulated quotes are emitted to Kafka (
mm.quote.simulated.v1) - All risk checks are performed as if real
- Useful for testing strategies, validation, and backtesting
Performance Characteristics
Section titled “Performance Characteristics”- Quote Computation: Sub-millisecond latency
- Order Placement: < 50ms (via Order Service HTTP)
- Market Data Processing: Real-time with lag monitoring
- Actor Queue: 1000-message buffer per symbol
- Rate Limiting: Configurable orders per second per symbol
Observability
Section titled “Observability”Metrics (Prometheus)
Section titled “Metrics (Prometheus)”Exposed at http://localhost:9090/metrics:
mm_inventory_usdt{symbol}- Current inventory valuemm_open_orders{symbol}- Open order countmm_spread_bps{symbol}- Quoted spreadmm_pnl_usdt{symbol,type}- Realized and unrealized P&Lmm_kill_switch_total{symbol,reason}- Kill switch trigger countmm_quote_compute_latency_seconds- Quote computation latency histogrammm_order_placement_latency_seconds- Order placement latency histogrammm_kafka_lag_seconds{topic}- Kafka consumer lagmm_state_gauge{symbol,state}- Current state per symbol
Tracing
Section titled “Tracing”OpenTelemetry distributed tracing configured via OPENOBSERVE_OTLP_ENDPOINT.
Logging
Section titled “Logging”Structured logging via zap with:
- Service name
- Timestamp (ISO8601)
- Log level (DEBUG, INFO, WARN, ERROR)
- Symbol identifier
- Trace ID (for correlation)
- Stack traces for errors
Kafka Integration
Section titled “Kafka Integration”Consumed Topics
Section titled “Consumed Topics”md.orderbook.snap.v1- Order book snapshots (initial state)md.orderbook.delta.v1- Order book incremental updatesmd.trades.v1- Individual trade ticksmd.mark.v1- Mark price updates (for perpetuals)engine.event.v1- Trade executions from matching enginemd.instrument.created.v1- New instrument eventsmd.instrument.updated.v1- Instrument updates (metadata changes)md.instrument.halt.v1- Trading halt notificationsmd.instrument.resume.v1- Trading resume notifications
Published Topics
Section titled “Published Topics”mm.quote.simulated.v1- Simulated quotes (shadow mode only)mm.risk.triggered.v1- Risk event notifications (kill switch, limit breaches)mm.state.changed.v1- State transition events (ACTIVE ↔ PAUSED ↔ HALTED)
All events use Avro serialization with schemas stored in shared/kafka-schema/market-maker-service/.
Database Management
Section titled “Database Management”Type-safe SQL code generation from SQL queries. All database queries are defined in SQL files at internal/repository/sqlc/queries/*.sql and generated to Go code.
Declarative database schema management. Schema is defined in internal/infra/db/schema.sql and migrations are generated using Atlas.
Database Schema
Section titled “Database Schema”mm_configs: Per-symbol configurationmm_states: Current state (ACTIVE/PAUSED/HALTED)mm_positions: Position tracking with P&Lmm_orders: Open orders trackingmm_fills: Fill historyoutbox: Transactional outbox pattern for Kafka events
Startup Behavior
Section titled “Startup Behavior”On service startup:
- Loads all MM configs from database
- Loads open MM orders from database
- Rebuilds inventory from fill history
- Sets all symbols to PAUSED state (safety default)
- Waits for explicit enable/resume commands via API
- Subscribes to Kafka topics and begins consuming market data
Note: The service NEVER auto-starts market making. Manual intervention is required.
Safety Constraints
Section titled “Safety Constraints”The Market Maker Service DOES NOT:
Section titled “The Market Maker Service DOES NOT:”- ❌ Mutate order books directly
- ❌ Bypass Order Service
- ❌ Access user wallets or positions
- ❌ Use privileged matching logic
- ❌ Place orders unless state = ACTIVE
- ❌ Auto-restart after kill switch (requires manual reset)
The Market Maker Service ALWAYS:
Section titled “The Market Maker Service ALWAYS:”- ✅ Cancels all orders on ANY uncertainty
- ✅ Routes orders through Order Service REST API
- ✅ Tags orders with
SYSTEM_MARKET_MAKERaccount type - ✅ Requires manual intervention after kill switch
- ✅ Starts in PAUSED state on startup
- ✅ Validates tick size and lot size from metadata