转载一篇由AllTick团队最近更新的一篇关于撮合引擎的科普介绍。AllTick目前为海外交易所和个人量化交易者提供实时行情数据接口,团队也为直接为交易所提供技术支持,其中就包括撮合引擎的研发等。也算是专业对口了。
--
撮合引擎是交易系统的一个关键模块,它由多个核心组件组成,包括撮合算法、数据管理和通信接口等。撮合引擎专门用于匹配市场参与者的买卖订单,撮合双方并生成交易订单。它是全球市场高效运作的核心,每天有大量流动性通过市场进行交换,而撮合引擎在其中发挥了决定性作用。其他交易模块可以被视为撮合引擎的辅助系统,因为没有撮合引擎,就无法实现真正意义上的市场交易。无论您的交易平台专注于哪种资产类别,无论是外汇、贵金属、股票CFD,你都需要一个撮合引擎来处理和匹配市场订单。
撮合的概念可以追溯到19世纪,当时股票交易主要依赖人工喊价来匹配订单,撮合主要靠吼。比如纽约证券交易所(NYSE),交易员会在交易大厅大声喊出买卖意向,简单暴力,这被称为“公开喊价”系统。后来随着电子技术的进步,市场逐渐从人工撮合转向电子撮合。1971年,美国纳斯达克交易所第一个推出了电子撮合系统,所有订单均由电脑匹配,这个变革大大提高了交易效率,并为后来的撮合引擎发展奠定了基础。
撮合订单的处理流程
一、订单接收
市场参与者通过交易平台提交订单,可以是买单或卖单。通常包含价格、数量以及订单类型(市价单或限价单)等信息。
二、订单排队
撮合引擎将收到的订单按照预定义的规则进行排队。通常情况下,订单会根据先进先出原则排列。
三、订单撮合
撮合引擎在订单簿中查找与新订单匹配的对手盘。如果订单能够匹配(即买单价格大于等于卖单价格),撮合引擎将这些订单进行成交处理。
四、成交确认与通知
一旦订单匹配成功,撮合引擎会确认成交并将交易结果通知给买卖双方及相关的清算系统。
订单簿管理
订单簿是撮合引擎的核心组成部分,记录了当前市场中所有未成交的限价订单。订单簿通常分为买单簿和卖单簿,按照价格和时间优先级排序。
- 买单簿:按照出价从高到低排序,优先处理出价最高的买单。
- 卖单簿:与买单簿相反,根据出价从低往高排,出价低的卖单会优先被处理。
订单簿的管理是确保市场流动性和价格发现过程的关键因素。撮合引擎需要实时更新订单簿,反映市场动态变化。
市价单与限价单的撮合机制
市价单(Market Order):
- 以市场当前价格立即成交的订单,一般会优先匹配订单簿中当前最优的对手订单。
- 市价单在撮合过程中不关注价格,只关注交易的速度和成交的确定性,在流动性不足时可能会导致滑点。
限价单(Limit Order):
- 以指定价格成交的订单,只有在市场价格达到或优于客户指定价格时才会成交。限价单会进入订单簿,并按照价格和时间优先级排队。
- 限价单在撮合过程中确保了价格,但不一定能够成交。
撮合算法代码示例
下面将介绍三种主流的撮合算法,并附上代码示例,希望对大家有帮助。
价格-时间优先算法
也被称为“先进先出”(FIFO),是最常见的撮合机制。订单根据价格优先级和时间优先级排序。价格较高的买单和价格较低的卖单优先成交;如果价格相同,则较早提交的订单优先成交。
import heapq
class Order:
def __init__(self, order_id, order_type, price, quantity):
self.order_id = order_id
self.order_type = order_type # 'buy' or 'sell'
self.price = price
self.quantity = quantity
self.timestamp = None
def __lt__(self, other):
if self.order_type == 'buy':
# For buy orders, higher prices have priority; if prices are equal, earlier orders have priority
return (self.price, -self.timestamp) > (other.price, -other.timestamp)
else:
# For sell orders, lower prices have priority; if prices are equal, earlier orders have priority
return (self.price, self.timestamp) < (other.price, other.timestamp)
class OrderBook:
def __init__(self):
self.buy_orders = [] # Max-heap for buy orders (negative prices for max-heap)
self.sell_orders = [] # Min-heap for sell orders
self.timestamp_counter = 0
def add_order(self, order):
order.timestamp = self.timestamp_counter
self.timestamp_counter += 1
if order.order_type == 'buy':
heapq.heappush(self.buy_orders, order)
else:
heapq.heappush(self.sell_orders, order)
def match_orders(self):
matches = []
while self.buy_orders and self.sell_orders:
highest_buy_order = self.buy_orders[0]
lowest_sell_order = self.sell_orders[0]
if highest_buy_order.price >= lowest_sell_order.price:
quantity_to_trade = min(highest_buy_order.quantity, lowest_sell_order.quantity)
matches.append((highest_buy_order.order_id, lowest_sell_order.order_id, quantity_to_trade))
highest_buy_order.quantity -= quantity_to_trade
lowest_sell_order.quantity -= quantity_to_trade
if highest_buy_order.quantity == 0:
heapq.heappop(self.buy_orders)
if lowest_sell_order.quantity == 0:
heapq.heappop(self.sell_orders)
else:
break
return matches
# Example usage
order_book = OrderBook()
# Add some buy and sell orders
order_book.add_order(Order(1, 'buy', 101, 10))
order_book.add_order(Order(2, 'buy', 102, 5))
order_book.add_order(Order(3, 'sell', 100, 8))
order_book.add_order(Order(4, 'sell', 99, 10))
# Match orders
matches = order_book.match_orders()
for match in matches:
print(f"Matched Buy Order {match[0]} with Sell Order {match[1]} for {match[2]} units")
比例分配算法
在此算法中,订单是根据其数量在同一价格水平上按比例匹配。简单来说,大单比小单更容易成交。这种算法在期货市场比较常见。上代码:
import heapq
from collections import defaultdict
class Order:
def __init__(self, order_id, order_type, price, quantity):
self.order_id = order_id
self.order_type = order_type # 'buy' or 'sell'
self.price = price
self.quantity = quantity
self.timestamp = None
class OrderBook:
def __init__(self):
self.buy_orders = defaultdict(list) # Buy orders grouped by price
self.sell_orders = defaultdict(list) # Sell orders grouped by price
self.timestamp_counter = 0
def add_order(self, order):
order.timestamp = self.timestamp_counter
self.timestamp_counter += 1
if order.order_type == 'buy':
self.buy_orders[order.price].append(order)
else:
self.sell_orders[order.price].append(order)
def match_orders(self):
matches = []
# Sort buy and sell prices
buy_prices = sorted(self.buy_orders.keys(), reverse=True)
sell_prices = sorted(self.sell_orders.keys())
while buy_prices and sell_prices:
highest_buy_price = buy_prices[0]
lowest_sell_price = sell_prices[0]
if highest_buy_price >= lowest_sell_price:
buy_orders_at_price = self.buy_orders[highest_buy_price]
sell_orders_at_price = self.sell_orders[lowest_sell_price]
# Calculate total buy and sell quantities at this price
total_buy_quantity = sum(order.quantity for order in buy_orders_at_price)
total_sell_quantity = sum(order.quantity for order in sell_orders_at_price)
# Determine the amount to trade based on the smaller side
quantity_to_trade = min(total_buy_quantity, total_sell_quantity)
# Pro-rata allocation for buy and sell orders
for order in buy_orders_at_price:
proportion = order.quantity / total_buy_quantity
traded_quantity = quantity_to_trade * proportion
matches.append((order.order_id, lowest_sell_price, traded_quantity))
order.quantity -= traded_quantity
for order in sell_orders_at_price:
proportion = order.quantity / total_sell_quantity
traded_quantity = quantity_to_trade * proportion
matches.append((order.order_id, highest_buy_price, traded_quantity))
order.quantity -= traded_quantity
# Remove fully matched orders
self.buy_orders[highest_buy_price] = [o for o in buy_orders_at_price if o.quantity > 0]
self.sell_orders[lowest_sell_price] = [o for o in sell_orders_at_price if o.quantity > 0]
if not self.buy_orders[highest_buy_price]:
del self.buy_orders[highest_buy_price]
buy_prices.pop(0)
if not self.sell_orders[lowest_sell_price]:
del self.sell_orders[lowest_sell_price]
sell_prices.pop(0)
else:
break
return matches
# Example usage
order_book = OrderBook()
# Add some buy and sell orders
order_book.add_order(Order(1, 'buy', 100, 10))
order_book.add_order(Order(2, 'buy', 100, 20))
order_book.add_order(Order(3, 'sell', 100, 15))
order_book.add_order(Order(4, 'sell', 100, 5))
# Match orders
matches = order_book.match_orders()
for match in matches:
print(f"Order {match[0]} matched at price {match[1]} for {match[2]:.2f} units")
混合优先算法
这种算法结合了前面两种的元素,以实现平衡的撮合效果。它可以根据市场需求动态调整不同优先级之间的权重,灵活性较高。
import heapq
from collections import defaultdict
class Order:
def __init__(self, order_id, order_type, price, quantity):
self.order_id = order_id
self.order_type = order_type # 'buy' or 'sell'
self.price = price
self.quantity = quantity
self.timestamp = None
def __lt__(self, other):
return self.timestamp < other.timestamp
class OrderBook:
def __init__(self):
self.buy_orders = defaultdict(list) # Buy orders grouped by price
self.sell_orders = defaultdict(list) # Sell orders grouped by price
self.timestamp_counter = 0
def add_order(self, order):
order.timestamp = self.timestamp_counter
self.timestamp_counter += 1
if order.order_type == 'buy':
heapq.heappush(self.buy_orders[order.price], order)
else:
heapq.heappush(self.sell_orders[order.price], order)
def match_orders(self):
matches = []
# Sort buy and sell prices
buy_prices = sorted(self.buy_orders.keys(), reverse=True)
sell_prices = sorted(self.sell_orders.keys())
while buy_prices and sell_prices:
highest_buy_price = buy_prices[0]
lowest_sell_price = sell_prices[0]
if highest_buy_price >= lowest_sell_price:
buy_orders_at_price = self.buy_orders[highest_buy_price]
sell_orders_at_price = self.sell_orders[lowest_sell_price]
# Calculate total buy and sell quantities at this price
total_buy_quantity = sum(order.quantity for order in buy_orders_at_price)
total_sell_quantity = sum(order.quantity for order in sell_orders_at_price)
# Determine the amount to trade based on the smaller side
quantity_to_trade = min(total_buy_quantity, total_sell_quantity)
# Priority based matching and then pro-rata allocation
while quantity_to_trade > 0 and buy_orders_at_price and sell_orders_at_price:
buy_order = buy_orders_at_price[0]
sell_order = sell_orders_at_price[0]
if buy_order.quantity <= sell_order.quantity:
matches.append((buy_order.order_id, sell_order.order_id, buy_order.quantity))
sell_order.quantity -= buy_order.quantity
quantity_to_trade -= buy_order.quantity
heapq.heappop(buy_orders_at_price)
if sell_order.quantity == 0:
heapq.heappop(sell_orders_at_price)
else:
matches.append((buy_order.order_id, sell_order.order_id, sell_order.quantity))
buy_order.quantity -= sell_order.quantity
quantity_to_trade -= sell_order.quantity
heapq.heappop(sell_orders_at_price)
if buy_order.quantity == 0:
heapq.heappop(buy_orders_at_price)
# Remove empty price levels
if not buy_orders_at_price:
del self.buy_orders[highest_buy_price]
buy_prices.pop(0)
if not sell_orders_at_price:
del self.sell_orders[lowest_sell_price]
sell_prices.pop(0)
else:
break
return matches
# Example usage
order_book = OrderBook()
# Add some buy and sell orders
order_book.add_order(Order(1, 'buy', 100, 15))
order_book.add_order(Order(2, 'buy', 100, 25))
order_book.add_order(Order(3, 'sell', 100, 30))
order_book.add_order(Order(4, 'sell', 100, 10))
# Match orders
matches = order_book.match_orders()
for match in matches:
print(f"Buy Order {match[0]} matched with Sell Order {match[1]} for {match[2]:.2f} units")