Exchange Trading Bot Development
A bot for your own exchange is different from a bot for Binance. Here you have direct access to internal API, ability to market make without network latency, control over commissions. This is both a liquidity provisioning tool and a monetization tool for the exchange.
Why Exchange Needs Its Own Bots
Young exchange without liquidity is dead. Traders go where there's tight spread and deep orderbook. Solution: internal market-making bot that:
- Maintains tight spread (0.05–0.1% for stable pairs)
- Keeps orderbook depth (sufficient volume at each level)
- Watches price vs external exchanges (Binance, OKX)
This is legitimate practice — most young exchanges use internal market makers on launch.
Market Maker Bot Architecture
Quoting Strategy
class MarketMakerBot:
def __init__(self, pair: str, config: MMConfig):
self.pair = pair
self.spread_pct = config.spread_pct # 0.1% = 0.001
self.order_levels = config.order_levels # number of levels (5-10)
self.level_spacing = config.level_spacing # distance between levels
self.level_size = config.level_size # volume per level
self.reference_exchange = config.reference # Binance for price feed
async def update_quotes(self):
# Get reference price from external exchange
ref_price = await self.get_reference_price()
# Compute bid/ask
half_spread = ref_price * self.spread_pct / 2
best_bid = ref_price - half_spread
best_ask = ref_price + half_spread
# Generate multiple levels
new_bids = []
new_asks = []
for i in range(self.order_levels):
bid_price = best_bid * (1 - self.level_spacing * i)
ask_price = best_ask * (1 + self.level_spacing * i)
size = self.level_size * (1 + i * 0.5) # increase size farther from mid
new_bids.append({'price': bid_price, 'size': size})
new_asks.append({'price': ask_price, 'size': size})
await self.refresh_orders(new_bids, new_asks)
async def refresh_orders(self, new_bids, new_asks):
# Cancel old orders and place new ones atomically
# Use bulk cancel + bulk place to minimize time without quotes
await self.exchange.cancel_all_orders(self.pair)
await asyncio.gather(
*[self.exchange.place_order(self.pair, 'buy', b['price'], b['size'])
for b in new_bids],
*[self.exchange.place_order(self.pair, 'sell', a['price'], a['size'])
for a in new_asks]
)
Inventory Management
With active trading, inventory (base/quote ratio) shifts. Needs rebalancing:
def calculate_inventory_skew(self, base_balance: float, quote_balance: float,
mid_price: float) -> float:
"""
Returns skew (-1.0 to +1.0)
-1.0: all balance in base (bought too much) → lower bid, raise ask
+1.0: all balance in quote (sold too much) → raise bid, lower ask
"""
base_value = base_balance * mid_price
total_value = base_value + quote_balance
if total_value == 0:
return 0.0
ideal_pct = 0.5 # target 50/50 balance
current_pct = base_value / total_value
return (ideal_pct - current_pct) * 2 # normalize to [-1, 1]
def apply_inventory_skew(self, mid_price: float, skew: float) -> tuple:
"""Shift quotes to reduce imbalance"""
skew_adjustment = mid_price * self.skew_factor * skew
adjusted_mid = mid_price + skew_adjustment
bid = adjusted_mid * (1 - self.spread_pct / 2)
ask = adjusted_mid * (1 + self.spread_pct / 2)
return bid, ask
Protection from Market Moves
Sharp market move with open positions is risky. Protection:
async def check_price_deviation(self):
"""Stop quoting on sharp market move"""
current_ref = await self.get_reference_price()
price_change = abs(current_ref - self.last_ref_price) / self.last_ref_price
if price_change > self.max_price_change: # e.g. 0.5%
await self.cancel_all_orders()
await asyncio.sleep(self.pause_duration) # pause N seconds
self.last_ref_price = current_ref
Internal Exchange Access
Key advantage of internal bot — direct access to exchange internal API, bypassing:
- HTTP overhead (internal call via IPC or gRPC)
- Rate limits (internal bots excluded)
- Commissions (MM bot may have zero or negative commissions)
// Direct matching engine call without HTTP
type InternalBotConnector struct {
matchingEngine *MatchingEngine
balanceManager *BalanceManager
}
func (c *InternalBotConnector) PlaceOrder(order Order) ([]Trade, error) {
// Direct call, no network
return c.matchingEngine.AddOrder(order)
}
func (c *InternalBotConnector) GetOrderBook(pair string) OrderBook {
return c.matchingEngine.GetSnapshot(pair)
}
This reduces latency from 1–5 ms (HTTP) to < 100 microseconds.
Monitoring and P&L
class BotMetrics:
def __init__(self):
self.filled_volume = defaultdict(float)
self.pnl = defaultdict(float)
self.spread_captured = defaultdict(float)
def on_fill(self, trade: Trade):
pair = trade.pair
self.filled_volume[pair] += trade.quantity
# P&L calculation: each fill with positive spread = income
if trade.is_maker:
# Maker fill: we earned the spread
spread_earned = abs(trade.price - self.mid_price[pair]) * trade.quantity
self.spread_captured[pair] += spread_earned
def get_stats(self) -> dict:
return {pair: {
'volume_24h': self.filled_volume[pair],
'spread_captured': self.spread_captured[pair],
'effective_spread_bps': self.spread_captured[pair] / self.filled_volume[pair] * 10000
if self.filled_volume[pair] > 0 else 0
} for pair in self.filled_volume}
Developing MM bot with quoting strategy, inventory management, and monitoring dashboard: 3–5 weeks. Includes strategy, inventory manager, monitoring, and internal API integration.







