Market making represents one of the most consistent profit strategies in crypto derivatives trading, yet it remains underutilized by retail algorithmic traders. By continuously placing buy and sell orders around the current market price, market makers capture the bid-ask spread while providing liquidity to the exchange. On LMEX, this strategy becomes particularly attractive due to competitive maker rebates and deep liquidity across major perpetual contracts like BTC-PERP and ETH-PERP.
This guide demonstrates how to build a production-ready market making bot in Python, complete with risk management, inventory controls, and dynamic spread adjustment based on market volatility.
Understanding Market Making Mechanics on LMEX
Market making profits from the spread between bid and ask prices. Your bot places limit orders on both sides of the order book, earning the difference when both orders fill. The key challenges involve managing inventory risk, adjusting spreads based on volatility, and handling rapid price movements that could leave you with stale orders.
LMEX's maker-taker fee structure incentivizes market making with rebates for providing liquidity. The exchange's low latency and robust API make it ideal for high-frequency market making strategies. Success depends on maintaining tight spreads while avoiding excessive inventory accumulation in any single direction.
Core Market Making Bot Architecture
The foundation of any market making bot requires several critical components: real-time price monitoring, order placement and cancellation logic, inventory tracking, and risk management controls. Here's the core structure:
import asyncio
import aiohttp
import json
import hmac
import hashlib
import time
from decimal import Decimal, ROUND_HALF_UP
from dataclasses import dataclass
from typing import Dict, List, Optional
@dataclass
class MarketMakerConfig:
symbol: str
spread_bps: int = 20 # 20 basis points = 0.2%
max_position_size: float = 1000.0
order_size: float = 10.0
max_orders_per_side: int = 3
price_precision: int = 2
size_precision: int = 4
class LMEXMarketMaker:
def __init__(self, api_key: str, api_secret: str, config: MarketMakerConfig):
self.api_key = api_key
self.api_secret = api_secret
self.config = config
self.base_url = "https://api.lmex.exchange"
self.session = None
# State tracking
self.current_position = 0.0
self.active_orders = {}
self.last_price = None
self.bid_orders = []
self.ask_orders = []
async def initialize(self):
"""Initialize the trading session and fetch current position"""
self.session = aiohttp.ClientSession()
await self.fetch_current_position()
def generate_signature(self, timestamp: str, method: str, path: str, body: str = "") -> str:
"""Generate LMEX API signature"""
message = timestamp + method + path + body
return hmac.new(
self.api_secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
async def make_request(self, method: str, endpoint: str, data: dict = None) -> dict:
"""Make authenticated request to LMEX API"""
timestamp = str(int(time.time() * 1000))
path = f"/v1/{endpoint}"
body = json.dumps(data) if data else ""
headers = {
'LMEX-API-KEY': self.api_key,
'LMEX-TIMESTAMP': timestamp,
'LMEX-SIGNATURE': self.generate_signature(timestamp, method, path, body),
'Content-Type': 'application/json'
}
async with self.session.request(method, self.base_url + path,
headers=headers, data=body) as response:
return await response.json()
async def fetch_current_position(self):
"""Get current position for the trading symbol"""
response = await self.make_request('GET', f'positions/{self.config.symbol}')
if response.get('success'):
self.current_position = float(response['data']['size'])
def calculate_order_prices(self, mid_price: float) -> tuple:
"""Calculate bid and ask prices based on spread configuration"""
spread = mid_price * (self.config.spread_bps / 10000.0)
bid_price = Decimal(str(mid_price - spread)).quantize(
Decimal('0.' + '0' * (self.config.price_precision - 1) + '1'),
rounding=ROUND_HALF_UP
)
ask_price = Decimal(str(mid_price + spread)).quantize(
Decimal('0.' + '0' * (self.config.price_precision - 1) + '1'),
rounding=ROUND_HALF_UP
)
return float(bid_price), float(ask_price)
This architecture establishes the essential framework for market making operations. The configuration system allows easy adjustment of spread parameters, position limits, and order sizing. The authentication system handles LMEX's signature requirements, while the position tracking maintains awareness of current inventory levels.
Dynamic Spread Adjustment and Order Management
Effective market making requires dynamic spread adjustment based on market conditions. During high volatility periods, wider spreads protect against adverse selection, while tight spreads maximize capture rates during stable conditions:
import numpy as np
from collections import deque
class VolatilityBasedMarketMaker(LMEXMarketMaker):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.price_history = deque(maxlen=100)
self.volatility_window = 20
self.base_spread_bps = self.config.spread_bps
self.volatility_multiplier = 2.0
async def update_market_data(self):
"""Fetch latest market data and update volatility metrics"""
response = await self.make_request('GET', f'ticker/{self.config.symbol}')
if response.get('success'):
ticker_data = response['data']
current_price = float(ticker_data['last_price'])
self.last_price = current_price
self.price_history.append(current_price)
return {
'price': current_price,
'bid': float(ticker_data['best_bid']),
'ask': float(ticker_data['best_ask']),
'volume': float(ticker_data['volume_24h'])
}
return None
def calculate_volatility(self) -> float:
"""Calculate rolling volatility from price history"""
if len(self.price_history) < self.volatility_window:
return 0.01 # Default 1% volatility
prices = np.array(list(self.price_history)[-self.volatility_window:])
returns = np.diff(np.log(prices))
return float(np.std(returns) * np.sqrt(1440)) # Annualized to minutes
def adjust_spread_for_volatility(self, base_spread_bps: int, volatility: float) -> int:
"""Dynamically adjust spread based on market volatility"""
volatility_adjustment = max(1.0, volatility * self.volatility_multiplier)
adjusted_spread = int(base_spread_bps * volatility_adjustment)
return min(adjusted_spread, base_spread_bps * 5) # Cap at 5x base spread
def should_place_orders(self, market_data: dict) -> bool:
"""Determine if conditions are suitable for placing orders"""
# Avoid trading during extreme inventory imbalances
inventory_ratio = abs(self.current_position) / self.config.max_position_size
if inventory_ratio > 0.8: