Skip to content

Instantly share code, notes, and snippets.

@williamardianto
Created August 25, 2024 15:08
Show Gist options
  • Select an option

  • Save williamardianto/c57eb47d4e1b84516793c61b9cd8f1b5 to your computer and use it in GitHub Desktop.

Select an option

Save williamardianto/c57eb47d4e1b84516793c61b9cd8f1b5 to your computer and use it in GitHub Desktop.

Revisions

  1. williamardianto created this gist Aug 25, 2024.
    165 changes: 165 additions & 0 deletions orderbook.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,165 @@
    from enum import Enum
    from typing import List
    from dataclasses import dataclass, field

    class OrderType(Enum):
    LIMIT = 1
    STOP = 2
    MARKET = 3

    @dataclass
    class Order:
    id: str
    symbol: str
    quantity: int
    price: float
    order_type: OrderType

    @dataclass
    class Bid:
    price: float
    orders: List[Order] = field(default_factory=list)

    @dataclass
    class Ask:
    price: float
    orders: List[Order] = field(default_factory=list)

    class OrderBook:
    def __init__(self):
    self.bids: List[Bid] = []
    self.asks: List[Ask] = []

    def place_order(self, order: Order):
    if order.order_type == OrderType.MARKET:
    self._match_market_order(order)
    elif order.order_type == OrderType.LIMIT:
    self._place_limit_order(order)
    elif order.order_type == OrderType.STOP:
    self._place_stop_order(order)

    def _match_market_order(self, order: Order):
    if order.quantity > 0:
    self._match_buy_market_order(order)
    else:
    self._match_sell_market_order(order)

    def _match_buy_market_order(self, order: Order):
    remaining_quantity = order.quantity
    while remaining_quantity > 0 and self.asks:
    ask = self.asks[0]
    for ask_order in ask.orders:
    if remaining_quantity <= 0:
    break
    matched_quantity = min(remaining_quantity, ask_order.quantity)
    remaining_quantity -= matched_quantity
    ask_order.quantity -= matched_quantity
    print(f"Matched {matched_quantity} shares at price {ask.price}")
    if not ask.orders:
    self.asks.pop(0)

    def _match_sell_market_order(self, order: Order):
    remaining_quantity = -order.quantity
    while remaining_quantity > 0 and self.bids:
    bid = self.bids[-1]
    for bid_order in bid.orders:
    if remaining_quantity <= 0:
    break
    matched_quantity = min(remaining_quantity, bid_order.quantity)
    remaining_quantity -= matched_quantity
    bid_order.quantity -= matched_quantity
    print(f"Matched {matched_quantity} shares at price {bid.price}")
    if not bid.orders:
    self.bids.pop()

    def _place_limit_order(self, order: Order):
    if order.quantity > 0:
    self._place_limit_bid(order)
    else:
    self._place_limit_ask(order)

    def _place_limit_bid(self, order: Order):
    for ask in self.asks:
    if order.price >= ask.price:
    self._match_limit_bid_with_ask(order, ask)
    if order.quantity == 0:
    return
    self._add_bid(order)

    def _match_limit_bid_with_ask(self, bid_order: Order, ask: Ask):
    for ask_order in ask.orders:
    if bid_order.quantity <= 0:
    break
    matched_quantity = min(bid_order.quantity, ask_order.quantity)
    bid_order.quantity -= matched_quantity
    ask_order.quantity -= matched_quantity
    print(f"Matched {matched_quantity} shares at price {ask.price}")
    ask.orders = [order for order in ask.orders if order.quantity > 0]
    if not ask.orders:
    self.asks.remove(ask)

    def _add_bid(self, order: Order):
    for bid in self.bids:
    if order.price == bid.price:
    bid.orders.append(order)
    return
    self.bids.append(Bid(order.price, [order]))
    self.bids.sort(key=lambda b: b.price, reverse=True)

    def _place_limit_ask(self, order: Order):
    for bid in self.bids:
    if order.price <= bid.price:
    self._match_limit_ask_with_bid(order, bid)
    if order.quantity == 0:
    return
    self._add_ask(order)

    def _match_limit_ask_with_bid(self, ask_order: Order, bid: Bid):
    for bid_order in bid.orders:
    if ask_order.quantity <= 0:
    break
    matched_quantity = min(-ask_order.quantity, bid_order.quantity)
    ask_order.quantity += matched_quantity
    bid_order.quantity -= matched_quantity
    print(f"Matched {matched_quantity} shares at price {bid.price}")
    bid.orders = [order for order in bid.orders if order.quantity > 0]
    if not bid.orders:
    self.bids.remove(bid)

    def _add_ask(self, order: Order):
    for ask in self.asks:
    if order.price == ask.price:
    ask.orders.append(order)
    return
    self.asks.append(Ask(order.price, [order]))
    self.asks.sort(key=lambda a: a.price)

    def _place_stop_order(self, order: Order):
    # Simplified implementation, assuming stop orders are placed as market orders when the stop price is reached
    if order.quantity > 0:
    for ask in self.asks:
    if ask.price >= order.price:
    self._match_market_order(Order(order.id, order.symbol, order.quantity, 0, OrderType.MARKET))
    break
    else:
    for bid in self.bids:
    if bid.price <= order.price:
    self._match_market_order(Order(order.id, order.symbol, order.quantity, 0, OrderType.MARKET))
    break

    # Usage example
    order_book = OrderBook()

    # Place limit orders
    order_book.place_order(Order("1", "AAPL", 100, 150.0, OrderType.LIMIT))
    order_book.place_order(Order("2", "AAPL", -50, 160.0, OrderType.LIMIT))
    order_book.place_order(Order("3", "AAPL", 200, 140.0, OrderType.LIMIT))
    order_book.place_order(Order("4", "AAPL", -100, 155.0, OrderType.LIMIT))

    # Place market orders
    order_book.place_order(Order("5", "AAPL", 150, 0, OrderType.MARKET))
    order_book.place_order(Order("6", "AAPL", -120, 0, OrderType.MARKET))

    # Place stop orders
    order_book.place_order(Order("7", "AAPL", 100, 145.0, OrderType.STOP))
    order_book.place_order(Order("8", "AAPL", -80, 165.0, OrderType.STOP))